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      if (name.includes('mandril')) {
89        console.log(name, new Uint8Array(buffer).slice(0, 20));
90      }
91      GM.LoadResource(name, buffer);
92    }
93  }
94
95  // There's a global set of known hashes that we preload with the md5 hashes that are already
96  // uploaded to Gold. This saves us some time to encode them and write them to disk.
97  function LoadKnownHashes(GM, hashes) {
98    log(`Loading ${hashes.length} hashes`);
99    console.time('load_hashes');
100    for (const hash of hashes.split('\n')) {
101      if (hash.length < 5) { // be sure not to add empty lines
102        continue;
103      }
104      GM.LoadKnownDigest(hash);
105    }
106    console.timeEnd('load_hashes');
107    log('hashes loaded');
108  }
109
110  const gmSkipList = new Set([
111    // gm names can be added here to skip, if failing.
112  ]);
113
114  async function RunGMs(GM) {
115    const canvas = document.getElementById('gm_canvas');
116    const ctx = GM.GetWebGLContext(canvas, 2);
117    const grcontext = GM.MakeGrContext(ctx);
118    window._results = [];
119
120    const names = GM.ListGMs();
121    names.sort();
122    for (const name of names) {
123      if (gmSkipList.has(name)) {
124        continue;
125      }
126      log(`Starting GM ${name}`);
127      const pngAndMetadata = GM.RunGM(grcontext, name);
128      if (!pngAndMetadata || !pngAndMetadata.hash) {
129        console.debug('No output for ', name);
130        continue; // Was skipped
131      }
132      log(`    drew ${pngAndMetadata.hash}`);
133      window._results.push({
134        name: name,
135        digest: pngAndMetadata.hash,
136      });
137      if (pngAndMetadata.png) {
138        await postPNG(pngAndMetadata.hash, pngAndMetadata.png);
139      }
140      window._testsProgress++;
141    }
142    grcontext.delete();
143  }
144
145  async function postPNG(hash, data) {
146    attemptedPOSTs += 1;
147    await fetch('/write_png', {
148      method: 'POST',
149      body: data,
150      headers: {
151        'Content-type': 'image/png',
152        'X-MD5-Hash': hash, // this will be used server side to create the name of the png.
153      }
154    }).then((resp) => {
155      if (resp.ok) {
156        successfulPOSTs += 1;
157      } else {
158        console.error('not ok response', resp);
159      }
160    }).catch((e) => {
161      console.error('Could not post PNG', e);
162    });
163  }
164
165  const testSkipList = new Set([
166    // These tests all crash https://bugs.chromium.org/p/skia/issues/detail?id=10869
167
168
169    // note, to catch these crashes, you must compile a debug build,
170    // run with --manual_mode and open the developer console,
171    // and enable pause on exceptions in the sources tab, or the browser will just close
172    // the instant this test crashes.
173
174    // These tests fail when doing a dlopen call
175    // "To use dlopen, you need to use Emscripten's linking support"
176    // Some of these appear to hit the default case instead of the GLES case in GrContextFactory.cpp
177    // which isn't expected to work. If they had a GLES context, they'd probably pass.
178    'AsyncReadPixelsContextShutdown',
179    'GrContextFactory_abandon',
180    'GrContext_abandonContext',
181    'GrContext_oomed',
182    'GrDDLImage_MakeSubset',
183    'InitialTextureClear',
184    'PinnedImageTest',
185    'PromiseImageTextureShutdown',
186
187    // These tests time out
188    'SkTraceMemoryDump_ownedGLRenderTarget',
189
190    // wasm doesn't have threading
191    'GrContextFactory_executorAndTaskGroup',
192    'GrContextFactory_sharedContexts',
193    'RefCnt',
194    'SkRuntimeEffectThreaded',
195    'SkScalerCacheMultiThread',
196    'String_Threaded',
197
198    // These tests are crashing for unknown reasons
199    'AdvancedBlendTest',
200    'FILEStreamWithOffset',
201    'Data',
202    'ES2BlendWithNoTexture',
203
204    // keys invalid
205    'GrPathKeys',
206
207    // flaky crash.
208    // crash seems more likely the faster you hit the "Start Tests" button.
209    'CCPR_cache_animationAtlasReuse',
210    'CCPR_cache_deferredCleanup',
211    'CCPR_cache_hashTable',
212    'CCPR_cache_mostlyVisible',
213    'CCPR_cache_multiFlush',
214    'CCPR_cache_multiTileCache',
215    'CCPR_cache_partialInvalidate',
216    'CCPR_cache_recycleEntries',
217    'CCPR_cleanup',
218    'CCPR_cleanupWithTexAllocFail',
219    'CCPR_busyPath',
220    'CCPR_evictCacheEntryForPendingDrawOp',
221
222    // Creates only 35 of 36 expected fragment processor factories
223    'ProcessorCloneTest',
224    'ProcessorOptimizationValidationTest',
225    'ProcessorRefTest',
226    'Programs',
227
228    // Apparently fail only on release builds / bots
229    'FlushFinishedProcTest',
230    'WritePixelsNonTextureMSAA_Gpu',
231  ]);
232
233  async function RunTests(GM) {
234    const canvas = document.getElementById('gm_canvas');
235    const ctx = GM.GetWebGLContext(canvas, 2);
236    // This sets up the GL context for all tests.
237    const grcontext = GM.MakeGrContext(ctx);
238    if (!grcontext) {
239      window._error = 'Could not make GrContext for tests';
240      return;
241    }
242    // We run these tests in batchs so as not to lock up the main thread, which makes it easier
243    // to read the progress as well as making the page more responsive when debugging.
244    await new Promise((resolve, reject) => {
245      const names = GM.ListTests();
246      names.sort();
247      console.log(names);
248      let testIdx = -1;
249      const nextBatch = () => {
250        for (let i = 0; i < 10 && testIdx < names.length; i++) {
251          testIdx++;
252          const name = names[testIdx];
253          if (!name) {
254            testIdx = names.length;
255            break;
256          }
257          if (testSkipList.has(name)) {
258            continue;
259          }
260          log(`Running test ${name}`);
261          try {
262            const result = GM.RunTest(name);
263            log('    ' + result.result, result.msg || '');
264            if (result.result !== 'passed' && result.result !== 'skipped') {
265              window._failed.push(name);
266            }
267          } catch (e) {
268            log(`${name} crashed with ${e.toString()} ${e.stack}`);
269            window._error = e.toString();
270            reject();
271            return;
272          }
273          window._testsProgress++;
274        }
275        if (testIdx >= names.length) {
276          resolve();
277          return;
278        }
279        setTimeout(nextBatch);
280      };
281      setTimeout(nextBatch);
282    });
283
284    grcontext.delete();
285  }
286</script>
287</body>
288</html>
289