Bug 1436996 [wpt PR 9435] - IndexedDB WPT: Break down interleaved-cursors test., a=testonly
authorVictor Costan <pwnall@chromium.org>
Tue, 06 Mar 2018 18:35:15 +0000
changeset 462371 d2a80b7b02e4d1c533898f87c40e80f276ec0fae
parent 462370 81f2c8ba7da47c8f111fc8256f330eb38bd2e651
child 462372 86368a236996124702480f4ab44b38b391268ad0
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1436996, 708175, 908248, 535411
milestone60.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 1436996 [wpt PR 9435] - IndexedDB WPT: Break down interleaved-cursors test., a=testonly Automatic update from web-platform-tests The test is currently flaky on the CQ (see associated bug) due to timing out. This CL breaks down the test in a -small and a -large file. The size of the largest test is also reduced from 500 cursors to 250 cursors. The two changes combined should greatly reduce the chance of timeouts. Bug: 708175 Change-Id: Ic58c59740a0176e6bc5e6e87b66c5d772a67526a Reviewed-on: https://chromium-review.googlesource.com/908248 Commit-Queue: Joshua Bell <jsbell@chromium.org> Reviewed-by: Joshua Bell <jsbell@chromium.org> Cr-Commit-Position: refs/heads/master@{#535411} <!-- Reviewable:start --> <!-- Reviewable:end --> wpt-commits: 13b72a6d7631a855aec02993c1ae170d096de8ea wpt-pr: 9435 reapplied-commits: 370e267e160568862f1fd9ec246ab5bb840f586e, fe4514c84e7ad28e46bad5da93381deb99b177f3, 7806af854343c043a2645a4034fdc7812f65daad, 9ddfd21554293dec5a4bf2e5375ae4f3c9f2ded0, 75f63c4d1ebc949647184fd60972fc7b9fd4affb, 1f3a5b496acd2288cc8cf0c32af86cb35157ea4e, 88b42bd5847abac58a62c4d6b33c1509bfce5f3d, 15c2e4c690700c6c115f8afe5e44ded10d943538, c8d461ef1437641ae7d4ea1d21e1e60cd62910b0, a6088a5f48ee299386a84d2f771902267d7355b1, 0634cd8f08ebe0905a9188fb1398c7b5f889c5dc, c8ee4a012dae506ae06bb5b2ad50942b04c1aaaa, c2c352456a4cf62dcc12f851138b04397675a445, b93a8879555d2fa7e7d4e00a275513a3a6338b35, b86e1331cb36634fd33677043b61fc0c1d8485bc, 44ddf14fd3346658c3223f13652073fafbfa48fa, a1a5840a6bb53e305ba02bcbeb215659342d0edb, 7465cb110ae5ec2e2ca73182caf5293f0efc8fd5, aad5349b3458bc3414e274b33fa86a1123901ff2, eca0907980d2769c449894a6277c60c1a306792f, 38626987c0cfd6e715cfcc6f4f1a1209191a03c5, e4a67f7ddcde6cd99348e9104bd7ed07074da44a, bb3c9990840a0fae2afc840b5952d7874785b112, 042d7adef0bdb9dc80e825c3997ace7519477c42, 99f1ea44fc7915b8b7b33bce4732fa8765fd3ac2, b81999f30c1516a70c153de51a0331d14c8faead
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/IndexedDB/interleaved-cursors-common.js
testing/web-platform/tests/IndexedDB/interleaved-cursors-large.html
testing/web-platform/tests/IndexedDB/interleaved-cursors-small.html
testing/web-platform/tests/IndexedDB/interleaved-cursors.html
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -184785,16 +184785,21 @@
      {}
     ]
    ],
    "IndexedDB/interfaces.idl": [
     [
      {}
     ]
    ],
+   "IndexedDB/interleaved-cursors-common.js": [
+    [
+     {}
+    ]
+   ],
    "IndexedDB/nested-cloning-common.js": [
     [
      {}
     ]
    ],
    "IndexedDB/support-promises.js": [
     [
      {}
@@ -302310,19 +302315,27 @@
     ]
    ],
    "IndexedDB/interfaces.worker.js": [
     [
      "/IndexedDB/interfaces.worker.html",
      {}
     ]
    ],
