diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/IndexedDB/request-event-ordering.html | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/IndexedDB/request-event-ordering.html')
-rw-r--r-- | testing/web-platform/tests/IndexedDB/request-event-ordering.html | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/testing/web-platform/tests/IndexedDB/request-event-ordering.html b/testing/web-platform/tests/IndexedDB/request-event-ordering.html new file mode 100644 index 0000000000..71eda0dd1d --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/request-event-ordering.html @@ -0,0 +1,369 @@ +<!doctype html> +<meta charset="utf8"> +<meta name="timeout" content="long"> +<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="resources/support-promises.js"></script> +<script> +'use strict'; + +// Should be large enough to trigger large value handling in the IndexedDB +// engines that have special code paths for large values. +const wrapThreshold = 128 * 1024; + +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; + + 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; + } + + 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 'add': // Tests returning a primary key. + request = store.add( + { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); + break; + case 'put': // Tests returning a primary key. + request = store.put( + { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); + break; + case 'put-with-id': // Tests returning success or a primary key. + request = store.put( + { key: `k${primaryKey}`, value: [`small-${primaryKey}`], + id: primaryKey }); + break; + case 'get': // Tests returning a value. + case 'get-empty': // Tests returning undefined. + request = store.get(primaryKey); + break; + case 'getall': // Tests returning an array of values. + request = store.getAll(); + break; + case 'error': // Tests returning an error. + request = store.put( + { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); + request.onerror = testCase.step_func(event => { + event.preventDefault(); + results.push([requestId, request.error]); + resolve(); + }); + 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 => { + event.preventDefault(); + reject(request.error); + }); + }); +} + +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': + case 'put-with-id': + case 'add': + assert_equals( + result[1], primaryKey, + `${opcode} 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 'getall': + assert_equals( + result[1].length, primaryKey, + 'getAll should return all the objects in the store'); + for (let i = 0; i < primaryKey; ++i) { + const object = result[1][i]; + assert_equals( + object.id, i + 1, + `getAll result ${i + 1} should match put value (primary key)`); + assert_equals( + object.key, `k${i + 1}`, + `get result ${i + 1} should match put value (key)`); + + const expectedValue = (i == 0 || i == 2) ? + largeValue(wrapThreshold, i + 1) : [`small-${i + 1}`]; + assert_equals( + object.value.join(','), object.value.join(','), + `get result ${i + 1} 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], + ['add', 5], + ['open', 2], + ['continue', 2], + ['get', 4], + ['get-empty', 6], + ['count', 5], + ['put-with-id', 5], + ['put', 6], + ['error', 3], + ['continue', 4], + ['count', 6], + ['get-empty', 7], + ['open', 4], + ['open-empty', 7], + ['add', 7], +]); + +eventsTest('large values', [ + ['open', 1], + ['get', 1], + ['getall', 4], + ['get', 3], + ['continue', 3], + ['open', 3], +]); + +eventsTest('large value followed by small values', [ + ['get', 1], + ['getall', 4], + ['open', 2], + ['continue-empty', null], + ['get', 2], + ['get-empty', 5], + ['count', 4], + ['continue-empty', null], + ['open-empty', 5], + ['add', 5], + ['error', 1], + ['continue', 2], + ['get-empty', 6], + ['put-with-id', 5], + ['put', 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], + ['getall', 4], + ['open', 2], + ['continue-empty', null], + ['add', 5], + ['get', 3], + ['count', 5], + ['get-empty', 6], + ['put-with-id', 5], + ['getall', 5], + ['continue', 3], + ['open-empty', 6], + ['put', 6], + ['error', 1], + ['continue', 2], + ['open', 4], + ['get-empty', 7], + ['count', 6], + ['continue', 3], + ['add', 7], + ['getall', 7], + ['error', 3], + ['count', 7], +]); + +</script> |