Bug 1594612 [wpt PR 20140] - Roll WebGPU CTS, a=testonly
authorKai Ninomiya <kainino@chromium.org>
Mon, 25 Nov 2019 16:49:41 +0000
changeset 504459 f7e62dc54112710ef00d7358ad469f96c8c7f1d6
parent 504458 bf228d83752f337e3a2a958410dbabb894345044
child 504460 820c7e9047f4ba8d6ddc0ca1335bce2260607a4b
push id101897
push userwptsync@mozilla.com
push dateFri, 29 Nov 2019 11:10:32 +0000
treeherderautoland@47be1b3fdda6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1594612, 20140, 1902131, 713275
milestone72.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 1594612 [wpt PR 20140] - Roll WebGPU CTS, a=testonly Automatic update from web-platform-tests Roll WebGPU CTS Prepares us to roll the vertex state CTS changes along with the vertex state changes in Dawn and Chrome. TBR: enga@chromium.org Change-Id: I5ed5eebdff7d8e7559b0044686caacea9defd850 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1902131 Reviewed-by: Kai Ninomiya <kainino@chromium.org> Commit-Queue: Kai Ninomiya <kainino@chromium.org> Cr-Commit-Position: refs/heads/master@{#713275} -- wpt-commits: b490a2b1777c8062c0eb3ff0115735a5bc9c1bab wpt-pr: 20140
testing/web-platform/tests/webgpu/cts.html
testing/web-platform/tests/webgpu/framework/fixture.js
testing/web-platform/tests/webgpu/framework/logger.js
testing/web-platform/tests/webgpu/framework/test_group.js
testing/web-platform/tests/webgpu/framework/util/stack.js
testing/web-platform/tests/webgpu/framework/version.js
testing/web-platform/tests/webgpu/runtime/wpt.js
testing/web-platform/tests/webgpu/suites/cts/canvas/context_creation.spec.js
testing/web-platform/tests/webgpu/suites/cts/command_buffer/compute/basic.spec.js
testing/web-platform/tests/webgpu/suites/cts/command_buffer/render/rendering.spec.js
testing/web-platform/tests/webgpu/suites/cts/command_buffer/render/storeop.spec.js
testing/web-platform/tests/webgpu/suites/cts/gpu_test.js
testing/web-platform/tests/webgpu/suites/cts/validation/createRenderPipeline.spec.js
testing/web-platform/tests/webgpu/suites/cts/validation/render_pass.spec.js
testing/web-platform/tests/webgpu/suites/cts/validation/setVertexBuffer.spec.js
testing/web-platform/tests/webgpu/suites/cts/validation/vertex_input.spec.js
--- a/testing/web-platform/tests/webgpu/cts.html
+++ b/testing/web-platform/tests/webgpu/cts.html
@@ -1,25 +1,30 @@
 <!-- AUTO-GENERATED - DO NOT EDIT. See gen_wpt_cts_html.ts. -->
 <!--
     This test suite is built from the TypeScript sources at:
     https://github.com/gpuweb/cts
 
+    If you are debugging WebGPU conformance tests, it's highly recommended that
+    you use the standalone interactive runner in that repository, which
+    provides tools for easier debugging and editing (source maps, debug
+    logging, warn/skip functionality, etc.)
+
     NOTE:
-    The WPT version of this file is generated with *one test spec per variant*.
-    If your harness needs more fine-grained suppressions, you'll need to
+    The WPT version of this file is generated with *one variant per test spec
+    file*. If your harness needs more fine-grained suppressions, you'll need to
     generate your own variants list from your suppression list. For example, if
     test file cts:a/b: has 3 tests and you need to suppress a single case, you
     might break it out into the following variants:
 
-    - cts:a/b:test1~
-    - cts:a/b:test2={"x":1}
-    - cts:a/b:test2={"x":2}  // <- suppress this one
-    - cts:a/b:test2={"x":3}
-    - cts:a/b:test3~
+      - cts:a/b:test1~
+      - cts:a/b:test2={"x":1}
+      - cts:a/b:test2={"x":2}  // <- suppress this one
+      - cts:a/b:test2={"x":3}
+      - cts:a/b:test3~
 
     When run under browser CI, the original cts.html should be skipped, and
     this alternate version should be run instead, under a non-exported WPT test
     directory (e.g. Chromium's wpt_internal).
 -->
 
 <!doctype html>
 <title>WebGPU CTS</title>
--- a/testing/web-platform/tests/webgpu/framework/fixture.js
+++ b/testing/web-platform/tests/webgpu/framework/fixture.js
@@ -1,17 +1,18 @@
 /**
 * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
 **/
 
 function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
 
-// A Fixture is a class used to instantiate each test case at run time.
+export class SkipTestCase extends Error {} // A Fixture is a class used to instantiate each test case at run time.
 // A new instance of the Fixture is created for every single test case
 // (i.e. every time the test function is run).
+
 export class Fixture {
   constructor(rec, params) {
     _defineProperty(this, "params", void 0);
 
     _defineProperty(this, "rec", void 0);
 
     _defineProperty(this, "eventualExpectations", []);
 
@@ -28,16 +29,20 @@ export class Fixture {
   debug(msg) {
     this.rec.debug(msg);
   }
 
   log(msg) {
     this.rec.log(msg);
   }
 
+  skip(msg) {
+    throw new SkipTestCase(msg);
+  }
+
   async finalize() {
     if (this.numOutstandingAsyncExpectations !== 0) {
       throw new Error('there were outstanding asynchronous expectations (e.g. shouldReject) at the end of the test');
     }
 
     await Promise.all(this.eventualExpectations);
   }
 
@@ -59,24 +64,24 @@ export class Fixture {
   eventualAsyncExpectation(fn) {
     const promise = fn();
     this.eventualExpectations.push(promise);
     return promise;
   }
 
   expectErrorValue(expectedName, ex, m) {
     if (!(ex instanceof Error)) {
-      this.fail('THREW NON-ERROR');
+      this.fail('THREW non-error value, of type ' + typeof ex);
       return;
     }
 
     const actualName = ex.name;
 
     if (actualName !== expectedName) {
-      this.fail(`THREW ${actualName} INSTEAD OF ${expectedName}${m}`);
+      this.fail(`THREW ${actualName}, instead of ${expectedName}${m}`);
     } else {
       this.debug(`OK: threw ${actualName}${m}`);
     }
   }
 
   shouldReject(expectedName, p, msg) {
     this.eventualAsyncExpectation(async () => {
       const m = msg ? ': ' + msg : '';
--- a/testing/web-platform/tests/webgpu/framework/logger.js
+++ b/testing/web-platform/tests/webgpu/framework/logger.js
@@ -1,14 +1,15 @@
 /**
 * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
 **/
 
 function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
 
+import { SkipTestCase } from './fixture.js';
 import { makeQueryString } from './url_query.js';
 import { extractPublicParams } from './url_query.js';
 import { getStackTrace, now } from './util/index.js';
 import { version } from './version.js';
 export class Logger {
   constructor() {
     _defineProperty(this, "results", []);
   }
@@ -44,50 +45,56 @@ export class TestSpecRecorder {
       status: 'running',
       timems: -1
     };
     this.result.cases.push(result);
     return [new TestCaseRecorder(result), result];
   }
 
 }
+var PassState;
+
+(function (PassState) {
+  PassState[PassState["pass"] = 0] = "pass";
+  PassState[PassState["skip"] = 1] = "skip";
+  PassState[PassState["warn"] = 2] = "warn";
+  PassState[PassState["fail"] = 3] = "fail";
+})(PassState || (PassState = {}));
+
 export class TestCaseRecorder {
   constructor(result) {
     _defineProperty(this, "result", void 0);
 
-    _defineProperty(this, "failed", false);
-
-    _defineProperty(this, "warned", false);
+    _defineProperty(this, "state", PassState.pass);
 
     _defineProperty(this, "startTime", -1);
 
     _defineProperty(this, "logs", []);
 
     _defineProperty(this, "debugging", false);
 
     this.result = result;
   }
 
   start(debug = false) {
     this.startTime = now();
     this.logs = [];
-    this.failed = false;
-    this.warned = false;
+    this.state = PassState.pass;
     this.debugging = debug;
   }
 
   finish() {
     if (this.startTime < 0) {
       throw new Error('finish() before start()');
     }
 
     const endTime = now(); // Round to next microsecond to avoid storing useless .xxxx00000000000002 in results.
 
     this.result.timems = Math.ceil((endTime - this.startTime) * 1000) / 1000;
-    this.result.status = this.failed ? 'fail' : this.warned ? 'warn' : 'pass';
+    this.result.status = PassState[this.state];
     this.result.logs = this.logs;
     this.debugging = false;
   }
 
   debug(msg) {
     if (!this.debugging) {
       return;
     }
@@ -95,38 +102,53 @@ export class TestCaseRecorder {
     this.log('DEBUG: ' + msg);
   }
 
   log(msg) {
     this.logs.push(msg);
   }
 
   warn(msg) {
-    this.warned = true;
+    this.setState(PassState.warn);
     let m = 'WARN';
 
     if (msg) {
       m += ': ' + msg;
     }
 
     m += ' ' + getStackTrace(new Error());
     this.log(m);
   }
 
   fail(msg) {
-    this.failed = true;
+    this.setState(PassState.fail);
     let m = 'FAIL';
 
     if (msg) {
       m += ': ' + msg;
     }
 
-    m += ' ' + getStackTrace(new Error());
+    m += '\n' + getStackTrace(new Error());
+    this.log(m);
+  }
+
+  skipped(ex) {
+    this.setState(PassState.skip);
+    const m = 'SKIPPED: ' + getStackTrace(ex);
     this.log(m);
   }
 
-  threw(e) {
-    this.failed = true;
-    this.log('EXCEPTION: ' + e.name + ': ' + e.message + '\n' + getStackTrace(e));
+  threw(ex) {
+    if (ex instanceof SkipTestCase) {
+      this.skipped(ex);
+      return;
+    }
+
+    this.setState(PassState.fail);
+    this.log('EXCEPTION: ' + ex.name + ':\n' + ex.message + '\n' + getStackTrace(ex));
+  }
+
+  setState(state) {
+    this.state = Math.max(this.state, state);
   }
 
 }
 //# sourceMappingURL=logger.js.map
\ No newline at end of file
--- a/testing/web-platform/tests/webgpu/framework/test_group.js
+++ b/testing/web-platform/tests/webgpu/framework/test_group.js
@@ -131,30 +131,28 @@ class RunCaseSpecific {
   }
 
   async run(debug) {
     const [rec, res] = this.recorder.record(this.id.test, this.id.params);
     rec.start(debug);
 
     try {
       const inst = new this.fixture(rec, this.params || {});
-      await inst.init();
 
       try {
+        await inst.init();
         await this.fn(inst);
-      } catch (ex) {
-        // There was an exception from the test itself.
-        rec.threw(ex);
-      } // Runs as long as constructor and init succeeded, even if the test rejected.
-
-
-      await inst.finalize();
+      } finally {
+        // Runs as long as constructor succeeded, even if initialization or the test failed.
+        await inst.finalize();
+      }
     } catch (ex) {
-      // There was an exception from constructor, init, or finalize.
-      // (An error from finalize may have been an eventualAsyncExpectation failure.)
+      // There was an exception from constructor, init, test, or finalize.
+      // An error from init or test may have been a SkipTestCase.
+      // An error from finalize may have been an eventualAsyncExpectation failure.
       rec.threw(ex);
     }
 
     rec.finish();
     return res;
   }
 
   injectResult(result) {
--- a/testing/web-platform/tests/webgpu/framework/util/stack.js
+++ b/testing/web-platform/tests/webgpu/framework/util/stack.js
@@ -19,20 +19,19 @@ export function getStackTrace(e) {
     const part = parts[i].trim();
     const isSuites = suitesRegex.test(part);
 
     if (found && !isSuites) {
       break;
     }
 
     if (isSuites) {
+      stack.push(part);
       found = true;
     }
-
-    stack.push(part);
   }
 
   return stack.join('\n');
 } // *** Examples ***
 //
 // Node fail()
 // > Error:
 // >    at CaseRecorder.fail (/Users/kainino/src/cts-experiment/src/framework/logger.ts:99:30)
--- a/testing/web-platform/tests/webgpu/framework/version.js
+++ b/testing/web-platform/tests/webgpu/framework/version.js
@@ -1,3 +1,3 @@
 // AUTO-GENERATED - DO NOT EDIT. See tools/gen_version.
 
-export const version = 'ba0a130a078256d45504069de88f0d91d4631578';
+export const version = '3dc37c83a70667e9a92df773f34d154ec600d203';
--- a/testing/web-platform/tests/webgpu/runtime/wpt.js
+++ b/testing/web-platform/tests/webgpu/runtime/wpt.js
@@ -34,16 +34,17 @@ import { TestWorker } from './helper/tes
           if (worker) {
             r = await worker.run(name);
             t.injectResult(r);
           } else {
             r = await t.run();
           }
 
           this.step(() => {
+            // Unfortunately, it seems not possible to surface any logs for warn/skip.
             if (r.status === 'fail') {
               throw (r.logs || []).join('\n');
             }
           });
           this.done();
         });
         running.push(p);
         return p;
--- a/testing/web-platform/tests/webgpu/suites/cts/canvas/context_creation.spec.js
+++ b/testing/web-platform/tests/webgpu/suites/cts/canvas/context_creation.spec.js
@@ -1,22 +1,19 @@
 /**
 * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
 **/
 
 export const description = ``;
-import { TestGroup } from '../../../framework/index.js';
-import { GPUTest } from '../gpu_test.js'; // TODO: doesn't need to use GPUTest
-
-export const g = new TestGroup(GPUTest);
-g.test('getContext returns GPUCanvasContext', async t => {
+import { Fixture, TestGroup } from '../../../framework/index.js';
+export const g = new TestGroup(Fixture);
+g.test('canvas element getContext returns GPUCanvasContext', async t => {
   if (typeof document === 'undefined') {
     // Skip if there is no document (Workers, Node)
-    // TODO: Use t.skip()
-    return;
+    t.skip('DOM is not available to create canvas element');
   }
 
   const canvas = document.createElement('canvas');
   canvas.width = 10;
   canvas.height = 10; // TODO: fix types so these aren't necessary
 
   const ctx = canvas.getContext('gpupresent');
   t.expect(ctx instanceof window.GPUCanvasContext);
--- a/testing/web-platform/tests/webgpu/suites/cts/command_buffer/compute/basic.spec.js
+++ b/testing/web-platform/tests/webgpu/suites/cts/command_buffer/compute/basic.spec.js
@@ -43,17 +43,17 @@ g.test('memcpy', async t => {
       resource: {
         buffer: dst,
         offset: 0,
         size: 4
       }
     }],
     layout: bgl
   });
-  const module = t.device.createShaderModule({
+  const module = t.createShaderModule({
     code:
     /* GLSL(
      *       'compute',
      *       `#version 310 es
      *         layout(std140, set = 0, binding = 0) buffer Src {
      *           int value;
      *         } src;
      *         layout(std140, set = 0, binding = 1) buffer Dst {
--- a/testing/web-platform/tests/webgpu/suites/cts/command_buffer/render/rendering.spec.js
+++ b/testing/web-platform/tests/webgpu/suites/cts/command_buffer/render/rendering.spec.js
@@ -16,32 +16,32 @@ g.test('fullscreen quad', async t => {
     size: {
       width: 1,
       height: 1,
       depth: 1
     },
     usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT
   });
   const colorAttachmentView = colorAttachment.createView();
-  const vertexModule = t.device.createShaderModule({
+  const vertexModule = t.createShaderModule({
     code:
     /* GLSL(
      *       'vertex',
      *       `#version 310 es
      *         void main() {
      *           const vec2 pos[3] = vec2[3](
      *               vec2(-1.f, -3.f), vec2(3.f, 1.f), vec2(-1.f, 1.f));
      *           gl_Position = vec4(pos[gl_VertexIndex], 0.f, 1.f);
      *         }
      *       `
      *     )
      */
     new Uint32Array([119734787, 66304, 524295, 39, 0, 131089, 1, 393227, 1, 1280527431, 1685353262, 808793134, 0, 196622, 0, 1, 458767, 0, 4, 1852399981, 0, 10, 26, 196611, 1, 310, 262149, 4, 1852399981, 0, 393221, 8, 1348430951, 1700164197, 2019914866, 0, 393222, 8, 0, 1348430951, 1953067887, 7237481, 458758, 8, 1, 1348430951, 1953393007, 1702521171, 0, 196613, 10, 0, 393221, 26, 1449094247, 1702130277, 1684949368, 30821, 327685, 29, 1701080681, 1818386808, 101, 327752, 8, 0, 11, 0, 327752, 8, 1, 11, 1, 196679, 8, 2, 262215, 26, 11, 42, 131091, 2, 196641, 3, 2, 196630, 6, 32, 262167, 7, 6, 4, 262174, 8, 7, 6, 262176, 9, 3, 8, 262203, 9, 10, 3, 262165, 11, 32, 1, 262187, 11, 12, 0, 262167, 13, 6, 2, 262165, 14, 32, 0, 262187, 14, 15, 3, 262172, 16, 13, 15, 262187, 6, 17, 3212836864, 262187, 6, 18, 3225419776, 327724, 13, 19, 17, 18, 262187, 6, 20, 1077936128, 262187, 6, 21, 1065353216, 327724, 13, 22, 20, 21, 327724, 13, 23, 17, 21, 393260, 16, 24, 19, 22, 23, 262176, 25, 1, 11, 262203, 25, 26, 1, 262176, 28, 7, 16, 262176, 30, 7, 13, 262187, 6, 33, 0, 262176, 37, 3, 7, 327734, 2, 4, 0, 3, 131320, 5, 262203, 28, 29, 7, 262205, 11, 27, 26, 196670, 29, 24, 327745, 30, 31, 29, 27, 262205, 13, 32, 31, 327761, 6, 34, 32, 0, 327761, 6, 35, 32, 1, 458832, 7, 36, 34, 35, 33, 21, 327745, 37, 38, 10, 12, 196670, 38, 36, 65789, 65592])
   });
-  const fragmentModule = t.device.createShaderModule({
+  const fragmentModule = t.createShaderModule({
     code:
     /* GLSL(
      *       'fragment',
      *       `#version 310 es
      *         precision mediump float;
      *         layout(location = 0) out vec4 fragColor;
      *         void main() {
      *           fragColor = vec4(0.0, 1.0, 0.0, 1.0);
--- a/testing/web-platform/tests/webgpu/suites/cts/command_buffer/render/storeop.spec.js
+++ b/testing/web-platform/tests/webgpu/suites/cts/command_buffer/render/storeop.spec.js
@@ -13,17 +13,17 @@ g.test('storeOp controls whether 1x1 dra
       width: 1,
       height: 1,
       depth: 1
     },
     format: 'r8unorm',
     usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT
   }); // create render pipeline
 
-  const vertexModule = t.device.createShaderModule({
+  const vertexModule = t.createShaderModule({
     code:
     /* GLSL(
      *       'vertex',
      *       `#version 450
      *       const vec2 pos[3] = vec2[3](
      *                               vec2( 1.0f, -1.0f),
      *                               vec2( 1.0f,  1.0f),
      *                               vec2(-1.0f,  1.0f)
@@ -31,17 +31,17 @@ g.test('storeOp controls whether 1x1 dra
      *
      *       void main() {
      *           gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
      *       }`
      *     )
      */
     new Uint32Array([119734787, 66304, 524295, 39, 0, 131089, 1, 393227, 1, 1280527431, 1685353262, 808793134, 0, 196622, 0, 1, 458767, 0, 4, 1852399981, 0, 13, 26, 196611, 2, 450, 262149, 4, 1852399981, 0, 393221, 11, 1348430951, 1700164197, 2019914866, 0, 393222, 11, 0, 1348430951, 1953067887, 7237481, 458758, 11, 1, 1348430951, 1953393007, 1702521171, 0, 458758, 11, 2, 1130327143, 1148217708, 1635021673, 6644590, 458758, 11, 3, 1130327143, 1147956341, 1635021673, 6644590, 196613, 13, 0, 393221, 26, 1449094247, 1702130277, 1684949368, 30821, 327685, 29, 1701080681, 1818386808, 101, 327752, 11, 0, 11, 0, 327752, 11, 1, 11, 1, 327752, 11, 2, 11, 3, 327752, 11, 3, 11, 4, 196679, 11, 2, 262215, 26, 11, 42, 131091, 2, 196641, 3, 2, 196630, 6, 32, 262167, 7, 6, 4, 262165, 8, 32, 0, 262187, 8, 9, 1, 262172, 10, 6, 9, 393246, 11, 7, 6, 10, 10, 262176, 12, 3, 11, 262203, 12, 13, 3, 262165, 14, 32, 1, 262187, 14, 15, 0, 262167, 16, 6, 2, 262187, 8, 17, 3, 262172, 18, 16, 17, 262187, 6, 19, 1065353216, 262187, 6, 20, 3212836864, 327724, 16, 21, 19, 20, 327724, 16, 22, 19, 19, 327724, 16, 23, 20, 19, 393260, 18, 24, 21, 22, 23, 262176, 25, 1, 14, 262203, 25, 26, 1, 262176, 28, 7, 18, 262176, 30, 7, 16, 262187, 6, 33, 0, 262176, 37, 3, 7, 327734, 2, 4, 0, 3, 131320, 5, 262203, 28, 29, 7, 262205, 14, 27, 26, 196670, 29, 24, 327745, 30, 31, 29, 27, 262205, 16, 32, 31, 327761, 6, 34, 32, 0, 327761, 6, 35, 32, 1, 458832, 7, 36, 34, 35, 33, 19, 327745, 37, 38, 13, 15, 196670, 38, 36, 65789, 65592])
   });
-  const fragmentModule = t.device.createShaderModule({
+  const fragmentModule = t.createShaderModule({
     code:
     /* GLSL(
      *       'fragment',
      *       `#version 450
      *       layout(location = 0) out vec4 fragColor;
      *       void main() {
      *           fragColor = vec4(1.0, 0.0, 0.0, 1.0);
      *       }`
--- a/testing/web-platform/tests/webgpu/suites/cts/gpu_test.js
+++ b/testing/web-platform/tests/webgpu/suites/cts/gpu_test.js
@@ -1,73 +1,106 @@
 /**
 * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
 **/
 
 function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
 
 import { getGPU } from '../../framework/gpu/implementation.js';
 import { Fixture } from '../../framework/index.js';
-let glslangInstance; // TODO: Should this gain some functionality currently only in UnitTest?
-
+let glslangInstance;
 export class GPUTest extends Fixture {
   constructor(...args) {
     super(...args);
 
     _defineProperty(this, "device", undefined);
 
     _defineProperty(this, "queue", undefined);
+
+    _defineProperty(this, "initialized", false);
+
+    _defineProperty(this, "supportsSPIRV", true);
   }
 
   async init() {
     super.init();
     const gpu = getGPU();
     const adapter = await gpu.requestAdapter();
     this.device = await adapter.requestDevice();
     this.queue = this.device.getQueue();
+    const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
+
+    if (isSafari) {
+      this.supportsSPIRV = false;
+    }
+
+    try {
+      await this.device.popErrorScope();
+      throw new Error('There was an error scope on the stack at the beginning of the test');
+    } catch (ex) {}
+
     this.device.pushErrorScope('out-of-memory');
     this.device.pushErrorScope('validation');
+    this.initialized = true;
   }
 
   async finalize() {
     super.finalize();
-    const gpuValidationError = await this.device.popErrorScope();
+
+    if (this.initialized) {
+      const gpuValidationError = await this.device.popErrorScope();
 
-    if (gpuValidationError !== null) {
-      if (!(gpuValidationError instanceof GPUValidationError)) throw new Error();
-      this.fail(`Unexpected validation error occurred: ${gpuValidationError.message}`);
-    }
+      if (gpuValidationError !== null) {
+        if (!(gpuValidationError instanceof GPUValidationError)) throw new Error();
+        this.fail(`Unexpected validation error occurred: ${gpuValidationError.message}`);
+      }
 
-    const gpuOutOfMemoryError = await this.device.popErrorScope();
+      const gpuOutOfMemoryError = await this.device.popErrorScope();
 
-    if (gpuOutOfMemoryError !== null) {
-      if (!(gpuOutOfMemoryError instanceof GPUOutOfMemoryError)) throw new Error();
-      this.fail('Unexpected out-of-memory error occurred');
+      if (gpuOutOfMemoryError !== null) {
+        if (!(gpuOutOfMemoryError instanceof GPUOutOfMemoryError)) throw new Error();
+        this.fail('Unexpected out-of-memory error occurred');
+      }
     }
   }
 
   async initGLSL() {
     if (!glslangInstance) {
       const glslangPath = '../../glslang.js';
-      const glslangModule = (await import(glslangPath)).default;
+      let glslangModule;
+
+      try {
+        glslangModule = (await import(glslangPath)).default;
+      } catch (ex) {
+        this.skip('glslang is not available');
+      }
+
       await new Promise(resolve => {
         glslangModule().then(glslang => {
           glslangInstance = glslang;
           resolve();
         });
       });
     }
   }
 
-  makeShaderModule(stage, source) {
-    if (!glslangInstance) {
-      throw new Error('GLSL is not instantiated. Run `await t.initGLSL()` first');
+  createShaderModule(desc) {
+    if (!this.supportsSPIRV) {
+      this.skip('SPIR-V not available');
     }
 
-    const code = glslangInstance.compileGLSL(source, stage, false);
+    return this.device.createShaderModule(desc);
+  }
+
+  makeShaderModuleFromGLSL(stage, glsl) {
+    if (!glslangInstance) {
+      throw new Error('GLSL compiler is not instantiated. Run `await t.initGLSL()` first');
+    }
+
+    const code = glslangInstance.compileGLSL(glsl, stage, false);
     return this.device.createShaderModule({
       code
     });
   } // TODO: add an expectContents for textures, which logs data: uris on failure
 
 
   expectContents(src, expected) {
     const exp = new Uint8Array(expected.buffer, expected.byteOffset, expected.byteLength);
--- a/testing/web-platform/tests/webgpu/suites/cts/validation/createRenderPipeline.spec.js
+++ b/testing/web-platform/tests/webgpu/suites/cts/validation/createRenderPipeline.spec.js
@@ -33,17 +33,17 @@ class F extends ValidationTest {
       colorStates,
       sampleCount,
       depthStencilState
     };
   }
 
   getVertexStage() {
     return {
-      module: this.device.createShaderModule({
+      module: this.createShaderModule({
         code:
         /* GLSL(
          *           'vertex',
          *           `#version 450
          *             void main() {
          *               gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
          *             }
          *           `
@@ -69,17 +69,17 @@ class F extends ValidationTest {
     const code = `
       #version 450
       layout(location = 0) out ${fragColorType} fragColor;
       void main() {
         fragColor = ${fragColorType}(0.0, 1.0, 0.0, 1.0);
       }
     `;
     return {
-      module: this.makeShaderModule('fragment', code),
+      module: this.makeShaderModuleFromGLSL('fragment', code),
       entryPoint: 'main'
     };
   }
 
   getPipelineLayout() {
     return this.device.createPipelineLayout({
       bindGroupLayouts: []
     });
--- a/testing/web-platform/tests/webgpu/suites/cts/validation/render_pass.spec.js
+++ b/testing/web-platform/tests/webgpu/suites/cts/validation/render_pass.spec.js
@@ -12,34 +12,34 @@ class F extends ValidationTest {
   getUniformBuffer() {
     return this.device.createBuffer({
       size: 4 * Float32Array.BYTES_PER_ELEMENT,
       usage: GPUBufferUsage.UNIFORM
     });
   }
 
   createRenderPipeline(pipelineLayout) {
-    const vertexModule = this.device.createShaderModule({
+    const vertexModule = this.createShaderModule({
       code:
       /* GLSL(
        *         'vertex',
        *         `#version 450
        *           layout (set = 0, binding = 0) uniform vertexUniformBuffer {
        *               mat2 transform;
        *           };
        *           void main() {
        *               const vec2 pos[3] = vec2[3](vec2(-1.f, -1.f), vec2(1.f, -1.f), vec2(-1.f, 1.f));
        *               gl_Position = vec4(transform * pos[gl_VertexIndex], 0.f, 1.f);
        *           }
        *         `
        *       )
        */
       new Uint32Array([119734787, 66304, 524295, 47, 0, 131089, 1, 393227, 1, 1280527431, 1685353262, 808793134, 0, 196622, 0, 1, 458767, 0, 4, 1852399981, 0, 13, 33, 196611, 2, 450, 262149, 4, 1852399981, 0, 393221, 11, 1348430951, 1700164197, 2019914866, 0, 393222, 11, 0, 1348430951, 1953067887, 7237481, 458758, 11, 1, 1348430951, 1953393007, 1702521171, 0, 458758, 11, 2, 1130327143, 1148217708, 1635021673, 6644590, 458758, 11, 3, 1130327143, 1147956341, 1635021673, 6644590, 196613, 13, 0, 458757, 18, 1953654134, 1851095141, 1919903337, 1718960749, 7497062, 393222, 18, 0, 1851880052, 1919903347, 109, 196613, 20, 0, 393221, 33, 1449094247, 1702130277, 1684949368, 30821, 327685, 36, 1701080681, 1818386808, 101, 327752, 11, 0, 11, 0, 327752, 11, 1, 11, 1, 327752, 11, 2, 11, 3, 327752, 11, 3, 11, 4, 196679, 11, 2, 262216, 18, 0, 5, 327752, 18, 0, 35, 0, 327752, 18, 0, 7, 16, 196679, 18, 2, 262215, 20, 34, 0, 262215, 20, 33, 0, 262215, 33, 11, 42, 131091, 2, 196641, 3, 2, 196630, 6, 32, 262167, 7, 6, 4, 262165, 8, 32, 0, 262187, 8, 9, 1, 262172, 10, 6, 9, 393246, 11, 7, 6, 10, 10, 262176, 12, 3, 11, 262203, 12, 13, 3, 262165, 14, 32, 1, 262187, 14, 15, 0, 262167, 16, 6, 2, 262168, 17, 16, 2, 196638, 18, 17, 262176, 19, 2, 18, 262203, 19, 20, 2, 262176, 21, 2, 17, 262187, 8, 24, 3, 262172, 25, 16, 24, 262187, 6, 26, 3212836864, 327724, 16, 27, 26, 26, 262187, 6, 28, 1065353216, 327724, 16, 29, 28, 26, 327724, 16, 30, 26, 28, 393260, 25, 31, 27, 29, 30, 262176, 32, 1, 14, 262203, 32, 33, 1, 262176, 35, 7, 25, 262176, 37, 7, 16, 262187, 6, 41, 0, 262176, 45, 3, 7, 327734, 2, 4, 0, 3, 131320, 5, 262203, 35, 36, 7, 327745, 21, 22, 20, 15, 262205, 17, 23, 22, 262205, 14, 34, 33, 196670, 36, 31, 327745, 37, 38, 36, 34, 262205, 16, 39, 38, 327825, 16, 40, 23, 39, 327761, 6, 42, 40, 0, 327761, 6, 43, 40, 1, 458832, 7, 44, 42, 43, 41, 28, 327745, 45, 46, 13, 15, 196670, 46, 44, 65789, 65592])
     });
-    const fragmentModule = this.device.createShaderModule({
+    const fragmentModule = this.createShaderModule({
       code:
       /* GLSL(
        *         'fragment',
        *         `#version 450
        *           layout (set = 1, binding = 0) uniform fragmentUniformBuffer {
        *             vec4 color;
        *           };
        *           layout(location = 0) out vec4 fragColor;
--- a/testing/web-platform/tests/webgpu/suites/cts/validation/setVertexBuffer.spec.js
+++ b/testing/web-platform/tests/webgpu/suites/cts/validation/setVertexBuffer.spec.js
@@ -46,31 +46,31 @@ class F extends ValidationTest {
     const code = `
       #version 450
       ${range(bufferCount, i => `\nlayout(location = ${i}) in vec3 a_position${i};`).join('')}
       void main() {
         gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
        }
     `;
     return {
-      module: this.makeShaderModule('vertex', code),
+      module: this.makeShaderModuleFromGLSL('vertex', code),
       entryPoint: 'main'
     };
   }
 
   getFragmentStage() {
     const code = `
       #version 450
       layout(location = 0) out vec4 fragColor;
       void main() {
         fragColor = vec4(0.0, 1.0, 0.0, 1.0);
       }
     `;
     return {
-      module: this.makeShaderModule('fragment', code),
+      module: this.makeShaderModuleFromGLSL('fragment', code),
       entryPoint: 'main'
     };
   }
 
   getPipelineLayout() {
     return this.device.createPipelineLayout({
       bindGroupLayouts: []
     });
--- a/testing/web-platform/tests/webgpu/suites/cts/validation/vertex_input.spec.js
+++ b/testing/web-platform/tests/webgpu/suites/cts/validation/vertex_input.spec.js
@@ -38,31 +38,31 @@ class F extends ValidationTest {
       }],
       vertexInput
     };
     return descriptor;
   }
 
   getVertexStage(code) {
     return {
-      module: this.makeShaderModule('vertex', code),
+      module: this.makeShaderModuleFromGLSL('vertex', code),
       entryPoint: 'main'
     };
   }
 
   getFragmentStage() {
     const code = `
       #version 450
       layout(location = 0) out vec4 fragColor;
       void main() {
         fragColor = vec4(0.0, 1.0, 0.0, 1.0);
       }
     `;
     return {
-      module: this.makeShaderModule('fragment', code),
+      module: this.makeShaderModuleFromGLSL('fragment', code),
       entryPoint: 'main'
     };
   }
 
   getPipelineLayout() {
     return this.device.createPipelineLayout({
       bindGroupLayouts: []
     });