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