Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / chrome / test / data / indexeddb / perf_shared.js
blob41681e18a834602b8549996a662d4b02017df134
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 var automation = {
6   results: {}
7 };
9 automation.setDone = function() {
10   this.setStatus("Test complete.");
11   document.cookie = '__done=1; path=/';
14 automation.addResult = function(name, result) {
15   result = "" + result;
16   this.results[name] = result;
17   var elt = document.getElementById('results');
18   var div = document.createElement('div');
19   div.textContent = name + ": " + result;
20   elt.appendChild(div);
23 automation.getResults = function() {
24   return this.results;
27 automation.setStatus = function(s) {
28   document.getElementById('status').textContent = s;
31 function assert(t) {
32   if (!t) {
33     var e = new Error("Assertion failed!");
34     console.log(e.stack);
35     throw e;
36   }
39 function onError(e) {
40   var s = "Caught error.";
41   if (e.target && e.target.error)
42     s += "\n" + e.target.error.name + "\n" + e.target.error.message;
43   console.log(s);
44   automation.setStatus(s);
45   e.stopPropagation();
46   throw new Error(e);
49 var baseVersion = 2;  // The version with our object stores.
50 var curVersion;
52 // Valid options fields:
53 //  indexName: the name of an index to create on each object store
54 //  indexKeyPath: the key path for that index
55 //  indexIsUnique: the "unique" option for IDBIndexParameters
56 //  indexIsMultiEntry: the "multiEntry" option for IDBIndexParameters
58 function createDatabase(
59     name, objectStoreNames, handler, errorHandler, optionSets) {
60   var openRequest = indexedDB.open(name, baseVersion);
61   openRequest.onblocked = errorHandler;
62   openRequest.onerror = errorHandler;
63   function createObjectStores(db) {
64     for (var store in objectStoreNames) {
65       var name = objectStoreNames[store];
66       assert(!db.objectStoreNames.contains(name));
67       var os = db.createObjectStore(name);
68       if (optionSets) {
69         for (o in optionSets) {
70           var options = optionSets[o];
71           assert(options.indexName);
72           assert('indexKeyPath' in options);
73           os.createIndex(options.indexName, options.indexKeyPath,
74               { unique: options.indexIsUnique,
75                 multiEntry: options.indexIsMultiEntry });
76         }
77       }
78     }
79   }
80   openRequest.onupgradeneeded = function(ev) {
81     // This is the spec-compliant path, which doesn't yet run in Chrome, but
82     // works in Firefox.
83     assert(openRequest == ev.target);
84     var db = openRequest.result;
85     db.onerror = errorHandler;
86     createObjectStores(db);
87     // onsuccess will get called after this exits.
88   };
89   openRequest.onsuccess = function(ev) {
90     assert(openRequest == ev.target);
91     var db = openRequest.result;
92     curVersion = db.version;
93     db.onerror = function(ev) {
94       console.log("db error", arguments, openRequest.error.message);
95       errorHandler(ev);
96     };
97     if (curVersion != baseVersion) {
98       // This is the legacy path, which runs only in Chrome.
99       var setVersionRequest = db.setVersion(baseVersion);
100       setVersionRequest.onerror = errorHandler;
101       setVersionRequest.onsuccess = function(e) {
102         assert(setVersionRequest == e.target);
103         createObjectStores(db);
104         var versionTransaction = setVersionRequest.result;
105         versionTransaction.oncomplete = function() { handler(db); };
106         versionTransaction.onerror = onError;
107       };
108     } else {
109       handler(db);
110     }
111   };
114 // You must close all database connections before calling this.
115 function alterObjectStores(
116     name, objectStoreNames, func, handler, errorHandler) {
117   var version = curVersion + 1;
118   var openRequest = indexedDB.open(name, version);
119   openRequest.onblocked = errorHandler;
120   openRequest.onupgradeneeded = function(ev) {
121     doAlteration(ev.target.transaction);
122     // onsuccess will get called after this exits.
123   };
124   openRequest.onsuccess = function(ev) {
125     assert(openRequest == ev.target);
126     var db = openRequest.result;
127     db.onerror = function(ev) {
128       console.log("error altering db", arguments,
129           openRequest.error.message);
130       errorHandler();
131     };
132     if (db.version != version) {
133       // This is the legacy path, which runs only in Chrome before M23.
134       var setVersionRequest = db.setVersion(version);
135       setVersionRequest.onerror = errorHandler;
136       setVersionRequest.onsuccess =
137           function(e) {
138             curVersion = db.version;
139             assert(setVersionRequest == e.target);
140             var versionTransaction = setVersionRequest.result;
141             versionTransaction.oncomplete = function() { handler(db); };
142             versionTransaction.onerror = onError;
143             doAlteration(versionTransaction);
144           };
145     } else {
146       handler(db);
147     }
148   };
149   function doAlteration(target) {
150     for (var store in objectStoreNames) {
151       func(target.objectStore(objectStoreNames[store]));
152     }
153   }
156 function getTransaction(db, objectStoreNames, mode, opt_handler) {
157   var transaction = db.transaction(objectStoreNames, mode);
158   transaction.onerror = onError;
159   transaction.onabort = onError;
160   if (opt_handler) {
161     transaction.oncomplete = opt_handler;
162   }
163   return transaction;
166 function deleteDatabase(name, opt_handler) {
167   var deleteRequest = indexedDB.deleteDatabase(name);
168   deleteRequest.onerror = onError;
169   deleteRequest.onblocked = onError;
170   if (opt_handler) {
171     deleteRequest.onsuccess = opt_handler;
172   }
175 function getCompletionFunc(db, testName, startTime, onTestComplete) {
176   function onDeleted() {
177     automation.setStatus("Deleted database.");
178     onTestComplete();
179   }
180   return function() {
181     var duration = window.performance.now() - startTime;
182     // Ignore the cleanup time for this test.
183     automation.addResult(testName, duration);
184     automation.setStatus("Deleting database.");
185     db.close();
186     deleteDatabase(testName, onDeleted);
187   };
190 function getDisplayName(args) {
191   function functionName(f) {
192     // Function.prototype.name is nonstandard, and not implemented in IE10-
193     return f.name || f.toString().match(/^function\s*([^(\s]*)/)[1];
194   }
195   // The last arg is the completion callback the test runner tacks on.
196   // TODO(ericu): Make test errors delete the database automatically.
197   return functionName(getDisplayName.caller) + (args.length > 1 ? "_" : "") +
198       Array.prototype.slice.call(args, 0, args.length - 1).join("_");
201 // Pad a string [or object convertible to a string] to a fixed width; use this
202 // to have numeric strings sort properly.
203 function padToWidth(s, width) {
204   s = String(s);
205   assert(s.length <= width);
206   if (s.length < width) {
207     s = stringOfLength(width - s.length, '0') + s;
208   }
209   return s;
212 function stringOfLength(n, c) {
213   if (c == null)
214     c = 'X';
215   assert(n > 0);
216   assert(n == Math.floor(n));
217   return new Array(n + 1).join(c);
220 function getSimpleKey(i) {
221   return "key " + padToWidth(i, 10);
224 function getSimpleValue(i) {
225   return "value " + padToWidth(i, 10);
228 function getIndexableValue(i) {
229   return { id: getSimpleValue(i) };
232 function getForwardIndexKey(i) {
233   return i;
236 function getBackwardIndexKey(i) {
237   return -i;
240 // This is useful for indexing by keypath; the two names should be ordered in
241 // opposite directions for all i in uint32 range.
242 function getObjectValue(i) {
243   return {
244     firstName: getForwardIndexKey(i),
245     lastName: getBackwardIndexKey(i)
246   };
249 function getNFieldName(k) {
250   return "field" + k;
253 function getNFieldObjectValue(i, n) {
254   assert(Math.floor(n) == n);
255   assert(n > 0);
256   var o = {};
257   for (; n > 0; --n) {
258     // The value varies per field, each object will tend to be unique,
259     // and thanks to the modulus, indexing on different fields will give you
260     // different ordering for large-enough data sets.
261     o[getNFieldName(n - 1)] = Math.pow(i + 0.5, n + 0.5) % 65536;
262   }
263   return o;
266 function putLinearValues(
267     transaction, objectStoreNames, numKeys, getKey, getValue) {
268   if (!getKey)
269     getKey = getSimpleKey;
270   if (!getValue)
271     getValue = getSimpleValue;
272   for (var i in objectStoreNames) {
273     var os = transaction.objectStore(objectStoreNames[i]);
274     for (var j = 0; j < numKeys; ++j) {
275       var request = os.put(getValue(j), getKey(j));
276       request.onerror = onError;
277     }
278   }
281 function verifyResultNonNull(result) {
282   assert(result != null);
285 function getRandomValues(
286     transaction, objectStoreNames, numReads, numKeys, indexName, getKey) {
287   if (!getKey)
288     getKey = getSimpleKey;
289   for (var i in objectStoreNames) {
290     var os = transaction.objectStore(objectStoreNames[i]);
291     var source = os;
292     if (indexName)
293       source = source.index(indexName);
294     for (var j = 0; j < numReads; ++j) {
295       var rand = Math.floor(random() * numKeys);
296       var request = source.get(getKey(rand));
297       request.onerror = onError;
298       request.onsuccess = verifyResultNonNull;
299     }
300   }
303 function putRandomValues(
304     transaction, objectStoreNames, numPuts, numKeys, getKey, getValue) {
305   if (!getKey)
306     getKey = getSimpleKey;
307   if (!getValue)
308     getValue = getSimpleValue;
309   for (var i in objectStoreNames) {
310     var os = transaction.objectStore(objectStoreNames[i]);
311     for (var j = 0; j < numPuts; ++j) {
312       var rand = Math.floor(random() * numKeys);
313       var request = os.put(getValue(rand), getKey(rand));
314       request.onerror = onError;
315     }
316   }
319 function getSpecificValues(transaction, objectStoreNames, indexName, keys) {
320   for (var i in objectStoreNames) {
321     var os = transaction.objectStore(objectStoreNames[i]);
322     var source = os;
323     if (indexName)
324       source = source.index(indexName);
325     for (var j = 0; j < keys.length; ++j) {
326       var request = source.get(keys[j]);
327       request.onerror = onError;
328       request.onsuccess = verifyResultNonNull;
329     }
330   }
333 // getKey should be deterministic, as we assume that a cursor that starts at
334 // getKey(X) and runs through getKey(X + K) has exactly K values available.
335 // This is annoying to guarantee generally when using an index, so we avoid both
336 // ends of the key space just in case and use simple indices.
337 // TODO(ericu): Figure out if this can be simplified and we can remove uses of
338 // getObjectValue in favor of getNFieldObjectValue.
339 function getValuesFromCursor(
340     transaction, inputObjectStoreName, numReads, numKeys, indexName, getKey,
341     readKeysOnly, outputObjectStoreName) {
342   assert(2 * numReads < numKeys);
343   if (!getKey)
344     getKey = getSimpleKey;
345   var rand = Math.floor(random() * (numKeys - 2 * numReads)) + numReads;
346   var values = [];
347   var queryObject = transaction.objectStore(inputObjectStoreName);
348   assert(queryObject);
349   if (indexName)
350     queryObject = queryObject.index(indexName);
351   var keyRange = IDBKeyRange.bound(
352       getKey(rand), getKey(rand + numReads), false, true);
353   var request;
354   if (readKeysOnly) {
355     request = queryObject.openKeyCursor(keyRange);
356   } else {
357     request = queryObject.openCursor(keyRange);
358   }
359   var oos;
360   if (outputObjectStoreName)
361     oos = transaction.objectStore(outputObjectStoreName);
362   var numReadsLeft = numReads;
363   request.onsuccess = function(event) {
364     var cursor = event.target.result;
365     if (cursor) {
366       assert(numReadsLeft);
367       --numReadsLeft;
368       if (oos)
369         // Put in random order for maximum difficulty.  We add in numKeys just
370         // in case we're writing back to the same store; this way we won't
371         // affect the number of keys available to the cursor, since we're always
372         // outside its range.
373         oos.put(cursor.value, numKeys + random());
374       values.push({key: cursor.key, value: cursor.value});
375       cursor.continue();
376     } else {
377       assert(!numReadsLeft);
378     }
379   };
380   request.onerror = onError;
383 function runTransactionBatch(db, count, batchFunc, objectStoreNames, mode,
384     onComplete) {
385   var numTransactionsRunning = 0;
387   runOneBatch(db);
389   function runOneBatch(db) {
390     if (count <= 0) {
391       return;
392     }
393     --count;
394     ++numTransactionsRunning;
395     var transaction = getTransaction(db, objectStoreNames, mode,
396         function() {
397           assert(!--numTransactionsRunning);
398           if (count <= 0) {
399             onComplete();
400           } else {
401             runOneBatch(db);
402           }
403         });
405     batchFunc(transaction);
406   }
409 // Use random() instead of Math.random() so runs are repeatable.
410 var random = (function(seed) {
412   // Implementation of: http://www.burtleburtle.net/bob/rand/smallprng.html
413   function uint32(x) { return x >>> 0; }
414   function rot(x, k) { return (x << k) | (x >> (32 - k)); }
416   function SmallPRNG(seed) {
417     seed = uint32(seed);
418     this.a = 0xf1ea5eed;
419     this.b = this.c = this.d = seed;
420     for (var i = 0; i < 20; ++i)
421       this.ranval();
422   }
424   SmallPRNG.prototype.ranval = function() {
425     var e = uint32(this.a - rot(this.b, 27));
426     this.a = this.b ^ rot(this.c, 17);
427     this.b = uint32(this.c + this.d);
428     this.c = uint32(this.d + e);
429     this.d = uint32(e + this.a);
430     return this.d;
431   };
433   var prng = new SmallPRNG(seed);
434   return function() { return prng.ranval() / 0x100000000; };
435 }(0));