Bug 1482539 [wpt PR 12416] - [Layered API] Update async local storage implementation and tests, a=testonly
authorDomenic Denicola <domenic@chromium.org>
Wed, 22 Aug 2018 13:18:06 +0000
changeset 831093 468b233349f8631fa18346514c05e02138fc6ecf
parent 831092 936e291dacf2ead85b5c640962618bc36df8d12d
child 831094 cc030f559f30b24804d3e8d770c5d7be46878c87
push id118868
push userbmo:zjz@zjz.name
push dateFri, 24 Aug 2018 07:04:39 +0000
reviewerstestonly
bugs1482539, 12416, 1171573, 584064
milestone63.0a1
Bug 1482539 [wpt PR 12416] - [Layered API] Update async local storage implementation and tests, a=testonly Automatic update from web-platform-tests[Layered API] Update async local storage implementation and tests This CL updates the implementation and tests to be adapted versions of https://github.com/domenic/async-local-storage/tree/73580d0151f04849ca9e691807907e95c4d17d12/prototype. Change-Id: I9071883a6fef8ef97c405a9a749bf9c2d3cfb58a Reviewed-on: https://chromium-review.googlesource.com/1171573 Reviewed-by: Joshua Bell <jsbell@chromium.org> Reviewed-by: Hiroshige Hayashizaki <hiroshige@chromium.org> Commit-Queue: Domenic Denicola <domenic@chromium.org> Cr-Commit-Position: refs/heads/master@{#584064} -- wpt-commits: 0ca82a786fe09415e21f558d2d28421fa35cf0f5 wpt-pr: 12416
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/async-local-storage/api-surface.tentative.https.html
testing/web-platform/tests/async-local-storage/helpers/als-tests.js
testing/web-platform/tests/async-local-storage/helpers/class-assert.js
testing/web-platform/tests/async-local-storage/helpers/equality-asserters.js
testing/web-platform/tests/async-local-storage/key-types.tentative.https.html
testing/web-platform/tests/async-local-storage/non-secure-context-dynamic-import.tentative.html
testing/web-platform/tests/async-local-storage/non-secure-context-import-statement.tentative.html
testing/web-platform/tests/async-local-storage/non-secure-context-script-element.tentative.html
testing/web-platform/tests/async-local-storage/storage-smoke-test.https.tentative.html
testing/web-platform/tests/async-local-storage/storage-smoke-test.tentative.https.html
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -195394,16 +195394,31 @@
      {}
     ]
    ],
    "async-local-storage/META.yml": [
     [
      {}
     ]
    ],
+   "async-local-storage/helpers/als-tests.js": [
+    [
+     {}
+    ]
+   ],
+   "async-local-storage/helpers/class-assert.js": [
+    [
+     {}
+    ]
+   ],
+   "async-local-storage/helpers/equality-asserters.js": [
+    [
+     {}
+    ]
+   ],
    "audio-output/META.yml": [
     [
      {}
     ]
    ],
    "background-fetch/META.yml": [
     [
      {}
@@ -322955,19 +322970,49 @@
     ]
    ],
    "appmanifest/idlharness.window.js": [
     [
      "/appmanifest/idlharness.window.html",
      {}
     ]
    ],
