xref: /aosp_15_r20/external/skia/tools/run-wasm-gm-tests/run-wasm-gm-tests.html (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker<!-- This runs the GMs and unit tests which have been compiled to WASM. When this completes,
2*c8dee2aaSAndroid Build Coastguard Workereither window._error will be set or window._testsDone will be true and window._results will be an
3*c8dee2aaSAndroid Build Coastguard Workerarray of the test names and what they drew.
4*c8dee2aaSAndroid Build Coastguard Worker-->
5*c8dee2aaSAndroid Build Coastguard Worker<!DOCTYPE html>
6*c8dee2aaSAndroid Build Coastguard Worker<html>
7*c8dee2aaSAndroid Build Coastguard Worker<head>
8*c8dee2aaSAndroid Build Coastguard Worker  <title>WASM Runner of GMs and Unit Tests</title>
9*c8dee2aaSAndroid Build Coastguard Worker  <meta charset="utf-8" />
10*c8dee2aaSAndroid Build Coastguard Worker  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
11*c8dee2aaSAndroid Build Coastguard Worker  <meta name="viewport" content="width=device-width, initial-scale=1.0">
12*c8dee2aaSAndroid Build Coastguard Worker  <script src="/static/wasm_gm_tests.js" type="text/javascript" charset="utf-8"></script>
13*c8dee2aaSAndroid Build Coastguard Worker  <style type="text/css" media="screen">
14*c8dee2aaSAndroid Build Coastguard Worker    #status_text {
15*c8dee2aaSAndroid Build Coastguard Worker      min-width: 900px;
16*c8dee2aaSAndroid Build Coastguard Worker      min-height: 500px;
17*c8dee2aaSAndroid Build Coastguard Worker    }
18*c8dee2aaSAndroid Build Coastguard Worker  </style>
19*c8dee2aaSAndroid Build Coastguard Worker</head>
20*c8dee2aaSAndroid Build Coastguard Worker<body>
21*c8dee2aaSAndroid Build Coastguard Worker<main>
22*c8dee2aaSAndroid Build Coastguard Worker  <button id=start_tests>Start Tests</button>
23*c8dee2aaSAndroid Build Coastguard Worker  <br>
24*c8dee2aaSAndroid Build Coastguard Worker  <pre id=status_text></pre>
25*c8dee2aaSAndroid Build Coastguard Worker
26*c8dee2aaSAndroid Build Coastguard Worker  <canvas id=gm_canvas></canvas>
27*c8dee2aaSAndroid Build Coastguard Worker</main>
28*c8dee2aaSAndroid Build Coastguard Worker<script type="text/javascript" charset="utf-8">
29*c8dee2aaSAndroid Build Coastguard Worker  const loadTestsPromise = InitWasmGMTests({
30*c8dee2aaSAndroid Build Coastguard Worker    locateFile: (file) => '/static/'+file,
31*c8dee2aaSAndroid Build Coastguard Worker  });
32*c8dee2aaSAndroid Build Coastguard Worker
33*c8dee2aaSAndroid Build Coastguard Worker  const loadKnownHashesPromise = fetch('/static/hashes.txt').then((resp) => resp.text());
34*c8dee2aaSAndroid Build Coastguard Worker
35*c8dee2aaSAndroid Build Coastguard Worker  const resourceNames = [];
36*c8dee2aaSAndroid Build Coastguard Worker  const loadResourceListing = fetch('/static/resource_listing.json').then((resp) => resp.json())
37*c8dee2aaSAndroid Build Coastguard Worker    .then((json) => {
38*c8dee2aaSAndroid Build Coastguard Worker    console.debug('should fetch resources', json);
39*c8dee2aaSAndroid Build Coastguard Worker    const loadingPromises = [];
40*c8dee2aaSAndroid Build Coastguard Worker    for (const resource of json) {
41*c8dee2aaSAndroid Build Coastguard Worker      resourceNames.push(resource);
42*c8dee2aaSAndroid Build Coastguard Worker      const url = `/static/resources/${resource}`;
43*c8dee2aaSAndroid Build Coastguard Worker      loadingPromises.push(fetch(url).then((resp) => resp.arrayBuffer()));
44*c8dee2aaSAndroid Build Coastguard Worker    }
45*c8dee2aaSAndroid Build Coastguard Worker    return Promise.all(loadingPromises).catch((e) => {
46*c8dee2aaSAndroid Build Coastguard Worker      console.error(e);
47*c8dee2aaSAndroid Build Coastguard Worker      window._error = `Failed getting resources: ${JSON.stringify(e)}`;
48*c8dee2aaSAndroid Build Coastguard Worker    });
49*c8dee2aaSAndroid Build Coastguard Worker  });
50*c8dee2aaSAndroid Build Coastguard Worker
51*c8dee2aaSAndroid Build Coastguard Worker  let attemptedPOSTs = 0;
52*c8dee2aaSAndroid Build Coastguard Worker  let successfulPOSTs = 0;
53*c8dee2aaSAndroid Build Coastguard Worker  Promise.all([loadTestsPromise, loadKnownHashesPromise, loadResourceListing]).then(([GM, hashes, resourceBuffers]) => {
54*c8dee2aaSAndroid Build Coastguard Worker    GM.Init();
55*c8dee2aaSAndroid Build Coastguard Worker    LoadResources(GM, resourceNames, resourceBuffers);
56*c8dee2aaSAndroid Build Coastguard Worker    LoadKnownHashes(GM, hashes);
57*c8dee2aaSAndroid Build Coastguard Worker    document.getElementById('start_tests').addEventListener('click', async () => {
58*c8dee2aaSAndroid Build Coastguard Worker      window._testsProgress = 0;
59*c8dee2aaSAndroid Build Coastguard Worker      window._log = 'Starting\n';
60*c8dee2aaSAndroid Build Coastguard Worker      window._failed = [];
61*c8dee2aaSAndroid Build Coastguard Worker      await RunTests(GM);
62*c8dee2aaSAndroid Build Coastguard Worker      if (window._error) {
63*c8dee2aaSAndroid Build Coastguard Worker        console.log(window._error);
64*c8dee2aaSAndroid Build Coastguard Worker        return;
65*c8dee2aaSAndroid Build Coastguard Worker      }
66*c8dee2aaSAndroid Build Coastguard Worker      await RunGMs(GM);
67*c8dee2aaSAndroid Build Coastguard Worker      if (attemptedPOSTs !== successfulPOSTs) {
68*c8dee2aaSAndroid Build Coastguard Worker        window._error = `Failed to POST all the PNG files (expected ${attemptedPOSTs}, finished ${successfulPOSTs})`;
69*c8dee2aaSAndroid Build Coastguard Worker      } else {
70*c8dee2aaSAndroid Build Coastguard Worker        window._testsDone = true;
71*c8dee2aaSAndroid Build Coastguard Worker      }
72*c8dee2aaSAndroid Build Coastguard Worker    });
73*c8dee2aaSAndroid Build Coastguard Worker    window._testsReady = true;
74*c8dee2aaSAndroid Build Coastguard Worker  });
75*c8dee2aaSAndroid Build Coastguard Worker
76*c8dee2aaSAndroid Build Coastguard Worker  const statusElement = document.getElementById('status_text');
77*c8dee2aaSAndroid Build Coastguard Worker  function log(line) {
78*c8dee2aaSAndroid Build Coastguard Worker    console.log(line);
79*c8dee2aaSAndroid Build Coastguard Worker    line += '\n';
80*c8dee2aaSAndroid Build Coastguard Worker    statusElement.innerText += line;
81*c8dee2aaSAndroid Build Coastguard Worker    window._log += line;
82*c8dee2aaSAndroid Build Coastguard Worker  }
83*c8dee2aaSAndroid Build Coastguard Worker
84*c8dee2aaSAndroid Build Coastguard Worker  function LoadResources(GM, resourceNames, resourceBuffers) {
85*c8dee2aaSAndroid Build Coastguard Worker    for (let i = 0; i < resourceNames.length; i++) {
86*c8dee2aaSAndroid Build Coastguard Worker      const name = resourceNames[i];
87*c8dee2aaSAndroid Build Coastguard Worker      const buffer = resourceBuffers[i];
88*c8dee2aaSAndroid Build Coastguard Worker      GM.LoadResource(name, buffer);
89*c8dee2aaSAndroid Build Coastguard Worker    }
90*c8dee2aaSAndroid Build Coastguard Worker  }
91*c8dee2aaSAndroid Build Coastguard Worker
92*c8dee2aaSAndroid Build Coastguard Worker  // There's a global set of known hashes that we preload with the md5 hashes that are already
93*c8dee2aaSAndroid Build Coastguard Worker  // uploaded to Gold. This saves us some time to encode them and write them to disk.
94*c8dee2aaSAndroid Build Coastguard Worker  function LoadKnownHashes(GM, hashes) {
95*c8dee2aaSAndroid Build Coastguard Worker    log(`Loading ${hashes.length} hashes`);
96*c8dee2aaSAndroid Build Coastguard Worker    console.time('load_hashes');
97*c8dee2aaSAndroid Build Coastguard Worker    for (const hash of hashes.split('\n')) {
98*c8dee2aaSAndroid Build Coastguard Worker      if (hash.length < 5) { // be sure not to add empty lines
99*c8dee2aaSAndroid Build Coastguard Worker        continue;
100*c8dee2aaSAndroid Build Coastguard Worker      }
101*c8dee2aaSAndroid Build Coastguard Worker      GM.LoadKnownDigest(hash);
102*c8dee2aaSAndroid Build Coastguard Worker    }
103*c8dee2aaSAndroid Build Coastguard Worker    console.timeEnd('load_hashes');
104*c8dee2aaSAndroid Build Coastguard Worker    log('hashes loaded');
105*c8dee2aaSAndroid Build Coastguard Worker  }
106*c8dee2aaSAndroid Build Coastguard Worker
107*c8dee2aaSAndroid Build Coastguard Worker  const gmSkipList = new Set([
108*c8dee2aaSAndroid Build Coastguard Worker    'exoticformats', // Uses SkFILEStream to load resource.
109*c8dee2aaSAndroid Build Coastguard Worker  ]);
110*c8dee2aaSAndroid Build Coastguard Worker
111*c8dee2aaSAndroid Build Coastguard Worker  async function RunGMs(GM) {
112*c8dee2aaSAndroid Build Coastguard Worker    const canvas = document.getElementById('gm_canvas');
113*c8dee2aaSAndroid Build Coastguard Worker    const ctx = GM.GetWebGLContext(canvas, 2);
114*c8dee2aaSAndroid Build Coastguard Worker    const grcontext = GM.MakeGrContext(ctx);
115*c8dee2aaSAndroid Build Coastguard Worker    if (!grcontext) {
116*c8dee2aaSAndroid Build Coastguard Worker      window._error = 'Could not make GrContext for gms';
117*c8dee2aaSAndroid Build Coastguard Worker      return;
118*c8dee2aaSAndroid Build Coastguard Worker    }
119*c8dee2aaSAndroid Build Coastguard Worker    window._results = [];
120*c8dee2aaSAndroid Build Coastguard Worker    // We run these tests in batches so as not to lock up the main thread, which makes it easier
121*c8dee2aaSAndroid Build Coastguard Worker    // to read the progress as well as making the page more responsive when debugging.
122*c8dee2aaSAndroid Build Coastguard Worker    await new Promise((resolve, reject) => {
123*c8dee2aaSAndroid Build Coastguard Worker      const names = GM.ListGMs();
124*c8dee2aaSAndroid Build Coastguard Worker      names.sort();
125*c8dee2aaSAndroid Build Coastguard Worker      // When debugging locally, it can be handy to skip to a certain GM by using
126*c8dee2aaSAndroid Build Coastguard Worker      // names.indexOf here instead of 0.
127*c8dee2aaSAndroid Build Coastguard Worker      let testIdx = 0;
128*c8dee2aaSAndroid Build Coastguard Worker      const nextBatch = async () => {
129*c8dee2aaSAndroid Build Coastguard Worker        for (let i = 0; i < 10 && testIdx < names.length; i++) {
130*c8dee2aaSAndroid Build Coastguard Worker          testIdx++;
131*c8dee2aaSAndroid Build Coastguard Worker          const name = names[testIdx];
132*c8dee2aaSAndroid Build Coastguard Worker          if (!name) {
133*c8dee2aaSAndroid Build Coastguard Worker            testIdx = names.length;
134*c8dee2aaSAndroid Build Coastguard Worker            break;
135*c8dee2aaSAndroid Build Coastguard Worker          }
136*c8dee2aaSAndroid Build Coastguard Worker          if (gmSkipList.has(name)) {
137*c8dee2aaSAndroid Build Coastguard Worker            continue;
138*c8dee2aaSAndroid Build Coastguard Worker          }
139*c8dee2aaSAndroid Build Coastguard Worker          log(`Starting GM ${name}`);
140*c8dee2aaSAndroid Build Coastguard Worker          const pngAndMetadata = GM.RunGM(grcontext, name);
141*c8dee2aaSAndroid Build Coastguard Worker          if (!pngAndMetadata || !pngAndMetadata.hash) {
142*c8dee2aaSAndroid Build Coastguard Worker            log('    No output (was skipped)');
143*c8dee2aaSAndroid Build Coastguard Worker            continue; // Was skipped
144*c8dee2aaSAndroid Build Coastguard Worker          }
145*c8dee2aaSAndroid Build Coastguard Worker          log(`    drew ${pngAndMetadata.hash}`);
146*c8dee2aaSAndroid Build Coastguard Worker          window._results.push({
147*c8dee2aaSAndroid Build Coastguard Worker            name: name,
148*c8dee2aaSAndroid Build Coastguard Worker            digest: pngAndMetadata.hash,
149*c8dee2aaSAndroid Build Coastguard Worker          });
150*c8dee2aaSAndroid Build Coastguard Worker          if (pngAndMetadata.png) {
151*c8dee2aaSAndroid Build Coastguard Worker            await postPNG(pngAndMetadata.hash, pngAndMetadata.png);
152*c8dee2aaSAndroid Build Coastguard Worker          }
153*c8dee2aaSAndroid Build Coastguard Worker          window._testsProgress++;
154*c8dee2aaSAndroid Build Coastguard Worker        }
155*c8dee2aaSAndroid Build Coastguard Worker        if (testIdx >= names.length) {
156*c8dee2aaSAndroid Build Coastguard Worker          resolve();
157*c8dee2aaSAndroid Build Coastguard Worker          return;
158*c8dee2aaSAndroid Build Coastguard Worker        }
159*c8dee2aaSAndroid Build Coastguard Worker        setTimeout(nextBatch);
160*c8dee2aaSAndroid Build Coastguard Worker      };
161*c8dee2aaSAndroid Build Coastguard Worker      setTimeout(nextBatch);
162*c8dee2aaSAndroid Build Coastguard Worker    });
163*c8dee2aaSAndroid Build Coastguard Worker    grcontext.delete();
164*c8dee2aaSAndroid Build Coastguard Worker  }
165*c8dee2aaSAndroid Build Coastguard Worker
166*c8dee2aaSAndroid Build Coastguard Worker  async function postPNG(hash, data) {
167*c8dee2aaSAndroid Build Coastguard Worker    attemptedPOSTs += 1;
168*c8dee2aaSAndroid Build Coastguard Worker    await fetch('/write_png', {
169*c8dee2aaSAndroid Build Coastguard Worker      method: 'POST',
170*c8dee2aaSAndroid Build Coastguard Worker      body: data,
171*c8dee2aaSAndroid Build Coastguard Worker      headers: {
172*c8dee2aaSAndroid Build Coastguard Worker        'Content-type': 'image/png',
173*c8dee2aaSAndroid Build Coastguard Worker        'X-MD5-Hash': hash, // this will be used server side to create the name of the png.
174*c8dee2aaSAndroid Build Coastguard Worker      }
175*c8dee2aaSAndroid Build Coastguard Worker    }).then((resp) => {
176*c8dee2aaSAndroid Build Coastguard Worker      if (resp.ok) {
177*c8dee2aaSAndroid Build Coastguard Worker        successfulPOSTs += 1;
178*c8dee2aaSAndroid Build Coastguard Worker      } else {
179*c8dee2aaSAndroid Build Coastguard Worker        console.error('not ok response', resp);
180*c8dee2aaSAndroid Build Coastguard Worker        log('not ok response ' + resp.toString());
181*c8dee2aaSAndroid Build Coastguard Worker      }
182*c8dee2aaSAndroid Build Coastguard Worker    }).catch((e) => {
183*c8dee2aaSAndroid Build Coastguard Worker      console.error('Could not post PNG', e);
184*c8dee2aaSAndroid Build Coastguard Worker      log('Could not post PNG ' + resp.toString());
185*c8dee2aaSAndroid Build Coastguard Worker    });
186*c8dee2aaSAndroid Build Coastguard Worker  }
187*c8dee2aaSAndroid Build Coastguard Worker
188*c8dee2aaSAndroid Build Coastguard Worker  const testSkipList = new Set([
189*c8dee2aaSAndroid Build Coastguard Worker    // These tests all crash https://bugs.chromium.org/p/skia/issues/detail?id=10869
190*c8dee2aaSAndroid Build Coastguard Worker
191*c8dee2aaSAndroid Build Coastguard Worker
192*c8dee2aaSAndroid Build Coastguard Worker    // note, to catch these crashes, you must compile a debug build,
193*c8dee2aaSAndroid Build Coastguard Worker    // run with --manual_mode and open the developer console,
194*c8dee2aaSAndroid Build Coastguard Worker    // and enable pause on exceptions in the sources tab, or the browser will just close
195*c8dee2aaSAndroid Build Coastguard Worker    // the instant this test crashes.
196*c8dee2aaSAndroid Build Coastguard Worker
197*c8dee2aaSAndroid Build Coastguard Worker    // These tests fail when doing a dlopen call
198*c8dee2aaSAndroid Build Coastguard Worker    // 'To use dlopen, you need to use Emscripten's linking support'
199*c8dee2aaSAndroid Build Coastguard Worker    // Some of these appear to hit the default case instead of the GLES case in GrContextFactory.cpp
200*c8dee2aaSAndroid Build Coastguard Worker    // which isn't expected to work. If they had a GLES context, they'd probably pass.
201*c8dee2aaSAndroid Build Coastguard Worker    'AsyncReadPixelsContextShutdown',
202*c8dee2aaSAndroid Build Coastguard Worker    'GrContextFactory_abandon',
203*c8dee2aaSAndroid Build Coastguard Worker    'GrContext_abandonContext',
204*c8dee2aaSAndroid Build Coastguard Worker    'GrContext_oomed',
205*c8dee2aaSAndroid Build Coastguard Worker    'GrDDLImage_MakeSubset',
206*c8dee2aaSAndroid Build Coastguard Worker    'InitialTextureClear',
207*c8dee2aaSAndroid Build Coastguard Worker    'PinnedImageTest',
208*c8dee2aaSAndroid Build Coastguard Worker    'PromiseImageTextureShutdown',
209*c8dee2aaSAndroid Build Coastguard Worker
210*c8dee2aaSAndroid Build Coastguard Worker    // These tests time out
211*c8dee2aaSAndroid Build Coastguard Worker    'SkTraceMemoryDump_ownedGLRenderTarget',
212*c8dee2aaSAndroid Build Coastguard Worker    'GrStyledShape',
213*c8dee2aaSAndroid Build Coastguard Worker
214*c8dee2aaSAndroid Build Coastguard Worker    // wasm doesn't have threading
215*c8dee2aaSAndroid Build Coastguard Worker    'GrContextFactory_executorAndTaskGroup',
216*c8dee2aaSAndroid Build Coastguard Worker    'GrContextFactory_sharedContexts',
217*c8dee2aaSAndroid Build Coastguard Worker    'RefCnt',
218*c8dee2aaSAndroid Build Coastguard Worker    'SkRuntimeEffectThreaded',
219*c8dee2aaSAndroid Build Coastguard Worker    'SkStrikeMultiThread',
220*c8dee2aaSAndroid Build Coastguard Worker    'String_Threaded',
221*c8dee2aaSAndroid Build Coastguard Worker
222*c8dee2aaSAndroid Build Coastguard Worker    // These tests are crashing for unknown reasons
223*c8dee2aaSAndroid Build Coastguard Worker    'AdvancedBlendTest',
224*c8dee2aaSAndroid Build Coastguard Worker    'Data',
225*c8dee2aaSAndroid Build Coastguard Worker    'ES2BlendWithNoTexture',
226*c8dee2aaSAndroid Build Coastguard Worker    'TextureBindingsResetTest',
227*c8dee2aaSAndroid Build Coastguard Worker
228*c8dee2aaSAndroid Build Coastguard Worker    // keys invalid
229*c8dee2aaSAndroid Build Coastguard Worker    'GrPathKeys',
230*c8dee2aaSAndroid Build Coastguard Worker
231*c8dee2aaSAndroid Build Coastguard Worker    // Creates only 35 of 36 expected fragment processor factories
232*c8dee2aaSAndroid Build Coastguard Worker    'ProcessorCloneTest',
233*c8dee2aaSAndroid Build Coastguard Worker    'ProcessorOptimizationValidationTest',
234*c8dee2aaSAndroid Build Coastguard Worker    'ProcessorRefTest',
235*c8dee2aaSAndroid Build Coastguard Worker    'Programs',
236*c8dee2aaSAndroid Build Coastguard Worker
237*c8dee2aaSAndroid Build Coastguard Worker    // Apparently fail only on release builds / bots
238*c8dee2aaSAndroid Build Coastguard Worker    'FlushFinishedProcTest',
239*c8dee2aaSAndroid Build Coastguard Worker    'WritePixelsNonTextureMSAA_Gpu',
240*c8dee2aaSAndroid Build Coastguard Worker
241*c8dee2aaSAndroid Build Coastguard Worker    // These SkSL tests fail on the Quadro P400s in the Golo
242*c8dee2aaSAndroid Build Coastguard Worker    'SkSLCommaSideEffects_Ganesh',
243*c8dee2aaSAndroid Build Coastguard Worker    'SkSLMatrixScalarNoOpFolding_Ganesh',
244*c8dee2aaSAndroid Build Coastguard Worker    'SkSLPreserveSideEffects_Ganesh',
245*c8dee2aaSAndroid Build Coastguard Worker    'SkSLStructFieldNoFolding_Ganesh',
246*c8dee2aaSAndroid Build Coastguard Worker
247*c8dee2aaSAndroid Build Coastguard Worker    // These tests use files on disk, which is not supported for WASM
248*c8dee2aaSAndroid Build Coastguard Worker    'Stream',
249*c8dee2aaSAndroid Build Coastguard Worker    'StreamBuffer',
250*c8dee2aaSAndroid Build Coastguard Worker    'StreamPeek',
251*c8dee2aaSAndroid Build Coastguard Worker    'FILEStreamWithOffset',
252*c8dee2aaSAndroid Build Coastguard Worker  ]);
253*c8dee2aaSAndroid Build Coastguard Worker
254*c8dee2aaSAndroid Build Coastguard Worker  async function RunTests(GM) {
255*c8dee2aaSAndroid Build Coastguard Worker    const canvas = document.getElementById('gm_canvas');
256*c8dee2aaSAndroid Build Coastguard Worker    const ctx = GM.GetWebGLContext(canvas, 2);
257*c8dee2aaSAndroid Build Coastguard Worker    // This sets up the GL context for all tests.
258*c8dee2aaSAndroid Build Coastguard Worker    const grcontext = GM.MakeGrContext(ctx);
259*c8dee2aaSAndroid Build Coastguard Worker    if (!grcontext) {
260*c8dee2aaSAndroid Build Coastguard Worker      window._error = 'Could not make GrContext for tests';
261*c8dee2aaSAndroid Build Coastguard Worker      return;
262*c8dee2aaSAndroid Build Coastguard Worker    }
263*c8dee2aaSAndroid Build Coastguard Worker    // We run these tests in batches so as not to lock up the main thread, which makes it easier
264*c8dee2aaSAndroid Build Coastguard Worker    // to read the progress as well as making the page more responsive when debugging.
265*c8dee2aaSAndroid Build Coastguard Worker    await new Promise((resolve, reject) => {
266*c8dee2aaSAndroid Build Coastguard Worker      const names = GM.ListTests();
267*c8dee2aaSAndroid Build Coastguard Worker      names.sort();
268*c8dee2aaSAndroid Build Coastguard Worker      console.log(names);
269*c8dee2aaSAndroid Build Coastguard Worker      // When debugging locally, it can be handy to skip to a certain test by using
270*c8dee2aaSAndroid Build Coastguard Worker      // names.indexOf here instead of 0.
271*c8dee2aaSAndroid Build Coastguard Worker      let testIdx = 0;
272*c8dee2aaSAndroid Build Coastguard Worker      const nextBatch = () => {
273*c8dee2aaSAndroid Build Coastguard Worker        for (let i = 0; i < 10 && testIdx < names.length; i++) {
274*c8dee2aaSAndroid Build Coastguard Worker          testIdx++;
275*c8dee2aaSAndroid Build Coastguard Worker          const name = names[testIdx];
276*c8dee2aaSAndroid Build Coastguard Worker          if (!name) {
277*c8dee2aaSAndroid Build Coastguard Worker            testIdx = names.length;
278*c8dee2aaSAndroid Build Coastguard Worker            break;
279*c8dee2aaSAndroid Build Coastguard Worker          }
280*c8dee2aaSAndroid Build Coastguard Worker          if (testSkipList.has(name)) {
281*c8dee2aaSAndroid Build Coastguard Worker            continue;
282*c8dee2aaSAndroid Build Coastguard Worker          }
283*c8dee2aaSAndroid Build Coastguard Worker          log(`Running test ${name}`);
284*c8dee2aaSAndroid Build Coastguard Worker          try {
285*c8dee2aaSAndroid Build Coastguard Worker            const result = GM.RunTest(name);
286*c8dee2aaSAndroid Build Coastguard Worker            log('    ' + result.result, result.msg || '');
287*c8dee2aaSAndroid Build Coastguard Worker            if (result.result !== 'passed' && result.result !== 'skipped') {
288*c8dee2aaSAndroid Build Coastguard Worker              window._failed.push(name);
289*c8dee2aaSAndroid Build Coastguard Worker            }
290*c8dee2aaSAndroid Build Coastguard Worker          } catch (e) {
291*c8dee2aaSAndroid Build Coastguard Worker            log(`${name} crashed with ${e.toString()} ${e.stack}`);
292*c8dee2aaSAndroid Build Coastguard Worker            window._error = e.toString();
293*c8dee2aaSAndroid Build Coastguard Worker            reject();
294*c8dee2aaSAndroid Build Coastguard Worker            return;
295*c8dee2aaSAndroid Build Coastguard Worker          }
296*c8dee2aaSAndroid Build Coastguard Worker          window._testsProgress++;
297*c8dee2aaSAndroid Build Coastguard Worker        }
298*c8dee2aaSAndroid Build Coastguard Worker        if (testIdx >= names.length) {
299*c8dee2aaSAndroid Build Coastguard Worker          resolve();
300*c8dee2aaSAndroid Build Coastguard Worker          return;
301*c8dee2aaSAndroid Build Coastguard Worker        }
302*c8dee2aaSAndroid Build Coastguard Worker        setTimeout(nextBatch);
303*c8dee2aaSAndroid Build Coastguard Worker      };
304*c8dee2aaSAndroid Build Coastguard Worker      setTimeout(nextBatch);
305*c8dee2aaSAndroid Build Coastguard Worker    });
306*c8dee2aaSAndroid Build Coastguard Worker
307*c8dee2aaSAndroid Build Coastguard Worker    grcontext.delete();
308*c8dee2aaSAndroid Build Coastguard Worker  }
309*c8dee2aaSAndroid Build Coastguard Worker</script>
310*c8dee2aaSAndroid Build Coastguard Worker</body>
311*c8dee2aaSAndroid Build Coastguard Worker</html>
312