Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / netwerk / test / unit / head_cache2.js
blob246f3e85317fe5a17947143477dd498804a8bd96
1 /* import-globals-from head_cache.js */
2 /* import-globals-from head_channels.js */
4 "use strict";
6 var callbacks = [];
8 // Expect an existing entry
9 const NORMAL = 0;
10 // Expect a new entry
11 const NEW = 1 << 0;
12 // Return early from onCacheEntryCheck and set the callback to state it expects onCacheEntryCheck to happen
13 const NOTVALID = 1 << 1;
14 // Throw from onCacheEntryAvailable
15 const THROWAVAIL = 1 << 2;
16 // Open entry for reading-only
17 const READONLY = 1 << 3;
18 // Expect the entry to not be found
19 const NOTFOUND = 1 << 4;
20 // Return ENTRY_NEEDS_REVALIDATION from onCacheEntryCheck
21 const REVAL = 1 << 5;
22 // Return ENTRY_PARTIAL from onCacheEntryCheck, in combo with NEW or RECREATE bypasses check for emptiness of the entry
23 const PARTIAL = 1 << 6;
24 // Expect the entry is doomed, i.e. the output stream should not be possible to open
25 const DOOMED = 1 << 7;
26 // Don't trigger the go-on callback until the entry is written
27 const WAITFORWRITE = 1 << 8;
28 // Don't write data (i.e. don't open output stream)
29 const METAONLY = 1 << 9;
30 // Do recreation of an existing cache entry
31 const RECREATE = 1 << 10;
32 // Do not give me the entry
33 const NOTWANTED = 1 << 11;
34 // Tell the cache to wait for the entry to be completely written first
35 const COMPLETE = 1 << 12;
36 // Don't write meta/data and don't set valid in the callback, consumer will do it manually
37 const DONTFILL = 1 << 13;
38 // Used in combination with METAONLY, don't call setValid() on the entry after metadata has been set
39 const DONTSETVALID = 1 << 14;
40 // Notify before checking the data, useful for proper callback ordering checks
41 const NOTIFYBEFOREREAD = 1 << 15;
42 // It's allowed to not get an existing entry (result of opening is undetermined)
43 const MAYBE_NEW = 1 << 16;
45 var log_c2 = true;
46 function LOG_C2(o, m) {
47 if (!log_c2) {
48 return;
50 if (!m) {
51 dump("TEST-INFO | CACHE2: " + o + "\n");
52 } else {
53 dump(
54 "TEST-INFO | CACHE2: callback #" +
55 o.order +
56 "(" +
57 (o.workingData ? o.workingData.substr(0, 10) : "---") +
58 ") " +
59 m +
60 "\n"
65 function pumpReadStream(inputStream, goon) {
66 if (inputStream.isNonBlocking()) {
67 // non-blocking stream, must read via pump
68 var pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(
69 Ci.nsIInputStreamPump
71 pump.init(inputStream, 0, 0, true);
72 let data = "";
73 pump.asyncRead({
74 onStartRequest() {},
75 onDataAvailable(aRequest, aInputStream) {
76 var wrapper = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
77 Ci.nsIScriptableInputStream
79 wrapper.init(aInputStream);
80 var str = wrapper.read(wrapper.available());
81 LOG_C2("reading data '" + str.substring(0, 5) + "'");
82 data += str;
84 onStopRequest(aRequest, aStatusCode) {
85 LOG_C2("done reading data: " + aStatusCode);
86 Assert.equal(aStatusCode, Cr.NS_OK);
87 goon(data);
89 });
90 } else {
91 // blocking stream
92 let data = read_stream(inputStream, inputStream.available());
93 goon(data);
97 OpenCallback.prototype = {
98 QueryInterface: ChromeUtils.generateQI(["nsICacheEntryOpenCallback"]),
99 onCacheEntryCheck(entry) {
100 LOG_C2(this, "onCacheEntryCheck");
101 Assert.ok(!this.onCheckPassed);
102 this.onCheckPassed = true;
104 if (this.behavior & NOTVALID) {
105 LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_WANTED");
106 return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
109 if (this.behavior & NOTWANTED) {
110 LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_NOT_WANTED");
111 return Ci.nsICacheEntryOpenCallback.ENTRY_NOT_WANTED;
114 Assert.equal(entry.getMetaDataElement("meto"), this.workingMetadata);
116 // check for sane flag combination
117 Assert.notEqual(this.behavior & (REVAL | PARTIAL), REVAL | PARTIAL);
119 if (this.behavior & (REVAL | PARTIAL)) {
120 LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_NEEDS_REVALIDATION");
121 return Ci.nsICacheEntryOpenCallback.ENTRY_NEEDS_REVALIDATION;
124 if (this.behavior & COMPLETE) {
125 LOG_C2(
126 this,
127 "onCacheEntryCheck DONE, return RECHECK_AFTER_WRITE_FINISHED"
129 // Specific to the new backend because of concurrent read/write:
130 // when a consumer returns RECHECK_AFTER_WRITE_FINISHED from onCacheEntryCheck
131 // the cache calls this callback again after the entry write has finished.
132 // This gives the consumer a chance to recheck completeness of the entry
133 // again.
134 // Thus, we reset state as onCheck would have never been called.
135 this.onCheckPassed = false;
136 // Don't return RECHECK_AFTER_WRITE_FINISHED on second call of onCacheEntryCheck.
137 this.behavior &= ~COMPLETE;
138 return Ci.nsICacheEntryOpenCallback.RECHECK_AFTER_WRITE_FINISHED;
141 LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_WANTED");
142 return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
144 onCacheEntryAvailable(entry, isnew, status) {
145 if (this.behavior & MAYBE_NEW && isnew) {
146 this.behavior |= NEW;
149 LOG_C2(this, "onCacheEntryAvailable, " + this.behavior);
150 Assert.ok(!this.onAvailPassed);
151 this.onAvailPassed = true;
153 Assert.equal(isnew, !!(this.behavior & NEW));
155 if (this.behavior & (NOTFOUND | NOTWANTED)) {
156 Assert.equal(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
157 Assert.ok(!entry);
158 if (this.behavior & THROWAVAIL) {
159 this.throwAndNotify(entry);
161 this.goon(entry);
162 } else if (this.behavior & (NEW | RECREATE)) {
163 Assert.ok(!!entry);
165 if (this.behavior & RECREATE) {
166 entry = entry.recreate();
167 Assert.ok(!!entry);
170 if (this.behavior & THROWAVAIL) {
171 this.throwAndNotify(entry);
174 if (!(this.behavior & WAITFORWRITE)) {
175 this.goon(entry);
178 if (!(this.behavior & PARTIAL)) {
179 try {
180 entry.getMetaDataElement("meto");
181 Assert.ok(false);
182 } catch (ex) {}
185 if (this.behavior & DONTFILL) {
186 Assert.equal(false, this.behavior & WAITFORWRITE);
187 return;
190 let self = this;
191 executeSoon(function () {
192 // emulate network latency
193 entry.setMetaDataElement("meto", self.workingMetadata);
194 entry.metaDataReady();
195 if (self.behavior & METAONLY) {
196 // Since forcing GC/CC doesn't trigger OnWriterClosed, we have to set the entry valid manually :(
197 if (!(self.behavior & DONTSETVALID)) {
198 entry.setValid();
201 if (self.behavior & WAITFORWRITE) {
202 self.goon(entry);
205 return;
207 executeSoon(function () {
208 // emulate more network latency
209 if (self.behavior & DOOMED) {
210 LOG_C2(self, "checking doom state");
211 try {
212 let os = entry.openOutputStream(0, -1);
213 // Unfortunately, in the undetermined state we cannot even check whether the entry
214 // is actually doomed or not.
215 os.close();
216 Assert.ok(!!(self.behavior & MAYBE_NEW));
217 } catch (ex) {
218 Assert.ok(true);
220 if (self.behavior & WAITFORWRITE) {
221 self.goon(entry);
223 return;
226 var offset = self.behavior & PARTIAL ? entry.dataSize : 0;
227 LOG_C2(self, "openOutputStream @ " + offset);
228 let os = entry.openOutputStream(offset, -1);
229 LOG_C2(self, "writing data");
230 var wrt = os.write(self.workingData, self.workingData.length);
231 Assert.equal(wrt, self.workingData.length);
232 os.close();
233 if (self.behavior & WAITFORWRITE) {
234 self.goon(entry);
238 } else {
239 // NORMAL
240 Assert.ok(!!entry);
241 Assert.equal(entry.getMetaDataElement("meto"), this.workingMetadata);
242 if (this.behavior & THROWAVAIL) {
243 this.throwAndNotify(entry);
245 if (this.behavior & NOTIFYBEFOREREAD) {
246 this.goon(entry, true);
249 let self = this;
250 pumpReadStream(entry.openInputStream(0), function (data) {
251 Assert.equal(data, self.workingData);
252 self.onDataCheckPassed = true;
253 LOG_C2(self, "entry read done");
254 self.goon(entry);
258 selfCheck() {
259 LOG_C2(this, "selfCheck");
261 Assert.ok(this.onCheckPassed || this.behavior & MAYBE_NEW);
262 Assert.ok(this.onAvailPassed);
263 Assert.ok(this.onDataCheckPassed || this.behavior & MAYBE_NEW);
265 throwAndNotify(entry) {
266 LOG_C2(this, "Throwing");
267 var self = this;
268 executeSoon(function () {
269 LOG_C2(self, "Notifying");
270 self.goon(entry);
272 throw Components.Exception("", Cr.NS_ERROR_FAILURE);
276 function OpenCallback(behavior, workingMetadata, workingData, goon) {
277 this.behavior = behavior;
278 this.workingMetadata = workingMetadata;
279 this.workingData = workingData;
280 this.goon = goon;
281 this.onCheckPassed =
282 (!!(behavior & (NEW | RECREATE)) || !workingMetadata) &&
283 !(behavior & NOTVALID);
284 this.onAvailPassed = false;
285 this.onDataCheckPassed =
286 !!(behavior & (NEW | RECREATE | NOTWANTED)) || !workingMetadata;
287 callbacks.push(this);
288 this.order = callbacks.length;
291 VisitCallback.prototype = {
292 QueryInterface: ChromeUtils.generateQI(["nsICacheStorageVisitor"]),
293 onCacheStorageInfo(num, consumption) {
294 LOG_C2(this, "onCacheStorageInfo: num=" + num + ", size=" + consumption);
295 Assert.equal(this.num, num);
296 Assert.equal(this.consumption, consumption);
297 if (!this.entries) {
298 this.notify();
301 onCacheEntryInfo(
302 aURI,
303 aIdEnhance,
304 aDataSize,
305 aAltDataSize,
306 aFetchCount,
307 aLastModifiedTime,
308 aExpirationTime,
309 aPinned,
310 aInfo
312 var key = (aIdEnhance ? aIdEnhance + ":" : "") + aURI.asciiSpec;
313 LOG_C2(this, "onCacheEntryInfo: key=" + key);
315 function findCacheIndex(element) {
316 if (typeof element === "string") {
317 return element === key;
318 } else if (typeof element === "object") {
319 return (
320 element.uri === key &&
321 element.lci.isAnonymous === aInfo.isAnonymous &&
322 ChromeUtils.isOriginAttributesEqual(
323 element.lci.originAttributes,
324 aInfo.originAttributes
329 return false;
332 Assert.ok(!!this.entries);
334 var index = this.entries.findIndex(findCacheIndex);
335 Assert.ok(index > -1);
337 this.entries.splice(index, 1);
339 onCacheEntryVisitCompleted() {
340 LOG_C2(this, "onCacheEntryVisitCompleted");
341 if (this.entries) {
342 Assert.equal(this.entries.length, 0);
344 this.notify();
346 notify() {
347 Assert.ok(!!this.goon);
348 var goon = this.goon;
349 this.goon = null;
350 executeSoon(goon);
352 selfCheck() {
353 Assert.ok(!this.entries || !this.entries.length);
357 function VisitCallback(num, consumption, entries, goon) {
358 this.num = num;
359 this.consumption = consumption;
360 this.entries = entries;
361 this.goon = goon;
362 callbacks.push(this);
363 this.order = callbacks.length;
366 EvictionCallback.prototype = {
367 QueryInterface: ChromeUtils.generateQI(["nsICacheEntryDoomCallback"]),
368 onCacheEntryDoomed(result) {
369 Assert.equal(this.expectedSuccess, result == Cr.NS_OK);
370 this.goon();
372 selfCheck() {},
375 function EvictionCallback(success, goon) {
376 this.expectedSuccess = success;
377 this.goon = goon;
378 callbacks.push(this);
379 this.order = callbacks.length;
382 MultipleCallbacks.prototype = {
383 fired() {
384 if (--this.pending == 0) {
385 var self = this;
386 if (this.delayed) {
387 executeSoon(function () {
388 self.goon();
390 } else {
391 this.goon();
395 add() {
396 ++this.pending;
400 function MultipleCallbacks(number, goon, delayed) {
401 this.pending = number;
402 this.goon = goon;
403 this.delayed = delayed;
406 function wait_for_cache_index(continue_func) {
407 // This callback will not fire before the index is in the ready state. nsICacheStorage.exists() will
408 // no longer throw after this point.
409 Services.cache2.asyncGetDiskConsumption({
410 onNetworkCacheDiskConsumption() {
411 continue_func();
413 // eslint-disable-next-line mozilla/use-chromeutils-generateqi
414 QueryInterface() {
415 return this;
420 function finish_cache2_test() {
421 callbacks.forEach(function (callback) {
422 callback.selfCheck();
424 do_test_finished();