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 a small bunch of batches of reads of the same keys from an object store.
73 [testReadCache
, 10, kDontUseIndex
],
74 // Make a bunch of batches of reads of the same keys from an index.
75 [testReadCache
, 50, kUseIndex
],
76 // Make a small bunch of batches of reads of the same keys from an object store.
77 [testReadCache
, 10, kDontUseIndex
],
78 // Make a bunch of batches of reads of the same keys from an index.
79 [testReadCache
, 50, kUseIndex
],
80 // Create and delete an index on a store that already contains data [produces
81 // a timing result for each of creation and deletion].
82 [testCreateAndDeleteIndex
, 5000],
83 // Walk through multiple cursors into the same object store, round-robin, until
84 // you've reached the end of each of them.
85 [testWalkingMultipleCursors
, 5],
86 // Walk through many cursors into the same object store, round-robin, until
87 // you've reached the end of each of them.
88 [testWalkingMultipleCursors
, 50],
89 // Open an object store cursor, then continue(key) to the last value.
90 [testCursorSeeks
, 2000, 10, 4, kDontUseIndex
],
91 // Open an index key cursor, then continue(key) to the last value.
92 [testCursorSeeks
, 2000, 10, 4, kUseIndex
],
104 function runNextTest() {
105 var filter
= testFilter
| window
.location
.hash
.slice(1);
107 while (currentTest
< tests
.length
) {
108 test
= tests
[currentTest
];
110 if (!filter
|| f
.name
== filter
)
115 if (currentTest
< tests
.length
) {
116 test
.push(runNextTest
);
120 onAllTestsComplete();
124 function onAllTestsComplete() {
125 var overallDuration
= window
.performance
.now() - overallTestStartTime
;
126 automation
.addResult("OverallTestDuration", overallDuration
);
127 automation
.setDone();
131 // This is the only test that includes database creation and deletion in its
132 // results; the others just test specific operations. To see only the
133 // creation/deletion without the specific operations used to build up the data
134 // in the object stores here, subtract off the results of
135 // testCreateKeysInStores.
136 function testCreateAndDeleteDatabase(
137 numKeys
, numStores
, payloadLength
, onTestComplete
) {
138 var testName
= getDisplayName(arguments
);
139 assert(numKeys
>= 0);
140 assert(numStores
>= 1);
141 var objectStoreNames
= [];
142 for (var i
=0; i
< numStores
; ++i
) {
143 objectStoreNames
.push("store " + i
);
145 var value
= stringOfLength(payloadLength
);
146 function getValue() {
150 automation
.setStatus("Creating database.");
151 var startTime
= window
.performance
.now();
153 createDatabase(testName
, objectStoreNames
, onCreated
, onError
);
155 function onCreated(db
) {
156 automation
.setStatus("Constructing transaction.");
158 getTransaction(db
, objectStoreNames
, "readwrite",
159 function() { onValuesWritten(db
); });
160 putLinearValues(transaction
, objectStoreNames
, numKeys
, null, getValue
);
163 function onValuesWritten(db
) {
164 automation
.setStatus("Deleting database.");
166 deleteDatabase(testName
, onDeleted
);
169 function onDeleted() {
170 var duration
= window
.performance
.now() - startTime
;
171 automation
.addResult(testName
, duration
);
172 automation
.setStatus("Deleted database.");
177 function testCreateKeysInStores(
178 numKeys
, numStores
, payloadLength
, onTestComplete
) {
179 var testName
= getDisplayName(arguments
);
180 assert(numKeys
>= 0);
181 assert(numStores
>= 1);
182 var objectStoreNames
= [];
183 for (var i
=0; i
< numStores
; ++i
) {
184 objectStoreNames
.push("store " + i
);
186 var value
= stringOfLength(payloadLength
);
187 function getValue() {
191 automation
.setStatus("Creating database.");
192 createDatabase(testName
, objectStoreNames
, onCreated
, onError
);
194 function onCreated(db
) {
195 automation
.setStatus("Constructing transaction.");
197 getCompletionFunc(db
, testName
, window
.performance
.now(),
200 getTransaction(db
, objectStoreNames
, "readwrite", completionFunc
);
201 putLinearValues(transaction
, objectStoreNames
, numKeys
, null, getValue
);
205 function testRandomReadsAndWrites(
206 numKeys
, numReadsPerTransaction
, numWritesPerTransaction
, numTransactions
,
207 useIndexForReads
, onTestComplete
) {
209 if (useIndexForReads
)
211 var testName
= getDisplayName(arguments
);
212 var objectStoreNames
= ["store"];
213 var getKey
= getSimpleKey
;
214 var getValue
= useIndexForReads
? getIndexableValue
: getSimpleValue
;
216 automation
.setStatus("Creating database.");
218 if (useIndexForReads
) {
220 indexName
: indexName
,
222 indexIsUnique
: false,
223 indexIsMultiEntry
: false,
226 createDatabase(testName
, objectStoreNames
, onCreated
, onError
, options
);
228 function onCreated(db
) {
229 automation
.setStatus("Setting up test database.");
230 var transaction
= getTransaction(db
, objectStoreNames
, "readwrite",
231 function() { onSetupComplete(db
); });
232 putLinearValues(transaction
, objectStoreNames
, numKeys
, null,
233 function() { return "test value"; });
236 function onSetupComplete(db
) {
237 automation
.setStatus("Setup complete.");
239 getCompletionFunc(db
, testName
, window
.performance
.now(),
241 var mode
= "readonly";
242 if (numWritesPerTransaction
)
244 runTransactionBatch(db
, numTransactions
, batchFunc
, objectStoreNames
, mode
,
248 function batchFunc(transaction
) {
249 getRandomValues(transaction
, objectStoreNames
, numReadsPerTransaction
,
250 numKeys
, indexName
, getKey
);
251 putRandomValues(transaction
, objectStoreNames
, numWritesPerTransaction
,
252 numKeys
, getKey
, getValue
);
256 function testReadCache(numTransactions
, useIndexForReads
, onTestComplete
) {
258 var numReadsPerTransaction
= 50;
259 var numTransactionsLeft
= numTransactions
;
261 if (useIndexForReads
)
263 var testName
= getDisplayName(arguments
);
264 var objectStoreNames
= ["store"];
265 var getKey
= getSimpleKey
;
266 var getValue
= useIndexForReads
? getIndexableValue
: getSimpleValue
;
269 for (var i
=0; i
< numReadsPerTransaction
; ++i
) {
270 keys
.push(getKey(Math
.floor(random() * numKeys
)));
273 automation
.setStatus("Creating database.");
275 if (useIndexForReads
) {
277 indexName
: indexName
,
279 indexIsUnique
: false,
280 indexIsMultiEntry
: false,
283 createDatabase(testName
, objectStoreNames
, onCreated
, onError
, options
);
285 function onCreated(db
) {
286 automation
.setStatus("Setting up test database.");
287 var transaction
= getTransaction(db
, objectStoreNames
, "readwrite",
288 function() { onSetupComplete(db
); });
289 putLinearValues(transaction
, objectStoreNames
, numKeys
, getKey
,
294 function onSetupComplete(db
) {
295 automation
.setStatus("Setup complete.");
297 getCompletionFunc(db
, testName
, window
.performance
.now(),
299 runTransactionBatch(db
, numTransactions
, batchFunc
, objectStoreNames
,
300 "readonly", completionFunc
);
303 function batchFunc(transaction
) {
304 getSpecificValues(transaction
, objectStoreNames
, indexName
, keys
);
308 function testCreateAndDeleteIndex(numKeys
, onTestComplete
) {
309 var testName
= getDisplayName(arguments
);
310 var objectStoreNames
= ["store"];
312 automation
.setStatus("Creating database.");
313 createDatabase(testName
, objectStoreNames
, onCreated
, onError
);
316 function onCreated(db
) {
317 automation
.setStatus("Initializing data.");
318 var transaction
= getTransaction(db
, objectStoreNames
, "readwrite",
319 function() { onPopulated(db
); });
320 putLinearValues(transaction
, objectStoreNames
, numKeys
, null, getValue
);
323 function getValue(i
) {
324 return { firstName
: i
+ " first name", lastName
: i
+ " last name" };
327 function onPopulated(db
) {
329 automation
.setStatus("Building index.");
330 startTime
= window
.performance
.now();
331 var f = function(objectStore
) {
332 objectStore
.createIndex("index", "firstName", {unique
: true});
334 alterObjectStores(testName
, objectStoreNames
, f
, onIndexCreated
, onError
);
337 var indexCreationCompleteTime
;
338 function onIndexCreated(db
) {
340 indexCreationCompleteTime
= window
.performance
.now();
341 automation
.addResult("testCreateIndex",
342 indexCreationCompleteTime
- startTime
);
343 var f = function(objectStore
) {
344 objectStore
.deleteIndex("index");
346 automation
.setStatus("Deleting index.");
347 alterObjectStores(testName
, objectStoreNames
, f
, onIndexDeleted
, onError
);
350 function onIndexDeleted(db
) {
351 var duration
= window
.performance
.now() - indexCreationCompleteTime
;
352 // Ignore the cleanup time for this test.
353 automation
.addResult("testDeleteIndex", duration
);
354 automation
.setStatus("Deleting database.");
356 deleteDatabase(testName
, onDeleted
);
359 function onDeleted() {
360 automation
.setStatus("Deleted database.");
365 function testCursorReadsAndRandomWrites(
366 readKeysOnly
, useIndexForReads
, writeAlso
, sameStoreForWrites
,
368 // There's no key cursor unless you're reading from an index.
369 assert(useIndexForReads
|| !readKeysOnly
);
370 // If we're writing to another store, having an index would constrain our
371 // writes, as we create both object stores with the same configurations.
372 // We could do that if needed, but it's simpler not to.
373 assert(!useIndexForReads
|| !writeAlso
);
375 var numReadsPerTransaction
= 1000;
376 var testName
= getDisplayName(arguments
);
377 var objectStoreNames
= ["input store"];
380 if (sameStoreForWrites
) {
381 outputStoreName
= objectStoreNames
[0];
383 outputStoreName
= "output store";
384 objectStoreNames
.push(outputStoreName
);
387 var getKeyForRead
= getSimpleKey
;
389 if (useIndexForReads
) {
391 getKeyForRead = function(i
) {
392 // This depends on the implementations of getValuesFromCursor and
393 // getObjectValue. We reverse the order of the iteration here so that
394 // setting up bounds from k to k+n with n>0 works. Without this reversal,
395 // the upper bound is below the lower bound.
396 return getBackwardIndexKey(numKeys
- i
);
400 automation
.setStatus("Creating database.");
402 if (useIndexForReads
) {
404 indexName
: indexName
,
405 indexKeyPath
: "lastName", // depends on getBackwardIndexKey()
407 indexIsMultiEntry
: false,
410 createDatabase(testName
, objectStoreNames
, onCreated
, onError
, options
);
412 function onCreated(db
) {
413 automation
.setStatus("Setting up test database.");
414 var transaction
= getTransaction(db
, objectStoreNames
, "readwrite",
415 function() { onSetupComplete(db
); });
416 putLinearValues(transaction
, objectStoreNames
, numKeys
, getSimpleKey
,
419 function onSetupComplete(db
) {
420 automation
.setStatus("Setup complete.");
422 getCompletionFunc(db
, testName
, window
.performance
.now(),
424 var mode
= "readonly";
428 getTransaction(db
, objectStoreNames
, mode
, completionFunc
);
431 transaction
, objectStoreNames
[0], numReadsPerTransaction
, numKeys
,
432 indexName
, getKeyForRead
, readKeysOnly
, outputStoreName
);
436 function testWalkingMultipleCursors(numCursors
, onTestComplete
) {
438 var numHitsPerKey
= 10;
439 var testName
= getDisplayName(arguments
);
440 var objectStoreNames
= ["input store"];
441 var indexName
= "index name";
442 var getKey
= getSimpleKey
;
443 var getValue
= getIndexableValue
;
445 automation
.setStatus("Creating database.");
447 indexName
: indexName
,
449 indexIsUnique
: false,
450 indexIsMultiEntry
: false,
452 createDatabase(testName
, objectStoreNames
, onCreated
, onError
, options
);
454 function onCreated(db
) {
455 automation
.setStatus("Setting up test database.");
456 var transaction
= getTransaction(db
, objectStoreNames
, "readwrite",
457 function() { onSetupComplete(db
); });
458 // This loop adds the same value numHitsPerKey times for each key.
459 for (var i
= 0; i
< numHitsPerKey
; ++i
) {
460 putLinearValues(transaction
, objectStoreNames
, numKeys
, getKeyFunc(i
),
464 // While the value is the same each time through the putLinearValues loop, we
465 // want the key to keep increaasing for each copy.
466 function getKeyFunc(k
) {
468 return getKey(k
* numKeys
+ i
);
472 function onSetupComplete(db
) {
473 automation
.setStatus("Setup complete.");
475 getCompletionFunc(db
, testName
, window
.performance
.now(),
478 getTransaction(db
, objectStoreNames
, "readonly", verifyComplete
);
480 walkSeveralCursors(transaction
, numKeys
);
482 var responseCounts
= [];
483 var cursorsRunning
= numCursors
;
484 function walkSeveralCursors(transaction
, numKeys
) {
485 var source
= transaction
.objectStore(objectStoreNames
[0]).index(indexName
);
487 var continueCursorIndex
= 0;
488 for (var i
= 0; i
< numCursors
; ++i
) {
489 var rand
= Math
.floor(random() * numKeys
);
490 // Since we have numHitsPerKey copies of each value in the database,
491 // IDBKeyRange.only will return numHitsPerKey results, each referring to a
492 // different key with the matching value.
493 var request
= source
.openCursor(IDBKeyRange
.only(getSimpleValue(rand
)));
494 responseCounts
.push(0);
495 request
.onerror
= onError
;
496 request
.onsuccess = function(event
) {
497 assert(cursorsRunning
);
498 var request
= event
.target
;
499 if (!("requestIndex" in request
)) {
500 assert(requests
.length
< numCursors
);
501 request
.requestIndex
= requests
.length
;
502 requests
.push(request
);
504 var cursor
= event
.target
.result
;
506 assert(responseCounts
[request
.requestIndex
] < numHitsPerKey
);
507 ++responseCounts
[request
.requestIndex
];
509 assert(responseCounts
[request
.requestIndex
] == numHitsPerKey
);
512 if (cursorsRunning
) {
513 if (requests
.length
== numCursors
) {
514 requests
[continueCursorIndex
++].result
.continue();
515 continueCursorIndex
%= numCursors
;
521 function verifyComplete() {
522 assert(!cursorsRunning
);
527 function testCursorSeeks(
528 numKeys
, numSeeksPerTransaction
, numTransactions
, useIndexForReads
,
530 var testName
= getDisplayName(arguments
);
531 var objectStoreNames
= ["store"];
532 var getKey
= useIndexForReads
? getForwardIndexKey
: getSimpleKey
;
534 if (useIndexForReads
) {
538 automation
.setStatus("Creating database.");
540 if (useIndexForReads
) {
542 indexName
: indexName
,
543 indexKeyPath
: "firstName",
545 indexIsMultiEntry
: false,
548 createDatabase(testName
, objectStoreNames
, onCreated
, onError
, options
);
550 function onCreated(db
) {
551 automation
.setStatus("Setting up test database.");
552 var transaction
= getTransaction(db
, objectStoreNames
, "readwrite",
553 function() { onSetupComplete(db
); });
554 putLinearValues(transaction
, objectStoreNames
, numKeys
, getSimpleKey
,
558 function onSetupComplete(db
) {
559 automation
.setStatus("Setup complete.");
561 getCompletionFunc(db
, testName
, window
.performance
.now(),
563 var mode
= "readonly";
564 runTransactionBatch(db
, numTransactions
, batchFunc
, objectStoreNames
, mode
,
568 function batchFunc(transaction
) {
569 for (var i
in objectStoreNames
) {
570 var source
= transaction
.objectStore(objectStoreNames
[i
]);
571 if (useIndexForReads
)
572 source
= source
.index(indexName
);
573 for (var j
= 0; j
< numSeeksPerTransaction
; ++j
) {
579 function randomSeek(source
) {
580 var request
= useIndexForReads
? source
.openKeyCursor()
581 : source
.openCursor();
583 request
.onerror
= onError
;
584 request
.onsuccess = function() {
585 var cursor
= request
.result
;
586 if (cursor
&& first
) {
588 cursor
.continue(getKey(numKeys
- 1));