test_runner.js: Reuse web workers (and devices) between CTS tests

This is necessary to run more than a few tests on the worker test steps;
otherwise they quickly run up against limits on the number of GPUDevices
that can be alive at once (which is due to some separate leak / slow GC
issue, which is harder to fix).

This was also the cause of all of the OOMs when I accidentally started
running all tests on workers: https://crbug.com/330573129#comment9

Bug: chromium:331351978, chromium:330596242, dawn:2479
Change-Id: I3c6477dae9088370a6cc2825e42dfec38eb43fcc
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/182421
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Brian Sheedy <bsheedy@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/webgpu-cts/test_runner.js b/webgpu-cts/test_runner.js
index 7d691e5..9178d3e 100644
--- a/webgpu-cts/test_runner.js
+++ b/webgpu-cts/test_runner.js
@@ -175,14 +175,25 @@
   globalTestConfig.unrollConstEvalLoops = true;
 }
 
+let lastOptionsKey, testWorker;
+
 async function runCtsTest(queryString) {
   const { queries, options } = parseSearchParamLikeWithCTSOptions(queryString);
-  const testWorker =
-    options.worker === null ? null :
-    options.worker === 'dedicated' ? new TestDedicatedWorker(options) :
-    options.worker === 'shared' ? new TestSharedWorker(options) :
-    options.worker === 'service' ? new TestServiceWorker(options) :
-    unreachable();
+
+  // Set up a worker with the options passed into the test, avoiding creating
+  // a new worker if one was already set up for the last test.
+  // In practice, the options probably won't change between tests in a single
+  // invocation of run_gpu_integration_test.py, but this handles if they do.
+  const currentOptionsKey = JSON.stringify(options);
+  if (currentOptionsKey !== lastOptionsKey) {
+    lastOptionsKey = currentOptionsKey;
+    testWorker =
+      options.worker === null ? null :
+      options.worker === 'dedicated' ? new TestDedicatedWorker(options) :
+      options.worker === 'shared' ? new TestSharedWorker(options) :
+      options.worker === 'service' ? new TestServiceWorker(options) :
+      unreachable();
+  }
 
   const loader = new DefaultTestFileLoader();
   const filterQuery = parseQuery(queries[0]);