-   "IndexedDB/interleaved-cursors.html": [
-    [
-     "/IndexedDB/interleaved-cursors.html",
+   "IndexedDB/interleaved-cursors-large.html": [
+    [
+     "/IndexedDB/interleaved-cursors-large.html",
+     {
+      "timeout": "long"
+     }
+    ]
+   ],
+   "IndexedDB/interleaved-cursors-small.html": [
+    [
+     "/IndexedDB/interleaved-cursors-small.html",
      {
       "timeout": "long"
      }
     ]
    ],
    "IndexedDB/key-conversion-exceptions.htm": [
     [
      "/IndexedDB/key-conversion-exceptions.htm",
@@ -391567,18 +391580,26 @@
   "IndexedDB/interfaces.idl": [
    "f367517cad717e2066ad8179df9ba5aa3b402c97",
    "support"
   ],
   "IndexedDB/interfaces.worker.js": [
    "fdacaee0ed6b2a97b579495f5944df04e70b7deb",
    "testharness"
   ],
-  "IndexedDB/interleaved-cursors.html": [
-   "131b1965caae735de9ef8893e6ca55f24ce36f81",
+  "IndexedDB/interleaved-cursors-common.js": [
+   "6744105fe1a15a91058e3e5993f8a1f88548e3a3",
+   "support"
+  ],
+  "IndexedDB/interleaved-cursors-large.html": [
+   "4ee13538a407fe15fe310c8776d84c7526b7b363",
+   "testharness"
+  ],
+  "IndexedDB/interleaved-cursors-small.html": [
+   "04e4aad6c03ba0bc413a72f7674587816e1f63a2",
    "testharness"
   ],
   "IndexedDB/key-conversion-exceptions.htm": [
    "aed58068ec48e3fd682a195bd5b0ea3f87c5a0e7",
    "testharness"
   ],
   "IndexedDB/key_invalid.htm": [
    "8c0257c4231c413dde10c6f541f17f57634bff1c",
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/IndexedDB/interleaved-cursors-common.js
@@ -0,0 +1,188 @@
+// Infrastructure shared by interleaved-cursors-{small,large}.html
+
+// Number of objects that each iterator goes over.
+const itemCount = 10;
+
+// Ratio of small objects to large objects.
+const largeObjectRatio = 5;
+
+// Size of large objects. This should exceed the size of a block in the storage
+// method underlying the browser's IndexedDB implementation. For example, this
+// needs to exceed the LevelDB block size on Chrome, and the SQLite block size
+// on Firefox.
+const largeObjectSize = 48 * 1024;
+
+function objectKey(cursorIndex, itemIndex) {
+  return `${cursorIndex}-key-${itemIndex}`;
+}
+
+function objectValue(cursorIndex, itemIndex) {
+  if ((cursorIndex * itemCount + itemIndex) % largeObjectRatio === 0) {
+    // We use a typed array (as opposed to a string) because IndexedDB
+    // implementations may serialize strings using UTF-8 or UTF-16, yielding
+    // larger IndexedDB entries than we'd expect. It's very unlikely that an
+    // IndexedDB implementation would use anything other than the raw buffer to
+    // serialize a typed array.
+    const buffer = new Uint8Array(largeObjectSize);
+
+    // Some IndexedDB implementations, like LevelDB, compress their data blocks
+    // before storing them to disk. We use a simple 32-bit xorshift PRNG, which
+    // should be sufficient to foil any fast generic-purpose compression scheme.
+
+    // 32-bit xorshift - the seed can't be zero
+    let state = 1000 + (cursorIndex * itemCount + itemIndex);
+
+    for (let i = 0; i < largeObjectSize; ++i) {
+      state ^= state << 13;
+      state ^= state >> 17;
+      state ^= state << 5;
+      buffer[i] = state & 0xff;
+    }
+
+    return buffer;
+  }
+  return [cursorIndex, 'small', itemIndex];
+}
+
+// Writes the objects to be read by one cursor. Returns a promise that resolves
+// when the write completes.
+//
+// We want to avoid creating a large transaction, because that is outside the
+// test's scope, and it's a bad practice. So we break up the writes across
+// multiple transactions. For simplicity, each transaction writes all the
+// objects that will be read by a cursor.
+function writeCursorObjects(database, cursorIndex) {
+  return new Promise((resolve, reject) => {
+    const transaction = database.transaction('cache', 'readwrite');
+    transaction.onabort = () => { reject(transaction.error); };
+
+    const store = transaction.objectStore('cache');
+    for (let i = 0; i < itemCount; ++i) {
+      store.put({
+          key: objectKey(cursorIndex, i), value: objectValue(cursorIndex, i)});
+    }
+    transaction.oncomplete = resolve;
+  });
+}
+
+// Returns a promise that resolves when the store has been populated.
+function populateTestStore(testCase, database, cursorCount) {
+  let promiseChain = Promise.resolve();
+
+  for (let i = 0; i < cursorCount; ++i)
+    promiseChain = promiseChain.then(() => writeCursorObjects(database, i));
+
+  return promiseChain;
+}
+
+// Reads cursors in an interleaved fashion, as shown below.
+//
+// Given N cursors, each of which points to the beginning of a K-item sequence,
+// the following accesses will be made.
+//
+// OC(i)    = open cursor i
+// RD(i, j) = read result of cursor i, which should be at item j
+// CC(i)    = continue cursor i
+// |        = wait for onsuccess on the previous OC or CC
+//
+// OC(1)            | RD(1, 1) OC(2) | RD(2, 1) OC(3) | ... | RD(n-1, 1) CC(n) |
+// RD(n, 1)   CC(1) | RD(1, 2) CC(2) | RD(2, 2) CC(3) | ... | RD(n-1, 2) CC(n) |
+// RD(n, 2)   CC(1) | RD(1, 3) CC(2) | RD(2, 3) CC(3) | ... | RD(n-1, 3) CC(n) |
+// ...
+// RD(n, k-1) CC(1) | RD(1, k) CC(2) | RD(2, k) CC(3) | ... | RD(n-1, k) CC(n) |
+// RD(n, k)           done
+function interleaveCursors(testCase, store, cursorCount) {
+  return new Promise((resolve, reject) => {
+    // The cursors used for iteration are stored here so each cursor's onsuccess
+    // handler can call continue() on the next cursor.
+    const cursors = [];
+
+    // The results of IDBObjectStore.openCursor() calls are stored here so we
+    // we can change the requests' onsuccess handler after every
+    // IDBCursor.continue() call.
+    const requests = [];
+
+    const checkCursorState = (cursorIndex, itemIndex) => {
+      const cursor = cursors[cursorIndex];
+      assert_equals(cursor.key, objectKey(cursorIndex, itemIndex));
+      assert_equals(cursor.value.key, objectKey(cursorIndex, itemIndex));
+      assert_equals(
+          cursor.value.value.join('-'),
+          objectValue(cursorIndex, itemIndex).join('-'));
+    };
+
+    const openCursor = (cursorIndex, callback) => {
+      const request = store.openCursor(
+          IDBKeyRange.lowerBound(objectKey(cursorIndex, 0)));
+      requests[cursorIndex] = request;
+
+      request.onsuccess = testCase.step_func(() => {
+        const cursor = request.result;
+        cursors[cursorIndex] = cursor;
+        checkCursorState(cursorIndex, 0);
+        callback();
+      });
+      request.onerror = event => reject(request.error);
+    };
+
+    const readItemFromCursor = (cursorIndex, itemIndex, callback) => {
+      const request = requests[cursorIndex];
+      request.onsuccess = testCase.step_func(() => {
+        const cursor = request.result;
+        cursors[cursorIndex] = cursor;
+        checkCursorState(cursorIndex, itemIndex);
+        callback();
+      });
+
+      const cursor = cursors[cursorIndex];
+      cursor.continue();
+    };
+
+    // We open all the cursors one at a time, then cycle through the cursors and
+    // call continue() on each of them. This access pattern causes maximal
+    // trashing to an LRU cursor cache. Eviction scheme aside, any cache will
+    // have to evict some cursors, and this access pattern verifies that the
+    // cache correctly restores the state of evicted cursors.
+    const steps = [];
+    for (let cursorIndex = 0; cursorIndex < cursorCount; ++cursorIndex)
+      steps.push(openCursor.bind(null, cursorIndex));
+    for (let itemIndex = 1; itemIndex < itemCount; ++itemIndex) {
+      for (let cursorIndex = 0; cursorIndex < cursorCount; ++cursorIndex)
+        steps.push(readItemFromCursor.bind(null, cursorIndex, itemIndex));
+    }
+
+    const runStep = (stepIndex) => {
+      if (stepIndex === steps.length) {
+        resolve();
+        return;
+      }
+      steps[stepIndex](() => { runStep(stepIndex + 1); });
+    };
+    runStep(0);
+  });
+}
+
+function cursorTest(cursorCount) {
+  promise_test(testCase => {
+    return createDatabase(testCase, (database, transaction) => {
+      const store = database.createObjectStore('cache',
+          { keyPath: 'key', autoIncrement: true });
+    }).then(database => {
+      return populateTestStore(testCase, database, cursorCount).then(
+          () => database);
+    }).then(database => {
+      database.close();
+    }).then(() => {
+      return openDatabase(testCase);
+    }).then(database => {
+      const transaction = database.transaction('cache', 'readonly');
+      transaction.onabort = () => { reject(transaction.error); };
+
+      const store = transaction.objectStore('cache');
+      return interleaveCursors(testCase, store, cursorCount).then(
+          () => database);
+    }).then(database => {
+      database.close();
+    });
+  }, `${cursorCount} cursors`);
+}
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/IndexedDB/interleaved-cursors-large.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>IndexedDB: Interleaved iteration of multiple cursors</title>
+<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 src="interleaved-cursors-common.js"></script>
+<script>
+cursorTest(250);
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/IndexedDB/interleaved-cursors-small.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>IndexedDB: Interleaved iteration of multiple cursors</title>
+<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 src="interleaved-cursors-common.js"></script>
+<script>
+cursorTest(1);
+cursorTest(10);
+cursorTest(100);
+</script>
deleted file mode 100644
--- a/testing/web-platform/tests/IndexedDB/interleaved-cursors.html
+++ /dev/null
@@ -1,196 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>IndexedDB: Interleaved iteration of multiple cursors</title>
-<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>
-// Number of objects that each iterator goes over.
-const itemCount = 10;
-
-// Ratio of small objects to large objects.
-const largeObjectRatio = 5;
-
-// Size of large objects. This should exceed the size of a block in the storage
-// method underlying the browser's IndexedDB implementation. For example, this
-// needs to exceed the LevelDB block size on Chrome, and the SQLite block size
-// on Firefox.
-const largeObjectSize = 48 * 1024;
-
-function objectKey(cursorIndex, itemIndex) {
-  return `${cursorIndex}-key-${itemIndex}`;
-}
-
-function objectValue(cursorIndex, itemIndex) {
-  if ((cursorIndex * itemCount + itemIndex) % largeObjectRatio === 0) {
-    // We use a typed array (as opposed to a string) because IndexedDB
-    // implementations may serialize strings using UTF-8 or UTF-16, yielding
-    // larger IndexedDB entries than we'd expect. It's very unlikely that an
-    // IndexedDB implementation would use anything other than the raw buffer to
-    // serialize a typed array.
-    const buffer = new Uint8Array(largeObjectSize);
-
-    // Some IndexedDB implementations, like LevelDB, compress their data blocks
-    // before storing them to disk. We use a simple 32-bit xorshift PRNG, which
-    // should be sufficient to foil any fast generic-purpose compression scheme.
-
-    // 32-bit xorshift - the seed can't be zero
-    let state = 1000 + (cursorIndex * itemCount + itemIndex);
-
-    for (let i = 0; i < largeObjectSize; ++i) {
-      state ^= state << 13;
-      state ^= state >> 17;
-      state ^= state << 5;
-      buffer[i] = state & 0xff;
-    }
-
-    return buffer;
-  }
-  return [cursorIndex, 'small', itemIndex];
-}
-
-// Writes the objects to be read by one cursor. Returns a promise that resolves
-// when the write completes.
-//
-// We want to avoid creating a large transaction, because that is outside the
-// test's scope, and it's a bad practice. So we break up the writes across
-// multiple transactions. For simplicity, each transaction writes all the
-// objects that will be read by a cursor.
-function writeCursorObjects(database, cursorIndex) {
-  return new Promise((resolve, reject) => {
-    const transaction = database.transaction('cache', 'readwrite');
-    transaction.onabort = () => { reject(transaction.error); };
-
-    const store = transaction.objectStore('cache');
-    for (let i = 0; i < itemCount; ++i) {
-      store.put({
-          key: objectKey(cursorIndex, i), value: objectValue(cursorIndex, i)});
-    }
-    transaction.oncomplete = resolve;
-  });
-}
-
-// Returns a promise that resolves when the store has been populated.
-function populateTestStore(testCase, database, cursorCount) {
-  let promiseChain = Promise.resolve();
-
-  for (let i = 0; i < cursorCount; ++i)
-    promiseChain = promiseChain.then(() => writeCursorObjects(database, i));
-
-  return promiseChain;
-}
-
-// Reads cursors in an interleaved fashion, as shown below.
-//
-// Given N cursors, each of which points to the beginning of a K-item sequence,
-// the following accesses will be made.
-//
-// OC(i)    = open cursor i
-// RD(i, j) = read result of cursor i, which should be at item j
-// CC(i)    = continue cursor i
-// |        = wait for onsuccess on the previous OC or CC
-//
-// OC(1)            | RD(1, 1) OC(2) | RD(2, 1) OC(3) | ... | RD(n-1, 1) CC(n) |
-// RD(n, 1)   CC(1) | RD(1, 2) CC(2) | RD(2, 2) CC(3) | ... | RD(n-1, 2) CC(n) |
-// RD(n, 2)   CC(1) | RD(1, 3) CC(2) | RD(2, 3) CC(3) | ... | RD(n-1, 3) CC(n) |
-// ...
-// RD(n, k-1) CC(1) | RD(1, k) CC(2) | RD(2, k) CC(3) | ... | RD(n-1, k) CC(n) |
-// RD(n, k)           done
-function interleaveCursors(testCase, store, cursorCount) {
-  return new Promise((resolve, reject) => {
-    // The cursors used for iteration are stored here so each cursor's onsuccess
-    // handler can call continue() on the next cursor.
-    const cursors = [];
-
-    // The results of IDBObjectStore.openCursor() calls are stored here so we
-    // we can change the requests' onsuccess handler after every
-    // IDBCursor.continue() call.
-    const requests = [];
-
-    const checkCursorState = (cursorIndex, itemIndex) => {
-      const cursor = cursors[cursorIndex];
-      assert_equals(cursor.key, objectKey(cursorIndex, itemIndex));
-      assert_equals(cursor.value.key, objectKey(cursorIndex, itemIndex));
-      assert_equals(
-          cursor.value.value.join('-'),
-          objectValue(cursorIndex, itemIndex).join('-'));
-    };
-
-    const openCursor = (cursorIndex, callback) => {
-      const request = store.openCursor(
-          IDBKeyRange.lowerBound(objectKey(cursorIndex, 0)));
-      requests[cursorIndex] = request;
-
-      request.onsuccess = testCase.step_func(() => {
-        const cursor = request.result;
-        cursors[cursorIndex] = cursor;
-        checkCursorState(cursorIndex, 0);
-        callback();
-      });
-      request.onerror = event => reject(request.error);
-    };
-
-    const readItemFromCursor = (cursorIndex, itemIndex, callback) => {
-      const request = requests[cursorIndex];
-      request.onsuccess = testCase.step_func(() => {
-        const cursor = request.result;
-        cursors[cursorIndex] = cursor;
-        checkCursorState(cursorIndex, itemIndex);
-        callback();
-      });
-
-      const cursor = cursors[cursorIndex];
-      cursor.continue();
-    };
-
-    // We open all the cursors one at a time, then cycle through the cursors and
-    // call continue() on each of them. This access pattern causes maximal
-    // trashing to an LRU cursor cache. Eviction scheme aside, any cache will
-    // have to evict some cursors, and this access pattern verifies that the
-    // cache correctly restores the state of evicted cursors.
-    const steps = [];
-    for (let cursorIndex = 0; cursorIndex < cursorCount; ++cursorIndex)
-      steps.push(openCursor.bind(null, cursorIndex));
-    for (let itemIndex = 1; itemIndex < itemCount; ++itemIndex) {
-      for (let cursorIndex = 0; cursorIndex < cursorCount; ++cursorIndex)
-        steps.push(readItemFromCursor.bind(null, cursorIndex, itemIndex));
-    }
-
-    const runStep = (stepIndex) => {
-      if (stepIndex === steps.length) {
-        resolve();
-        return;
-      }
-      steps[stepIndex](() => { runStep(stepIndex + 1); });
-    };
-    runStep(0);
-  });
-}
-
-for (let cursorCount of [1, 10, 100, 500]) {
-  promise_test(testCase => {
-    return createDatabase(testCase, (database, transaction) => {
-      const store = database.createObjectStore('cache',
-          { keyPath: 'key', autoIncrement: true });
-    }).then(database => {
-      return populateTestStore(testCase, database, cursorCount).then(
-          () => database);
-    }).then(database => {
-      database.close();
-    }).then(() => {
-      return openDatabase(testCase);
-    }).then(database => {
-      const transaction = database.transaction('cache', 'readonly');
-      transaction.onabort = () => { reject(transaction.error); };
-
-      const store = transaction.objectStore('cache');
-      return interleaveCursors(testCase, store, cursorCount).then(
-          () => database);
-    }).then(database => {
-      database.close();
-    });
-  }, `${cursorCount} cursors`);
-}
-</script>