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],
101 function runNextTest() {
102 var filter = window.location.hash.slice(1);
104 while (currentTest < tests.length) {
105 test = tests[currentTest];
107 if (!filter || f.name == filter)
112 if (currentTest < tests.length) {
113 test.push(runNextTest);
117 onAllTestsComplete();
121 function onAllTestsComplete() {
122 var overallDuration = window.performance.now() - overallTestStartTime;
123 automation.addResult("OverallTestDuration", overallDuration);
124 automation.setDone();
127 // This is the only test that includes database creation and deletion in its
128 // results; the others just test specific operations. To see only the
129 // creation/deletion without the specific operations used to build up the data
130 // in the object stores here, subtract off the results of
131 // testCreateKeysInStores.
132 function testCreateAndDeleteDatabase(
133 numKeys, numStores, payloadLength, onTestComplete) {
134 var testName = getDisplayName(arguments);
135 assert(numKeys >= 0);
136 assert(numStores >= 1);
137 var objectStoreNames = [];
138 for (var i=0; i < numStores; ++i) {
139 objectStoreNames.push("store " + i);
141 var value = stringOfLength(payloadLength);
142 function getValue() {
146 automation.setStatus("Creating database.");
147 var startTime = window.performance.now();
149 createDatabase(testName, objectStoreNames, onCreated, onError);
151 function onCreated(db) {
152 automation.setStatus("Constructing transaction.");
154 getTransaction(db, objectStoreNames, "readwrite",
155 function() { onValuesWritten(db); });
156 putLinearValues(transaction, objectStoreNames, numKeys, null, getValue);
159 function onValuesWritten(db) {
160 automation.setStatus("Deleting database.");
162 deleteDatabase(testName, onDeleted);
165 function onDeleted() {
166 var duration = window.performance.now() - startTime;
167 automation.addResult(testName, duration);
168 automation.setStatus("Deleted database.");
173 function testCreateKeysInStores(
174 numKeys, numStores, payloadLength, onTestComplete) {
175 var testName = getDisplayName(arguments);
176 assert(numKeys >= 0);
177 assert(numStores >= 1);
178 var objectStoreNames = [];
179 for (var i=0; i < numStores; ++i) {
180 objectStoreNames.push("store " + i);
182 var value = stringOfLength(payloadLength);
183 function getValue() {
187 automation.setStatus("Creating database.");
188 createDatabase(testName, objectStoreNames, onCreated, onError);
190 function onCreated(db) {
191 automation.setStatus("Constructing transaction.");
193 getCompletionFunc(db, testName, window.performance.now(),
196 getTransaction(db, objectStoreNames, "readwrite", completionFunc);
197 putLinearValues(transaction, objectStoreNames, numKeys, null, getValue);
201 function testRandomReadsAndWrites(
202 numKeys, numReadsPerTransaction, numWritesPerTransaction, numTransactions,
203 useIndexForReads, onTestComplete) {
205 if (useIndexForReads)
207 var testName = getDisplayName(arguments);
208 var objectStoreNames = ["store"];
209 var getKey = getSimpleKey;
210 var getValue = useIndexForReads ? getIndexableValue : getSimpleValue;
212 automation.setStatus("Creating database.");
214 if (useIndexForReads) {
216 indexName: indexName,
218 indexIsUnique: false,
219 indexIsMultiEntry: false,
222 createDatabase(testName, objectStoreNames, onCreated, onError, options);
224 function onCreated(db) {
225 automation.setStatus("Setting up test database.");
226 var transaction = getTransaction(db, objectStoreNames, "readwrite",
227 function() { onSetupComplete(db); });
228 putLinearValues(transaction, objectStoreNames, numKeys, null,
229 function() { return "test value"; });
232 function onSetupComplete(db) {
233 automation.setStatus("Setup complete.");
235 getCompletionFunc(db, testName, window.performance.now(),
237 var mode = "readonly";
238 if (numWritesPerTransaction)
240 runTransactionBatch(db, numTransactions, batchFunc, objectStoreNames, mode,
244 function batchFunc(transaction) {
245 getRandomValues(transaction, objectStoreNames, numReadsPerTransaction,
246 numKeys, indexName, getKey);
247 putRandomValues(transaction, objectStoreNames, numWritesPerTransaction,
248 numKeys, getKey, getValue);
252 function testReadCache(numTransactions, useIndexForReads, onTestComplete) {
254 var numReadsPerTransaction = 50;
255 var numTransactionsLeft = numTransactions;
257 if (useIndexForReads)
259 var testName = getDisplayName(arguments);
260 var objectStoreNames = ["store"];
261 var getKey = getSimpleKey;
262 var getValue = useIndexForReads ? getIndexableValue : getSimpleValue;
265 for (var i=0; i < numReadsPerTransaction; ++i) {
266 keys.push(getKey(Math.floor(random() * numKeys)));
269 automation.setStatus("Creating database.");
271 if (useIndexForReads) {
273 indexName: indexName,
275 indexIsUnique: false,
276 indexIsMultiEntry: false,
279 createDatabase(testName, objectStoreNames, onCreated, onError, options);
281 function onCreated(db) {
282 automation.setStatus("Setting up test database.");
283 var transaction = getTransaction(db, objectStoreNames, "readwrite",
284 function() { onSetupComplete(db); });
285 putLinearValues(transaction, objectStoreNames, numKeys, getKey,
290 function onSetupComplete(db) {
291 automation.setStatus("Setup complete.");
293 getCompletionFunc(db, testName, window.performance.now(),
295 runTransactionBatch(db, numTransactions, batchFunc, objectStoreNames,
296 "readonly", completionFunc);
299 function batchFunc(transaction) {
300 getSpecificValues(transaction, objectStoreNames, indexName, keys);
304 function testCreateAndDeleteIndex(numKeys, onTestComplete) {
305 var testName = getDisplayName(arguments);
306 var objectStoreNames = ["store"];
308 automation.setStatus("Creating database.");
309 createDatabase(testName, objectStoreNames, onCreated, onError);
312 function onCreated(db) {
313 automation.setStatus("Initializing data.");
314 var transaction = getTransaction(db, objectStoreNames, "readwrite",
315 function() { onPopulated(db); });
316 putLinearValues(transaction, objectStoreNames, numKeys, null, getValue);
319 function getValue(i) {
320 return { firstName: i + " first name", lastName: i + " last name" };
323 function onPopulated(db) {
325 automation.setStatus("Building index.");
326 startTime = window.performance.now();
327 var f = function(objectStore) {
328 objectStore.createIndex("index", "firstName", {unique: true});
330 alterObjectStores(testName, objectStoreNames, f, onIndexCreated, onError);
333 var indexCreationCompleteTime;
334 function onIndexCreated(db) {
336 indexCreationCompleteTime = window.performance.now();
337 automation.addResult("testCreateIndex",
338 indexCreationCompleteTime - startTime);
339 var f = function(objectStore) {
340 objectStore.deleteIndex("index");
342 automation.setStatus("Deleting index.");
343 alterObjectStores(testName, objectStoreNames, f, onIndexDeleted, onError);
346 function onIndexDeleted(db) {
347 var duration = window.performance.now() - indexCreationCompleteTime;
348 // Ignore the cleanup time for this test.
349 automation.addResult("testDeleteIndex", duration);
350 automation.setStatus("Deleting database.");
352 deleteDatabase(testName, onDeleted);
355 function onDeleted() {
356 automation.setStatus("Deleted database.");
361 function testCursorReadsAndRandomWrites(
362 readKeysOnly, useIndexForReads, writeAlso, sameStoreForWrites,
364 // There's no key cursor unless you're reading from an index.
365 assert(useIndexForReads || !readKeysOnly);
366 // If we're writing to another store, having an index would constrain our
367 // writes, as we create both object stores with the same configurations.
368 // We could do that if needed, but it's simpler not to.
369 assert(!useIndexForReads || !writeAlso);
371 var numReadsPerTransaction = 1000;
372 var testName = getDisplayName(arguments);
373 var objectStoreNames = ["input store"];
376 if (sameStoreForWrites) {
377 outputStoreName = objectStoreNames[0];
379 outputStoreName = "output store";
380 objectStoreNames.push(outputStoreName);
383 var getKeyForRead = getSimpleKey;
385 if (useIndexForReads) {
387 getKeyForRead = function(i) {
388 // This depends on the implementations of getValuesFromCursor and
389 // getObjectValue. We reverse the order of the iteration here so that
390 // setting up bounds from k to k+n with n>0 works. Without this reversal,
391 // the upper bound is below the lower bound.
392 return getBackwardIndexKey(numKeys - i);
396 automation.setStatus("Creating database.");
398 if (useIndexForReads) {
400 indexName: indexName,
401 indexKeyPath: "lastName", // depends on getBackwardIndexKey()
403 indexIsMultiEntry: false,
406 createDatabase(testName, objectStoreNames, onCreated, onError, options);
408 function onCreated(db) {
409 automation.setStatus("Setting up test database.");
410 var transaction = getTransaction(db, objectStoreNames, "readwrite",
411 function() { onSetupComplete(db); });
412 putLinearValues(transaction, objectStoreNames, numKeys, getSimpleKey,
415 function onSetupComplete(db) {
416 automation.setStatus("Setup complete.");
418 getCompletionFunc(db, testName, window.performance.now(),
420 var mode = "readonly";
424 getTransaction(db, objectStoreNames, mode, completionFunc);
427 transaction, objectStoreNames[0], numReadsPerTransaction, numKeys,
428 indexName, getKeyForRead, readKeysOnly, outputStoreName);
432 function testWalkingMultipleCursors(numCursors, onTestComplete) {
434 var numHitsPerKey = 10;
435 var testName = getDisplayName(arguments);
436 var objectStoreNames = ["input store"];
437 var indexName = "index name";
438 var getKey = getSimpleKey;
439 var getValue = getIndexableValue;
441 automation.setStatus("Creating database.");
443 indexName: indexName,
445 indexIsUnique: false,
446 indexIsMultiEntry: false,
448 createDatabase(testName, objectStoreNames, onCreated, onError, options);
450 function onCreated(db) {
451 automation.setStatus("Setting up test database.");
452 var transaction = getTransaction(db, objectStoreNames, "readwrite",
453 function() { onSetupComplete(db); });
454 // This loop adds the same value numHitsPerKey times for each key.
455 for (var i = 0; i < numHitsPerKey; ++i) {
456 putLinearValues(transaction, objectStoreNames, numKeys, getKeyFunc(i),
460 // While the value is the same each time through the putLinearValues loop, we
461 // want the key to keep increaasing for each copy.
462 function getKeyFunc(k) {
464 return getKey(k * numKeys + i);
468 function onSetupComplete(db) {
469 automation.setStatus("Setup complete.");
471 getCompletionFunc(db, testName, window.performance.now(),
474 getTransaction(db, objectStoreNames, "readonly", verifyComplete);
476 walkSeveralCursors(transaction, numKeys);
478 var responseCounts = [];
479 var cursorsRunning = numCursors;
480 function walkSeveralCursors(transaction, numKeys) {
481 var source = transaction.objectStore(objectStoreNames[0]).index(indexName);
483 var continueCursorIndex = 0;
484 for (var i = 0; i < numCursors; ++i) {
485 var rand = Math.floor(random() * numKeys);
486 // Since we have numHitsPerKey copies of each value in the database,
487 // IDBKeyRange.only will return numHitsPerKey results, each referring to a
488 // different key with the matching value.
489 var request = source.openCursor(IDBKeyRange.only(getSimpleValue(rand)));
490 responseCounts.push(0);
491 request.onerror = onError;
492 request.onsuccess = function(event) {
493 assert(cursorsRunning);
494 var request = event.target;
495 if (!("requestIndex" in request)) {
496 assert(requests.length < numCursors);
497 request.requestIndex = requests.length;
498 requests.push(request);
500 var cursor = event.target.result;
502 assert(responseCounts[request.requestIndex] < numHitsPerKey);
503 ++responseCounts[request.requestIndex];
505 assert(responseCounts[request.requestIndex] == numHitsPerKey);
508 if (cursorsRunning) {
509 if (requests.length == numCursors) {
510 requests[continueCursorIndex++].result.continue();
511 continueCursorIndex %= numCursors;
517 function verifyComplete() {
518 assert(!cursorsRunning);
523 function testCursorSeeks(
524 numKeys, numSeeksPerTransaction, numTransactions, useIndexForReads,
526 var testName = getDisplayName(arguments);
527 var objectStoreNames = ["store"];
528 var getKey = useIndexForReads ? getForwardIndexKey : getSimpleKey;
530 if (useIndexForReads) {
534 automation.setStatus("Creating database.");
536 if (useIndexForReads) {
538 indexName: indexName,
539 indexKeyPath: "firstName",
541 indexIsMultiEntry: false,
544 createDatabase(testName, objectStoreNames, onCreated, onError, options);
546 function onCreated(db) {
547 automation.setStatus("Setting up test database.");
548 var transaction = getTransaction(db, objectStoreNames, "readwrite",
549 function() { onSetupComplete(db); });
550 putLinearValues(transaction, objectStoreNames, numKeys, getSimpleKey,
554 function onSetupComplete(db) {
555 automation.setStatus("Setup complete.");
557 getCompletionFunc(db, testName, window.performance.now(),
559 var mode = "readonly";
560 runTransactionBatch(db, numTransactions, batchFunc, objectStoreNames, mode,
564 function batchFunc(transaction) {
565 for (var i in objectStoreNames) {
566 var source = transaction.objectStore(objectStoreNames[i]);
567 if (useIndexForReads)
568 source = source.index(indexName);
569 for (var j = 0; j < numSeeksPerTransaction; ++j) {
575 function randomSeek(source) {
576 var request = useIndexForReads ? source.openKeyCursor()
577 : source.openCursor();
579 request.onerror = onError;
580 request.onsuccess = function() {
581 var cursor = request.result;
582 if (cursor && first) {
584 cursor.continue(getKey(numKeys - 1));