Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / tools / perf / page_sets / indexeddb_perf / perf_test.js
blob3f264505f154eb87f3f0ab711968876d2fc916a8
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();
6 var kUseIndex = true;
7 var kDontUseIndex = false;
8 var kReadKeysOnly = true;
9 var kReadDataToo = false;
10 var kWriteToo = true;
11 var kDontWrite = false;
12 var kWriteSameStore = true;
13 var kWriteDifferentStore = false;
14 var kPlaceholderArg = false;
15 var kDontRead = false;
16 var kAlternateWithReads = true;
18 var tests = [
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
57 // large store.
58   [testRandomReadsAndWritesWithIndex, 10000,  500,   0,  5],
59 // Read and write a few random items, reading from an index, in each of many
60 // transactions.
61   [testRandomReadsAndWritesWithIndex, 1000,  5,    5,  50],
63 // Read a long, contiguous sequence of an object store via a cursor.
64   [testCursorReadsAndRandomWrites, kReadDataToo, kDontUseIndex, kDontWrite,
65    kPlaceholderArg],
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,
73    kWriteSameStore],
74 // Read a sequence of an index into an object store via a cursor.
75   [testCursorReadsAndRandomWrites, kReadDataToo, kUseIndex, kDontWrite,
76    kPlaceholderArg],
77 // Read a sequence of an index into an object store via a key cursor.
78   [testCursorReadsAndRandomWrites, kReadKeysOnly, kUseIndex, kDontWrite,
79    kPlaceholderArg],
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);
145 var currentTest = 0;
146 var testFilter;
147 var done = false;
149 function test() {
150   done = false;
151   runNextTest();
155 function runNextTest() {
156   var running_test, f;
157   while (currentTest < tests.length) {
158     running_test = tests[currentTest];
159     f = running_test.shift();
160     if (!testFilter || f.name == testFilter)
161       break;
162     ++currentTest;
163   }
165   if (currentTest < tests.length) {
166     running_test.push(runNextTest);
167     f.apply(null, running_test);
168     ++currentTest;
169   } else {
170     onAllTestsComplete();
171   }
174 function onAllTestsComplete() {
175   var overallDuration = window.performance.now() - overallTestStartTime;
176   automation.addResult("OverallTestDuration", overallDuration);
177   automation.setDone();
178   done = true;
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);
191   }
192   var value = stringOfLength(payloadLength);
193   function getValue() {
194     return value;
195   }
197   automation.setStatus("Creating databases.");
198   var startTime = window.performance.now();
200   var numCreated = 0;
201   for (var i = 0; i < numDatabases; i++) {
202     createDatabase(testName + i, objectStoreNames, onCreated, onError);
203   }
205   function onCreated(db) {
206     automation.setStatus("Constructing transactions.");
207     var transaction =
208         getTransaction(db, objectStoreNames, "readwrite",
209             function() { openLoop(db, numOpens); });
210     putLinearValues(transaction, objectStoreNames, numKeys, null, getValue);
211   }
213   function openLoop(db, timesLeft) {
214     db.close();
215     if (timesLeft == 0) {
216       deleteDatabase(db.name, onDeleted);
217       return;
218     }
219     createDatabase(db.name, objectStoreNames,
220         function(db) { openLoop(db, timesLeft - 1); }, onError)
221   }
223   var numDeleted = 0;
224   function onDeleted() {
225     var duration = window.performance.now() - startTime;
226     automation.addResult(testName, duration);
227     automation.setStatus("Deleted database.");
228     if (++numDeleted == numDatabases) {
229       onTestComplete();
230     }
231   }
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);
242   }
243   var value = stringOfLength(payloadLength);
244   function getValue() {
245     return value;
246   }
248   automation.setStatus("Creating database.");
249   createDatabase(testName, objectStoreNames, onCreated, onError);
251   function onCreated(db) {
252     automation.setStatus("Constructing transaction.");
253     var completionFunc =
254         getCompletionFunc(db, testName, window.performance.now(),
255             onTestComplete);
256     var transaction =
257         getTransaction(db, objectStoreNames, "readwrite", completionFunc);
258     putLinearValues(transaction, objectStoreNames, numKeys, null, getValue);
259   }
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);
275   }
276   var value = stringOfLength(payloadLength);
277   function getValue() {
278     return value;
279   }
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.");
288     var transaction =
289         getTransaction(db, objectStoreNames, "readwrite",
290             function() { onValuesWritten(db); });
291     putLinearValues(transaction, objectStoreNames, numKeys, null, getValue);
292   }
294   function onValuesWritten(db) {
295     automation.setStatus("Deleting database.");
296     db.close();
297     deleteDatabase(testName, onDeleted);
298   }
300   function onDeleted() {
301     var duration = window.performance.now() - startTime;
302     automation.addResult(testName, duration);
303     automation.setStatus("Deleted database.");
304     onTestComplete();
305   }
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);
316   }
317   var value = stringOfLength(payloadLength);
318   function getValue() {
319     return value;
320   }
322   automation.setStatus("Creating database.");
323   createDatabase(testName, objectStoreNames, onCreated, onError);
325   function onCreated(db) {
326     automation.setStatus("Constructing transaction.");
327     var completionFunc =
328         getCompletionFunc(db, testName, window.performance.now(),
329             onTestComplete);
330     var transaction =
331         getTransaction(db, objectStoreNames, "readwrite", completionFunc);
332     putLinearValues(transaction, objectStoreNames, numKeys, null, getValue);
333   }
336 function testRandomReadsAndWrites(
337     numKeys, numReadsPerTransaction, numWritesPerTransaction, numTransactions,
338     useIndexForReads, onTestComplete) {
339   var indexName;
340   if (useIndexForReads)
341     indexName = "index";
342   var testName = getDisplayName(arguments);
343   var objectStoreNames = ["store"];
344   var getKey = getSimpleKey;
345   var getValue = useIndexForReads ? getIndexableValue : getSimpleValue;
347   automation.setStatus("Creating database.");
348   var options;
349   if (useIndexForReads) {
350     options = [{
351       indexName: indexName,
352       indexKeyPath: "id",
353       indexIsUnique: false,
354       indexIsMultiEntry: false,
355     }];
356   }
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"; });
365   }
367   function onSetupComplete(db) {
368     automation.setStatus("Setup complete.");
369     var completionFunc =
370         getCompletionFunc(db, testName, window.performance.now(),
371             onTestComplete);
372     var mode = "readonly";
373     if (numWritesPerTransaction)
374       mode = "readwrite";
375     runTransactionBatch(db, numTransactions, batchFunc, objectStoreNames, mode,
376         completionFunc);
377   }
379   function batchFunc(transaction) {
380     getRandomValues(transaction, objectStoreNames, numReadsPerTransaction,
381         numKeys, indexName, getKey);
382     putRandomValues(transaction, objectStoreNames, numWritesPerTransaction,
383         numKeys, getKey, getValue);
384   }
387 function testReadCache(numTransactions, useIndexForReads, onTestComplete) {
388   var numKeys = 10000;
389   var numReadsPerTransaction = 50;
390   var numTransactionsLeft = numTransactions;
391   var indexName;
392   if (useIndexForReads)
393     indexName = "index";
394   var testName = getDisplayName(arguments);
395   var objectStoreNames = ["store"];
396   var getKey = getSimpleKey;
397   var getValue = useIndexForReads ? getIndexableValue : getSimpleValue;
398   var keys = [];
400   for (var i=0; i < numReadsPerTransaction; ++i) {
401     keys.push(getKey(Math.floor(random() * numKeys)));
402   }
404   automation.setStatus("Creating database.");
405   var options;
406   if (useIndexForReads) {
407     options = [{
408       indexName: indexName,
409       indexKeyPath: "id",
410       indexIsUnique: false,
411       indexIsMultiEntry: false,
412     }];
413   }
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,
421         getValue);
422   }
424   var completionFunc;
425   function onSetupComplete(db) {
426     automation.setStatus("Setup complete.");
427     completionFunc =
428         getCompletionFunc(db, testName, window.performance.now(),
429             onTestComplete);
430     runTransactionBatch(db, numTransactions, batchFunc, objectStoreNames,
431         "readonly", completionFunc);
432   }
434   function batchFunc(transaction) {
435     getSpecificValues(transaction, objectStoreNames, indexName, keys);
436   }
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);
446   var startTime;
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);
452   }
454   function getValue(i) {
455     return { firstName: i + " first name", lastName: i + " last name" };
456   }
458   function onPopulated(db) {
459     db.close();
460     automation.setStatus("Building index.");
461     startTime = window.performance.now();
462     var f = function(objectStore) {
463       objectStore.createIndex("index", "firstName", {unique: true});
464     };
465     alterObjectStores(testName, objectStoreNames, f, onIndexCreated, onError);
466   }
468   var indexCreationCompleteTime;
469   function onIndexCreated(db) {
470     db.close();
471     indexCreationCompleteTime = window.performance.now();
472     automation.addResult("testCreateIndex",
473         indexCreationCompleteTime - startTime);
474     var f = function(objectStore) {
475       objectStore.deleteIndex("index");
476     };
477     automation.setStatus("Deleting index.");
478     alterObjectStores(testName, objectStoreNames, f, onIndexDeleted, onError);
479   }
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.");
486     db.close();
487     deleteDatabase(testName, onDeleted);
488   }
490   function onDeleted() {
491     automation.setStatus("Deleted database.");
492     onTestComplete();
493   }
496 function testCursorReadsAndRandomWrites(
497     readKeysOnly, useIndexForReads, writeAlso, sameStoreForWrites,
498     onTestComplete) {
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);
505   var numKeys = 10000;
506   var numReadsPerTransaction = 1000;
507   var testName = getDisplayName(arguments);
508   var objectStoreNames = ["input store"];
509   var outputStoreName;
510   if (writeAlso) {
511     if (sameStoreForWrites) {
512       outputStoreName = objectStoreNames[0];
513     } else {
514       outputStoreName = "output store";
515       objectStoreNames.push(outputStoreName);
516     }
517   }
518   var getKeyForRead = getSimpleKey;
519   var indexName;
520   if (useIndexForReads) {
521     indexName = "index";
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);
528     };
529   }
531   automation.setStatus("Creating database.");
532   var options;
533   if (useIndexForReads) {
534     options = [{
535       indexName: indexName,
536       indexKeyPath: "lastName", // depends on getBackwardIndexKey()
537       indexIsUnique: true,
538       indexIsMultiEntry: false,
539     }];
540   }
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,
548         getObjectValue);
549   }
550   function onSetupComplete(db) {
551     automation.setStatus("Setup complete.");
552     var completionFunc =
553         getCompletionFunc(db, testName, window.performance.now(),
554             onTestComplete);
555     var mode = "readonly";
556     if (writeAlso)
557       mode = "readwrite";
558     var transaction =
559         getTransaction(db, objectStoreNames, mode, completionFunc);
561     getValuesFromCursor(
562         transaction, objectStoreNames[0], numReadsPerTransaction, numKeys,
563         indexName, getKeyForRead, readKeysOnly, outputStoreName);
564   }
567 function testWalkingMultipleCursors(numCursors, onTestComplete) {
568   var numKeys = 1000;
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.");
577   var options = [{
578     indexName: indexName,
579     indexKeyPath: "id",
580     indexIsUnique: false,
581     indexIsMultiEntry: false,
582   }];
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),
592           getValue);
593     }
594   }
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) {
598     return function(i) {
599       return getKey(k * numKeys + i);
600     };
601   }
602   var completionFunc;
603   function onSetupComplete(db) {
604     automation.setStatus("Setup complete.");
605     completionFunc =
606         getCompletionFunc(db, testName, window.performance.now(),
607             onTestComplete);
608     var transaction =
609         getTransaction(db, objectStoreNames, "readonly", verifyComplete);
611     walkSeveralCursors(transaction, numKeys);
612   }
613   var responseCounts = [];
614   var cursorsRunning = numCursors;
615   function walkSeveralCursors(transaction, numKeys) {
616     var source = transaction.objectStore(objectStoreNames[0]).index(indexName);
617     var requests = [];
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);
634         }
635         var cursor = event.target.result;
636         if (cursor) {
637           assert(responseCounts[request.requestIndex] < numHitsPerKey);
638           ++responseCounts[request.requestIndex];
639         } else {
640           assert(responseCounts[request.requestIndex] == numHitsPerKey);
641           --cursorsRunning;
642         }
643         if (cursorsRunning) {
644           if (requests.length == numCursors) {
645             requests[continueCursorIndex++].result.continue();
646             continueCursorIndex %= numCursors;
647           }
648         }
649       };
650     }
651   }
652   function verifyComplete() {
653     assert(!cursorsRunning);
654     completionFunc();
655   }
658 function testCursorSeeks(
659     numKeys, numSeeksPerTransaction, numTransactions, useIndexForReads,
660     onTestComplete) {
661   var testName = getDisplayName(arguments);
662   var objectStoreNames = ["store"];
663   var getKey = useIndexForReads ? getForwardIndexKey : getSimpleKey;
664   var indexName;
665   if (useIndexForReads) {
666     indexName = "index";
667   }
669   automation.setStatus("Creating database.");
670   var options;
671   if (useIndexForReads) {
672     options = [{
673       indexName: indexName,
674       indexKeyPath: "firstName",
675       indexIsUnique: true,
676       indexIsMultiEntry: false,
677     }];
678   }
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,
686         getObjectValue);
687   }
689   function onSetupComplete(db) {
690     automation.setStatus("Setup complete.");
691     var completionFunc =
692           getCompletionFunc(db, testName, window.performance.now(),
693               onTestComplete);
694     var mode = "readonly";
695     runTransactionBatch(db, numTransactions, batchFunc, objectStoreNames, mode,
696         completionFunc);
697   }
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) {
705         randomSeek(source);
706       }
707     }
708   }
710   function randomSeek(source) {
711     var request = useIndexForReads ? source.openKeyCursor()
712           : source.openCursor();
713     var first = true;
714     request.onerror = onError;
715     request.onsuccess = function() {
716       var cursor = request.result;
717       if (cursor && first) {
718         first = false;
719         cursor.continue(getKey(numKeys - 1));
720       }
721     };
722   }