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.
9 automation
.setDone = function() {
10 this.setStatus("Test complete.");
11 document
.cookie
= '__done=1; path=/';
14 automation
.addResult = function(name
, result
) {
16 this.results
[name
] = result
;
17 var elt
= document
.getElementById('results');
18 var div
= document
.createElement('div');
19 div
.textContent
= name
+ ": " + result
;
23 automation
.getResults = function() {
27 automation
.setStatus = function(s
) {
28 document
.getElementById('status').textContent
= s
;
33 var e
= new Error("Assertion failed!");
40 var s
= "Caught error.";
41 if (e
.target
&& e
.target
.error
)
42 s
+= "\n" + e
.target
.error
.name
+ "\n" + e
.target
.error
.message
;
44 automation
.setStatus(s
);
49 var baseVersion
= 2; // The version with our object stores.
52 // Valid options fields:
53 // indexName: the name of an index to create on each object store
54 // indexKeyPath: the key path for that index
55 // indexIsUnique: the "unique" option for IDBIndexParameters
56 // indexIsMultiEntry: the "multiEntry" option for IDBIndexParameters
58 function createDatabase(
59 name
, objectStoreNames
, handler
, errorHandler
, optionSets
) {
60 var openRequest
= indexedDB
.open(name
, baseVersion
);
61 openRequest
.onblocked
= errorHandler
;
62 openRequest
.onerror
= errorHandler
;
63 function createObjectStores(db
) {
64 for (var store
in objectStoreNames
) {
65 var name
= objectStoreNames
[store
];
66 assert(!db
.objectStoreNames
.contains(name
));
67 var os
= db
.createObjectStore(name
);
69 for (o
in optionSets
) {
70 var options
= optionSets
[o
];
71 assert(options
.indexName
);
72 assert('indexKeyPath' in options
);
73 os
.createIndex(options
.indexName
, options
.indexKeyPath
,
74 { unique
: options
.indexIsUnique
,
75 multiEntry
: options
.indexIsMultiEntry
});
80 openRequest
.onupgradeneeded = function(ev
) {
81 // This is the spec-compliant path, which doesn't yet run in Chrome, but
83 assert(openRequest
== ev
.target
);
84 var db
= openRequest
.result
;
85 db
.onerror
= errorHandler
;
86 createObjectStores(db
);
87 // onsuccess will get called after this exits.
89 openRequest
.onsuccess = function(ev
) {
90 assert(openRequest
== ev
.target
);
91 var db
= openRequest
.result
;
92 curVersion
= db
.version
;
93 db
.onerror = function(ev
) {
94 console
.log("db error", arguments
, openRequest
.error
.message
);
97 if (curVersion
!= baseVersion
) {
98 // This is the legacy path, which runs only in Chrome.
99 var setVersionRequest
= db
.setVersion(baseVersion
);
100 setVersionRequest
.onerror
= errorHandler
;
101 setVersionRequest
.onsuccess = function(e
) {
102 assert(setVersionRequest
== e
.target
);
103 createObjectStores(db
);
104 var versionTransaction
= setVersionRequest
.result
;
105 versionTransaction
.oncomplete = function() { handler(db
); };
106 versionTransaction
.onerror
= onError
;
114 // You must close all database connections before calling this.
115 function alterObjectStores(
116 name
, objectStoreNames
, func
, handler
, errorHandler
) {
117 var version
= curVersion
+ 1;
118 var openRequest
= indexedDB
.open(name
, version
);
119 openRequest
.onblocked
= errorHandler
;
120 openRequest
.onupgradeneeded = function(ev
) {
121 doAlteration(ev
.target
.transaction
);
122 // onsuccess will get called after this exits.
124 openRequest
.onsuccess = function(ev
) {
125 assert(openRequest
== ev
.target
);
126 var db
= openRequest
.result
;
127 db
.onerror = function(ev
) {
128 console
.log("error altering db", arguments
,
129 openRequest
.error
.message
);
132 if (db
.version
!= version
) {
133 // This is the legacy path, which runs only in Chrome before M23.
134 var setVersionRequest
= db
.setVersion(version
);
135 setVersionRequest
.onerror
= errorHandler
;
136 setVersionRequest
.onsuccess
=
138 curVersion
= db
.version
;
139 assert(setVersionRequest
== e
.target
);
140 var versionTransaction
= setVersionRequest
.result
;
141 versionTransaction
.oncomplete = function() { handler(db
); };
142 versionTransaction
.onerror
= onError
;
143 doAlteration(versionTransaction
);
149 function doAlteration(target
) {
150 for (var store
in objectStoreNames
) {
151 func(target
.objectStore(objectStoreNames
[store
]));
156 function getTransaction(db
, objectStoreNames
, mode
, opt_handler
) {
157 var transaction
= db
.transaction(objectStoreNames
, mode
);
158 transaction
.onerror
= onError
;
159 transaction
.onabort
= onError
;
161 transaction
.oncomplete
= opt_handler
;
166 function deleteDatabase(name
, opt_handler
) {
167 var deleteRequest
= indexedDB
.deleteDatabase(name
);
168 deleteRequest
.onerror
= onError
;
169 deleteRequest
.onblocked
= onError
;
171 deleteRequest
.onsuccess
= opt_handler
;
175 function getCompletionFunc(db
, testName
, startTime
, onTestComplete
) {
176 function onDeleted() {
177 automation
.setStatus("Deleted database.");
181 var duration
= window
.performance
.now() - startTime
;
182 // Ignore the cleanup time for this test.
183 automation
.addResult(testName
, duration
);
184 automation
.setStatus("Deleting database.");
186 deleteDatabase(testName
, onDeleted
);
190 function getDisplayName(args
) {
191 function functionName(f
) {
192 // Function.prototype.name is nonstandard, and not implemented in IE10-
193 return f
.name
|| f
.toString().match(/^function\s*([^(\s]*)/)[1];
195 // The last arg is the completion callback the test runner tacks on.
196 // TODO(ericu): Make test errors delete the database automatically.
197 return functionName(getDisplayName
.caller
) + (args
.length
> 1 ? "_" : "") +
198 Array
.prototype.slice
.call(args
, 0, args
.length
- 1).join("_");
201 // Pad a string [or object convertible to a string] to a fixed width; use this
202 // to have numeric strings sort properly.
203 function padToWidth(s
, width
) {
205 assert(s
.length
<= width
);
206 if (s
.length
< width
) {
207 s
= stringOfLength(width
- s
.length
, '0') + s
;
212 function stringOfLength(n
, c
) {
216 assert(n
== Math
.floor(n
));
217 return new Array(n
+ 1).join(c
);
220 function getSimpleKey(i
) {
221 return "key " + padToWidth(i
, 10);
224 function getSimpleValue(i
) {
225 return "value " + padToWidth(i
, 10);
228 function getIndexableValue(i
) {
229 return { id
: getSimpleValue(i
) };
232 function getForwardIndexKey(i
) {
236 function getBackwardIndexKey(i
) {
240 // This is useful for indexing by keypath; the two names should be ordered in
241 // opposite directions for all i in uint32 range.
242 function getObjectValue(i
) {
244 firstName
: getForwardIndexKey(i
),
245 lastName
: getBackwardIndexKey(i
)
249 function getNFieldName(k
) {
253 function getNFieldObjectValue(i
, n
) {
254 assert(Math
.floor(n
) == n
);
258 // The value varies per field, each object will tend to be unique,
259 // and thanks to the modulus, indexing on different fields will give you
260 // different ordering for large-enough data sets.
261 o
[getNFieldName(n
- 1)] = Math
.pow(i
+ 0.5, n
+ 0.5) % 65536;
266 function putLinearValues(
267 transaction
, objectStoreNames
, numKeys
, getKey
, getValue
) {
269 getKey
= getSimpleKey
;
271 getValue
= getSimpleValue
;
272 for (var i
in objectStoreNames
) {
273 var os
= transaction
.objectStore(objectStoreNames
[i
]);
274 for (var j
= 0; j
< numKeys
; ++j
) {
275 var request
= os
.put(getValue(j
), getKey(j
));
276 request
.onerror
= onError
;
281 function verifyResultNonNull(result
) {
282 assert(result
!= null);
285 function getRandomValues(
286 transaction
, objectStoreNames
, numReads
, numKeys
, indexName
, getKey
) {
288 getKey
= getSimpleKey
;
289 for (var i
in objectStoreNames
) {
290 var os
= transaction
.objectStore(objectStoreNames
[i
]);
293 source
= source
.index(indexName
);
294 for (var j
= 0; j
< numReads
; ++j
) {
295 var rand
= Math
.floor(random() * numKeys
);
296 var request
= source
.get(getKey(rand
));
297 request
.onerror
= onError
;
298 request
.onsuccess
= verifyResultNonNull
;
303 function putRandomValues(
304 transaction
, objectStoreNames
, numPuts
, numKeys
, getKey
, getValue
) {
306 getKey
= getSimpleKey
;
308 getValue
= getSimpleValue
;
309 for (var i
in objectStoreNames
) {
310 var os
= transaction
.objectStore(objectStoreNames
[i
]);
311 for (var j
= 0; j
< numPuts
; ++j
) {
312 var rand
= Math
.floor(random() * numKeys
);
313 var request
= os
.put(getValue(rand
), getKey(rand
));
314 request
.onerror
= onError
;
319 function getSpecificValues(transaction
, objectStoreNames
, indexName
, keys
) {
320 for (var i
in objectStoreNames
) {
321 var os
= transaction
.objectStore(objectStoreNames
[i
]);
324 source
= source
.index(indexName
);
325 for (var j
= 0; j
< keys
.length
; ++j
) {
326 var request
= source
.get(keys
[j
]);
327 request
.onerror
= onError
;
328 request
.onsuccess
= verifyResultNonNull
;
333 // getKey should be deterministic, as we assume that a cursor that starts at
334 // getKey(X) and runs through getKey(X + K) has exactly K values available.
335 // This is annoying to guarantee generally when using an index, so we avoid both
336 // ends of the key space just in case and use simple indices.
337 // TODO(ericu): Figure out if this can be simplified and we can remove uses of
338 // getObjectValue in favor of getNFieldObjectValue.
339 function getValuesFromCursor(
340 transaction
, inputObjectStoreName
, numReads
, numKeys
, indexName
, getKey
,
341 readKeysOnly
, outputObjectStoreName
) {
342 assert(2 * numReads
< numKeys
);
344 getKey
= getSimpleKey
;
345 var rand
= Math
.floor(random() * (numKeys
- 2 * numReads
)) + numReads
;
347 var queryObject
= transaction
.objectStore(inputObjectStoreName
);
350 queryObject
= queryObject
.index(indexName
);
351 var keyRange
= IDBKeyRange
.bound(
352 getKey(rand
), getKey(rand
+ numReads
), false, true);
355 request
= queryObject
.openKeyCursor(keyRange
);
357 request
= queryObject
.openCursor(keyRange
);
360 if (outputObjectStoreName
)
361 oos
= transaction
.objectStore(outputObjectStoreName
);
362 var numReadsLeft
= numReads
;
363 request
.onsuccess = function(event
) {
364 var cursor
= event
.target
.result
;
366 assert(numReadsLeft
);
369 // Put in random order for maximum difficulty. We add in numKeys just
370 // in case we're writing back to the same store; this way we won't
371 // affect the number of keys available to the cursor, since we're always
372 // outside its range.
373 oos
.put(cursor
.value
, numKeys
+ random());
374 values
.push({key
: cursor
.key
, value
: cursor
.value
});
377 assert(!numReadsLeft
);
380 request
.onerror
= onError
;
383 function runTransactionBatch(db
, count
, batchFunc
, objectStoreNames
, mode
,
385 var numTransactionsRunning
= 0;
389 function runOneBatch(db
) {
394 ++numTransactionsRunning
;
395 var transaction
= getTransaction(db
, objectStoreNames
, mode
,
397 assert(!--numTransactionsRunning
);
405 batchFunc(transaction
);
409 // Use random() instead of Math.random() so runs are repeatable.
410 var random
= (function(seed
) {
412 // Implementation of: http://www.burtleburtle.net/bob/rand/smallprng.html
413 function uint32(x
) { return x
>>> 0; }
414 function rot(x
, k
) { return (x
<< k
) | (x
>> (32 - k
)); }
416 function SmallPRNG(seed
) {
419 this.b
= this.c
= this.d
= seed
;
420 for (var i
= 0; i
< 20; ++i
)
424 SmallPRNG
.prototype.ranval = function() {
425 var e
= uint32(this.a
- rot(this.b
, 27));
426 this.a
= this.b
^ rot(this.c
, 17);
427 this.b
= uint32(this.c
+ this.d
);
428 this.c
= uint32(this.d
+ e
);
429 this.d
= uint32(e
+ this.a
);
433 var prng
= new SmallPRNG(seed
);
434 return function() { return prng
.ranval() / 0x100000000; };