-   "async-local-storage/storage-smoke-test.https.tentative.html": [
-    [
-     "/async-local-storage/storage-smoke-test.https.tentative.html",
+   "async-local-storage/api-surface.tentative.https.html": [
+    [
+     "/async-local-storage/api-surface.tentative.https.html",
+     {}
+    ]
+   ],
+   "async-local-storage/key-types.tentative.https.html": [
+    [
+     "/async-local-storage/key-types.tentative.https.html",
+     {}
+    ]
+   ],
+   "async-local-storage/non-secure-context-dynamic-import.tentative.html": [
+    [
+     "/async-local-storage/non-secure-context-dynamic-import.tentative.html",
+     {}
+    ]
+   ],
+   "async-local-storage/non-secure-context-import-statement.tentative.html": [
+    [
+     "/async-local-storage/non-secure-context-import-statement.tentative.html",
+     {}
+    ]
+   ],
+   "async-local-storage/non-secure-context-script-element.tentative.html": [
+    [
+     "/async-local-storage/non-secure-context-script-element.tentative.html",
+     {}
+    ]
+   ],
+   "async-local-storage/storage-smoke-test.tentative.https.html": [
+    [
+     "/async-local-storage/storage-smoke-test.tentative.https.html",
      {}
     ]
    ],
    "audio-output/idlharness.window.js": [
     [
      "/audio-output/idlharness.window.html",
      {}
     ]
@@ -432596,18 +432641,50 @@
   "appmanifest/idlharness.window.js": [
    "55e8b9871e794c944f329e0e9df6ec140124c660",
    "testharness"
   ],
   "async-local-storage/META.yml": [
    "1bbe9e5ac609aa33914ad79d4af7cb2fdf45b9c7",
    "support"
   ],
-  "async-local-storage/storage-smoke-test.https.tentative.html": [
-   "b4d66dabc7a177742666377718b30f84a10de744",
+  "async-local-storage/api-surface.tentative.https.html": [
+   "eea51abd539787f531cbf32e28737c135c63c8d7",
+   "testharness"
+  ],
+  "async-local-storage/helpers/als-tests.js": [
+   "fd6d6844f72e39d16e070ec461b99ab62a0b4547",
+   "support"
+  ],
+  "async-local-storage/helpers/class-assert.js": [
+   "31b25cab9f2d88d8df59a0b4ecb35eef3765e380",
+   "support"
+  ],
+  "async-local-storage/helpers/equality-asserters.js": [
+   "ad4623c179d75c8d4ce8b1fa8503f943bf6a7c77",
+   "support"
+  ],
+  "async-local-storage/key-types.tentative.https.html": [
+   "771ee2f9749a00ec4e33019512a9bf8d145a3ce6",
+   "testharness"
+  ],
+  "async-local-storage/non-secure-context-dynamic-import.tentative.html": [
+   "9270f6c82fa2018d1d6c3a199cbe5f6ca2403b56",
+   "testharness"
+  ],
+  "async-local-storage/non-secure-context-import-statement.tentative.html": [
+   "879729696dbb6a767530d77fbd94af0b42afe6b4",
+   "testharness"
+  ],
+  "async-local-storage/non-secure-context-script-element.tentative.html": [
+   "feeddafc8daa02556eb0c5fe068dbde1d45642da",
+   "testharness"
+  ],
+  "async-local-storage/storage-smoke-test.tentative.https.html": [
+   "f978480ff2b80ba5f892c2a2c429e882d655574d",
    "testharness"
   ],
   "audio-output/META.yml": [
    "ea6b29e42f38abeaa5eff6ed6c2fad18eed4e536",
    "support"
   ],
   "audio-output/idlharness.window.js": [
    "f10e523bcdc530ee1dbd04a52541ac0b343e9376",
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/async-local-storage/api-surface.tentative.https.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Async local storage API surface</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script type="module">
+import { storage, StorageArea } from "std:async-local-storage";
+import * as classAssert from "./helpers/class-assert.js";
+import { testWithArea } from "./helpers/als-tests.js";
+
+test(() => {
+  classAssert.isConstructor(StorageArea);
+  classAssert.functionName(StorageArea, "StorageArea");
+  classAssert.functionLength(StorageArea, 1);
+  classAssert.hasClassPrototype(StorageArea);
+  classAssert.hasPrototypeConstructorLink(StorageArea);
+  classAssert.propertyKeys(StorageArea, ["length", "prototype", "name"], []);
+}, "StorageArea constructor");
+
+test(() => {
+  classAssert.propertyKeys(StorageArea.prototype, [
+    "constructor", "set", "get", "has", "delete", "clear",
+    "keys", "values", "entries", "backingStore"
+  ], []);
+
+  classAssert.methods(StorageArea.prototype, {
+    set: 2,
+    get: 1,
+    has: 1,
+    delete: 1,
+    clear: 0,
+    keys: 0,
+    values: 0,
+    entries: 0
+  });
+
+  classAssert.accessors(StorageArea.prototype, {
+    backingStore: ["get"]
+  });
+}, "StorageArea.prototype methods and properties");
+
+testWithArea(async area => {
+  classAssert.propertyKeys(area, [], []);
+}, "Instances don't have any properties")
+
+test(() => {
+  assert_equals(storage instanceof StorageArea, true, "instanceof");
+  assert_equals(storage.constructor, StorageArea, ".constructor property");
+  assert_equals(Object.getPrototypeOf(storage), StorageArea.prototype, "[[Prototype]]");
+}, "Built-in storage export is a StorageArea");
+
+testWithArea(async area => {
+  assert_false(Symbol.toStringTag in StorageArea.prototype,
+    "Symbol.toStringTag must not be in the prototype");
+  assert_equals(Object.prototype.toString.call(StorageArea.prototype), "[object Object]",
+    "The prototype must not be customized");
+
+  assert_equals(Object.prototype.toString.call(area), "[object Object]",
+    "A constructed StorageArea must not be customized");
+    assert_equals(Object.prototype.toString.call(storage), "[object Object]",
+    "The default storage area must not be customized");
+}, "No custom toStringTag");
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/async-local-storage/helpers/als-tests.js
@@ -0,0 +1,72 @@
+import { StorageArea, storage as defaultArea } from "std:async-local-storage";
+import { assertArrayCustomEquals } from "./equality-asserters.js";
+
+export function testWithArea(testFn, description) {
+  promise_test(t => {
+    const area = new StorageArea(description);
+    t.add_cleanup(t => area.clear());
+
+    return testFn(area, t);
+  }, description);
+}
+
+export function testWithDefaultArea(testFn, description) {
+  promise_test(t => {
+    t.add_cleanup(t => defaultArea.clear());
+
+    return testFn(defaultArea, t);
+  }, description);
+}
+
+// These two functions take a key/value and use them to test
+// set()/get()/delete()/has()/keys()/values()/entries(). The keyEqualityAsserter should be a
+// function from ./equality-asserters.js.
+
+export function testVariousMethodsWithDefaultArea(label, key, value, keyEqualityAsserter) {
+  testWithDefaultArea(testVariousMethodsInner(key, value, keyEqualityAsserter), label);
+}
+
+export function testVariousMethods(label, key, value, keyEqualityAsserter) {
+  testWithArea(testVariousMethodsInner(key, value, keyEqualityAsserter), label);
+}
+
+function testVariousMethodsInner(key, value, keyEqualityAsserter) {
+  return async area => {
+    await assertPromiseEquals(area.set(key, value), undefined, "set()", "undefined");
+
+    await assertPromiseEquals(area.get(key), value, "get()", "the set value");
+    await assertPromiseEquals(area.has(key), true, "has()", "true");
+
+    const keysPromise = area.keys();
+    assertIsPromise(keysPromise, "keys()");
+    assertArrayCustomEquals(await keysPromise, [key], keyEqualityAsserter, "keys() must have the key");
+
+    const valuesPromise = area.values();
+    assertIsPromise(valuesPromise);
+    assert_array_equals(await valuesPromise, [value], "values() must have the value");
+
+    const entriesPromise = area.entries();
+    assertIsPromise(entriesPromise, "entries()");
+    const entries = await entriesPromise;
+    assert_true(Array.isArray(entries), "entries() must give an array");
+    assert_equals(entries.length, 1, "entries() must have only one value");
+    assert_true(Array.isArray(entries[0]), "entries() 0th element must be an array");
+    assert_equals(entries[0].length, 2, "entries() 0th element must have 2 elements");
+    keyEqualityAsserter(entries[0][0], key, "entries() 0th element's 0th element must be the key");
+    assert_equals(entries[0][1], value, "entries() 0th element's 1st element must be the value");
+
+    await assertPromiseEquals(area.delete(key), undefined, "delete()", "undefined");
+
+    await assertPromiseEquals(area.get(key), undefined, "get()", "undefined after deleting");
+    await assertPromiseEquals(area.has(key), false, "has()", "false after deleting");
+  };
+}
+
+async function assertPromiseEquals(promise, expected, label, expectedLabel) {
+  assertIsPromise(promise, label);
+  assert_equals(await promise, expected, label + " must fulfill with " + expectedLabel);
+}
+
+function assertIsPromise(promise, label) {
+  assert_equals(promise.constructor, Promise, label + " must return a promise");
+}
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/async-local-storage/helpers/class-assert.js
@@ -0,0 +1,107 @@
+export function isConstructor(o) {
+  assert_equals(typeof o, "function", "Must be a function according to typeof");
+  assert_true(isConstructorTest(o), "Must be a constructor according to the meta-object protocol");
+  assert_throws(new TypeError(), () => o(), "Attempting to call (not construct) must throw");
+}
+
+export function functionLength(o, expected, label) {
+  const lengthExpected = { writable: false, enumerable: false, configurable: true };
+  const { value } = propertyDescriptor(o, "length", lengthExpected);
+
+  assert_equals(value, expected, `${formatLabel(label)}length value`);
+}
+
+export function functionName(o, expected, label) {
+  const lengthExpected = { writable: false, enumerable: false, configurable: true };
+  const { value } = propertyDescriptor(o, "name", lengthExpected);
+
+  assert_equals(value, expected, `${formatLabel(label)}name value`);
+}
+
+export function hasClassPrototype(o) {
+  const prototypeExpected = { writable: false, enumerable: false, configurable: false };
+  const { value } = propertyDescriptor(o, "prototype", prototypeExpected);
+  assert_equals(typeof value, "object", "prototype must be an object");
+  assert_not_equals(value, null, "prototype must not be null");
+}
+
+export function hasPrototypeConstructorLink(klass) {
+  const constructorExpected = { writable: true, enumerable: false, configurable: true };
+  const { value } = propertyDescriptor(klass.prototype, "constructor", constructorExpected);
+  assert_equals(value, klass, "constructor property must match");
+}
+
+export function propertyKeys(o, expectedNames, expectedSymbols, label) {
+  label = formatLabel(label);
+  assert_array_equals(Object.getOwnPropertyNames(o), expectedNames, `${label}property names`);
+  assert_array_equals(Object.getOwnPropertySymbols(o), expectedSymbols,
+    `${label}property symbols`);
+}
+
+export function methods(o, expectedMethods) {
+  for (const [name, length] of Object.entries(expectedMethods)) {
+    method(o, name, length);
+  }
+}
+
+export function accessors(o, expectedAccessors) {
+  for (const [name, accessorTypes] of Object.entries(expectedAccessors)) {
+    accessor(o, name, accessorTypes);
+  }
+}
+
+function method(o, prop, length) {
+  const methodExpected = { writable: true, enumerable: false, configurable: true };
+  const { value } = propertyDescriptor(o, prop, methodExpected);
+
+  assert_equals(typeof value, "function", `${prop} method must be a function according to typeof`);
+  assert_false(isConstructorTest(value),
+    `${prop} method must not be a constructor according to the meta-object protocol`);
+  functionLength(value, length, prop);
+  functionName(value, prop, prop);
+  propertyKeys(value, ["length", "name"], [], prop);
+}
+
+function accessor(o, prop, expectedAccessorTypes) {
+  const accessorExpected = { enumerable: false, configurable: true };
+  const propDesc = propertyDescriptor(o, prop, accessorExpected);
+
+  for (const possibleType of ["get", "set"]) {
+    const accessorFunc = propDesc[possibleType];
+    if (expectedAccessorTypes.includes(possibleType)) {
+      const label = `${prop}'s ${possibleType}ter`;
+
+      assert_equals(typeof accessorFunc, "function",
+        `${label} must be a function according to typeof`);
+      assert_false(isConstructorTest(accessorFunc),
+        `${label} must not be a constructor according to the meta-object protocol`);
+
+      functionLength(accessorFunc, possibleType === "get" ? 0 : 1, label);
+      functionName(accessorFunc, `${possibleType} ${prop}`, label);
+      propertyKeys(accessorFunc, ["length", "name"], [], label);
+    } else {
+      assert_equals(accessorFunc, undefined, `${prop} must not have a ${possibleType}ter`);
+    }
+  }
+}
+
+function propertyDescriptor(obj, prop, mustMatch) {
+  const propDesc = Object.getOwnPropertyDescriptor(obj, prop);
+  for (const key in Object.keys(mustMatch)) {
+    assert_equals(propDesc[key], mustMatch[key], `${prop} ${key}`);
+  }
+  return propDesc;
+}
+
+function isConstructorTest(o) {
+  try {
+    new (new Proxy(o, {construct: () => ({})}));
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+function formatLabel(label) {
+  return label !== undefined ? ` ${label}` : "";
+}
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/async-local-storage/helpers/equality-asserters.js
@@ -0,0 +1,37 @@
+export function assertEqualDates(actual, expected, label) {
+  assert_equals(expected.constructor, Date,
+    "assertEqualDates usage check: expected must be a Date");
+
+  const labelPart = label === undefined ? "" : `${label}: `;
+  assert_equals(actual.constructor, Date, `${labelPart}must be a Date`);
+  assert_equals(actual.valueOf(), expected.valueOf(), `${labelPart}timestamps must match`);
+}
+
+export function assertEqualArrayBuffers(actual, expected, label) {
+  assert_equals(expected.constructor, ArrayBuffer,
+    "assertEqualArrayBuffers usage check: expected must be an ArrayBuffer");
+
+  const labelPart = label === undefined ? "" : `${label}: `;
+  assert_equals(actual.constructor, ArrayBuffer, `${labelPart}must be an ArrayBuffer`);
+  assert_array_equals(new Uint8Array(actual), new Uint8Array(expected), `${labelPart}must match`);
+}
+
+export function assertArrayBufferEqualsABView(actual, expected, label) {
+  assert_true(ArrayBuffer.isView(expected),
+    "assertArrayBufferEqualsABView usage check: expected must be an ArrayBuffer view");
+
+  assertEqualArrayBuffers(actual, expected.buffer, label);
+}
+
+export function assertArrayCustomEquals(actual, expected, equalityAsserter, label) {
+  assert_true(Array.isArray(expected),
+    "assertArrayCustomEquals usage check: expected must be an Array");
+
+  const labelPart = label === undefined ? "" : `${label}: `;
+  assert_true(Array.isArray(actual), `${labelPart}must be an array`);
+  assert_equals(actual.length, expected.length, `${labelPart}length must be as expected`);
+
+  for (let i = 0; i < actual.length; ++i) {
+    equalityAsserter(actual[i], expected[i], `${labelPart}index ${i}`);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/async-local-storage/key-types.tentative.https.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Async local storage: tests against various key types</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script type="module">
+import { testWithArea, testVariousMethods } from "./helpers/als-tests.js";
+import { assertEqualDates, assertEqualArrayBuffers, assertArrayBufferEqualsABView }
+  from "./helpers/equality-asserters.js";
+
+const invalidKeys = {
+  "undefined": undefined,
+  "null": null,
+  "a boolean": true,
+  "a symbol": Symbol("a symbol"),
+  "an object": { an: "object" },
+  "a function": () => {},
+  "a regexp": /foo/,
+  "a Map": new Map(),
+  "a Set": new Set(),
+  "an IDBKeyRange": IDBKeyRange.only(5)
+};
+
+const validKeys = {
+  "a number": [5, assert_equals],
+  "a string": ["a string", assert_equals],
+  "a Date": [new Date(), assertEqualDates],
+  "a typed array": [new Uint8Array([1, 2]), assertArrayBufferEqualsABView],
+  "a DataView": [new DataView(new Uint8Array([3, 4]).buffer), assertArrayBufferEqualsABView],
+  "an ArrayBuffer": [new Uint8Array([5, 6]).buffer, assertEqualArrayBuffers]
+};
+
+const methods = ["set", "get", "has", "delete"];
+
+for (const method of methods) {
+  testWithArea(async (area, t) => {
+    for (const [description, key] of Object.entries(invalidKeys)) {
+      await promise_rejects(t, "DataError", area[method](key), description);
+    }
+  }, `${method}: invalid keys`);
+
+  testWithArea(async (area, t) => {
+    for (const [description, key] of Object.entries(invalidKeys)) {
+      await promise_rejects(t, "DataError", area[method]([key]), description);
+    }
+  }, `${method}: invalid keys, nested in arrays`);
+
+  testWithArea(async (area, t) => {
+    for (const [key] of Object.values(validKeys)) {
+      await area[method](key);
+    }
+  }, `${method}: valid keys`);
+
+  testWithArea(async (area, t) => {
+    for (const [key] of Object.values(validKeys)) {
+      await area[method]([key]);
+    }
+  }, `${method}: valid keys, nested in arrays`);
+}
+
+for (const [description, [key, equalityAsserter]] of Object.entries(validKeys)) {
+  testVariousMethods(`Storage methods smoke test: ${description} key`, key, 5, equalityAsserter);
+}
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/async-local-storage/non-secure-context-dynamic-import.tentative.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Async local storage: should not work in non-secure contexts when included via import()</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+  assert_false(self.isSecureContext, "This test must run in a non-secure context");
+}, "Prerequisite check");
+
+promise_test(t => {
+  return promise_rejects(t, "SecurityError", import("std:async-local-storage"));
+});
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/async-local-storage/non-secure-context-import-statement.tentative.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Async local storage: should not work in non-secure contexts when included via an import statement</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+setup({ allow_uncaught_exception: true });
+
+test(() => {
+  assert_false(self.isSecureContext, "This test must run in a non-secure context");
+}, "Prerequisite check");
+
+async_test(t => {
+  window.addEventListener("error", t.step_func_done(errorEvent => {
+    assert_equals(errorEvent.error.constructor, DOMException, "Must trigger a DOMException");
+    assert_equals(errorEvent.error.name, "SecurityError",
+      "The DOMException must be a \"SecurityError\"");
+  }, { once: true }));
+});
+</script>
+
+<script type="module">
+import "std:async-local-storage";
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/async-local-storage/non-secure-context-script-element.tentative.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Async local storage: should not work in non-secure contexts when included via a script element</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+setup({ allow_uncaught_exception: true });
+
+test(() => {
+  assert_false(self.isSecureContext, "This test must run in a non-secure context");
+}, "Prerequisite check");
+
+async_test(t => {
+  self.addEventListener("error", t.step_func_done(errorEvent => {
+    assert_equals(errorEvent.error.constructor, DOMException, "Must trigger a DOMException");
+    assert_equals(errorEvent.error.name, "SecurityError",
+      "The DOMException must be a \"SecurityError\"");
+  }, { once: true }));
+});
+</script>
+
+<script type="module" src="std:async-local-storage"></script>
deleted file mode 100644
--- a/testing/web-platform/tests/async-local-storage/storage-smoke-test.https.tentative.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Async local storage storage export smoke test</title>
-
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script type="module">
-import { storage } from "std:async-local-storage";
-
-test(() => {
-  const { backingStore } = storage;
-  assert_array_equals(Object.keys(backingStore), ["database", "store", "version"]);
-  assert_own_property(backingStore, "database");
-  assert_own_property(backingStore, "store");
-  assert_own_property(backingStore, "version");
-  assert_equals(Object.getPrototypeOf(backingStore), Object.prototype);
-
-  assert_equals(backingStore.database, "async-local-storage:default");
-  assert_equals(backingStore.store, "store");
-  assert_equals(backingStore.version, 1);
-}, "backingStore returns the correct object");
-
-promise_test(async (t) => {
-  t.add_cleanup(async () => {
-    await storage.clear();
-  });
-
-  assert_equals(await storage.set("key", "value"), undefined);
-
-  assert_equals(await storage.get("key"), "value");
-  assert_equals(await storage.has("key"), true);
-  assert_array_equals(await storage.keys(), ["key"]);
-  assert_array_equals(await storage.values(), ["value"]);
-
-  const entries = await storage.entries();
-  assert_true(Array.isArray(entries));
-  assert_equals(entries.length, 1);
-  assert_array_equals(entries[0], ["key", "value"]);
-
-  assert_equals(await storage.delete("key"), undefined);
-
-  assert_equals(await storage.get("key"), undefined);
-  assert_equals(await storage.has("key"), false);
-}, "storage methods work, at least for one entry with string key and value");
-</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/async-local-storage/storage-smoke-test.tentative.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Async local storage storage export smoke test</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script type="module">
+import { testVariousMethodsWithDefaultArea } from "./helpers/als-tests.js";
+import { storage } from "std:async-local-storage";
+
+test(() => {
+  const { backingStore } = storage;
+  assert_array_equals(Object.keys(backingStore), ["database", "store", "version"]);
+  assert_own_property(backingStore, "database");
+  assert_own_property(backingStore, "store");
+  assert_own_property(backingStore, "version");
+  assert_equals(Object.getPrototypeOf(backingStore), Object.prototype);
+
+  assert_equals(backingStore.database, "async-local-storage:default");
+  assert_equals(backingStore.store, "store");
+  assert_equals(backingStore.version, 1);
+}, "backingStore returns the correct object");
+
+testVariousMethodsWithDefaultArea(
+  "Storage methods smoke test with string key and value", "key", "value", assert_equals
+);
+</script>