| <!-- This runs the GMs and unit tests which have been compiled to WASM. When this completes, | 
 | either window._error will be set or window._testsDone will be true and window._results will be an | 
 | array of the test names and what they drew. | 
 | --> | 
 | <!DOCTYPE html> | 
 | <html> | 
 | <head> | 
 |   <title>WASM Runner of GMs and Unit Tests</title> | 
 |   <meta charset="utf-8" /> | 
 |   <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | 
 |   <meta name="viewport" content="width=device-width, initial-scale=1.0"> | 
 |   <script src="/static/wasm_gm_tests.js" type="text/javascript" charset="utf-8"></script> | 
 |   <style type="text/css" media="screen"> | 
 |     #status_text { | 
 |       min-width: 900px; | 
 |       min-height: 500px; | 
 |     } | 
 |   </style> | 
 | </head> | 
 | <body> | 
 | <main> | 
 |   <button id=start_tests>Start Tests</button> | 
 |   <br> | 
 |   <pre id=status_text></pre> | 
 |  | 
 |   <canvas id=gm_canvas></canvas> | 
 | </main> | 
 | <script type="text/javascript" charset="utf-8"> | 
 |   const loadTestsPromise = InitWasmGMTests({ | 
 |     locateFile: (file) => '/static/'+file, | 
 |   }); | 
 |  | 
 |   const loadKnownHashesPromise = fetch('/static/hashes.txt').then((resp) => resp.text()); | 
 |  | 
 |   const resourceNames = []; | 
 |   const loadResourceListing = fetch('/static/resource_listing.json').then((resp) => resp.json()) | 
 |     .then((json) => { | 
 |     console.debug('should fetch resources', json); | 
 |     const loadingPromises = []; | 
 |     for (const resource of json) { | 
 |       resourceNames.push(resource); | 
 |       const url = `/static/resources/${resource}`; | 
 |       loadingPromises.push(fetch(url).then((resp) => resp.arrayBuffer())); | 
 |     } | 
 |     return Promise.all(loadingPromises).catch((e) => { | 
 |       console.error(e); | 
 |       window._error = `Failed getting resources: ${JSON.stringify(e)}`; | 
 |     }); | 
 |   }); | 
 |  | 
 |   let attemptedPOSTs = 0; | 
 |   let successfulPOSTs = 0; | 
 |   Promise.all([loadTestsPromise, loadKnownHashesPromise, loadResourceListing]).then(([GM, hashes, resourceBuffers]) => { | 
 |     GM.Init(); | 
 |     LoadResources(GM, resourceNames, resourceBuffers); | 
 |     LoadKnownHashes(GM, hashes); | 
 |     document.getElementById('start_tests').addEventListener('click', async () => { | 
 |       window._testsProgress = 0; | 
 |       window._log = 'Starting\n'; | 
 |       window._failed = []; | 
 |       await RunTests(GM); | 
 |       if (window._error) { | 
 |         console.log(window._error); | 
 |         return; | 
 |       } | 
 |       await RunGMs(GM); | 
 |       if (attemptedPOSTs !== successfulPOSTs) { | 
 |         window._error = `Failed to POST all the PNG files (expected ${attemptedPOSTs}, finished ${successfulPOSTs})`; | 
 |       } else { | 
 |         window._testsDone = true; | 
 |       } | 
 |     }); | 
 |     window._testsReady = true; | 
 |   }); | 
 |  | 
 |   const statusElement = document.getElementById('status_text'); | 
 |   function log(line) { | 
 |     console.log(line); | 
 |     line += '\n'; | 
 |     statusElement.innerText += line; | 
 |     window._log += line; | 
 |   } | 
 |  | 
 |   function LoadResources(GM, resourceNames, resourceBuffers) { | 
 |     for (let i = 0; i < resourceNames.length; i++) { | 
 |       const name = resourceNames[i]; | 
 |       const buffer = resourceBuffers[i]; | 
 |       if (name.includes('mandril')) { | 
 |         console.log(name, new Uint8Array(buffer).slice(0, 20)); | 
 |       } | 
 |       GM.LoadResource(name, buffer); | 
 |     } | 
 |   } | 
 |  | 
 |   // There's a global set of known hashes that we preload with the md5 hashes that are already | 
 |   // uploaded to Gold. This saves us some time to encode them and write them to disk. | 
 |   function LoadKnownHashes(GM, hashes) { | 
 |     log(`Loading ${hashes.length} hashes`); | 
 |     console.time('load_hashes'); | 
 |     for (const hash of hashes.split('\n')) { | 
 |       if (hash.length < 5) { // be sure not to add empty lines | 
 |         continue; | 
 |       } | 
 |       GM.LoadKnownDigest(hash); | 
 |     } | 
 |     console.timeEnd('load_hashes'); | 
 |     log('hashes loaded'); | 
 |   } | 
 |  | 
 |   const gmSkipList = new Set([ | 
 |     // gm names can be added here to skip, if failing. | 
 |   ]); | 
 |  | 
 |   async function RunGMs(GM) { | 
 |     const canvas = document.getElementById('gm_canvas'); | 
 |     const ctx = GM.GetWebGLContext(canvas, 2); | 
 |     const grcontext = GM.MakeGrContext(ctx); | 
 |     window._results = []; | 
 |  | 
 |     const names = GM.ListGMs(); | 
 |     names.sort(); | 
 |     for (const name of names) { | 
 |       if (gmSkipList.has(name)) { | 
 |         continue; | 
 |       } | 
 |       log(`Starting GM ${name}`); | 
 |       const pngAndMetadata = GM.RunGM(grcontext, name); | 
 |       if (!pngAndMetadata || !pngAndMetadata.hash) { | 
 |         console.debug('No output for ', name); | 
 |         continue; // Was skipped | 
 |       } | 
 |       log(`    drew ${pngAndMetadata.hash}`); | 
 |       window._results.push({ | 
 |         name: name, | 
 |         digest: pngAndMetadata.hash, | 
 |       }); | 
 |       if (pngAndMetadata.png) { | 
 |         await postPNG(pngAndMetadata.hash, pngAndMetadata.png); | 
 |       } | 
 |       window._testsProgress++; | 
 |     } | 
 |     grcontext.delete(); | 
 |   } | 
 |  | 
 |   async function postPNG(hash, data) { | 
 |     attemptedPOSTs += 1; | 
 |     await fetch('/write_png', { | 
 |       method: 'POST', | 
 |       body: data, | 
 |       headers: { | 
 |         'Content-type': 'image/png', | 
 |         'X-MD5-Hash': hash, // this will be used server side to create the name of the png. | 
 |       } | 
 |     }).then((resp) => { | 
 |       if (resp.ok) { | 
 |         successfulPOSTs += 1; | 
 |       } else { | 
 |         console.error('not ok response', resp); | 
 |       } | 
 |     }).catch((e) => { | 
 |       console.error('Could not post PNG', e); | 
 |     }); | 
 |   } | 
 |  | 
 |   const testSkipList = new Set([ | 
 |     // These tests all crash https://bugs.chromium.org/p/skia/issues/detail?id=10869 | 
 |  | 
 |  | 
 |     // note, to catch these crashes, you must compile a debug build, | 
 |     // run with --manual_mode and open the developer console, | 
 |     // and enable pause on exceptions in the sources tab, or the browser will just close | 
 |     // the instant this test crashes. | 
 |  | 
 |     // These tests fail when doing a dlopen call | 
 |     // "To use dlopen, you need to use Emscripten's linking support" | 
 |     // Some of these appear to hit the default case instead of the GLES case in GrContextFactory.cpp | 
 |     // which isn't expected to work. If they had a GLES context, they'd probably pass. | 
 |     'AsyncReadPixelsContextShutdown', | 
 |     'GrContextFactory_abandon', | 
 |     'GrContext_abandonContext', | 
 |     'GrContext_oomed', | 
 |     'GrDDLImage_MakeSubset', | 
 |     'InitialTextureClear', | 
 |     'PinnedImageTest', | 
 |     'PromiseImageTextureShutdown', | 
 |  | 
 |     // These tests time out | 
 |     'SkTraceMemoryDump_ownedGLRenderTarget', | 
 |  | 
 |     // wasm doesn't have threading | 
 |     'GrContextFactory_executorAndTaskGroup', | 
 |     'GrContextFactory_sharedContexts', | 
 |     'RefCnt', | 
 |     'SkRuntimeEffectThreaded', | 
 |     'SkScalerCacheMultiThread', | 
 |     'String_Threaded', | 
 |  | 
 |     // These tests are crashing for unknown reasons | 
 |     'AdvancedBlendTest', | 
 |     'FILEStreamWithOffset', | 
 |     'Data', | 
 |     'ES2BlendWithNoTexture', | 
 |  | 
 |     // keys invalid | 
 |     'GrPathKeys', | 
 |  | 
 |     // Creates only 35 of 36 expected fragment processor factories | 
 |     'ProcessorCloneTest', | 
 |     'ProcessorOptimizationValidationTest', | 
 |     'ProcessorRefTest', | 
 |     'Programs', | 
 |  | 
 |     // Apparently fail only on release builds / bots | 
 |     'FlushFinishedProcTest', | 
 |     'WritePixelsNonTextureMSAA_Gpu', | 
 |   ]); | 
 |  | 
 |   async function RunTests(GM) { | 
 |     const canvas = document.getElementById('gm_canvas'); | 
 |     const ctx = GM.GetWebGLContext(canvas, 2); | 
 |     // This sets up the GL context for all tests. | 
 |     const grcontext = GM.MakeGrContext(ctx); | 
 |     if (!grcontext) { | 
 |       window._error = 'Could not make GrContext for tests'; | 
 |       return; | 
 |     } | 
 |     // We run these tests in batchs so as not to lock up the main thread, which makes it easier | 
 |     // to read the progress as well as making the page more responsive when debugging. | 
 |     await new Promise((resolve, reject) => { | 
 |       const names = GM.ListTests(); | 
 |       names.sort(); | 
 |       console.log(names); | 
 |       let testIdx = -1; | 
 |       const nextBatch = () => { | 
 |         for (let i = 0; i < 10 && testIdx < names.length; i++) { | 
 |           testIdx++; | 
 |           const name = names[testIdx]; | 
 |           if (!name) { | 
 |             testIdx = names.length; | 
 |             break; | 
 |           } | 
 |           if (testSkipList.has(name)) { | 
 |             continue; | 
 |           } | 
 |           log(`Running test ${name}`); | 
 |           try { | 
 |             const result = GM.RunTest(name); | 
 |             log('    ' + result.result, result.msg || ''); | 
 |             if (result.result !== 'passed' && result.result !== 'skipped') { | 
 |               window._failed.push(name); | 
 |             } | 
 |           } catch (e) { | 
 |             log(`${name} crashed with ${e.toString()} ${e.stack}`); | 
 |             window._error = e.toString(); | 
 |             reject(); | 
 |             return; | 
 |           } | 
 |           window._testsProgress++; | 
 |         } | 
 |         if (testIdx >= names.length) { | 
 |           resolve(); | 
 |           return; | 
 |         } | 
 |         setTimeout(nextBatch); | 
 |       }; | 
 |       setTimeout(nextBatch); | 
 |     }); | 
 |  | 
 |     grcontext.delete(); | 
 |   } | 
 | </script> | 
 | </body> | 
 | </html> |