Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / test / data / indexeddb / perf_shared.js
blobee2f858f11bab86cfba72132ba7ef4e52e8a83d6
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 window.indexedDB = window.indexedDB || window.webkitIndexedDB ||
6 window.mozIndexedDB || window.msIndexedDB;
7 window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
9 var automation = {
10 results: {}
13 automation.setDone = function() {
14 this.setStatus("Test complete.");
15 document.cookie = '__done=1; path=/';
18 automation.addResult = function(name, result) {
19 result = "" + result;
20 this.results[name] = result;
21 var elt = document.getElementById('results');
22 var div = document.createElement('div');
23 div.textContent = name + ": " + result;
24 elt.appendChild(div);
27 automation.getResults = function() {
28 return this.results;
31 automation.setStatus = function(s) {
32 document.getElementById('status').textContent = s;
35 function assert(t) {
36 if (!t) {
37 var e = new Error("Assertion failed!");
38 console.log(e.stack);
39 throw e;
43 function onError(e) {
44 var s = "Caught error.";
45 if (e.target && e.target.webkitErrorMessage)
46 s += "\n" + e.target.webkitErrorMessage;
47 else if (e.target && e.target.error)
48 s += "\n" + e.target.error.name;
49 console.log(s);
50 automation.setStatus(s);
51 e.stopPropagation();
52 throw new Error(e);
55 var baseVersion = 2; // The version with our object stores.
56 var curVersion;
58 // Valid options fields:
59 // indexName: the name of an index to create on each object store
60 // indexKeyPath: the key path for that index
61 // indexIsUnique: the "unique" option for IDBIndexParameters
62 // indexIsMultiEntry: the "multiEntry" option for IDBIndexParameters
64 function createDatabase(
65 name, objectStoreNames, handler, errorHandler, optionSets) {
66 var openRequest = indexedDB.open(name, baseVersion);
67 openRequest.onblocked = errorHandler;
68 openRequest.onerror = errorHandler;
69 function createObjectStores(db) {
70 for (var store in objectStoreNames) {
71 var name = objectStoreNames[store];
72 assert(!db.objectStoreNames.contains(name));
73 var os = db.createObjectStore(name);
74 if (optionSets) {
75 for (o in optionSets) {
76 var options = optionSets[o];
77 assert(options.indexName);
78 assert('indexKeyPath' in options);
79 os.createIndex(options.indexName, options.indexKeyPath,
80 { unique: options.indexIsUnique,
81 multiEntry: options.indexIsMultiEntry });
86 openRequest.onupgradeneeded = function(ev) {
87 // This is the spec-compliant path, which doesn't yet run in Chrome, but
88 // works in Firefox.
89 assert(openRequest == ev.target);
90 var db = openRequest.result;
91 db.onerror = errorHandler;
92 createObjectStores(db);
93 // onsuccess will get called after this exits.
95 openRequest.onsuccess = function(ev) {
96 assert(openRequest == ev.target);
97 var db = openRequest.result;
98 curVersion = db.version;
99 db.onerror = function(ev) {
100 console.log("db error", arguments, openRequest.webkitErrorMessage);
101 errorHandler(ev);
103 if (curVersion != baseVersion) {
104 // This is the legacy path, which runs only in Chrome.
105 var setVersionRequest = db.setVersion(baseVersion);
106 setVersionRequest.onerror = errorHandler;
107 setVersionRequest.onsuccess = function(e) {
108 assert(setVersionRequest == e.target);
109 createObjectStores(db);
110 var versionTransaction = setVersionRequest.result;
111 versionTransaction.oncomplete = function() { handler(db); };
112 versionTransaction.onerror = onError;
114 } else {
115 handler(db);
120 // You must close all database connections before calling this.
121 function alterObjectStores(
122 name, objectStoreNames, func, handler, errorHandler) {
123 var version = curVersion + 1;
124 var openRequest = indexedDB.open(name, version);
125 openRequest.onblocked = errorHandler;
126 openRequest.onupgradeneeded = function(ev) {
127 doAlteration(ev.target.transaction);
128 // onsuccess will get called after this exits.
130 openRequest.onsuccess = function(ev) {
131 assert(openRequest == ev.target);
132 var db = openRequest.result;
133 db.onerror = function(ev) {
134 console.log("error altering db", arguments,
135 openRequest.webkitErrorMessage);
136 errorHandler();
138 if (db.version != version) {
139 // This is the legacy path, which runs only in Chrome before M23.
140 var setVersionRequest = db.setVersion(version);
141 setVersionRequest.onerror = errorHandler;
142 setVersionRequest.onsuccess =
143 function(e) {
144 curVersion = db.version;
145 assert(setVersionRequest == e.target);
146 var versionTransaction = setVersionRequest.result;
147 versionTransaction.oncomplete = function() { handler(db); };
148 versionTransaction.onerror = onError;
149 doAlteration(versionTransaction);
151 } else {
152 handler(db);
155 function doAlteration(target) {
156 for (var store in objectStoreNames) {
157 func(target.objectStore(objectStoreNames[store]));
162 function getTransaction(db, objectStoreNames, mode, opt_handler) {
163 var transaction = db.transaction(objectStoreNames, mode);
164 transaction.onerror = onError;
165 transaction.onabort = onError;
166 if (opt_handler) {
167 transaction.oncomplete = opt_handler;
169 return transaction;
172 function deleteDatabase(name, opt_handler) {
173 var deleteRequest = indexedDB.deleteDatabase(name);
174 deleteRequest.onerror = onError;
175 deleteRequest.onblocked = onError;
176 if (opt_handler) {
177 deleteRequest.onsuccess = opt_handler;
181 function getCompletionFunc(db, testName, startTime, onTestComplete) {
182 function onDeleted() {
183 automation.setStatus("Deleted database.");
184 onTestComplete();
186 return function() {
187 var duration = window.performance.now() - startTime;
188 // Ignore the cleanup time for this test.
189 automation.addResult(testName, duration);
190 automation.setStatus("Deleting database.");
191 db.close();
192 deleteDatabase(testName, onDeleted);
196 function getDisplayName(args) {
197 function functionName(f) {
198 // Function.prototype.name is nonstandard, and not implemented in IE10-
199 return f.name || f.toString().match(/^function\s*([^(\s]*)/)[1];
201 // The last arg is the completion callback the test runner tacks on.
202 // TODO(ericu): Make test errors delete the database automatically.
203 return functionName(getDisplayName.caller) + (args.length > 1 ? "_" : "") +
204 Array.prototype.slice.call(args, 0, args.length - 1).join("_");
207 // Pad a string [or object convertible to a string] to a fixed width; use this
208 // to have numeric strings sort properly.
209 function padToWidth(s, width) {
210 s = String(s);
211 assert(s.length <= width);
212 if (s.length < width) {
213 s = stringOfLength(width - s.length, '0') + s;
215 return s;
218 function stringOfLength(n, c) {
219 if (c == null)
220 c = 'X';
221 assert(n > 0);
222 assert(n == Math.floor(n));
223 return new Array(n + 1).join(c);
226 function getSimpleKey(i) {
227 return "key " + padToWidth(i, 10);
230 function getSimpleValue(i) {
231 return "value " + padToWidth(i, 10);
234 function getIndexableValue(i) {
235 return { id: getSimpleValue(i) };
238 function getForwardIndexKey(i) {
239 return i;
242 function getBackwardIndexKey(i) {
243 return -i;
246 // This is useful for indexing by keypath; the two names should be ordered in
247 // opposite directions for all i in uint32 range.
248 function getObjectValue(i) {
249 return {
250 firstName: getForwardIndexKey(i),
251 lastName: getBackwardIndexKey(i)
255 function getNFieldName(k) {
256 return "field" + k;
259 function getNFieldObjectValue(i, n) {
260 assert(Math.floor(n) == n);
261 assert(n > 0);
262 var o = {};
263 for (; n > 0; --n) {
264 // The value varies per field, each object will tend to be unique,
265 // and thanks to the modulus, indexing on different fields will give you
266 // different ordering for large-enough data sets.
267 o[getNFieldName(n - 1)] = Math.pow(i + 0.5, n + 0.5) % 65536;
269 return o;
272 function putLinearValues(
273 transaction, objectStoreNames, numKeys, getKey, getValue) {
274 if (!getKey)
275 getKey = getSimpleKey;
276 if (!getValue)
277 getValue = getSimpleValue;
278 for (var i in objectStoreNames) {
279 var os = transaction.objectStore(objectStoreNames[i]);
280 for (var j = 0; j < numKeys; ++j) {
281 var request = os.put(getValue(j), getKey(j));
282 request.onerror = onError;
287 function verifyResultNonNull(result) {
288 assert(result != null);
291 function getRandomValues(
292 transaction, objectStoreNames, numReads, numKeys, indexName, getKey) {
293 if (!getKey)
294 getKey = getSimpleKey;
295 for (var i in objectStoreNames) {
296 var os = transaction.objectStore(objectStoreNames[i]);
297 var source = os;
298 if (indexName)
299 source = source.index(indexName);
300 for (var j = 0; j < numReads; ++j) {
301 var rand = Math.floor(random() * numKeys);
302 var request = source.get(getKey(rand));
303 request.onerror = onError;
304 request.onsuccess = verifyResultNonNull;
309 function putRandomValues(
310 transaction, objectStoreNames, numPuts, numKeys, getKey, getValue) {
311 if (!getKey)
312 getKey = getSimpleKey;
313 if (!getValue)
314 getValue = getSimpleValue;
315 for (var i in objectStoreNames) {
316 var os = transaction.objectStore(objectStoreNames[i]);
317 for (var j = 0; j < numPuts; ++j) {
318 var rand = Math.floor(random() * numKeys);
319 var request = os.put(getValue(rand), getKey(rand));
320 request.onerror = onError;
325 function getSpecificValues(transaction, objectStoreNames, indexName, keys) {
326 for (var i in objectStoreNames) {
327 var os = transaction.objectStore(objectStoreNames[i]);
328 var source = os;
329 if (indexName)
330 source = source.index(indexName);
331 for (var j = 0; j < keys.length; ++j) {
332 var request = source.get(keys[j]);
333 request.onerror = onError;
334 request.onsuccess = verifyResultNonNull;
339 // getKey should be deterministic, as we assume that a cursor that starts at
340 // getKey(X) and runs through getKey(X + K) has exactly K values available.
341 // This is annoying to guarantee generally when using an index, so we avoid both
342 // ends of the key space just in case and use simple indices.
343 // TODO(ericu): Figure out if this can be simplified and we can remove uses of
344 // getObjectValue in favor of getNFieldObjectValue.
345 function getValuesFromCursor(
346 transaction, inputObjectStoreName, numReads, numKeys, indexName, getKey,
347 readKeysOnly, outputObjectStoreName) {
348 assert(2 * numReads < numKeys);
349 if (!getKey)
350 getKey = getSimpleKey;
351 var rand = Math.floor(random() * (numKeys - 2 * numReads)) + numReads;
352 var values = [];
353 var queryObject = transaction.objectStore(inputObjectStoreName);
354 assert(queryObject);
355 if (indexName)
356 queryObject = queryObject.index(indexName);
357 var keyRange = IDBKeyRange.bound(
358 getKey(rand), getKey(rand + numReads), false, true);
359 var request;
360 if (readKeysOnly) {
361 request = queryObject.openKeyCursor(keyRange);
362 } else {
363 request = queryObject.openCursor(keyRange);
365 var oos;
366 if (outputObjectStoreName)
367 oos = transaction.objectStore(outputObjectStoreName);
368 var numReadsLeft = numReads;
369 request.onsuccess = function(event) {
370 var cursor = event.target.result;
371 if (cursor) {
372 assert(numReadsLeft);
373 --numReadsLeft;
374 if (oos)
375 // Put in random order for maximum difficulty. We add in numKeys just
376 // in case we're writing back to the same store; this way we won't
377 // affect the number of keys available to the cursor, since we're always
378 // outside its range.
379 oos.put(cursor.value, numKeys + random());
380 values.push({key: cursor.key, value: cursor.value});
381 cursor.continue();
382 } else {
383 assert(!numReadsLeft);
386 request.onerror = onError;
389 function runTransactionBatch(db, count, batchFunc, objectStoreNames, mode,
390 onComplete) {
391 var numTransactionsRunning = 0;
393 runOneBatch(db);
395 function runOneBatch(db) {
396 if (count <= 0) {
397 return;
399 --count;
400 ++numTransactionsRunning;
401 var transaction = getTransaction(db, objectStoreNames, mode,
402 function() {
403 assert(!--numTransactionsRunning);
404 if (count <= 0) {
405 onComplete();
406 } else {
407 runOneBatch(db);
411 batchFunc(transaction);
415 // Use random() instead of Math.random() so runs are repeatable.
416 var random = (function(seed) {
418 // Implementation of: http://www.burtleburtle.net/bob/rand/smallprng.html
419 function uint32(x) { return x >>> 0; }
420 function rot(x, k) { return (x << k) | (x >> (32 - k)); }
422 function SmallPRNG(seed) {
423 seed = uint32(seed);
424 this.a = 0xf1ea5eed;
425 this.b = this.c = this.d = seed;
426 for (var i = 0; i < 20; ++i)
427 this.ranval();
430 SmallPRNG.prototype.ranval = function() {
431 var e = uint32(this.a - rot(this.b, 27));
432 this.a = this.b ^ rot(this.c, 17);
433 this.b = uint32(this.c + this.d);
434 this.c = uint32(this.d + e);
435 this.d = uint32(e + this.a);
436 return this.d;
439 var prng = new SmallPRNG(seed);
440 return function() { return prng.ranval() / 0x100000000; };
441 }(0));