Skip to content

Commit

Permalink
Bug 1920254 [wpt PR 48298] - Convert compute_pressure_basic web tests…
Browse files Browse the repository at this point in the history
… to test_driver, a=testonly

Automatic update from web-platform-tests
Convert compute_pressure_basic web tests to test_driver

The purpose of this change is start using WebDriver. WebDriver commands
for Compute Pressure are defined in
https://www.w3.org/TR/compute-pressure/#automation

Dedicated workers need to manipulate virtual pressure sources, but
testdriver calls can only be made in the embedded window, so the
existing solution of having '.any.js' files with `META:
global=window,dedicatedworker` does not work anymore. The solution is
to use message passing so that workers make the embedder window invoke
the virtual pressure source calls and use WPT's variants concept to run
the same test under multiple configurations. Due to this limitation
'.any.js' files needs to be converted to '.window.js'. README.md has
been updated to include a detailed guide for creating tests.

WebDriver endpoints and virtual pressure sources work in Window and
Dedicated Worker scopes, but not shared worker ones: we store virtual
pressure source information in WebContentsUserData, which does not
integrate well with shared workers.

Bug: 347031400
Change-Id: I3cb482c0a689e84012735acd6bc9d33981a7d2f9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5873130
Reviewed-by: Raphael Kubo Da Costa <[email protected]>
Commit-Queue: Raphael Kubo Da Costa <[email protected]>
Reviewed-by: Reilly Grant <[email protected]>
Auto-Submit: Juha J Vainio <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1358424}

--

wpt-commits: 5a2cfec4558912fd7896472593d420054ae967ff
wpt-pr: 48298
  • Loading branch information
