netwerk/test/unit/head_cache2.js

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:6138a561ac21
1 const Cc = Components.classes;
2 const Ci = Components.interfaces;
3 const Cu = Components.utils;
4 const Cr = Components.results;
5
6 function newCacheBackEndUsed()
7 {
8 var cache1srv = Components.classes["@mozilla.org/network/cache-service;1"]
9 .getService(Components.interfaces.nsICacheService);
10 var cache2srv = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
11 .getService(Components.interfaces.nsICacheStorageService);
12
13 return cache1srv.cacheIOTarget != cache2srv.ioTarget;
14 }
15
16 var callbacks = new Array();
17
18 // Expect an existing entry
19 const NORMAL = 0;
20 // Expect a new entry
21 const NEW = 1 << 0;
22 // Return early from onCacheEntryCheck and set the callback to state it expects onCacheEntryCheck to happen
23 const NOTVALID = 1 << 1;
24 // Throw from onCacheEntryAvailable
25 const THROWAVAIL = 1 << 2;
26 // Open entry for reading-only
27 const READONLY = 1 << 3;
28 // Expect the entry to not be found
29 const NOTFOUND = 1 << 4;
30 // Return ENTRY_NEEDS_REVALIDATION from onCacheEntryCheck
31 const REVAL = 1 << 5;
32 // Return ENTRY_PARTIAL from onCacheEntryCheck, in combo with NEW or RECREATE bypasses check for emptiness of the entry
33 const PARTIAL = 1 << 6
34 // Expect the entry is doomed, i.e. the output stream should not be possible to open
35 const DOOMED = 1 << 7;
36 // Don't trigger the go-on callback until the entry is written
37 const WAITFORWRITE = 1 << 8;
38 // Don't write data (i.e. don't open output stream)
39 const METAONLY = 1 << 9;
40 // Do recreation of an existing cache entry
41 const RECREATE = 1 << 10;
42 // Do not give me the entry
43 const NOTWANTED = 1 << 11;
44 // Tell the cache to wait for the entry to be completely written first
45 const COMPLETE = 1 << 12;
46
47 var log_c2 = true;
48 function LOG_C2(o, m)
49 {
50 if (!log_c2) return;
51 if (!m)
52 dump("TEST-INFO | CACHE2: " + o + "\n");
53 else
54 dump("TEST-INFO | CACHE2: callback #" + o.order + "(" + (o.workingData || "---") + ") " + m + "\n");
55 }
56
57 function pumpReadStream(inputStream, goon)
58 {
59 if (inputStream.isNonBlocking()) {
60 // non-blocking stream, must read via pump
61 var pump = Cc["@mozilla.org/network/input-stream-pump;1"]
62 .createInstance(Ci.nsIInputStreamPump);
63 pump.init(inputStream, -1, -1, 0, 0, true);
64 var data = "";
65 pump.asyncRead({
66 onStartRequest: function (aRequest, aContext) { },
67 onDataAvailable: function (aRequest, aContext, aInputStream, aOffset, aCount)
68 {
69 var wrapper = Cc["@mozilla.org/scriptableinputstream;1"].
70 createInstance(Ci.nsIScriptableInputStream);
71 wrapper.init(aInputStream);
72 var str = wrapper.read(wrapper.available());
73 LOG_C2("reading data '" + str.substring(0,5) + "'");
74 data += str;
75 },
76 onStopRequest: function (aRequest, aContext, aStatusCode)
77 {
78 LOG_C2("done reading data: " + aStatusCode);
79 do_check_eq(aStatusCode, Cr.NS_OK);
80 goon(data);
81 },
82 }, null);
83 }
84 else {
85 // blocking stream
86 var data = read_stream(inputStream, inputStream.available());
87 goon(data);
88 }
89 }
90
91 OpenCallback.prototype =
92 {
93 QueryInterface: function listener_qi(iid) {
94 if (iid.equals(Ci.nsISupports) ||
95 iid.equals(Ci.nsICacheEntryOpenCallback)) {
96 return this;
97 }
98 throw Components.results.NS_ERROR_NO_INTERFACE;
99 },
100 onCacheEntryCheck: function(entry, appCache)
101 {
102 LOG_C2(this, "onCacheEntryCheck");
103 do_check_true(!this.onCheckPassed);
104 this.onCheckPassed = true;
105
106 if (this.behavior & NOTVALID) {
107 LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_WANTED");
108 return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
109 }
110
111 if (this.behavior & NOTWANTED) {
112 LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_NOT_WANTED");
113 return Ci.nsICacheEntryOpenCallback.ENTRY_NOT_WANTED;
114 }
115
116 do_check_eq(entry.getMetaDataElement("meto"), this.workingMetadata);
117
118 // check for sane flag combination
119 do_check_neq(this.behavior & (REVAL|PARTIAL), REVAL|PARTIAL);
120
121 if (this.behavior & (REVAL|PARTIAL)) {
122 LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_NEEDS_REVALIDATION");
123 return Ci.nsICacheEntryOpenCallback.ENTRY_NEEDS_REVALIDATION;
124 }
125
126 if (this.behavior & COMPLETE) {
127 LOG_C2(this, "onCacheEntryCheck DONE, return RECHECK_AFTER_WRITE_FINISHED");
128 if (newCacheBackEndUsed()) {
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 }
139 return Ci.nsICacheEntryOpenCallback.RECHECK_AFTER_WRITE_FINISHED;
140 }
141
142 LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_WANTED");
143 return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
144 },
145 onCacheEntryAvailable: function(entry, isnew, appCache, status)
146 {
147 LOG_C2(this, "onCacheEntryAvailable, " + this.behavior);
148 do_check_true(!this.onAvailPassed);
149 this.onAvailPassed = true;
150
151 do_check_eq(isnew, !!(this.behavior & NEW));
152
153 if (this.behavior & (NOTFOUND|NOTWANTED)) {
154 do_check_eq(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
155 do_check_false(!!entry);
156 if (this.behavior & THROWAVAIL)
157 this.throwAndNotify(entry);
158 this.goon(entry);
159 }
160 else if (this.behavior & (NEW|RECREATE)) {
161 do_check_true(!!entry);
162
163 if (this.behavior & RECREATE) {
164 entry = entry.recreate();
165 do_check_true(!!entry);
166 }
167
168 if (this.behavior & THROWAVAIL)
169 this.throwAndNotify(entry);
170
171 if (!(this.behavior & WAITFORWRITE))
172 this.goon(entry);
173
174 if (!(this.behavior & PARTIAL)) {
175 try {
176 entry.getMetaDataElement("meto");
177 do_check_true(false);
178 }
179 catch (ex) {}
180 }
181
182 var self = this;
183 do_execute_soon(function() { // emulate network latency
184 entry.setMetaDataElement("meto", self.workingMetadata);
185 entry.metaDataReady();
186 if (self.behavior & METAONLY) {
187 // Since forcing GC/CC doesn't trigger OnWriterClosed, we have to set the entry valid manually :(
188 entry.setValid();
189 entry.close();
190 return;
191 }
192 do_execute_soon(function() { // emulate more network latency
193 if (self.behavior & DOOMED) {
194 try {
195 var os = entry.openOutputStream(0);
196 do_check_true(false);
197 } catch (ex) {
198 do_check_true(true);
199 }
200 if (self.behavior & WAITFORWRITE)
201 self.goon(entry);
202 return;
203 }
204
205 var offset = (self.behavior & PARTIAL)
206 ? entry.dataSize
207 : 0;
208 LOG_C2(self, "openOutputStream @ " + offset);
209 var os = entry.openOutputStream(offset);
210 LOG_C2(self, "writing data");
211 var wrt = os.write(self.workingData, self.workingData.length);
212 do_check_eq(wrt, self.workingData.length);
213 os.close();
214 if (self.behavior & WAITFORWRITE)
215 self.goon(entry);
216
217 entry.close();
218 })
219 })
220 }
221 else { // NORMAL
222 do_check_true(!!entry);
223 do_check_eq(entry.getMetaDataElement("meto"), this.workingMetadata);
224 if (this.behavior & THROWAVAIL)
225 this.throwAndNotify(entry);
226
227 var wrapper = Cc["@mozilla.org/scriptableinputstream;1"].
228 createInstance(Ci.nsIScriptableInputStream);
229 var self = this;
230 pumpReadStream(entry.openInputStream(0), function(data) {
231 do_check_eq(data, self.workingData);
232 self.onDataCheckPassed = true;
233 LOG_C2(self, "entry read done");
234 self.goon(entry);
235 entry.close();
236 });
237 }
238 },
239 selfCheck: function()
240 {
241 LOG_C2(this, "selfCheck");
242
243 do_check_true(this.onCheckPassed);
244 do_check_true(this.onAvailPassed);
245 do_check_true(this.onDataCheckPassed);
246 },
247 throwAndNotify: function(entry)
248 {
249 LOG_C2(this, "Throwing");
250 var self = this;
251 do_execute_soon(function() {
252 LOG_C2(self, "Notifying");
253 self.goon(entry);
254 });
255 throw Cr.NS_ERROR_FAILURE;
256 }
257 };
258
259 function OpenCallback(behavior, workingMetadata, workingData, goon)
260 {
261 this.behavior = behavior;
262 this.workingMetadata = workingMetadata;
263 this.workingData = workingData;
264 this.goon = goon;
265 this.onCheckPassed = (!!(behavior & (NEW|RECREATE)) || !workingMetadata) && !(behavior & NOTVALID);
266 this.onAvailPassed = false;
267 this.onDataCheckPassed = !!(behavior & (NEW|RECREATE|NOTWANTED)) || !workingMetadata;
268 callbacks.push(this);
269 this.order = callbacks.length;
270 }
271
272 VisitCallback.prototype =
273 {
274 QueryInterface: function listener_qi(iid) {
275 if (iid.equals(Ci.nsISupports) ||
276 iid.equals(Ci.nsICacheStorageVisitor)) {
277 return this;
278 }
279 throw Components.results.NS_ERROR_NO_INTERFACE;
280 },
281 onCacheStorageInfo: function(num, consumption)
282 {
283 LOG_C2(this, "onCacheStorageInfo: num=" + num + ", size=" + consumption);
284 do_check_eq(this.num, num);
285 if (newCacheBackEndUsed()) {
286 // Consumption is as expected only in the new backend
287 do_check_eq(this.consumption, consumption);
288 }
289 if (!this.entries)
290 this.notify();
291 },
292 onCacheEntryInfo: function(entry)
293 {
294 var key = entry.key;
295 LOG_C2(this, "onCacheEntryInfo: key=" + key);
296
297 do_check_true(!!this.entries);
298
299 var index = this.entries.indexOf(key);
300 do_check_true(index > -1);
301
302 this.entries.splice(index, 1);
303 },
304 onCacheEntryVisitCompleted: function()
305 {
306 LOG_C2(this, "onCacheEntryVisitCompleted");
307 if (this.entries)
308 do_check_eq(this.entries.length, 0);
309 this.notify();
310 },
311 notify: function()
312 {
313 do_check_true(!!this.goon);
314 var goon = this.goon;
315 this.goon = null;
316 do_execute_soon(goon);
317 },
318 selfCheck: function()
319 {
320 do_check_true(!this.entries || !this.entries.length);
321 }
322 };
323
324 function VisitCallback(num, consumption, entries, goon)
325 {
326 this.num = num;
327 this.consumption = consumption;
328 this.entries = entries;
329 this.goon = goon;
330 callbacks.push(this);
331 this.order = callbacks.length;
332 }
333
334 EvictionCallback.prototype =
335 {
336 QueryInterface: function listener_qi(iid) {
337 if (iid.equals(Ci.nsISupports) ||
338 iid.equals(Ci.nsICacheEntryDoomCallback)) {
339 return this;
340 }
341 throw Components.results.NS_ERROR_NO_INTERFACE;
342 },
343 onCacheEntryDoomed: function(result)
344 {
345 do_check_eq(this.expectedSuccess, result == Cr.NS_OK);
346 this.goon();
347 },
348 selfCheck: function() {}
349 }
350
351 function EvictionCallback(success, goon)
352 {
353 this.expectedSuccess = success;
354 this.goon = goon;
355 callbacks.push(this);
356 this.order = callbacks.length;
357 }
358
359 MultipleCallbacks.prototype =
360 {
361 fired: function()
362 {
363 if (--this.pending == 0)
364 {
365 var self = this;
366 if (this.delayed)
367 do_execute_soon(function() { self.goon(); });
368 else
369 this.goon();
370 }
371 }
372 }
373
374 function MultipleCallbacks(number, goon, delayed)
375 {
376 this.pending = number;
377 this.goon = goon;
378 this.delayed = delayed;
379 }
380
381 function finish_cache2_test()
382 {
383 callbacks.forEach(function(callback, index) {
384 callback.selfCheck();
385 });
386 do_test_finished();
387 }

mercurial