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.
9 automation.setDone = function() {
10 this.setStatus("Test complete.");
11 document.cookie = '__done=1; path=/';
14 automation.addResult = function(name, result) {
16 this.results[name] = result;
17 var elt = document.getElementById('results');
18 var div = document.createElement('div');
19 div.textContent = name + ": " + result;
23 automation.getResults = function() {
27 automation.setStatus = function(s) {
28 document.getElementById('status').textContent = s;
33 var e = new Error("Assertion failed!");
40 var s = "Caught error.";
41 if (e.target && e.target.error)
42 s += "\n" + e.target.error.name + "\n" + e.target.error.message;
44 automation.setStatus(s);
49 var baseVersion = 2; // The version with our object stores.
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);
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 });
80 openRequest.onupgradeneeded = function(ev) {
81 // This is the spec-compliant path, which doesn't yet run in Chrome, but
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.
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);
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;
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.
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);
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 =
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);
149 function doAlteration(target) {
150 for (var store in objectStoreNames) {
151 func(target.objectStore(objectStoreNames[store]));
156 function getTransaction(db, objectStoreNames, mode, opt_handler) {
157 var transaction = db.transaction(objectStoreNames, mode);
158 transaction.onerror = onError;
159 transaction.onabort = onError;
161 transaction.oncomplete = opt_handler;
166 function deleteDatabase(name, opt_handler) {
167 var deleteRequest = indexedDB.deleteDatabase(name);
168 deleteRequest.onerror = onError;
169 deleteRequest.onblocked = onError;
171 deleteRequest.onsuccess = opt_handler;
175 function getCompletionFunc(db, testName, startTime, onTestComplete) {
176 function onDeleted() {
177 automation.setStatus("Deleted database.");
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.");
186 deleteDatabase(testName, onDeleted);
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];
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) {
205 assert(s.length <= width);
206 if (s.length < width) {
207 s = stringOfLength(width - s.length, '0') + s;
212 function stringOfLength(n, c) {
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) {
236 function getBackwardIndexKey(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) {
244 firstName: getForwardIndexKey(i),
245 lastName: getBackwardIndexKey(i)
249 function getNFieldName(k) {
253 function getNFieldObjectValue(i, n) {
254 assert(Math.floor(n) == 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;
266 function putLinearValues(
267 transaction, objectStoreNames, numKeys, getKey, getValue) {
269 getKey = getSimpleKey;
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;
281 function verifyResultNonNull(result) {
282 assert(result != null);
285 function getRandomValues(
286 transaction, objectStoreNames, numReads, numKeys, indexName, getKey) {
288 getKey = getSimpleKey;
289 for (var i in objectStoreNames) {
290 var os = transaction.objectStore(objectStoreNames[i]);
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;
303 function putRandomValues(
304 transaction, objectStoreNames, numPuts, numKeys, getKey, getValue) {
306 getKey = getSimpleKey;
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;
319 function getSpecificValues(transaction, objectStoreNames, indexName, keys) {
320 for (var i in objectStoreNames) {
321 var os = transaction.objectStore(objectStoreNames[i]);
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;
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);
344 getKey = getSimpleKey;
345 var rand = Math.floor(random() * (numKeys - 2 * numReads)) + numReads;
347 var queryObject = transaction.objectStore(inputObjectStoreName);
350 queryObject = queryObject.index(indexName);
351 var keyRange = IDBKeyRange.bound(
352 getKey(rand), getKey(rand + numReads), false, true);
355 request = queryObject.openKeyCursor(keyRange);
357 request = queryObject.openCursor(keyRange);
360 if (outputObjectStoreName)
361 oos = transaction.objectStore(outputObjectStoreName);
362 var numReadsLeft = numReads;
363 request.onsuccess = function(event) {
364 var cursor = event.target.result;
366 assert(numReadsLeft);
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});
377 assert(!numReadsLeft);
380 request.onerror = onError;
383 function runTransactionBatch(db, count, batchFunc, objectStoreNames, mode,
385 var numTransactionsRunning = 0;
389 function runOneBatch(db) {
394 ++numTransactionsRunning;
395 var transaction = getTransaction(db, objectStoreNames, mode,
397 assert(!--numTransactionsRunning);
405 batchFunc(transaction);
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) {
419 this.b = this.c = this.d = seed;
420 for (var i = 0; i < 20; ++i)
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);
433 var prng = new SmallPRNG(seed);
434 return function() { return prng.ranval() / 0x100000000; };