blob: 0c4bdd8371506678a246f10f745bfb35f84dbbbb [file] [log] [blame]
<!-- 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];
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([
'exoticformats', // Uses SkFILEStream to load resource.
// uses skresources::FileResourceProvider
'particles_mandrill',
'particles_sprite_frame',
]);
async function RunGMs(GM) {
const canvas = document.getElementById('gm_canvas');
const ctx = GM.GetWebGLContext(canvas, 2);
const grcontext = GM.MakeGrContext(ctx);
if (!grcontext) {
window._error = 'Could not make GrContext for gms';
return;
}
window._results = [];
// We run these tests in batches 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.ListGMs();
names.sort();
// When debugging locally, it can be handy to skip to a certain GM by using
// names.indexOf here instead of 0.
let testIdx = 0;
const nextBatch = async () => {
for (let i = 0; i < 10 && testIdx < names.length; i++) {
testIdx++;
const name = names[testIdx];
if (!name) {
testIdx = names.length;
break;
}
if (gmSkipList.has(name)) {
continue;
}
log(`Starting GM ${name}`);
const pngAndMetadata = GM.RunGM(grcontext, name);
if (!pngAndMetadata || !pngAndMetadata.hash) {
log(' No output (was skipped)');
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++;
}
if (testIdx >= names.length) {
resolve();
return;
}
setTimeout(nextBatch);
};
setTimeout(nextBatch);
});
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);
log('not ok response ' + resp.toString());
}
}).catch((e) => {
console.error('Could not post PNG', e);
log('Could not post PNG ' + resp.toString());
});
}
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',
'GrStyledShape',
// wasm doesn't have threading
'GrContextFactory_executorAndTaskGroup',
'GrContextFactory_sharedContexts',
'RefCnt',
'SkRuntimeEffectThreaded',
'SkScalerCacheMultiThread',
'String_Threaded',
// These tests are crashing for unknown reasons
'AdvancedBlendTest',
'Data',
'ES2BlendWithNoTexture',
'TextureBindingsResetTest',
// 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',
// These SkSL tests fail on the Quadro P400s in the Golo
'SkSLCommaSideEffects_GPU',
'SkSLMatrixScalarNoOpFolding_GPU',
'SkSLPreserveSideEffects_GPU',
'SkSLStructFieldNoFolding_GPU',
// These tests use files on disk, which is not supported for WASM
'Stream',
'StreamBuffer',
'StreamPeek',
'FILEStreamWithOffset',
]);
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 batches 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);
// When debugging locally, it can be handy to skip to a certain test by using
// names.indexOf here instead of 0.
let testIdx = 0;
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>