JuhaVainio authored and moz-wptsync-bot committed Sep 25, 2024
1 parent b3f053b commit cbd4a15
Show file tree
Hide file tree
Showing 4 changed files with 298 additions and 28 deletions.
61 changes: 60 additions & 1 deletion testing/web-platform/tests/compute-pressure/README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,61 @@
This directory contains (tentative) tests for the
This directory contains tests for the
[Compute Pressure](https://w3c.github.io/compute-pressure/) specification.

## How to write tests
### Tests that only need to run on a window
To test this API, one needs to be able to control the pressure data that will
be reported to script. At a high level, this is done by calling certain
[WebDriver endpoints](https://w3c.github.io/compute-pressure/#automation) via
their corresponding
[testdriver](https://web-platform-tests.org/writing-tests/testdriver.html#compute-pressure)
wrappers.

### Tests that need to run on windows and dedicated workers
Certain [testdriver
limitations](https://web-platform-tests.org/writing-tests/testdriver.html#using-test-driver-in-other-browsing-contexts)
require calls to be made from the top-level test context, which effectively
prevents us from simply [running the same test from multiple globals with
any.js](https://web-platform-tests.org/writing-tests/testharness.html#tests-for-other-or-multiple-globals-any-js).

What we do instead is [write all tests for the Window
global](https://web-platform-tests.org/writing-tests/testharness.html#window-tests),
use
[variants](https://web-platform-tests.org/writing-tests/testharness.html#specifying-test-variants)
for specifying different globals and using the `pressure_test()` and
`mark_as_done()` helpers.

In short, the boilerplate for a new test `foo.https.window.js` looks like this:

``` js
// META: variant=?globalScope=window
// META: variant=?globalScope=dedicated_worker
// META: script=/resources/testdriver.js
// META: script=/resources/testdriver-vendor.js
// META: script=/common/utils.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=./resources/common.js

pressure_test(async t => {
}, 'my test');

mark_as_done();
```

- The variants specify which global context the tests should run on. The only
two options are `window` and `dedicated_worker`.
- We need to include all those scripts for the testdriver and
[RemoteContext](../common/dispatcher/README.md) infrastructure to work.
- `pressure_test()` is a wrapper around a `promise_test()` that takes care of
running the test either in the current context (when `globalScope=window`) or
in a dedicated worker via `RemoteContext` and `fetch_tests_from_worker()`
(when `globalScope=dedicated_worker`).
- `mark_as_done()` is a no-op when `globalScope=window`, but is necessary when
`globalScope=dedicated_worker` to ensure that all tests have run and that
[`done()`](https://web-platform-tests.org/writing-tests/testharness-api.html#Test.done)
is called in the worker context.

### Shared workers
Since custom pressure states are stored in a top-level navigables, they are
currently not integrated with shared workers (see [spec issue
285](https://github.com/w3c/compute-pressure/issues/285)) and support for
testing shared workers is limited.
Original file line number Diff line number Diff line change
@@ -1,48 +1,67 @@
// META: timeout=long
// META: script=/resources/test-only-api.js
// META: script=resources/pressure-helpers.js
// META: global=window,dedicatedworker,sharedworker
// META: variant=?globalScope=window
// META: variant=?globalScope=dedicated_worker
// META: script=/resources/testdriver.js
// META: script=/resources/testdriver-vendor.js
// META: script=/common/utils.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=./resources/common.js

'use strict';

pressure_test((t, mockPressureService) => {
pressure_test(async (t) => {
const observer = new PressureObserver(() => {
assert_unreached('The observer callback should not be called');
});

mockPressureService.setExpectedFailure(
new DOMException('', 'NotSupportedError'));
await create_virtual_pressure_source('cpu', {supported: false});
t.add_cleanup(async () => {
await remove_virtual_pressure_source('cpu');
});
return promise_rejects_dom(t, 'NotSupportedError', observer.observe('cpu'));
}, 'Return NotSupportedError when calling observer()');

pressure_test(async (t, mockPressureService) => {
const changes = await new Promise(resolve => {
pressure_test(async (t) => {
await create_virtual_pressure_source('cpu');
t.add_cleanup(async () => {
await remove_virtual_pressure_source('cpu');
});

const changes = await new Promise(async (resolve) => {
const observer = new PressureObserver(resolve);
t.add_cleanup(() => observer.disconnect());
observer.observe('cpu');
mockPressureService.setPressureUpdate('cpu', 'critical');
mockPressureService.startPlatformCollector(/*sampleInterval=*/ 200);
await update_virtual_pressure_source('cpu', 'critical');
await observer.observe('cpu');
});
assert_true(changes.length === 1);
assert_equals(1, changes.length);
assert_equals(changes[0].state, 'critical');
assert_equals(changes[0].source, 'cpu');
assert_equals(typeof changes[0].time, 'number');
}, 'Basic functionality test');

pressure_test((t, mockPressureService) => {
pressure_test(async (t) => {
await create_virtual_pressure_source('cpu');
t.add_cleanup(async () => {
await remove_virtual_pressure_source('cpu');
});

const observer = new PressureObserver(() => {
assert_unreached('The observer callback should not be called');
});

await update_virtual_pressure_source('cpu', 'critical');
const promise = observer.observe('cpu');
observer.unobserve('cpu');
mockPressureService.setPressureUpdate('cpu', 'critical');
mockPressureService.startPlatformCollector(/*sampleInterval=*/ 200);

return promise_rejects_dom(t, 'AbortError', promise);
}, 'Removing observer before observe() resolves works');

pressure_test(async (t, mockPressureService) => {
pressure_test(async (t) => {
await create_virtual_pressure_source('cpu');
t.add_cleanup(async () => {
await remove_virtual_pressure_source('cpu');
});

const callbackPromises = [];
const observePromises = [];

Expand All @@ -55,26 +74,27 @@ pressure_test(async (t, mockPressureService) => {
}

await Promise.all(observePromises);

mockPressureService.setPressureUpdate('cpu', 'critical');
mockPressureService.startPlatformCollector(/*sampleInterval=*/ 200);

await update_virtual_pressure_source('cpu', 'critical');
return Promise.all(callbackPromises);
}, 'Calling observe() multiple times works');

pressure_test(async (t, mockPressureService) => {
pressure_test(async (t) => {
await create_virtual_pressure_source('cpu');
t.add_cleanup(async () => {
await remove_virtual_pressure_source('cpu');
});

const observer1_changes = [];
await new Promise(resolve => {
await new Promise(async (resolve) => {
const observer1 = new PressureObserver(changes => {
observer1_changes.push(changes);
resolve();
});
t.add_cleanup(() => observer1.disconnect());
observer1.observe('cpu');
mockPressureService.setPressureUpdate('cpu', 'critical');
mockPressureService.startPlatformCollector(/*sampleInterval=*/ 200);
await update_virtual_pressure_source('cpu', 'critical');
await observer1.observe('cpu');
});
assert_true(observer1_changes.length === 1);
assert_equals(1, observer1_changes.length);
assert_equals(observer1_changes[0][0].source, 'cpu');
assert_equals(observer1_changes[0][0].state, 'critical');

Expand All @@ -87,7 +107,9 @@ pressure_test(async (t, mockPressureService) => {
t.add_cleanup(() => observer2.disconnect());
observer2.observe('cpu');
});
assert_true(observer2_changes.length === 1);
assert_equals(1, observer2_changes.length);
assert_equals(observer2_changes[0][0].source, 'cpu');
assert_equals(observer2_changes[0][0].state, 'critical');
}, 'Starting a new observer after an observer has started works');

mark_as_done();
141 changes: 141 additions & 0 deletions testing/web-platform/tests/compute-pressure/resources/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
'use strict';

// Dependencies:
// * /common/utils.js
// * /common/dispatcher/dispatcher.js
//
// This file contains the required infrastructure to run Compute Pressure tests
// in both window and worker scopes transparently.
//
// We cannot just use the '.any.js' mechanism with "META: global=window,etc"
// because we need the worker needs to manipulate virtual pressure sources, and
// we can only do that by posting a message to the embedder window to do it due
// to testdriver's limitations and operation model.
//
// See README.md for how to use pressure_test() and other functions within this
// file.
//
// Example:
// - /compute-pressure/foo.https.window.js
// // META: variant=?globalScope=window
// // META: variant=?globalScope=dedicated_worker
// // META: script=/resources/testdriver.js
// // META: script=/resources/testdriver-vendor.js
// // META: script=/common/utils.js
// // META: script=/common/dispatcher/dispatcher.js
// // META: script=./resources/common.js
//
// pressure_test(async t => {
// await create_virtual_pressure_source("cpu");
// t.add_cleanup(async () => {
// await remove_virtual_pressure_source("cpu")
// });
// /* rest of the test */
// }, "my test");
//
// pressure_test(async t => { /* ... */ });
//
// mark_as_done();

class WindowHelper {
constructor() {
setup({explicit_done: true});

// These are the calls made by tests that use pressure_test(). We do not
// invoke the actual virtual pressure functions directly because of
// compatibility with the dedicated workers case, where these calls are
// done via postMessage (see resources/worker-support.js).
globalThis.create_virtual_pressure_source =
test_driver.create_virtual_pressure_source.bind(test_driver);
globalThis.remove_virtual_pressure_source =
test_driver.remove_virtual_pressure_source.bind(test_driver);
globalThis.update_virtual_pressure_source =
test_driver.update_virtual_pressure_source.bind(test_driver);
}

mark_as_done() {
done();
}

pressure_test(test_func, description) {
promise_test(test_func, description);
}
}

class DedicatedWorkerHelper {
constructor() {
this.token = token();

this.worker = new Worker(
`/compute-pressure/resources/worker-support.js?uuid=${this.token}`);
this.worker.onmessage = async (e) => {
if (!e.data.command) {
return;
}

switch (e.data.command) {
case 'create':
await test_driver.create_virtual_pressure_source(...e.data.params);
break;

case 'remove':
await test_driver.remove_virtual_pressure_source(...e.data.params);
break;

case 'update':
await test_driver.update_virtual_pressure_source(...e.data.params);
break;

default:
throw new Error(`Unexpected command '${e.data.command}'`);
}

this.worker.postMessage({
command: e.data.command,
id: e.data.id,
});
};

// We need to call this here so that the testharness RemoteContext
// infrastructure is set up before pressure_test() is called, as each test
// will be added after the worker scaffolding code has loaded.
this.fetch_tests_promise = fetch_tests_from_worker(this.worker);

this.ctx = new RemoteContext(this.token);

this.pending_tests = [];
}

async mark_as_done() {
await Promise.all(this.pending_tests);
await this.ctx.execute_script(() => {
done();
});
await this.fetch_tests_from_worker;
}

async pressure_test(test_func, description) {
this.pending_tests.push(this.ctx.execute_script(
`
(description) => promise_test(${test_func}, description);
`,
[description]));
}
}

let _pressureTestHelper;
const _globalScope = new URLSearchParams(location.search).get('globalScope');
switch (_globalScope) {
case 'window':
_pressureTestHelper = new WindowHelper();
break;
case 'dedicated_worker':
_pressureTestHelper = new DedicatedWorkerHelper();
break;
default:
throw new Error(`Invalid variant '${_globalScope}'`);
}

const pressure_test =
_pressureTestHelper.pressure_test.bind(_pressureTestHelper);
const mark_as_done = _pressureTestHelper.mark_as_done.bind(_pressureTestHelper);
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';

importScripts('/resources/testharness.js');
importScripts('/common/utils.js');
importScripts('/common/dispatcher/dispatcher.js');

function send_message(message) {
return new Promise((resolve, reject) => {
const id = token();
message.id = id;

addEventListener('message', function listener(e) {
if (!e.data.command || e.data.id !== id) {
return;
}

removeEventListener('message', listener);

if (e.data.command !== message.command) {
reject(`Expected reply with command '${message.command}', got '${
e.data.command}' instead`);
return;
}
if (e.data.error) {
reject(e.data.error);
return;
}
resolve();
});

postMessage(message);
});
}

function create_virtual_pressure_source(source, options = {}) {
return send_message({command: 'create', params: [source, options]});
}

function remove_virtual_pressure_source(source) {
return send_message({command: 'remove', params: [source]});
}

function update_virtual_pressure_source(source, state) {
return send_message({command: 'update', params: [source, state]});
}

const uuid = new URLSearchParams(location.search).get('uuid');
const executor = new Executor(uuid);

0 comments on commit cbd4a15

Please sign in to comment.