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