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