Chromium Code Reviews| Index: third_party/WebKit/LayoutTests/external/wpt/IndexedDB/request-event-ordering.html |
| diff --git a/third_party/WebKit/LayoutTests/external/wpt/IndexedDB/request-event-ordering.html b/third_party/WebKit/LayoutTests/external/wpt/IndexedDB/request-event-ordering.html |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..c0f78b4d845169dd86c5b2765e6fca387d927c43 |
| --- /dev/null |
| +++ b/third_party/WebKit/LayoutTests/external/wpt/IndexedDB/request-event-ordering.html |
| @@ -0,0 +1,340 @@ |
| +<!doctype html> |
| +<meta charset="utf8"> |
| +<title>IndexedDB: request result events are delivered in order</title> |
| +<link rel="help" href="https://w3c.github.io/IndexedDB/#abort-transaction"> |
| +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> |
| +<script src="/resources/testharness.js"></script> |
| +<script src="/resources/testharnessreport.js"></script> |
| +<script src="support-promises.js"></script> |
| +<script> |
| +'use strict'; |
| + |
| +// Should be large enough to trigger value wrapping in the IndexedDB engines |
| +// that implement wrapping. |
| +const wrapThreshold = 128 * 1024; |
| + |
| +// Returns an Uint8Array with pseudorandom data. |
| +function largeValue(size, seed) { |
|
jsbell
2017/05/15 23:37:37
Move this to support.js or support-promises.js ?
pwnall
2017/05/19 18:27:33
Done.
|
| + const buffer = new Uint8Array(size); |
| + |
| + // 32-bit xorshift - the seed can't be zero |
| + let state = 1000 + seed; |
| + |
| + for (let i = 0; i < size; ++i) { |
| + state ^= state << 13; |
| + state ^= state >> 17; |
| + state ^= state << 5; |
| + buffer[i] = state & 0xff; |
| + } |
| + |
| + return buffer; |
| +} |
| + |
| +function populateStore(store) { |
| + store.put({id: 1, key: 'k1', value: largeValue(wrapThreshold, 1) }); |
| + store.put({id: 2, key: 'k2', value: ['small-2'] }); |
| + store.put({id: 3, key: 'k3', value: largeValue(wrapThreshold, 3) }); |
| + store.put({id: 4, key: 'k4', value: ['small-4'] }); |
| +} |
| + |
| +// Assigns cursor indexes for operations that require open cursors. |
| +// |
| +// Returns the number of open cursors required to perform all operations. |
| +function assignCursors(operations) { |
| + return cursorCount; |
| +} |
| + |
| +// Opens index cursors for operations that require open cursors. |
| +// |
| +// onsuccess is called if all cursors are opened successfully. Otherwise, |
| +// onerror will be called at least once. |
| +function openCursors(testCase, index, operations, onerror, onsuccess) { |
| + let pendingCursors = 0; |
| + |
| +nextOperation: |
| + for (let operation of operations) { |
| + const opcode = operation[0]; |
| + const primaryKey = operation[1]; |
| + let request; |
| + switch (opcode) { |
| + case 'continue': |
| + request = index.openCursor( |
| + IDBKeyRange.lowerBound(`k${primaryKey - 1}`)); |
| + break; |
| + case 'continue-empty': |
| + // k4 is the last key in the data set, so calling continue() will get |
| + // the cursor past the end of the store. |
| + request = index.openCursor(IDBKeyRange.lowerBound('k4')); |
| + break; |
| + default: |
| + continue nextOperation; |
|
jsbell
2017/05/15 23:37:37
This label doesn't appear necessary
pwnall
2017/05/19 18:27:33
Done.
|
| + } |
| + |
| + operation[2] = request; |
| + ++pendingCursors; |
| + |
| + request.onsuccess = testCase.step_func(() => { |
| + --pendingCursors; |
| + if (!pendingCursors) |
| + onsuccess(); |
| + }); |
| + request.onerror = testCase.step_func(onerror); |
| + } |
| + |
| + if (!pendingCursors) |
| + onsuccess(); |
| +} |
| + |
| +function doOperation(testCase, store, index, operation, requestId, results) { |
| + const opcode = operation[0]; |
| + const primaryKey = operation[1]; |
| + const cursor = operation[2]; |
| + |
| + return new Promise((resolve, reject) => { |
| + let request; |
| + switch (opcode) { |
| + case 'put': // Tests returning a primary key. |
| + request = store.put( |
| + { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); |
| + break; |
| + case 'get': // Tests returning a value. |
| + case 'get-empty': // Tests returning undefined. |
| + request = store.get(primaryKey); |
| + break; |
| + case 'error': // Tests returning an error. |
| + request = store.put( |
| + { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); |
| + request.onerror = testCase.step_func(event => { |
| + results.push([requestId, request.error]); |
| + resolve(); |
| + event.preventDefault(); |
| + }); |
| + request.onsuccess = testCase.step_func(() => { |
| + reject(new Error('put with duplicate primary key succeded')); |
| + }); |
| + break; |
| + case 'continue': // Tests returning a key, primary key, and value. |
| + request = cursor; |
| + cursor.result.continue(); |
| + request.onsuccess = testCase.step_func(() => { |
| + const result = request.result; |
| + results.push( |
| + [requestId, result.key, result.primaryKey, result.value]); |
| + resolve(); |
| + }); |
| + request.onerror = null; |
| + break; |
| + case 'open': // Tests returning a cursor, key, primary key, and value. |
| + request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`)); |
| + request.onsuccess = testCase.step_func(() => { |
| + const result = request.result; |
| + results.push( |
| + [requestId, result.key, result.primaryKey, result.value]); |
| + resolve(); |
| + }); |
| + break; |
| + case 'continue-empty': // Tests returning a null result. |
| + request = cursor; |
| + cursor.result.continue(); |
| + request.onsuccess = testCase.step_func(() => { |
| + results.push([requestId, request.result]); |
| + resolve(); |
| + }); |
| + request.onerror = null; |
| + break; |
| + case 'open-empty': // Tests returning a null cursor. |
| + request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`)); |
| + request.onsuccess = testCase.step_func(() => { |
| + const result = request.result; |
| + results.push([requestId, request.result]); |
| + resolve(); |
| + }); |
| + break; |
| + case 'count': // Tests returning a numeric result. |
| + request = index.count(); |
| + request.onsuccess = testCase.step_func(() => { |
| + results.push([requestId, request.result]); |
| + resolve(); |
| + }); |
| + break; |
| + }; |
| + |
| + if (!request.onsuccess) { |
| + request.onsuccess = testCase.step_func(() => { |
| + results.push([requestId, request.result]); |
| + resolve(); |
| + }); |
| + } |
| + if (!request.onerror) |
| + request.onerror = testCase.step_func(event => { |
| + reject(request.error); |
| + event.preventDefault(); |
| + }); |
| + }); |
| +} |
| + |
| +function checkOperationResult(operation, result, requestId) { |
| + const opcode = operation[0]; |
| + const primaryKey = operation[1]; |
| + |
| + const expectedValue = (primaryKey == 1 || primaryKey == 3) ? |
| + largeValue(wrapThreshold, primaryKey) : [`small-${primaryKey}`]; |
| + |
| + const requestIndex = result[0]; |
| + assert_equals( |
| + requestIndex, requestId, 'result event order should match request order'); |
| + switch (opcode) { |
| + case 'put': |
| + assert_equals( |
| + result[1], primaryKey, |
| + "put result should be the new object's primary key"); |
| + break; |
| + case 'get': |
| + assert_equals( |
| + result[1].id, primaryKey, |
| + 'get result should match put value (primary key)'); |
| + assert_equals( |
| + result[1].key, `k${primaryKey}`, |
| + 'get result should match put value (key)'); |
| + assert_equals( |
| + result[1].value.join(','), expectedValue.join(','), |
| + 'get result should match put value (nested value)'); |
| + break; |
| + case 'get-empty': |
| + assert_equals( |
| + result[1], undefined, 'get-empty result should be undefined'); |
| + break; |
| + case 'error': |
| + assert_equals( |
| + result[1].name, 'ConstraintError', |
| + 'incorrect error from put with duplicate primary key'); |
| + break; |
| + case 'continue': |
| + case 'open': |
| + assert_equals( |
| + result[1], `k${primaryKey}`, |
| + `${opcode} key should match the key in the put value`); |
| + assert_equals( |
| + result[2], primaryKey, |
| + `${opcode} primary key should match the put value's primary key`); |
| + assert_equals( |
| + result[3].id, primaryKey, |
| + `${opcode} value should match put value (primary key)`); |
| + assert_equals( |
| + result[3].key, `k${primaryKey}`, |
| + `${opcode} value should match put value (key)`); |
| + assert_equals( |
| + result[3].value.join(','), expectedValue.join(','), |
| + `${opcode} value should match put value (nested value)`); |
| + break; |
| + case 'continue-empty': |
| + case 'open-empty': |
| + assert_equals(result[1], null, `${opcode} result should be null`); |
| + break; |
| + } |
| +} |
| + |
| +function eventsTest(label, operations) { |
| + promise_test(testCase => { |
| + return createDatabase(testCase, (database, transaction) => { |
| + const store = database.createObjectStore( |
| + 'test-store', { autoIncrement: true, keyPath: 'id' }); |
| + store.createIndex('test-index', 'key', { unique: true }); |
| + populateStore(store); |
| + }).then(database => { |
| + const transaction = database.transaction(['test-store'], 'readwrite'); |
| + const store = transaction.objectStore('test-store'); |
| + const index = store.index('test-index'); |
| + return new Promise((resolve, reject) => { |
| + openCursors(testCase, index, operations, reject, () => { |
| + const results = []; |
| + const promises = []; |
| + for (let i = 0; i < operations.length; ++i) { |
| + const promise = doOperation( |
| + testCase, store, index, operations[i], i, results); |
| + promises.push(promise); |
| + }; |
| + resolve(Promise.all(promises).then(() => results)); |
| + }); |
| + }); |
| + }).then(results => { |
| + assert_equals( |
| + results.length, operations.length, |
| + 'Promise.all should resolve after all sub-promises resolve'); |
| + for (let i = 0; i < operations.length; ++i) |
| + checkOperationResult(operations[i], results[i], i); |
| + }); |
| + }, label); |
| +} |
| + |
| +eventsTest('small values', [ |
| + ['get', 2], |
| + ['count', 4], |
| + ['continue-empty', null], |
| + ['get-empty', 5], |
| + ['put', 5], |
| + ['open', 2], |
| + ['continue', 2], |
| + ['get', 4], |
| + ['get-empty', 6], |
| + ['count', 5], |
| + ['put', 6], |
| + ['error', 3], |
| + ['continue', 4], |
| + ['count', 6], |
| + ['get-empty', 7], |
| + ['open', 4], |
| + ['open-empty', 7], |
| +]); |
| + |
| +eventsTest('large values', [ |
| + ['open', 1], |
| + ['get', 1], |
| + ['get', 3], |
| + ['continue', 3], |
| + ['open', 3], |
| +]); |
| + |
| +eventsTest('large value followed by small values', [ |
| + ['get', 1], |
| + ['open', 2], |
| + ['continue-empty', null], |
| + ['get', 2], |
| + ['get-empty', 5], |
| + ['count', 4], |
| + ['continue-empty', null], |
| + ['open-empty', 5], |
| + ['put', 5], |
| + ['error', 1], |
| + ['continue', 2], |
| + ['get-empty', 6], |
| +]); |
| + |
| +eventsTest('large values mixed with small values', [ |
| + ['get', 1], |
| + ['get', 2], |
| + ['get-empty', 5], |
| + ['count', 4], |
| + ['continue-empty', null], |
| + ['open', 1], |
| + ['continue', 2], |
| + ['open-empty', 5], |
| + ['open', 2], |
| + ['continue-empty', null], |
| + ['put', 5], |
| + ['get', 3], |
| + ['count', 5], |
| + ['get-empty', 6], |
| + ['continue', 3], |
| + ['open-empty', 6], |
| + ['put', 6], |
| + ['error', 1], |
| + ['continue', 2], |
| + ['open', 4], |
| + ['get-empty', 7], |
| + ['continue', 3], |
| + ['error', 3], |
| + ['count', 6], |
| +]); |
| + |
| +</script> |