Bug 1502249 [wpt PR 13727] - [IndexedDB]: Implement explicit commit() in renderer, a=testonly
authorAndreas Butler <andreasbutler@google.com>
Thu, 13 Dec 2018 13:54:56 +0000
changeset 450616 fabe53a456d0432c1b90d1e72cacc3121770284e
parent 450615 217919173553b736f48f6eb002da696c622c7160
child 450617 d22d350d546f210dc9109e5bf3f3611de34b87c9
push id35208
push usercsabou@mozilla.com
push dateSat, 15 Dec 2018 02:48:07 +0000
treeherdermozilla-central@d86d184dc7d6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1502249, 13727, 898257, 1300055, 612474
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1502249 [wpt PR 13727] - [IndexedDB]: Implement explicit commit() in renderer, a=testonly Automatic update from web-platform-tests [IndexedDB]: Implement explicit commit() in renderer Implementing an explicit commit function for the IndexedDB Transaction API. The addition of this API allows developers to preempt IndexedDB's autocommit functionality by sending their own explicit commit signal. Explainer: https://andreas-butler.github.io/idb-transaction-commit/EXPLAINER Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=898257 Change-Id: I5e8bdc697052139d014757408d9a7f2b6367655b Reviewed-on: https://chromium-review.googlesource.com/c/1300055 Commit-Queue: Andreas Butler <andreasbutler@google.com> Reviewed-by: Kinuko Yasuda <kinuko@chromium.org> Reviewed-by: Victor Costan <pwnall@chromium.org> Reviewed-by: Daniel Murphy <dmurph@chromium.org> Cr-Commit-Position: refs/heads/master@{#612474} -- wpt-commits: 026c2b6b9c04acb558f1cd4050e73492ef2a124d wpt-pr: 13727
testing/web-platform/tests/IndexedDB/idb-explicit-commit-throw.any.js
testing/web-platform/tests/IndexedDB/idb-explicit-commit.any.js
testing/web-platform/tests/IndexedDB/support-promises.js
testing/web-platform/tests/IndexedDB/support.js
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/IndexedDB/idb-explicit-commit-throw.any.js
@@ -0,0 +1,45 @@
+// META: script=support-promises.js
+
+/**
+ * This file contains a test that was separated out from the rest of the idb
+ * explict commit tests because it requires the flag 'allow_uncaught_exception',
+ * which prevents unintentionally thrown errors from failing tests.
+ *
+ * @author andreasbutler@google.com
+ */
+
+setup({allow_uncaught_exception:true});
+
+promise_test(async testCase => {
+  // Register an event listener that will prevent the intentionally thrown
+  // error from bubbling up to the window and failing the testharness. This
+  // is necessary because currently allow_uncaught_exception does not behave
+  // as expected for promise_test.
+  //
+  // Git issue: https://github.com/web-platform-tests/wpt/issues/14041
+  self.addEventListener('error', (event) => { event.preventDefault(); });
+
+  const db = await createDatabase(testCase, async db => {
+    await createBooksStore(testCase, db);
+  });
+
+  const txn = db.transaction(['books'], 'readwrite');
+  const objectStore = txn.objectStore('books');
+  const putRequest = objectStore.put({isbn:'one', title:'title'});
+  txn.commit();
+  putRequest.onsuccess = () => {
+    throw new Error('This error thrown after an explicit commit should not ' +
+        'prevent the transaction from committing.');
+  }
+  await promiseForTransaction(testCase, txn);
+
+  // Ensure that despite the uncaught error after the put request, the explicit
+  // commit still causes the request to be 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, 'title');
+}, 'Any errors in callbacks that run after an explicit commit will not stop '
+   + 'the commit from being processed.');
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/IndexedDB/idb-explicit-commit.any.js
@@ -0,0 +1,181 @@
+// 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, async db => {
+    await 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, async (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, async db => {
+    await 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, async db => {
+    await 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, async db => {
+    await 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, async db => {
+    await 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, async db => {
+    await 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, async db => {
+    await 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, async db => {
+    await 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.');
--- a/testing/web-platform/tests/IndexedDB/support-promises.js
+++ b/testing/web-platform/tests/IndexedDB/support-promises.js
@@ -307,8 +307,37 @@ function largeValue(size, seed) {
 async function deleteAllDatabases(testCase) {
   const dbs_to_delete = await indexedDB.databases();
   for( const db_info of dbs_to_delete) {
     let request = indexedDB.deleteDatabase(db_info.name);
     let eventWatcher = requestWatcher(testCase, request);
     await eventWatcher.wait_for('success');
   }
 }
+
+// Keeps the passed transaction alive indefinitely (by making requests
+// against the named store). Returns a function that asserts that the
+// transaction has not already completed and then ends the request loop so that
+// the transaction may autocommit and complete.
+function keepAlive(testCase, transaction, storeName) {
+  let completed = false;
+  transaction.addEventListener('complete', () => { completed = true; });
+
+  let keepSpinning = true;
+
+  function spin() {
+    if (!keepSpinning)
+      return;
+    transaction.objectStore(storeName).get(0).onsuccess = spin;
+  }
+  spin();
+
+  return testCase.step_func(() => {
+    assert_false(completed, 'Transaction completed while kept alive');
+    keepSpinning = false;
+  });
+}
+
+// Return a promise that resolves after a setTimeout finishes to break up the
+// scope of a function's execution.
+function timeoutPromise(ms) {
+  return new Promise(resolve => { setTimeout(resolve, ms); });
+}
--- a/testing/web-platform/tests/IndexedDB/support.js
+++ b/testing/web-platform/tests/IndexedDB/support.js
@@ -1,11 +1,8 @@
-var databaseName = "database";
-var databaseVersion = 1;
-
 /* Delete created databases
  *
  * Go through each finished test, see if it has an associated database. Close
  * that and delete the database. */
 add_completion_callback(function(tests)
 {
     for (var i in tests)
     {
@@ -165,30 +162,30 @@ function is_transaction_active(tx, store
   } catch (ex) {
     assert_equals(ex.name, 'TransactionInactiveError',
                   'Active check should either not throw anything, or throw ' +
                   'TransactionInactiveError');
     return false;
   }
 }
 
-// Keep the passed transaction alive indefinitely (by making requests
-// against the named store). Returns a function to to let the
-// transaction finish, and asserts that the transaction is not yet
-// finished.
+// Keeps the passed transaction alive indefinitely (by making requests
+// against the named store). Returns a function that asserts that the
+// transaction has not already completed and then ends the request loop so that
+// the transaction may autocommit and complete.
 function keep_alive(tx, store_name) {
   let completed = false;
   tx.addEventListener('complete', () => { completed = true; });
 
-  let pin = true;
+  let keepSpinning = true;
 
   function spin() {
-    if (!pin)
+    if (!keepSpinning)
       return;
     tx.objectStore(store_name).get(0).onsuccess = spin;
   }
   spin();
 
   return () => {
     assert_false(completed, 'Transaction completed while kept alive');
-    pin = false;
+    keepSpinning = false;
   };
 }