mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-12 06:08:24 +02:00
Automatic update from web-platform-tests [IndexedDB]: Explicit commit error timing handling. When a transaction is explicitly committed, there may yet be unhandled errors that the browser has sent but the renderer has not yet seen. This can cause strange behaviour (see the Github discussion link below). This patch keeps track of the 'handled errors' in the renderer and the 'sent errors' in the backend. The 'handled errors' number is sent when the transaction is explicitly committed, and the browser can compare this with the 'sent errors'. If they don't match, the transaction is aborted GitHub Discussion: https://github.com/w3c/IndexedDB/pull/242 Bug: 911877 Change-Id: I7ea7b9e20c70528de3f363e961f87a3d8f5798d3 Reviewed-on: https://chromium-review.googlesource.com/c/1378806 Reviewed-by: Kinuko Yasuda <kinuko@chromium.org> Reviewed-by: Chase Phillips <cmp@chromium.org> Reviewed-by: Daniel Murphy <dmurph@chromium.org> Commit-Queue: Andreas Butler <andreasbutler@google.com> Cr-Commit-Position: refs/heads/master@{#625780} -- wpt-commits: b0fbbb9ff451b18bf8a69fd54027bf59d21c2667 wpt-pr: 14761
292 lines
11 KiB
JavaScript
292 lines
11 KiB
JavaScript
// META: script=support-promises.js
|
|
|
|
/**
|
|
* This file contains the webplatform tests for the explicit commit() method
|
|
* of the IndexedDB transaction API.
|
|
*
|
|
* @author andreasbutler@google.com
|
|
*/
|
|
|
|
promise_test(async testCase => {
|
|
const db = await createDatabase(testCase, db => {
|
|
createBooksStore(testCase, db);
|
|
});
|
|
const txn = db.transaction(['books'], 'readwrite');
|
|
const objectStore = txn.objectStore('books');
|
|
objectStore.put({isbn: 'one', title: 'title1'});
|
|
objectStore.put({isbn: 'two', title: 'title2'});
|
|
objectStore.put({isbn: 'three', title: 'title3'});
|
|
txn.commit();
|
|
await promiseForTransaction(testCase, txn);
|
|
|
|
const txn2 = db.transaction(['books'], 'readonly');
|
|
const objectStore2 = txn2.objectStore('books');
|
|
const getRequestitle1 = objectStore2.get('one');
|
|
const getRequestitle2 = objectStore2.get('two');
|
|
const getRequestitle3 = objectStore2.get('three');
|
|
txn2.commit();
|
|
await promiseForTransaction(testCase, txn2);
|
|
assert_array_equals(
|
|
[getRequestitle1.result.title,
|
|
getRequestitle2.result.title,
|
|
getRequestitle3.result.title],
|
|
['title1', 'title2', 'title3'],
|
|
'All three retrieved titles should match those that were put.');
|
|
db.close();
|
|
}, 'Explicitly committed data can be read back out.');
|
|
|
|
|
|
promise_test(async testCase => {
|
|
let db = await createDatabase(testCase, () => {});
|
|
assert_equals(1, db.version, 'A database should be created as version 1');
|
|
db.close();
|
|
|
|
// Upgrade the versionDB database and explicitly commit its versionchange
|
|
// transaction.
|
|
db = await migrateDatabase(testCase, 2, (db, txn) => {
|
|
txn.commit();
|
|
});
|
|
assert_equals(2, db.version,
|
|
'The database version should have been incremented regardless of '
|
|
+ 'whether the versionchange transaction was explicitly or implicitly '
|
|
+ 'committed.');
|
|
db.close();
|
|
}, 'commit() on a version change transaction does not cause errors.');
|
|
|
|
|
|
promise_test(async testCase => {
|
|
const db = await createDatabase(testCase, db => {
|
|
createBooksStore(testCase, db);
|
|
});
|
|
const txn = db.transaction(['books'], 'readwrite');
|
|
const objectStore = txn.objectStore('books');
|
|
txn.commit();
|
|
assert_throws('TransactionInactiveError',
|
|
() => { objectStore.put({isbn: 'one', title: 'title1'}); },
|
|
'After commit is called, the transaction should be inactive.');
|
|
db.close();
|
|
}, 'A committed transaction becomes inactive immediately.');
|
|
|
|
|
|
promise_test(async testCase => {
|
|
const db = await createDatabase(testCase, db => {
|
|
createBooksStore(testCase, db);
|
|
});
|
|
const txn = db.transaction(['books'], 'readwrite');
|
|
const objectStore = txn.objectStore('books');
|
|
const putRequest = objectStore.put({isbn: 'one', title: 'title1'});
|
|
putRequest.onsuccess = testCase.step_func(() => {
|
|
assert_throws('TransactionInactiveError',
|
|
() => { objectStore.put({isbn:'two', title:'title2'}); },
|
|
'The transaction should not be active in the callback of a request after '
|
|
+ 'commit() is called.');
|
|
});
|
|
txn.commit();
|
|
await promiseForTransaction(testCase, txn);
|
|
db.close();
|
|
}, 'A committed transaction is inactive in future request callbacks.');
|
|
|
|
|
|
promise_test(async testCase => {
|
|
const db = await createDatabase(testCase, db => {
|
|
createBooksStore(testCase, db);
|
|
});
|
|
const txn = db.transaction(['books'], 'readwrite');
|
|
const objectStore = txn.objectStore('books');
|
|
txn.commit();
|
|
|
|
assert_throws('TransactionInactiveError',
|
|
() => { objectStore.put({isbn:'one', title:'title1'}); },
|
|
'After commit is called, the transaction should be inactive.');
|
|
|
|
const txn2 = db.transaction(['books'], 'readonly');
|
|
const objectStore2 = txn2.objectStore('books');
|
|
const getRequest = objectStore2.get('one');
|
|
await promiseForTransaction(testCase, txn2);
|
|
assert_equals(getRequest.result, undefined);
|
|
|
|
db.close();
|
|
}, 'Puts issued after commit are not fulfilled.');
|
|
|
|
|
|
promise_test(async testCase => {
|
|
const db = await createDatabase(testCase, db => {
|
|
createBooksStore(testCase, db);
|
|
});
|
|
const txn = db.transaction(['books'], 'readwrite');
|
|
const objectStore = txn.objectStore('books');
|
|
txn.abort();
|
|
assert_throws('InvalidStateError',
|
|
() => { txn.commit(); },
|
|
'The transaction should have been aborted.');
|
|
db.close();
|
|
}, 'Calling commit on an aborted transaction throws.');
|
|
|
|
|
|
promise_test(async testCase => {
|
|
const db = await createDatabase(testCase, db => {
|
|
createBooksStore(testCase, db);
|
|
});
|
|
const txn = db.transaction(['books'], 'readwrite');
|
|
const objectStore = txn.objectStore('books');
|
|
txn.commit();
|
|
assert_throws('InvalidStateError',
|
|
() => { txn.commit(); },
|
|
'The transaction should have already committed.');
|
|
db.close();
|
|
}, 'Calling commit on a committed transaction throws.');
|
|
|
|
|
|
promise_test(async testCase => {
|
|
const db = await createDatabase(testCase, db => {
|
|
createBooksStore(testCase, db);
|
|
});
|
|
const txn = db.transaction(['books'], 'readwrite');
|
|
const objectStore = txn.objectStore('books');
|
|
const putRequest = objectStore.put({isbn:'one', title:'title1'});
|
|
txn.commit();
|
|
assert_throws('InvalidStateError',
|
|
() => { txn.abort(); },
|
|
'The transaction should already have committed.');
|
|
const txn2 = db.transaction(['books'], 'readwrite');
|
|
const objectStore2 = txn2.objectStore('books');
|
|
const getRequest = objectStore2.get('one');
|
|
await promiseForTransaction(testCase, txn2);
|
|
assert_equals(
|
|
getRequest.result.title,
|
|
'title1',
|
|
'Explicitly committed data should be gettable.');
|
|
db.close();
|
|
}, 'Calling abort on a committed transaction throws and does not prevent '
|
|
+ 'persisting the data.');
|
|
|
|
|
|
promise_test(async testCase => {
|
|
const db = await createDatabase(testCase, db => {
|
|
createBooksStore(testCase, db);
|
|
});
|
|
const txn = db.transaction(['books'], 'readwrite');
|
|
const objectStore = txn.objectStore('books');
|
|
const releaseTxnFunction = keepAlive(testCase, txn, 'books');
|
|
|
|
// Break up the scope of execution to force the transaction into an inactive
|
|
// state.
|
|
await timeoutPromise(0);
|
|
|
|
assert_throws('InvalidStateError',
|
|
() => { txn.commit(); },
|
|
'The transaction should be inactive so calling commit should throw.');
|
|
releaseTxnFunction();
|
|
db.close();
|
|
}, 'Calling txn.commit() when txn is inactive should throw.');
|
|
|
|
|
|
promise_test(async testCase => {
|
|
const db = await createDatabase(testCase, db => {
|
|
createBooksStore(testCase, db);
|
|
createNotBooksStore(testCase, db);
|
|
});
|
|
// Txn1 should commit before txn2, even though txn2 uses commit().
|
|
const txn1 = db.transaction(['books'], 'readwrite');
|
|
txn1.objectStore('books').put({isbn: 'one', title: 'title1'});
|
|
const releaseTxnFunction = keepAlive(testCase, txn1, 'books');
|
|
|
|
const txn2 = db.transaction(['books'], 'readwrite');
|
|
txn2.objectStore('books').put({isbn:'one', title:'title2'});
|
|
txn2.commit();
|
|
|
|
// Exercise the IndexedDB transaction ordering by executing one with a
|
|
// different scope.
|
|
const txn3 = db.transaction(['not_books'], 'readwrite');
|
|
txn3.objectStore('not_books').put({'title': 'not_title'}, 'key');
|
|
txn3.oncomplete = function() {
|
|
releaseTxnFunction();
|
|
}
|
|
await Promise.all([promiseForTransaction(testCase, txn1),
|
|
promiseForTransaction(testCase, txn2)]);
|
|
|
|
// Read the data back to verify that txn2 executed last.
|
|
const txn4 = db.transaction(['books'], 'readonly');
|
|
const getRequest4 = txn4.objectStore('books').get('one');
|
|
await promiseForTransaction(testCase, txn4);
|
|
assert_equals(getRequest4.result.title, 'title2');
|
|
db.close();
|
|
}, 'Transactions with same scope should stay in program order, even if one '
|
|
+ 'calls commit.');
|
|
|
|
|
|
promise_test(async testCase => {
|
|
const db = await createDatabase(testCase, db => {
|
|
createBooksStore(testCase, db);
|
|
});
|
|
// Txn1 creates the book 'one' so the 'add()' below fails.
|
|
const txn1 = db.transaction(['books'], 'readwrite');
|
|
txn1.objectStore('books').add({isbn:'one', title:'title1'});
|
|
txn1.commit();
|
|
await promiseForTransaction(testCase, txn1);
|
|
|
|
// Txn2 should abort, because the 'add' call is invalid, and commit() was
|
|
// called.
|
|
const txn2 = db.transaction(['books'], 'readwrite');
|
|
const objectStore2 = txn2.objectStore('books');
|
|
objectStore2.put({isbn:'two', title:'title2'});
|
|
const addRequest = objectStore2.add({isbn:'one', title:'title2'});
|
|
txn2.commit();
|
|
txn2.oncomplete = () => { assert_unreached(
|
|
'Transaction with invalid "add" call should not be completed.'); };
|
|
|
|
// Wait for the transaction to complete. We have to explicitly wait for the
|
|
// error signal on the transaction because of the nature of the test tooling.
|
|
await Promise.all([
|
|
requestWatcher(testCase, addRequest).wait_for('error'),
|
|
transactionWatcher(testCase, txn2).wait_for(['error', 'abort'])
|
|
]);
|
|
|
|
// Read the data back to verify that txn2 was aborted.
|
|
const txn3 = db.transaction(['books'], 'readonly');
|
|
const objectStore3 = txn3.objectStore('books');
|
|
const getRequest1 = objectStore3.get('one');
|
|
const getRequest2 = objectStore3.count('two');
|
|
await promiseForTransaction(testCase, txn3);
|
|
assert_equals(getRequest1.result.title, 'title1');
|
|
assert_equals(getRequest2.result, 0);
|
|
db.close();
|
|
}, 'Transactions that explicitly commit and have errors should abort.');
|
|
|
|
|
|
promise_test(async testCase => {
|
|
const db = await createDatabase(testCase, db => {
|
|
createBooksStore(testCase, db);
|
|
});
|
|
const txn1 = db.transaction(['books'], 'readwrite');
|
|
txn1.objectStore('books').add({isbn: 'one', title: 'title1'});
|
|
txn1.commit();
|
|
await promiseForTransaction(testCase, txn1);
|
|
|
|
// The second add request will throw an error, but the onerror handler will
|
|
// appropriately catch the error allowing the valid put request on the
|
|
// transaction to commit.
|
|
const txn2 = db.transaction(['books'], 'readwrite');
|
|
const objectStore2 = txn2.objectStore('books');
|
|
objectStore2.put({isbn: 'two', title:'title2'});
|
|
const addRequest = objectStore2.add({isbn: 'one', title:'unreached_title'});
|
|
addRequest.onerror = (event) => {
|
|
event.preventDefault();
|
|
addRequest.transaction.commit();
|
|
};
|
|
|
|
// Wait for the transaction to complete. We have to explicitly wait for the
|
|
// error signal on the transaction because of the nature of the test tooling.
|
|
await transactionWatcher(testCase,txn2).wait_for(['error', 'complete'])
|
|
|
|
// Read the data back to verify that txn2 was committed.
|
|
const txn3 = db.transaction(['books'], 'readonly');
|
|
const objectStore3 = txn3.objectStore('books');
|
|
const getRequest1 = objectStore3.get('one');
|
|
const getRequest2 = objectStore3.get('two');
|
|
await promiseForTransaction(testCase, txn3);
|
|
assert_equals(getRequest1.result.title, 'title1');
|
|
assert_equals(getRequest2.result.title, 'title2');
|
|
db.close();
|
|
}, 'Transactions that handle all errors properly should be behave as ' +
|
|
'expected when an explicit commit is called in an onerror handler.');
|