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 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
= window
.performance
.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
= window
.performance
.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
= window
.performance
.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
, window
.performance
.now(),
215 getTransaction(db
, objectStoreNames
, "readwrite", completionFunc
);
216 putLinearValues(transaction
, objectStoreNames
, numKeys
, null, getValue
);
220 function testRandomReadsAndWrites(
221 numKeys
, numReadsPerTransaction
, numWritesPerTransaction
, numTransactions
,
222 useIndexForReads
, onTestComplete
) {
224 if (useIndexForReads
)
226 var testName
= getDisplayName(arguments
);
227 var objectStoreNames
= ["store"];
228 var getKey
= getSimpleKey
;
229 var getValue
= useIndexForReads
? getIndexableValue
: getSimpleValue
;
231 automation
.setStatus("Creating database.");
233 if (useIndexForReads
) {
235 indexName
: indexName
,
237 indexIsUnique
: false,
238 indexIsMultiEntry
: false,
241 createDatabase(testName
, objectStoreNames
, onCreated
, onError
, options
);
243 function onCreated(db
) {
244 automation
.setStatus("Setting up test database.");
245 var transaction
= getTransaction(db
, objectStoreNames
, "readwrite",
246 function() { onSetupComplete(db
); });
247 putLinearValues(transaction
, objectStoreNames
, numKeys
, null,
248 function() { return "test value"; });
251 function onSetupComplete(db
) {
252 automation
.setStatus("Setup complete.");
254 getCompletionFunc(db
, testName
, window
.performance
.now(),
256 var mode
= "readonly";
257 if (numWritesPerTransaction
)
259 runTransactionBatch(db
, numTransactions
, batchFunc
, objectStoreNames
, mode
,
263 function batchFunc(transaction
) {
264 getRandomValues(transaction
, objectStoreNames
, numReadsPerTransaction
,
265 numKeys
, indexName
, getKey
);
266 putRandomValues(transaction
, objectStoreNames
, numWritesPerTransaction
,
267 numKeys
, getKey
, getValue
);
271 function testReadCache(numTransactions
, useIndexForReads
, onTestComplete
) {
273 var numReadsPerTransaction
= 50;
274 var numTransactionsLeft
= numTransactions
;
276 if (useIndexForReads
)
278 var testName
= getDisplayName(arguments
);
279 var objectStoreNames
= ["store"];
280 var getKey
= getSimpleKey
;
281 var getValue
= useIndexForReads
? getIndexableValue
: getSimpleValue
;
284 for (var i
=0; i
< numReadsPerTransaction
; ++i
) {
285 keys
.push(getKey(Math
.floor(random() * numKeys
)));
288 automation
.setStatus("Creating database.");
290 if (useIndexForReads
) {
292 indexName
: indexName
,
294 indexIsUnique
: false,
295 indexIsMultiEntry
: false,
298 createDatabase(testName
, objectStoreNames
, onCreated
, onError
, options
);
300 function onCreated(db
) {
301 automation
.setStatus("Setting up test database.");
302 var transaction
= getTransaction(db
, objectStoreNames
, "readwrite",
303 function() { onSetupComplete(db
); });
304 putLinearValues(transaction
, objectStoreNames
, numKeys
, getKey
,
309 function onSetupComplete(db
) {
310 automation
.setStatus("Setup complete.");
312 getCompletionFunc(db
, testName
, window
.performance
.now(),
314 runTransactionBatch(db
, numTransactions
, batchFunc
, objectStoreNames
,
315 "readonly", completionFunc
);
318 function batchFunc(transaction
) {
319 getSpecificValues(transaction
, objectStoreNames
, indexName
, keys
);
323 function testCreateAndDeleteIndex(numKeys
, onTestComplete
) {
324 var testName
= getDisplayName(arguments
);
325 var objectStoreNames
= ["store"];
327 automation
.setStatus("Creating database.");
328 createDatabase(testName
, objectStoreNames
, onCreated
, onError
);
331 function onCreated(db
) {
332 automation
.setStatus("Initializing data.");
333 var transaction
= getTransaction(db
, objectStoreNames
, "readwrite",
334 function() { onPopulated(db
); });
335 putLinearValues(transaction
, objectStoreNames
, numKeys
, null, getValue
);
338 function getValue(i
) {
339 return { firstName
: i
+ " first name", lastName
: i
+ " last name" };
342 function onPopulated(db
) {
344 automation
.setStatus("Building index.");
345 startTime
= window
.performance
.now();
346 var f = function(objectStore
) {
347 objectStore
.createIndex("index", "firstName", {unique
: true});
349 alterObjectStores(testName
, objectStoreNames
, f
, onIndexCreated
, onError
);
352 var indexCreationCompleteTime
;
353 function onIndexCreated(db
) {
355 indexCreationCompleteTime
= window
.performance
.now();
356 automation
.addResult("testCreateIndex",
357 indexCreationCompleteTime
- startTime
);
358 var f = function(objectStore
) {
359 objectStore
.deleteIndex("index");
361 automation
.setStatus("Deleting index.");
362 alterObjectStores(testName
, objectStoreNames
, f
, onIndexDeleted
, onError
);
365 function onIndexDeleted(db
) {
366 var duration
= window
.performance
.now() - indexCreationCompleteTime
;
367 // Ignore the cleanup time for this test.
368 automation
.addResult("testDeleteIndex", duration
);
369 automation
.setStatus("Deleting database.");
371 deleteDatabase(testName
, onDeleted
);
374 function onDeleted() {
375 automation
.setStatus("Deleted database.");
380 function testCursorReadsAndRandomWrites(
381 readKeysOnly
, useIndexForReads
, writeAlso
, sameStoreForWrites
,
383 // There's no key cursor unless you're reading from an index.
384 assert(useIndexForReads
|| !readKeysOnly
);
385 // If we're writing to another store, having an index would constrain our
386 // writes, as we create both object stores with the same configurations.
387 // We could do that if needed, but it's simpler not to.
388 assert(!useIndexForReads
|| !writeAlso
);
390 var numReadsPerTransaction
= 1000;
391 var testName
= getDisplayName(arguments
);
392 var objectStoreNames
= ["input store"];
395 if (sameStoreForWrites
) {
396 outputStoreName
= objectStoreNames
[0];
398 outputStoreName
= "output store";
399 objectStoreNames
.push(outputStoreName
);
402 var getKeyForRead
= getSimpleKey
;
404 if (useIndexForReads
) {
406 getKeyForRead = function(i
) {
407 // This depends on the implementations of getValuesFromCursor and
408 // getObjectValue. We reverse the order of the iteration here so that
409 // setting up bounds from k to k+n with n>0 works. Without this reversal,
410 // the upper bound is below the lower bound.
411 return getBackwardIndexKey(numKeys
- i
);
415 automation
.setStatus("Creating database.");
417 if (useIndexForReads
) {
419 indexName
: indexName
,
420 indexKeyPath
: "lastName", // depends on getBackwardIndexKey()
422 indexIsMultiEntry
: false,
425 createDatabase(testName
, objectStoreNames
, onCreated
, onError
, options
);
427 function onCreated(db
) {
428 automation
.setStatus("Setting up test database.");
429 var transaction
= getTransaction(db
, objectStoreNames
, "readwrite",
430 function() { onSetupComplete(db
); });
431 putLinearValues(transaction
, objectStoreNames
, numKeys
, getSimpleKey
,
434 function onSetupComplete(db
) {
435 automation
.setStatus("Setup complete.");
437 getCompletionFunc(db
, testName
, window
.performance
.now(),
439 var mode
= "readonly";
443 getTransaction(db
, objectStoreNames
, mode
, completionFunc
);
446 transaction
, objectStoreNames
[0], numReadsPerTransaction
, numKeys
,
447 indexName
, getKeyForRead
, readKeysOnly
, outputStoreName
);
451 function testSporadicWrites(
452 numOperationsPerTransaction
, numIndices
, alternateWithReads
,
455 // With 30 transactions, spaced 50ms apart, we'll need at least 1.5s.
456 var numTransactions
= 30;
457 if (alternateWithReads
)
458 numTransactions
*= 2;
459 var delayBetweenBatches
= 50;
461 var testName
= getDisplayName(arguments
);
462 var numTransactionsLeft
= numTransactions
;
463 var objectStoreNames
= ["store"];
464 var numTransactionsRunning
= 0;
466 var getValue
= getSimpleValue
;
468 getValue = function (i
) { return getNFieldObjectValue(i
, numIndices
); };
470 automation
.setStatus("Creating database.");
472 for (var i
=0; i
< numIndices
; ++i
) {
474 o
.indexName
= "index " + i
;
475 o
.indexKeyPath
= getNFieldName(i
);
476 o
.indexIsUnique
= false;
477 o
.indexIsMultiEntry
= false;
480 createDatabase(testName
, objectStoreNames
, onCreated
, onError
, options
);
482 function onCreated(db
) {
483 automation
.setStatus("Setting up test database.");
484 var transaction
= getTransaction(db
, objectStoreNames
, "readwrite",
485 function() { onSetupComplete(db
); });
486 putLinearValues(transaction
, objectStoreNames
, numKeys
, getSimpleKey
,
490 function onSetupComplete(db
) {
491 automation
.setStatus("Setup complete.");
493 getCompletionFunc(db
, testName
, window
.performance
.now(),
498 function runOneBatch(db
) {
499 assert(numTransactionsLeft
);
500 if (--numTransactionsLeft
) {
501 setTimeout(function () { runOneBatch(db
); }, delayBetweenBatches
);
504 var mode
, transaction
;
505 if (alternateWithReads
) {
506 ++numTransactionsRunning
;
508 getTransaction(db
, objectStoreNames
, "readonly", batchComplete
);
509 getRandomValues(transaction
, objectStoreNames
,
510 numOperationsPerTransaction
, numKeys
);
512 ++numTransactionsRunning
;
514 getTransaction(db
, objectStoreNames
, "readwrite", batchComplete
);
515 putRandomValues(transaction
, objectStoreNames
, numOperationsPerTransaction
,
518 function batchComplete() {
519 assert(numTransactionsRunning
);
520 if (!--numTransactionsRunning
&& !numTransactionsLeft
)
526 function testWalkingMultipleCursors(numCursors
, onTestComplete
) {
528 var numHitsPerKey
= 10;
529 var testName
= getDisplayName(arguments
);
530 var objectStoreNames
= ["input store"];
531 var indexName
= "index name";
532 var getKey
= getSimpleKey
;
533 var getValue
= getIndexableValue
;
535 automation
.setStatus("Creating database.");
537 indexName
: indexName
,
539 indexIsUnique
: false,
540 indexIsMultiEntry
: false,
542 createDatabase(testName
, objectStoreNames
, onCreated
, onError
, options
);
544 function onCreated(db
) {
545 automation
.setStatus("Setting up test database.");
546 var transaction
= getTransaction(db
, objectStoreNames
, "readwrite",
547 function() { onSetupComplete(db
); });
548 // This loop adds the same value numHitsPerKey times for each key.
549 for (var i
= 0; i
< numHitsPerKey
; ++i
) {
550 putLinearValues(transaction
, objectStoreNames
, numKeys
, getKeyFunc(i
),
554 // While the value is the same each time through the putLinearValues loop, we
555 // want the key to keep increaasing for each copy.
556 function getKeyFunc(k
) {
558 return getKey(k
* numKeys
+ i
);
562 function onSetupComplete(db
) {
563 automation
.setStatus("Setup complete.");
565 getCompletionFunc(db
, testName
, window
.performance
.now(),
568 getTransaction(db
, objectStoreNames
, "readonly", verifyComplete
);
570 walkSeveralCursors(transaction
, numKeys
);
572 var responseCounts
= [];
573 var cursorsRunning
= numCursors
;
574 function walkSeveralCursors(transaction
, numKeys
) {
575 var source
= transaction
.objectStore(objectStoreNames
[0]).index(indexName
);
577 var continueCursorIndex
= 0;
578 for (var i
= 0; i
< numCursors
; ++i
) {
579 var rand
= Math
.floor(random() * numKeys
);
580 // Since we have numHitsPerKey copies of each value in the database,
581 // IDBKeyRange.only will return numHitsPerKey results, each referring to a
582 // different key with the matching value.
583 var request
= source
.openCursor(IDBKeyRange
.only(getSimpleValue(rand
)));
584 responseCounts
.push(0);
585 request
.onerror
= onError
;
586 request
.onsuccess = function(event
) {
587 assert(cursorsRunning
);
588 var request
= event
.target
;
589 if (!("requestIndex" in request
)) {
590 assert(requests
.length
< numCursors
);
591 request
.requestIndex
= requests
.length
;
592 requests
.push(request
);
594 var cursor
= event
.target
.result
;
596 assert(responseCounts
[request
.requestIndex
] < numHitsPerKey
);
597 ++responseCounts
[request
.requestIndex
];
599 assert(responseCounts
[request
.requestIndex
] == numHitsPerKey
);
602 if (cursorsRunning
) {
603 if (requests
.length
== numCursors
) {
604 requests
[continueCursorIndex
++].result
.continue();
605 continueCursorIndex
%= numCursors
;
611 function verifyComplete() {
612 assert(!cursorsRunning
);
617 function testCursorSeeks(
618 numKeys
, numSeeksPerTransaction
, numTransactions
, useIndexForReads
,
620 var testName
= getDisplayName(arguments
);
621 var objectStoreNames
= ["store"];
622 var getKey
= useIndexForReads
? getForwardIndexKey
: getSimpleKey
;
624 if (useIndexForReads
) {
628 automation
.setStatus("Creating database.");
630 if (useIndexForReads
) {
632 indexName
: indexName
,
633 indexKeyPath
: "firstName",
635 indexIsMultiEntry
: false,
638 createDatabase(testName
, objectStoreNames
, onCreated
, onError
, options
);
640 function onCreated(db
) {
641 automation
.setStatus("Setting up test database.");
642 var transaction
= getTransaction(db
, objectStoreNames
, "readwrite",
643 function() { onSetupComplete(db
); });
644 putLinearValues(transaction
, objectStoreNames
, numKeys
, getSimpleKey
,
648 function onSetupComplete(db
) {
649 automation
.setStatus("Setup complete.");
651 getCompletionFunc(db
, testName
, window
.performance
.now(),
653 var mode
= "readonly";
654 runTransactionBatch(db
, numTransactions
, batchFunc
, objectStoreNames
, mode
,
658 function batchFunc(transaction
) {
659 for (var i
in objectStoreNames
) {
660 var source
= transaction
.objectStore(objectStoreNames
[i
]);
661 if (useIndexForReads
)
662 source
= source
.index(indexName
);
663 for (var j
= 0; j
< numSeeksPerTransaction
; ++j
) {
669 function randomSeek(source
) {
670 var request
= useIndexForReads
? source
.openKeyCursor()
671 : source
.openCursor();
673 request
.onerror
= onError
;
674 request
.onsuccess = function() {
675 var cursor
= request
.result
;
676 if (cursor
&& first
) {
678 cursor
.continue(getKey(numKeys
- 1));