1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/netwerk/test/unit/head_cache2.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,387 @@ 1.4 +const Cc = Components.classes; 1.5 +const Ci = Components.interfaces; 1.6 +const Cu = Components.utils; 1.7 +const Cr = Components.results; 1.8 + 1.9 +function newCacheBackEndUsed() 1.10 +{ 1.11 + var cache1srv = Components.classes["@mozilla.org/network/cache-service;1"] 1.12 + .getService(Components.interfaces.nsICacheService); 1.13 + var cache2srv = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"] 1.14 + .getService(Components.interfaces.nsICacheStorageService); 1.15 + 1.16 + return cache1srv.cacheIOTarget != cache2srv.ioTarget; 1.17 +} 1.18 + 1.19 +var callbacks = new Array(); 1.20 + 1.21 +// Expect an existing entry 1.22 +const NORMAL = 0; 1.23 +// Expect a new entry 1.24 +const NEW = 1 << 0; 1.25 +// Return early from onCacheEntryCheck and set the callback to state it expects onCacheEntryCheck to happen 1.26 +const NOTVALID = 1 << 1; 1.27 +// Throw from onCacheEntryAvailable 1.28 +const THROWAVAIL = 1 << 2; 1.29 +// Open entry for reading-only 1.30 +const READONLY = 1 << 3; 1.31 +// Expect the entry to not be found 1.32 +const NOTFOUND = 1 << 4; 1.33 +// Return ENTRY_NEEDS_REVALIDATION from onCacheEntryCheck 1.34 +const REVAL = 1 << 5; 1.35 +// Return ENTRY_PARTIAL from onCacheEntryCheck, in combo with NEW or RECREATE bypasses check for emptiness of the entry 1.36 +const PARTIAL = 1 << 6 1.37 +// Expect the entry is doomed, i.e. the output stream should not be possible to open 1.38 +const DOOMED = 1 << 7; 1.39 +// Don't trigger the go-on callback until the entry is written 1.40 +const WAITFORWRITE = 1 << 8; 1.41 +// Don't write data (i.e. don't open output stream) 1.42 +const METAONLY = 1 << 9; 1.43 +// Do recreation of an existing cache entry 1.44 +const RECREATE = 1 << 10; 1.45 +// Do not give me the entry 1.46 +const NOTWANTED = 1 << 11; 1.47 +// Tell the cache to wait for the entry to be completely written first 1.48 +const COMPLETE = 1 << 12; 1.49 + 1.50 +var log_c2 = true; 1.51 +function LOG_C2(o, m) 1.52 +{ 1.53 + if (!log_c2) return; 1.54 + if (!m) 1.55 + dump("TEST-INFO | CACHE2: " + o + "\n"); 1.56 + else 1.57 + dump("TEST-INFO | CACHE2: callback #" + o.order + "(" + (o.workingData || "---") + ") " + m + "\n"); 1.58 +} 1.59 + 1.60 +function pumpReadStream(inputStream, goon) 1.61 +{ 1.62 + if (inputStream.isNonBlocking()) { 1.63 + // non-blocking stream, must read via pump 1.64 + var pump = Cc["@mozilla.org/network/input-stream-pump;1"] 1.65 + .createInstance(Ci.nsIInputStreamPump); 1.66 + pump.init(inputStream, -1, -1, 0, 0, true); 1.67 + var data = ""; 1.68 + pump.asyncRead({ 1.69 + onStartRequest: function (aRequest, aContext) { }, 1.70 + onDataAvailable: function (aRequest, aContext, aInputStream, aOffset, aCount) 1.71 + { 1.72 + var wrapper = Cc["@mozilla.org/scriptableinputstream;1"]. 1.73 + createInstance(Ci.nsIScriptableInputStream); 1.74 + wrapper.init(aInputStream); 1.75 + var str = wrapper.read(wrapper.available()); 1.76 + LOG_C2("reading data '" + str.substring(0,5) + "'"); 1.77 + data += str; 1.78 + }, 1.79 + onStopRequest: function (aRequest, aContext, aStatusCode) 1.80 + { 1.81 + LOG_C2("done reading data: " + aStatusCode); 1.82 + do_check_eq(aStatusCode, Cr.NS_OK); 1.83 + goon(data); 1.84 + }, 1.85 + }, null); 1.86 + } 1.87 + else { 1.88 + // blocking stream 1.89 + var data = read_stream(inputStream, inputStream.available()); 1.90 + goon(data); 1.91 + } 1.92 +} 1.93 + 1.94 +OpenCallback.prototype = 1.95 +{ 1.96 + QueryInterface: function listener_qi(iid) { 1.97 + if (iid.equals(Ci.nsISupports) || 1.98 + iid.equals(Ci.nsICacheEntryOpenCallback)) { 1.99 + return this; 1.100 + } 1.101 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.102 + }, 1.103 + onCacheEntryCheck: function(entry, appCache) 1.104 + { 1.105 + LOG_C2(this, "onCacheEntryCheck"); 1.106 + do_check_true(!this.onCheckPassed); 1.107 + this.onCheckPassed = true; 1.108 + 1.109 + if (this.behavior & NOTVALID) { 1.110 + LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_WANTED"); 1.111 + return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; 1.112 + } 1.113 + 1.114 + if (this.behavior & NOTWANTED) { 1.115 + LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_NOT_WANTED"); 1.116 + return Ci.nsICacheEntryOpenCallback.ENTRY_NOT_WANTED; 1.117 + } 1.118 + 1.119 + do_check_eq(entry.getMetaDataElement("meto"), this.workingMetadata); 1.120 + 1.121 + // check for sane flag combination 1.122 + do_check_neq(this.behavior & (REVAL|PARTIAL), REVAL|PARTIAL); 1.123 + 1.124 + if (this.behavior & (REVAL|PARTIAL)) { 1.125 + LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_NEEDS_REVALIDATION"); 1.126 + return Ci.nsICacheEntryOpenCallback.ENTRY_NEEDS_REVALIDATION; 1.127 + } 1.128 + 1.129 + if (this.behavior & COMPLETE) { 1.130 + LOG_C2(this, "onCacheEntryCheck DONE, return RECHECK_AFTER_WRITE_FINISHED"); 1.131 + if (newCacheBackEndUsed()) { 1.132 + // Specific to the new backend because of concurrent read/write: 1.133 + // when a consumer returns RECHECK_AFTER_WRITE_FINISHED from onCacheEntryCheck 1.134 + // the cache calls this callback again after the entry write has finished. 1.135 + // This gives the consumer a chance to recheck completeness of the entry 1.136 + // again. 1.137 + // Thus, we reset state as onCheck would have never been called. 1.138 + this.onCheckPassed = false; 1.139 + // Don't return RECHECK_AFTER_WRITE_FINISHED on second call of onCacheEntryCheck. 1.140 + this.behavior &= ~COMPLETE; 1.141 + } 1.142 + return Ci.nsICacheEntryOpenCallback.RECHECK_AFTER_WRITE_FINISHED; 1.143 + } 1.144 + 1.145 + LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_WANTED"); 1.146 + return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; 1.147 + }, 1.148 + onCacheEntryAvailable: function(entry, isnew, appCache, status) 1.149 + { 1.150 + LOG_C2(this, "onCacheEntryAvailable, " + this.behavior); 1.151 + do_check_true(!this.onAvailPassed); 1.152 + this.onAvailPassed = true; 1.153 + 1.154 + do_check_eq(isnew, !!(this.behavior & NEW)); 1.155 + 1.156 + if (this.behavior & (NOTFOUND|NOTWANTED)) { 1.157 + do_check_eq(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND); 1.158 + do_check_false(!!entry); 1.159 + if (this.behavior & THROWAVAIL) 1.160 + this.throwAndNotify(entry); 1.161 + this.goon(entry); 1.162 + } 1.163 + else if (this.behavior & (NEW|RECREATE)) { 1.164 + do_check_true(!!entry); 1.165 + 1.166 + if (this.behavior & RECREATE) { 1.167 + entry = entry.recreate(); 1.168 + do_check_true(!!entry); 1.169 + } 1.170 + 1.171 + if (this.behavior & THROWAVAIL) 1.172 + this.throwAndNotify(entry); 1.173 + 1.174 + if (!(this.behavior & WAITFORWRITE)) 1.175 + this.goon(entry); 1.176 + 1.177 + if (!(this.behavior & PARTIAL)) { 1.178 + try { 1.179 + entry.getMetaDataElement("meto"); 1.180 + do_check_true(false); 1.181 + } 1.182 + catch (ex) {} 1.183 + } 1.184 + 1.185 + var self = this; 1.186 + do_execute_soon(function() { // emulate network latency 1.187 + entry.setMetaDataElement("meto", self.workingMetadata); 1.188 + entry.metaDataReady(); 1.189 + if (self.behavior & METAONLY) { 1.190 + // Since forcing GC/CC doesn't trigger OnWriterClosed, we have to set the entry valid manually :( 1.191 + entry.setValid(); 1.192 + entry.close(); 1.193 + return; 1.194 + } 1.195 + do_execute_soon(function() { // emulate more network latency 1.196 + if (self.behavior & DOOMED) { 1.197 + try { 1.198 + var os = entry.openOutputStream(0); 1.199 + do_check_true(false); 1.200 + } catch (ex) { 1.201 + do_check_true(true); 1.202 + } 1.203 + if (self.behavior & WAITFORWRITE) 1.204 + self.goon(entry); 1.205 + return; 1.206 + } 1.207 + 1.208 + var offset = (self.behavior & PARTIAL) 1.209 + ? entry.dataSize 1.210 + : 0; 1.211 + LOG_C2(self, "openOutputStream @ " + offset); 1.212 + var os = entry.openOutputStream(offset); 1.213 + LOG_C2(self, "writing data"); 1.214 + var wrt = os.write(self.workingData, self.workingData.length); 1.215 + do_check_eq(wrt, self.workingData.length); 1.216 + os.close(); 1.217 + if (self.behavior & WAITFORWRITE) 1.218 + self.goon(entry); 1.219 + 1.220 + entry.close(); 1.221 + }) 1.222 + }) 1.223 + } 1.224 + else { // NORMAL 1.225 + do_check_true(!!entry); 1.226 + do_check_eq(entry.getMetaDataElement("meto"), this.workingMetadata); 1.227 + if (this.behavior & THROWAVAIL) 1.228 + this.throwAndNotify(entry); 1.229 + 1.230 + var wrapper = Cc["@mozilla.org/scriptableinputstream;1"]. 1.231 + createInstance(Ci.nsIScriptableInputStream); 1.232 + var self = this; 1.233 + pumpReadStream(entry.openInputStream(0), function(data) { 1.234 + do_check_eq(data, self.workingData); 1.235 + self.onDataCheckPassed = true; 1.236 + LOG_C2(self, "entry read done"); 1.237 + self.goon(entry); 1.238 + entry.close(); 1.239 + }); 1.240 + } 1.241 + }, 1.242 + selfCheck: function() 1.243 + { 1.244 + LOG_C2(this, "selfCheck"); 1.245 + 1.246 + do_check_true(this.onCheckPassed); 1.247 + do_check_true(this.onAvailPassed); 1.248 + do_check_true(this.onDataCheckPassed); 1.249 + }, 1.250 + throwAndNotify: function(entry) 1.251 + { 1.252 + LOG_C2(this, "Throwing"); 1.253 + var self = this; 1.254 + do_execute_soon(function() { 1.255 + LOG_C2(self, "Notifying"); 1.256 + self.goon(entry); 1.257 + }); 1.258 + throw Cr.NS_ERROR_FAILURE; 1.259 + } 1.260 +}; 1.261 + 1.262 +function OpenCallback(behavior, workingMetadata, workingData, goon) 1.263 +{ 1.264 + this.behavior = behavior; 1.265 + this.workingMetadata = workingMetadata; 1.266 + this.workingData = workingData; 1.267 + this.goon = goon; 1.268 + this.onCheckPassed = (!!(behavior & (NEW|RECREATE)) || !workingMetadata) && !(behavior & NOTVALID); 1.269 + this.onAvailPassed = false; 1.270 + this.onDataCheckPassed = !!(behavior & (NEW|RECREATE|NOTWANTED)) || !workingMetadata; 1.271 + callbacks.push(this); 1.272 + this.order = callbacks.length; 1.273 +} 1.274 + 1.275 +VisitCallback.prototype = 1.276 +{ 1.277 + QueryInterface: function listener_qi(iid) { 1.278 + if (iid.equals(Ci.nsISupports) || 1.279 + iid.equals(Ci.nsICacheStorageVisitor)) { 1.280 + return this; 1.281 + } 1.282 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.283 + }, 1.284 + onCacheStorageInfo: function(num, consumption) 1.285 + { 1.286 + LOG_C2(this, "onCacheStorageInfo: num=" + num + ", size=" + consumption); 1.287 + do_check_eq(this.num, num); 1.288 + if (newCacheBackEndUsed()) { 1.289 + // Consumption is as expected only in the new backend 1.290 + do_check_eq(this.consumption, consumption); 1.291 + } 1.292 + if (!this.entries) 1.293 + this.notify(); 1.294 + }, 1.295 + onCacheEntryInfo: function(entry) 1.296 + { 1.297 + var key = entry.key; 1.298 + LOG_C2(this, "onCacheEntryInfo: key=" + key); 1.299 + 1.300 + do_check_true(!!this.entries); 1.301 + 1.302 + var index = this.entries.indexOf(key); 1.303 + do_check_true(index > -1); 1.304 + 1.305 + this.entries.splice(index, 1); 1.306 + }, 1.307 + onCacheEntryVisitCompleted: function() 1.308 + { 1.309 + LOG_C2(this, "onCacheEntryVisitCompleted"); 1.310 + if (this.entries) 1.311 + do_check_eq(this.entries.length, 0); 1.312 + this.notify(); 1.313 + }, 1.314 + notify: function() 1.315 + { 1.316 + do_check_true(!!this.goon); 1.317 + var goon = this.goon; 1.318 + this.goon = null; 1.319 + do_execute_soon(goon); 1.320 + }, 1.321 + selfCheck: function() 1.322 + { 1.323 + do_check_true(!this.entries || !this.entries.length); 1.324 + } 1.325 +}; 1.326 + 1.327 +function VisitCallback(num, consumption, entries, goon) 1.328 +{ 1.329 + this.num = num; 1.330 + this.consumption = consumption; 1.331 + this.entries = entries; 1.332 + this.goon = goon; 1.333 + callbacks.push(this); 1.334 + this.order = callbacks.length; 1.335 +} 1.336 + 1.337 +EvictionCallback.prototype = 1.338 +{ 1.339 + QueryInterface: function listener_qi(iid) { 1.340 + if (iid.equals(Ci.nsISupports) || 1.341 + iid.equals(Ci.nsICacheEntryDoomCallback)) { 1.342 + return this; 1.343 + } 1.344 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.345 + }, 1.346 + onCacheEntryDoomed: function(result) 1.347 + { 1.348 + do_check_eq(this.expectedSuccess, result == Cr.NS_OK); 1.349 + this.goon(); 1.350 + }, 1.351 + selfCheck: function() {} 1.352 +} 1.353 + 1.354 +function EvictionCallback(success, goon) 1.355 +{ 1.356 + this.expectedSuccess = success; 1.357 + this.goon = goon; 1.358 + callbacks.push(this); 1.359 + this.order = callbacks.length; 1.360 +} 1.361 + 1.362 +MultipleCallbacks.prototype = 1.363 +{ 1.364 + fired: function() 1.365 + { 1.366 + if (--this.pending == 0) 1.367 + { 1.368 + var self = this; 1.369 + if (this.delayed) 1.370 + do_execute_soon(function() { self.goon(); }); 1.371 + else 1.372 + this.goon(); 1.373 + } 1.374 + } 1.375 +} 1.376 + 1.377 +function MultipleCallbacks(number, goon, delayed) 1.378 +{ 1.379 + this.pending = number; 1.380 + this.goon = goon; 1.381 + this.delayed = delayed; 1.382 +} 1.383 + 1.384 +function finish_cache2_test() 1.385 +{ 1.386 + callbacks.forEach(function(callback, index) { 1.387 + callback.selfCheck(); 1.388 + }); 1.389 + do_test_finished(); 1.390 +}