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 overallTestStartTime = window.performance.now();
7 var kDontUseIndex = false;
8 var kReadKeysOnly = true;
9 var kReadDataToo = false;
11 var kDontWrite = false;
12 var kWriteSameStore = true;
13 var kWriteDifferentStore = false;
14 var kPlaceholderArg = false;
15 var kDontRead = false;
16 var kAlternateWithReads = true;
19 // Create 30 databases, populate them with 20 object stores with 10 items
20 // each, and then open them 60 times. Each item is 100 bytes long.
21 [testCreateAndDeleteDatabases, 30, 60, 10, 20, 100],
22 // Create a single small item in a single object store, then delete everything.
23 [testCreateAndDeleteDatabase, 1, 1, 1],
24 // Create many small items in a single object store, then delete everything.
25 [testCreateAndDeleteDatabase, 1000, 1, 1],
26 // Create a single small item in many object stores, then delete everything.
27 [testCreateAndDeleteDatabase, 1, 1000, 1],
28 // Create many large items in a single object store, then delete everything.
29 [testCreateAndDeleteDatabase, 1000, 1, 10000],
30 // Create a single small item in a single object store.
31 [testCreateKeysInStores, 1, 1, 1],
32 // Create many small items in a single object store.
33 [testCreateKeysInStores, 1000, 1, 1],
34 // Create a single small item in many object stores.
35 [testCreateKeysInStores, 1, 1000, 1],
36 // Create many large items in a single object store.
37 [testCreateKeysInStores, 1000, 1, 10000],
39 // Read one item per transaction.
40 [testRandomReadsAndWritesWithoutIndex, 1000, 1, 0, 1000],
41 // Read a few random items in each of many transactions.
42 [testRandomReadsAndWritesWithoutIndex, 1000, 5, 0, 100],
43 // Read many random items in each of a few transactions.
44 [testRandomReadsAndWritesWithoutIndex, 1000, 500, 0, 5],
45 // Read many random items in each of a few transactions, in a large store.
46 [testRandomReadsAndWritesWithoutIndex, 10000, 500, 0, 5],
47 // Read and write a few random items in each of many transactions.
48 [testRandomReadsAndWritesWithoutIndex, 1000, 5, 5, 50],
50 // Read one item per transaction.
51 [testRandomReadsAndWritesWithIndex, 1000, 1, 0, 1000],
52 // Read a few random items from an index, in each of many transactions.
53 [testRandomReadsAndWritesWithIndex, 1000, 5, 0, 100],
54 // Read many random items from an index, in each of a few transactions.
55 [testRandomReadsAndWritesWithIndex, 1000, 500, 0, 5],
56 // Read many random items from an index, in each of a few transactions, in a
58 [testRandomReadsAndWritesWithIndex, 10000, 500, 0, 5],
59 // Read and write a few random items, reading from an index, in each of many
61 [testRandomReadsAndWritesWithIndex, 1000, 5, 5, 50],
63 // Read a long, contiguous sequence of an object store via a cursor.
64 [testCursorReadsAndRandomWrites, kReadDataToo, kDontUseIndex, kDontWrite,
66 // Read a sequence of an object store via a cursor, writing
67 // transformed values into another.
68 [testCursorReadsAndRandomWrites, kReadDataToo, kDontUseIndex, kWriteToo,
69 kWriteDifferentStore],
70 // Read a sequence of an object store via a cursor, writing
71 // transformed values into another.
72 [testCursorReadsAndRandomWrites, kReadDataToo, kDontUseIndex, kWriteToo,
74 // Read a sequence of an index into an object store via a cursor.
75 [testCursorReadsAndRandomWrites, kReadDataToo, kUseIndex, kDontWrite,
77 // Read a sequence of an index into an object store via a key cursor.
78 [testCursorReadsAndRandomWrites, kReadKeysOnly, kUseIndex, kDontWrite,
81 // Make a small bunch of batches of reads of the same keys from an object store.
82 [testReadCacheWithoutIndex, 10],
83 // Make a bunch of batches of reads of the same keys from an object store.
84 [testReadCacheWithoutIndex, 50],
85 // Make a small bunch of batches of reads of the same keys from an object store.
86 [testReadCacheWithIndex, 10],
87 // Make a bunch of batches of reads of the same keys from an index.
88 [testReadCacheWithIndex, 50],
90 // Create and delete an index on a store that already contains data [produces
91 // a timing result for each of creation and deletion].
92 [testCreateAndDeleteIndex, 5000],
93 // Walk through multiple cursors into the same object store, round-robin, until
94 // you've reached the end of each of them.
95 [testWalkingMultipleCursors, 5],
96 // Walk through many cursors into the same object store, round-robin, until
97 // you've reached the end of each of them.
98 [testWalkingMultipleCursors, 50],
99 // Open an object store cursor, then continue(key) to the last value.
100 [testCursorSeeksWithoutIndex, 2000, 10, 4],
101 // Open an index key cursor, then continue(key) to the last value.
102 [testCursorSeeksWithIndex, 2000, 10, 4],
106 function testRandomReadsAndWritesWithIndex(
107 numKeys, numReadsPerTransaction, numWritesPerTransaction,
108 numTransactions, onTestComplete) {
109 testRandomReadsAndWrites(numKeys, numReadsPerTransaction,
110 numWritesPerTransaction,
111 numTransactions, true, onTestComplete);
114 function testRandomReadsAndWritesWithoutIndex(
115 numKeys, numReadsPerTransaction, numWritesPerTransaction,
116 numTransactions, onTestComplete) {
117 testRandomReadsAndWrites(numKeys, numReadsPerTransaction,
118 numWritesPerTransaction,
119 numTransactions, false, onTestComplete);
123 function testReadCacheWithIndex(numTransactions, onTestComplete) {
124 testReadCache(numTransactions, true, onTestComplete);
127 function testReadCacheWithoutIndex(numTransactions, onTestComplete) {
128 testReadCache(numTransactions, false, onTestComplete)
131 function testCursorSeeksWithIndex(numKeys, numSeeksPerTransaction,
132 numTransactions, onTestComplete) {
133 testCursorSeeks(numKeys, numSeeksPerTransaction, numTransactions,
134 true, onTestComplete);
137 function testCursorSeeksWithoutIndex(numKeys, numSeeksPerTransaction,
138 numTransactions, onTestComplete) {
139 testCursorSeeks(numKeys, numSeeksPerTransaction, numTransactions,
140 false, onTestComplete);
155 function runNextTest() {
157 while (currentTest < tests.length) {
158 running_test = tests[currentTest];
159 f = running_test.shift();
160 if (!testFilter || f.name == testFilter)
165 if (currentTest < tests.length) {
166 running_test.push(runNextTest);
167 f.apply(null, running_test);
170 onAllTestsComplete();
174 function onAllTestsComplete() {
175 var overallDuration = window.performance.now() - overallTestStartTime;
176 automation.addResult("OverallTestDuration", overallDuration);
177 automation.setDone();
181 function testCreateAndDeleteDatabases(
182 numDatabases, numOpens, numKeys, numStores,
183 payloadLength, onTestComplete) {
184 var testName = getDisplayName(arguments);
185 assert(numOpens >= 1);
186 assert(numKeys >= 0);
187 assert(numStores >= 1);
188 var objectStoreNames = [];
189 for (var i=0; i < numStores; ++i) {
190 objectStoreNames.push("store " + i);
192 var value = stringOfLength(payloadLength);
193 function getValue() {
197 automation.setStatus("Creating databases.");
198 var startTime = window.performance.now();
201 for (var i = 0; i < numDatabases; i++) {
202 createDatabase(testName + i, objectStoreNames, onCreated, onError);
205 function onCreated(db) {
206 automation.setStatus("Constructing transactions.");
208 getTransaction(db, objectStoreNames, "readwrite",
209 function() { openLoop(db, numOpens); });
210 putLinearValues(transaction, objectStoreNames, numKeys, null, getValue);
213 function openLoop(db, timesLeft) {
215 if (timesLeft == 0) {
216 deleteDatabase(db.name, onDeleted);
219 createDatabase(db.name, objectStoreNames,
220 function(db) { openLoop(db, timesLeft - 1); }, onError)
224 function onDeleted() {
225 var duration = window.performance.now() - startTime;
226 automation.addResult(testName, duration);
227 automation.setStatus("Deleted database.");
228 if (++numDeleted == numDatabases) {
234 function testCreateKeysInStores(
235 numKeys, numStores, payloadLength, onTestComplete) {
236 var testName = getDisplayName(arguments);
237 assert(numKeys >= 0);
238 assert(numStores >= 1);
239 var objectStoreNames = [];
240 for (var i=0; i < numStores; ++i) {
241 objectStoreNames.push("store " + i);
243 var value = stringOfLength(payloadLength);
244 function getValue() {
248 automation.setStatus("Creating database.");
249 createDatabase(testName, objectStoreNames, onCreated, onError);
251 function onCreated(db) {
252 automation.setStatus("Constructing transaction.");
254 getCompletionFunc(db, testName, window.performance.now(),
257 getTransaction(db, objectStoreNames, "readwrite", completionFunc);
258 putLinearValues(transaction, objectStoreNames, numKeys, null, getValue);
262 // This is the only test that includes database creation and deletion in its
263 // results; the others just test specific operations. To see only the
264 // creation/deletion without the specific operations used to build up the data
265 // in the object stores here, subtract off the results of
266 // testCreateKeysInStores.
267 function testCreateAndDeleteDatabase(
268 numKeys, numStores, payloadLength, onTestComplete) {
269 var testName = getDisplayName(arguments);
270 assert(numKeys >= 0);
271 assert(numStores >= 1);
272 var objectStoreNames = [];
273 for (var i=0; i < numStores; ++i) {
274 objectStoreNames.push("store " + i);
276 var value = stringOfLength(payloadLength);
277 function getValue() {
281 automation.setStatus("Creating database.");
282 var startTime = window.performance.now();
284 createDatabase(testName, objectStoreNames, onCreated, onError);
286 function onCreated(db) {
287 automation.setStatus("Constructing transaction.");
289 getTransaction(db, objectStoreNames, "readwrite",
290 function() { onValuesWritten(db); });
291 putLinearValues(transaction, objectStoreNames, numKeys, null, getValue);
294 function onValuesWritten(db) {
295 automation.setStatus("Deleting database.");
297 deleteDatabase(testName, onDeleted);
300 function onDeleted() {
301 var duration = window.performance.now() - startTime;
302 automation.addResult(testName, duration);
303 automation.setStatus("Deleted database.");
308 function testCreateKeysInStores(
309 numKeys, numStores, payloadLength, onTestComplete) {
310 var testName = getDisplayName(arguments);
311 assert(numKeys >= 0);
312 assert(numStores >= 1);
313 var objectStoreNames = [];
314 for (var i=0; i < numStores; ++i) {
315 objectStoreNames.push("store " + i);
317 var value = stringOfLength(payloadLength);
318 function getValue() {
322 automation.setStatus("Creating database.");
323 createDatabase(testName, objectStoreNames, onCreated, onError);
325 function onCreated(db) {
326 automation.setStatus("Constructing transaction.");
328 getCompletionFunc(db, testName, window.performance.now(),
331 getTransaction(db, objectStoreNames, "readwrite", completionFunc);
332 putLinearValues(transaction, objectStoreNames, numKeys, null, getValue);
336 function testRandomReadsAndWrites(
337 numKeys, numReadsPerTransaction, numWritesPerTransaction, numTransactions,
338 useIndexForReads, onTestComplete) {
340 if (useIndexForReads)
342 var testName = getDisplayName(arguments);
343 var objectStoreNames = ["store"];
344 var getKey = getSimpleKey;
345 var getValue = useIndexForReads ? getIndexableValue : getSimpleValue;
347 automation.setStatus("Creating database.");
349 if (useIndexForReads) {
351 indexName: indexName,
353 indexIsUnique: false,
354 indexIsMultiEntry: false,
357 createDatabase(testName, objectStoreNames, onCreated, onError, options);
359 function onCreated(db) {
360 automation.setStatus("Setting up test database.");
361 var transaction = getTransaction(db, objectStoreNames, "readwrite",
362 function() { onSetupComplete(db); });
363 putLinearValues(transaction, objectStoreNames, numKeys, null,
364 function() { return "test value"; });
367 function onSetupComplete(db) {
368 automation.setStatus("Setup complete.");
370 getCompletionFunc(db, testName, window.performance.now(),
372 var mode = "readonly";
373 if (numWritesPerTransaction)
375 runTransactionBatch(db, numTransactions, batchFunc, objectStoreNames, mode,
379 function batchFunc(transaction) {
380 getRandomValues(transaction, objectStoreNames, numReadsPerTransaction,
381 numKeys, indexName, getKey);
382 putRandomValues(transaction, objectStoreNames, numWritesPerTransaction,
383 numKeys, getKey, getValue);
387 function testReadCache(numTransactions, useIndexForReads, onTestComplete) {
389 var numReadsPerTransaction = 50;
390 var numTransactionsLeft = numTransactions;
392 if (useIndexForReads)
394 var testName = getDisplayName(arguments);
395 var objectStoreNames = ["store"];
396 var getKey = getSimpleKey;
397 var getValue = useIndexForReads ? getIndexableValue : getSimpleValue;
400 for (var i=0; i < numReadsPerTransaction; ++i) {
401 keys.push(getKey(Math.floor(random() * numKeys)));
404 automation.setStatus("Creating database.");
406 if (useIndexForReads) {
408 indexName: indexName,
410 indexIsUnique: false,
411 indexIsMultiEntry: false,
414 createDatabase(testName, objectStoreNames, onCreated, onError, options);
416 function onCreated(db) {
417 automation.setStatus("Setting up test database.");
418 var transaction = getTransaction(db, objectStoreNames, "readwrite",
419 function() { onSetupComplete(db); });
420 putLinearValues(transaction, objectStoreNames, numKeys, getKey,
425 function onSetupComplete(db) {
426 automation.setStatus("Setup complete.");
428 getCompletionFunc(db, testName, window.performance.now(),
430 runTransactionBatch(db, numTransactions, batchFunc, objectStoreNames,
431 "readonly", completionFunc);
434 function batchFunc(transaction) {
435 getSpecificValues(transaction, objectStoreNames, indexName, keys);
439 function testCreateAndDeleteIndex(numKeys, onTestComplete) {
440 var testName = getDisplayName(arguments);
441 var objectStoreNames = ["store"];
443 automation.setStatus("Creating database.");
444 createDatabase(testName, objectStoreNames, onCreated, onError);
447 function onCreated(db) {
448 automation.setStatus("Initializing data.");
449 var transaction = getTransaction(db, objectStoreNames, "readwrite",
450 function() { onPopulated(db); });
451 putLinearValues(transaction, objectStoreNames, numKeys, null, getValue);
454 function getValue(i) {
455 return { firstName: i + " first name", lastName: i + " last name" };
458 function onPopulated(db) {
460 automation.setStatus("Building index.");
461 startTime = window.performance.now();
462 var f = function(objectStore) {
463 objectStore.createIndex("index", "firstName", {unique: true});
465 alterObjectStores(testName, objectStoreNames, f, onIndexCreated, onError);
468 var indexCreationCompleteTime;
469 function onIndexCreated(db) {
471 indexCreationCompleteTime = window.performance.now();
472 automation.addResult("testCreateIndex",
473 indexCreationCompleteTime - startTime);
474 var f = function(objectStore) {
475 objectStore.deleteIndex("index");
477 automation.setStatus("Deleting index.");
478 alterObjectStores(testName, objectStoreNames, f, onIndexDeleted, onError);
481 function onIndexDeleted(db) {
482 var duration = window.performance.now() - indexCreationCompleteTime;
483 // Ignore the cleanup time for this test.
484 automation.addResult("testDeleteIndex", duration);
485 automation.setStatus("Deleting database.");
487 deleteDatabase(testName, onDeleted);
490 function onDeleted() {
491 automation.setStatus("Deleted database.");
496 function testCursorReadsAndRandomWrites(
497 readKeysOnly, useIndexForReads, writeAlso, sameStoreForWrites,
499 // There's no key cursor unless you're reading from an index.
500 assert(useIndexForReads || !readKeysOnly);
501 // If we're writing to another store, having an index would constrain our
502 // writes, as we create both object stores with the same configurations.
503 // We could do that if needed, but it's simpler not to.
504 assert(!useIndexForReads || !writeAlso);
506 var numReadsPerTransaction = 1000;
507 var testName = getDisplayName(arguments);
508 var objectStoreNames = ["input store"];
511 if (sameStoreForWrites) {
512 outputStoreName = objectStoreNames[0];
514 outputStoreName = "output store";
515 objectStoreNames.push(outputStoreName);
518 var getKeyForRead = getSimpleKey;
520 if (useIndexForReads) {
522 getKeyForRead = function(i) {
523 // This depends on the implementations of getValuesFromCursor and
524 // getObjectValue. We reverse the order of the iteration here so that
525 // setting up bounds from k to k+n with n>0 works. Without this reversal,
526 // the upper bound is below the lower bound.
527 return getBackwardIndexKey(numKeys - i);
531 automation.setStatus("Creating database.");
533 if (useIndexForReads) {
535 indexName: indexName,
536 indexKeyPath: "lastName", // depends on getBackwardIndexKey()
538 indexIsMultiEntry: false,
541 createDatabase(testName, objectStoreNames, onCreated, onError, options);
543 function onCreated(db) {
544 automation.setStatus("Setting up test database.");
545 var transaction = getTransaction(db, objectStoreNames, "readwrite",
546 function() { onSetupComplete(db); });
547 putLinearValues(transaction, objectStoreNames, numKeys, getSimpleKey,
550 function onSetupComplete(db) {
551 automation.setStatus("Setup complete.");
553 getCompletionFunc(db, testName, window.performance.now(),
555 var mode = "readonly";
559 getTransaction(db, objectStoreNames, mode, completionFunc);
562 transaction, objectStoreNames[0], numReadsPerTransaction, numKeys,
563 indexName, getKeyForRead, readKeysOnly, outputStoreName);
567 function testWalkingMultipleCursors(numCursors, onTestComplete) {
569 var numHitsPerKey = 10;
570 var testName = getDisplayName(arguments);
571 var objectStoreNames = ["input store"];
572 var indexName = "index name";
573 var getKey = getSimpleKey;
574 var getValue = getIndexableValue;
576 automation.setStatus("Creating database.");
578 indexName: indexName,
580 indexIsUnique: false,
581 indexIsMultiEntry: false,
583 createDatabase(testName, objectStoreNames, onCreated, onError, options);
585 function onCreated(db) {
586 automation.setStatus("Setting up test database.");
587 var transaction = getTransaction(db, objectStoreNames, "readwrite",
588 function() { onSetupComplete(db); });
589 // This loop adds the same value numHitsPerKey times for each key.
590 for (var i = 0; i < numHitsPerKey; ++i) {
591 putLinearValues(transaction, objectStoreNames, numKeys, getKeyFunc(i),
595 // While the value is the same each time through the putLinearValues loop, we
596 // want the key to keep increaasing for each copy.
597 function getKeyFunc(k) {
599 return getKey(k * numKeys + i);
603 function onSetupComplete(db) {
604 automation.setStatus("Setup complete.");
606 getCompletionFunc(db, testName, window.performance.now(),
609 getTransaction(db, objectStoreNames, "readonly", verifyComplete);
611 walkSeveralCursors(transaction, numKeys);
613 var responseCounts = [];
614 var cursorsRunning = numCursors;
615 function walkSeveralCursors(transaction, numKeys) {
616 var source = transaction.objectStore(objectStoreNames[0]).index(indexName);
618 var continueCursorIndex = 0;
619 for (var i = 0; i < numCursors; ++i) {
620 var rand = Math.floor(random() * numKeys);
621 // Since we have numHitsPerKey copies of each value in the database,
622 // IDBKeyRange.only will return numHitsPerKey results, each referring to a
623 // different key with the matching value.
624 var request = source.openCursor(IDBKeyRange.only(getSimpleValue(rand)));
625 responseCounts.push(0);
626 request.onerror = onError;
627 request.onsuccess = function(event) {
628 assert(cursorsRunning);
629 var request = event.target;
630 if (!("requestIndex" in request)) {
631 assert(requests.length < numCursors);
632 request.requestIndex = requests.length;
633 requests.push(request);
635 var cursor = event.target.result;
637 assert(responseCounts[request.requestIndex] < numHitsPerKey);
638 ++responseCounts[request.requestIndex];
640 assert(responseCounts[request.requestIndex] == numHitsPerKey);
643 if (cursorsRunning) {
644 if (requests.length == numCursors) {
645 requests[continueCursorIndex++].result.continue();
646 continueCursorIndex %= numCursors;
652 function verifyComplete() {
653 assert(!cursorsRunning);
658 function testCursorSeeks(
659 numKeys, numSeeksPerTransaction, numTransactions, useIndexForReads,
661 var testName = getDisplayName(arguments);
662 var objectStoreNames = ["store"];
663 var getKey = useIndexForReads ? getForwardIndexKey : getSimpleKey;
665 if (useIndexForReads) {
669 automation.setStatus("Creating database.");
671 if (useIndexForReads) {
673 indexName: indexName,
674 indexKeyPath: "firstName",
676 indexIsMultiEntry: false,
679 createDatabase(testName, objectStoreNames, onCreated, onError, options);
681 function onCreated(db) {
682 automation.setStatus("Setting up test database.");
683 var transaction = getTransaction(db, objectStoreNames, "readwrite",
684 function() { onSetupComplete(db); });
685 putLinearValues(transaction, objectStoreNames, numKeys, getSimpleKey,
689 function onSetupComplete(db) {
690 automation.setStatus("Setup complete.");
692 getCompletionFunc(db, testName, window.performance.now(),
694 var mode = "readonly";
695 runTransactionBatch(db, numTransactions, batchFunc, objectStoreNames, mode,
699 function batchFunc(transaction) {
700 for (var i in objectStoreNames) {
701 var source = transaction.objectStore(objectStoreNames[i]);
702 if (useIndexForReads)
703 source = source.index(indexName);
704 for (var j = 0; j < numSeeksPerTransaction; ++j) {
710 function randomSeek(source) {
711 var request = useIndexForReads ? source.openKeyCursor()
712 : source.openCursor();
714 request.onerror = onError;
715 request.onsuccess = function() {
716 var cursor = request.result;
717 if (cursor && first) {
719 cursor.continue(getKey(numKeys - 1));