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 = Date.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 a single small item in a single object store, then delete everything.
20 [testCreateAndDeleteDatabase, 1, 1, 1],
21 // Create many small items in a single object store, then delete everything.
22 [testCreateAndDeleteDatabase, 1000, 1, 1],
23 // Create a single small item in many object stores, then delete everything.
24 [testCreateAndDeleteDatabase, 1, 1000, 1],
25 // Create many large items in a single object store, then delete everything.
26 [testCreateAndDeleteDatabase, 1000, 1, 10000],
27 // Create a single small item in a single object store.
28 [testCreateKeysInStores, 1, 1, 1],
29 // Create many small items in a single object store.
30 [testCreateKeysInStores, 1000, 1, 1],
31 // Create a single small item in many object stores.
32 [testCreateKeysInStores, 1, 1000, 1],
33 // Create many large items in a single object store.
34 [testCreateKeysInStores, 1000, 1, 10000],
35 // Read one item per transaction.
36 [testRandomReadsAndWrites, 1000, 1, 0, 1000, kDontUseIndex],
37 // Read a few random items in each of many transactions.
38 [testRandomReadsAndWrites, 1000, 5, 0, 100, kDontUseIndex],
39 // Read many random items in each of a few transactions.
40 [testRandomReadsAndWrites, 1000, 500, 0, 5, kDontUseIndex],
41 // Read many random items in each of a few transactions, in a large store.
42 [testRandomReadsAndWrites, 10000, 500, 0, 5, kDontUseIndex],
43 // Read a few random items from an index, in each of many transactions.
44 [testRandomReadsAndWrites, 1000, 5, 0, 100, kUseIndex],
45 // Read many random items from an index, in each of a few transactions.
46 [testRandomReadsAndWrites, 1000, 500, 0, 5, kUseIndex],
47 // Read many random items from an index, in each of a few transactions, in a
49 [testRandomReadsAndWrites, 10000, 500, 0, 5, kUseIndex],
50 // Read and write a few random items in each of many transactions.
51 [testRandomReadsAndWrites, 1000, 5, 5, 50, kDontUseIndex],
52 // Read and write a few random items, reading from an index, in each of many
54 [testRandomReadsAndWrites, 1000, 5, 5, 50, kUseIndex],
55 // Read a long, contiguous sequence of an object store via a cursor.
56 [testCursorReadsAndRandomWrites, kReadDataToo, kDontUseIndex, kDontWrite,
58 // Read a sequence of an object store via a cursor, writing
59 // transformed values into another.
60 [testCursorReadsAndRandomWrites, kReadDataToo, kDontUseIndex, kWriteToo,
61 kWriteDifferentStore],
62 // Read a sequence of an object store via a cursor, writing
63 // transformed values into another.
64 [testCursorReadsAndRandomWrites, kReadDataToo, kDontUseIndex, kWriteToo,
66 // Read a sequence of an index into an object store via a cursor.
67 [testCursorReadsAndRandomWrites, kReadDataToo, kUseIndex, kDontWrite,
69 // Read a sequence of an index into an object store via a key cursor.
70 [testCursorReadsAndRandomWrites, kReadKeysOnly, kUseIndex, kDontWrite,
72 // Make batches of random writes into a store, triggered by periodic setTimeout
74 [testSporadicWrites, 5, 0, kDontRead],
75 // Make large batches of random writes into a store, triggered by periodic
77 [testSporadicWrites, 50, 0, kDontRead],
78 // Make batches of random writes into a store with many indices, triggered by
79 // periodic setTimeout calls.
80 [testSporadicWrites, 5, 10, kDontRead],
81 // Make large batches of random writes into a store with many indices, triggered
82 // by periodic setTimeout calls.
83 [testSporadicWrites, 50, 10, kDontRead],
84 // Make batches of random writes into a store, triggered by periodic setTimeout
85 // calls. Intersperse read transactions to test read-write lock conflicts.
86 [testSporadicWrites, 5, 0, kAlternateWithReads],
87 // Make large batches of random writes into a store, triggered by periodic
88 // setTimeout calls. Intersperse read transactions to test read-write lock
90 [testSporadicWrites, 50, 0, kAlternateWithReads],
91 // Make a small bunch of batches of reads of the same keys from an object store.
92 [testReadCache, 10, kDontUseIndex],
93 // Make a bunch of batches of reads of the same keys from an index.
94 [testReadCache, 50, kUseIndex],
95 // Make a small bunch of batches of reads of the same keys from an object store.
96 [testReadCache, 10, kDontUseIndex],
97 // Make a bunch of batches of reads of the same keys from an index.
98 [testReadCache, 50, kUseIndex],
99 // Create and delete an index on a store that already contains data [produces
100 // a timing result for each of creation and deletion].
101 [testCreateAndDeleteIndex, 5000],
102 // Walk through multiple cursors into the same object store, round-robin, until
103 // you've reached the end of each of them.
104 [testWalkingMultipleCursors, 5],
105 // Walk through many cursors into the same object store, round-robin, until
106 // you've reached the end of each of them.
107 [testWalkingMultipleCursors, 50],
108 // Open an object store cursor, then continue(key) to the last value.
109 [testCursorSeeks, 2000, 10, 4, kDontUseIndex],
110 // Open an index key cursor, then continue(key) to the last value.
111 [testCursorSeeks, 2000, 10, 4, kUseIndex],
120 function runNextTest() {
121 var filter = window.location.hash.slice(1);
123 while (currentTest < tests.length) {
124 test = tests[currentTest];
126 if (!filter || f.name == filter)
131 if (currentTest < tests.length) {
132 test.push(runNextTest);
136 onAllTestsComplete();
140 function onAllTestsComplete() {
141 var overallDuration = Date.now() - overallTestStartTime;
142 automation.addResult("OverallTestDuration", overallDuration);
143 automation.setDone();
146 // This is the only test that includes database creation and deletion in its
147 // results; the others just test specific operations. To see only the
148 // creation/deletion without the specific operations used to build up the data
149 // in the object stores here, subtract off the results of
150 // testCreateKeysInStores.
151 function testCreateAndDeleteDatabase(
152 numKeys, numStores, payloadLength, onTestComplete) {
153 var testName = getDisplayName(arguments);
154 assert(numKeys >= 0);
155 assert(numStores >= 1);
156 var objectStoreNames = [];
157 for (var i=0; i < numStores; ++i) {
158 objectStoreNames.push("store " + i);
160 var value = stringOfLength(payloadLength);
161 function getValue() {
165 automation.setStatus("Creating database.");
166 var startTime = Date.now();
168 createDatabase(testName, objectStoreNames, onCreated, onError);
170 function onCreated(db) {
171 automation.setStatus("Constructing transaction.");
173 getTransaction(db, objectStoreNames, "readwrite",
174 function() { onValuesWritten(db); });
175 putLinearValues(transaction, objectStoreNames, numKeys, null, getValue);
178 function onValuesWritten(db) {
179 automation.setStatus("Deleting database.");
181 deleteDatabase(testName, onDeleted);
184 function onDeleted() {
185 var duration = Date.now() - startTime;
186 automation.addResult(testName, duration);
187 automation.setStatus("Deleted database.");
192 function testCreateKeysInStores(
193 numKeys, numStores, payloadLength, onTestComplete) {
194 var testName = getDisplayName(arguments);
195 assert(numKeys >= 0);
196 assert(numStores >= 1);
197 var objectStoreNames = [];
198 for (var i=0; i < numStores; ++i) {
199 objectStoreNames.push("store " + i);
201 var value = stringOfLength(payloadLength);
202 function getValue() {
206 automation.setStatus("Creating database.");
207 createDatabase(testName, objectStoreNames, onCreated, onError);
209 function onCreated(db) {
210 automation.setStatus("Constructing transaction.");
212 getCompletionFunc(db, testName, Date.now(), onTestComplete);
214 getTransaction(db, objectStoreNames, "readwrite", completionFunc);
215 putLinearValues(transaction, objectStoreNames, numKeys, null, getValue);
219 function testRandomReadsAndWrites(
220 numKeys, numReadsPerTransaction, numWritesPerTransaction, numTransactions,
221 useIndexForReads, onTestComplete) {
223 if (useIndexForReads)
225 var testName = getDisplayName(arguments);
226 var objectStoreNames = ["store"];
227 var getKey = getSimpleKey;
228 var getValue = useIndexForReads ? getIndexableValue : getSimpleValue;
230 automation.setStatus("Creating database.");
232 if (useIndexForReads) {
234 indexName: indexName,
236 indexIsUnique: false,
237 indexIsMultiEntry: false,
240 createDatabase(testName, objectStoreNames, onCreated, onError, options);
242 function onCreated(db) {
243 automation.setStatus("Setting up test database.");
244 var transaction = getTransaction(db, objectStoreNames, "readwrite",
245 function() { onSetupComplete(db); });
246 putLinearValues(transaction, objectStoreNames, numKeys, null,
247 function() { return "test value"; });
250 function onSetupComplete(db) {
251 automation.setStatus("Setup complete.");
253 getCompletionFunc(db, testName, Date.now(), onTestComplete);
254 var mode = "readonly";
255 if (numWritesPerTransaction)
257 runTransactionBatch(db, numTransactions, batchFunc, objectStoreNames, mode,
261 function batchFunc(transaction) {
262 getRandomValues(transaction, objectStoreNames, numReadsPerTransaction,
263 numKeys, indexName, getKey);
264 putRandomValues(transaction, objectStoreNames, numWritesPerTransaction,
265 numKeys, getKey, getValue);
269 function testReadCache(numTransactions, useIndexForReads, onTestComplete) {
271 var numReadsPerTransaction = 50;
272 var numTransactionsLeft = numTransactions;
274 if (useIndexForReads)
276 var testName = getDisplayName(arguments);
277 var objectStoreNames = ["store"];
278 var getKey = getSimpleKey;
279 var getValue = useIndexForReads ? getIndexableValue : getSimpleValue;
282 for (var i=0; i < numReadsPerTransaction; ++i) {
283 keys.push(getKey(Math.floor(random() * numKeys)));
286 automation.setStatus("Creating database.");
288 if (useIndexForReads) {
290 indexName: indexName,
292 indexIsUnique: false,
293 indexIsMultiEntry: false,
296 createDatabase(testName, objectStoreNames, onCreated, onError, options);
298 function onCreated(db) {
299 automation.setStatus("Setting up test database.");
300 var transaction = getTransaction(db, objectStoreNames, "readwrite",
301 function() { onSetupComplete(db); });
302 putLinearValues(transaction, objectStoreNames, numKeys, getKey,
307 function onSetupComplete(db) {
308 automation.setStatus("Setup complete.");
310 getCompletionFunc(db, testName, Date.now(), onTestComplete);
311 runTransactionBatch(db, numTransactions, batchFunc, objectStoreNames,
312 "readonly", completionFunc);
315 function batchFunc(transaction) {
316 getSpecificValues(transaction, objectStoreNames, indexName, keys);
320 function testCreateAndDeleteIndex(numKeys, onTestComplete) {
321 var testName = getDisplayName(arguments);
322 var objectStoreNames = ["store"];
324 automation.setStatus("Creating database.");
325 createDatabase(testName, objectStoreNames, onCreated, onError);
328 function onCreated(db) {
329 automation.setStatus("Initializing data.");
330 var transaction = getTransaction(db, objectStoreNames, "readwrite",
331 function() { onPopulated(db); });
332 putLinearValues(transaction, objectStoreNames, numKeys, null, getValue);
335 function getValue(i) {
336 return { firstName: i + " first name", lastName: i + " last name" };
339 function onPopulated(db) {
341 automation.setStatus("Building index.");
342 startTime = Date.now();
343 var f = function(objectStore) {
344 objectStore.createIndex("index", "firstName", {unique: true});
346 alterObjectStores(testName, objectStoreNames, f, onIndexCreated, onError);
349 var indexCreationCompleteTime;
350 function onIndexCreated(db) {
352 indexCreationCompleteTime = Date.now();
353 automation.addResult("testCreateIndex",
354 indexCreationCompleteTime - startTime);
355 var f = function(objectStore) {
356 objectStore.deleteIndex("index");
358 automation.setStatus("Deleting index.");
359 alterObjectStores(testName, objectStoreNames, f, onIndexDeleted, onError);
362 function onIndexDeleted(db) {
363 var duration = Date.now() - indexCreationCompleteTime;
364 // Ignore the cleanup time for this test.
365 automation.addResult("testDeleteIndex", duration);
366 automation.setStatus("Deleting database.");
368 deleteDatabase(testName, onDeleted);
371 function onDeleted() {
372 automation.setStatus("Deleted database.");
377 function testCursorReadsAndRandomWrites(
378 readKeysOnly, useIndexForReads, writeAlso, sameStoreForWrites,
380 // There's no key cursor unless you're reading from an index.
381 assert(useIndexForReads || !readKeysOnly);
382 // If we're writing to another store, having an index would constrain our
383 // writes, as we create both object stores with the same configurations.
384 // We could do that if needed, but it's simpler not to.
385 assert(!useIndexForReads || !writeAlso);
387 var numReadsPerTransaction = 1000;
388 var testName = getDisplayName(arguments);
389 var objectStoreNames = ["input store"];
392 if (sameStoreForWrites) {
393 outputStoreName = objectStoreNames[0];
395 outputStoreName = "output store";
396 objectStoreNames.push(outputStoreName);
399 var getKeyForRead = getSimpleKey;
401 if (useIndexForReads) {
403 getKeyForRead = function(i) {
404 // This depends on the implementations of getValuesFromCursor and
405 // getObjectValue. We reverse the order of the iteration here so that
406 // setting up bounds from k to k+n with n>0 works. Without this reversal,
407 // the upper bound is below the lower bound.
408 return getBackwardIndexKey(numKeys - i);
412 automation.setStatus("Creating database.");
414 if (useIndexForReads) {
416 indexName: indexName,
417 indexKeyPath: "lastName", // depends on getBackwardIndexKey()
419 indexIsMultiEntry: false,
422 createDatabase(testName, objectStoreNames, onCreated, onError, options);
424 function onCreated(db) {
425 automation.setStatus("Setting up test database.");
426 var transaction = getTransaction(db, objectStoreNames, "readwrite",
427 function() { onSetupComplete(db); });
428 putLinearValues(transaction, objectStoreNames, numKeys, getSimpleKey,
431 function onSetupComplete(db) {
432 automation.setStatus("Setup complete.");
434 getCompletionFunc(db, testName, Date.now(), onTestComplete);
435 var mode = "readonly";
439 getTransaction(db, objectStoreNames, mode, completionFunc);
442 transaction, objectStoreNames[0], numReadsPerTransaction, numKeys,
443 indexName, getKeyForRead, readKeysOnly, outputStoreName);
447 function testSporadicWrites(
448 numOperationsPerTransaction, numIndices, alternateWithReads,
451 // With 30 transactions, spaced 50ms apart, we'll need at least 1.5s.
452 var numTransactions = 30;
453 if (alternateWithReads)
454 numTransactions *= 2;
455 var delayBetweenBatches = 50;
457 var testName = getDisplayName(arguments);
458 var numTransactionsLeft = numTransactions;
459 var objectStoreNames = ["store"];
460 var numTransactionsRunning = 0;
462 var getValue = getSimpleValue;
464 getValue = function (i) { return getNFieldObjectValue(i, numIndices); };
466 automation.setStatus("Creating database.");
468 for (var i=0; i < numIndices; ++i) {
470 o.indexName = "index " + i;
471 o.indexKeyPath = getNFieldName(i);
472 o.indexIsUnique = false;
473 o.indexIsMultiEntry = false;
476 createDatabase(testName, objectStoreNames, onCreated, onError, options);
478 function onCreated(db) {
479 automation.setStatus("Setting up test database.");
480 var transaction = getTransaction(db, objectStoreNames, "readwrite",
481 function() { onSetupComplete(db); });
482 putLinearValues(transaction, objectStoreNames, numKeys, getSimpleKey,
486 function onSetupComplete(db) {
487 automation.setStatus("Setup complete.");
489 getCompletionFunc(db, testName, Date.now(), onTestComplete);
493 function runOneBatch(db) {
494 assert(numTransactionsLeft);
495 if (--numTransactionsLeft) {
496 setTimeout(function () { runOneBatch(db); }, delayBetweenBatches);
499 var mode, transaction;
500 if (alternateWithReads) {
501 ++numTransactionsRunning;
503 getTransaction(db, objectStoreNames, "readonly", batchComplete);
504 getRandomValues(transaction, objectStoreNames,
505 numOperationsPerTransaction, numKeys);
507 ++numTransactionsRunning;
509 getTransaction(db, objectStoreNames, "readwrite", batchComplete);
510 putRandomValues(transaction, objectStoreNames, numOperationsPerTransaction,
513 function batchComplete() {
514 assert(numTransactionsRunning);
515 if (!--numTransactionsRunning && !numTransactionsLeft)
521 function testWalkingMultipleCursors(numCursors, onTestComplete) {
523 var numHitsPerKey = 10;
524 var testName = getDisplayName(arguments);
525 var objectStoreNames = ["input store"];
526 var indexName = "index name";
527 var getKey = getSimpleKey;
528 var getValue = getIndexableValue;
530 automation.setStatus("Creating database.");
532 indexName: indexName,
534 indexIsUnique: false,
535 indexIsMultiEntry: false,
537 createDatabase(testName, objectStoreNames, onCreated, onError, options);
539 function onCreated(db) {
540 automation.setStatus("Setting up test database.");
541 var transaction = getTransaction(db, objectStoreNames, "readwrite",
542 function() { onSetupComplete(db); });
543 // This loop adds the same value numHitsPerKey times for each key.
544 for (var i = 0; i < numHitsPerKey; ++i) {
545 putLinearValues(transaction, objectStoreNames, numKeys, getKeyFunc(i),
549 // While the value is the same each time through the putLinearValues loop, we
550 // want the key to keep increaasing for each copy.
551 function getKeyFunc(k) {
553 return getKey(k * numKeys + i);
557 function onSetupComplete(db) {
558 automation.setStatus("Setup complete.");
560 getCompletionFunc(db, testName, Date.now(), onTestComplete);
562 getTransaction(db, objectStoreNames, "readonly", verifyComplete);
564 walkSeveralCursors(transaction, numKeys);
566 var responseCounts = [];
567 var cursorsRunning = numCursors;
568 function walkSeveralCursors(transaction, numKeys) {
569 var source = transaction.objectStore(objectStoreNames[0]).index(indexName);
571 var continueCursorIndex = 0;
572 for (var i = 0; i < numCursors; ++i) {
573 var rand = Math.floor(random() * numKeys);
574 // Since we have numHitsPerKey copies of each value in the database,
575 // IDBKeyRange.only will return numHitsPerKey results, each referring to a
576 // different key with the matching value.
577 var request = source.openCursor(IDBKeyRange.only(getSimpleValue(rand)));
578 responseCounts.push(0);
579 request.onerror = onError;
580 request.onsuccess = function(event) {
581 assert(cursorsRunning);
582 var request = event.target;
583 if (!("requestIndex" in request)) {
584 assert(requests.length < numCursors);
585 request.requestIndex = requests.length;
586 requests.push(request);
588 var cursor = event.target.result;
590 assert(responseCounts[request.requestIndex] < numHitsPerKey);
591 ++responseCounts[request.requestIndex];
593 assert(responseCounts[request.requestIndex] == numHitsPerKey);
596 if (cursorsRunning) {
597 if (requests.length == numCursors) {
598 requests[continueCursorIndex++].result.continue();
599 continueCursorIndex %= numCursors;
605 function verifyComplete() {
606 assert(!cursorsRunning);
611 function testCursorSeeks(
612 numKeys, numSeeksPerTransaction, numTransactions, useIndexForReads,
614 var testName = getDisplayName(arguments);
615 var objectStoreNames = ["store"];
616 var getKey = useIndexForReads ? getForwardIndexKey : getSimpleKey;
618 if (useIndexForReads) {
622 automation.setStatus("Creating database.");
624 if (useIndexForReads) {
626 indexName: indexName,
627 indexKeyPath: "firstName",
629 indexIsMultiEntry: false,
632 createDatabase(testName, objectStoreNames, onCreated, onError, options);
634 function onCreated(db) {
635 automation.setStatus("Setting up test database.");
636 var transaction = getTransaction(db, objectStoreNames, "readwrite",
637 function() { onSetupComplete(db); });
638 putLinearValues(transaction, objectStoreNames, numKeys, getSimpleKey,
642 function onSetupComplete(db) {
643 automation.setStatus("Setup complete.");
645 getCompletionFunc(db, testName, Date.now(), onTestComplete);
646 var mode = "readonly";
647 runTransactionBatch(db, numTransactions, batchFunc, objectStoreNames, mode,
651 function batchFunc(transaction) {
652 for (var i in objectStoreNames) {
653 var source = transaction.objectStore(objectStoreNames[i]);
654 if (useIndexForReads)
655 source = source.index(indexName);
656 for (var j = 0; j < numSeeksPerTransaction; ++j) {
662 function randomSeek(source) {
663 var request = useIndexForReads ? source.openKeyCursor()
664 : source.openCursor();
666 request.onerror = onError;
667 request.onsuccess = function() {
668 var cursor = request.result;
669 if (cursor && first) {
671 cursor.continue(getKey(numKeys - 1));