skia2/modules/canvaskit/tests/util.js
Kevin Lubick cc8a76f3c7 [canvaskit] Add gm helper functions to tests.
Note to the reviewer: Look at tests/util.js first and then
look at the others. Gerrit lets you ignore whitespace changes,
which I would recommend for this.

This emulates tests on the C++ side and dramatically reduces
boilerplate on the test code.

This also uses the beforeEach(async () => {}) trick to save
a lot of promise resolutions before each tests.

I try to clean up the style a bit as I go, seriously thinking
about adding eslint for at least the tests.

Change-Id: Iced4abb57f66572035ab5d1a54b374055e8aaa58
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/281439
Reviewed-by: Joe Gregorio <jcgregorio@google.com>
2020-04-03 19:12:02 +00:00

209 lines
7.6 KiB
JavaScript

// The size of the golden images (DMs)
const CANVAS_WIDTH = 600;
const CANVAS_HEIGHT = 600;
const _commonGM = (it, pause, name, callback, assetsToFetchOrPromisesToWaitOn) => {
const fetchPromises = [];
for (const assetOrPromise of assetsToFetchOrPromisesToWaitOn) {
// https://stackoverflow.com/a/9436948
if (typeof assetOrPromise === 'string' || assetOrPromise instanceof String) {
const newPromise = fetch(assetOrPromise)
.then((response) => response.arrayBuffer());
fetchPromises.push(newPromise);
} else if (typeof assetOrPromise.then === 'function') {
fetchPromises.push(assetOrPromise);
} else {
throw 'Neither a string nor a promise ' + assetOrPromise;
}
}
it('draws gm '+name, (done) => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
// if fetchPromises is empty, the returned promise will
// resolve right away and just call the callback.
Promise.all(fetchPromises).then((values) => {
try {
callback(surface.getCanvas(), values);
} catch (e) {
console.log(`gm ${name} failed with error`, e);
expect(e).toBeFalsy();
debugger;
done();
}
surface.flush();
if (pause) {
reportSurface(surface, name, null);
} else {
reportSurface(surface, name, done);
}
}).catch((e) => {
console.log(`could not load assets for gm ${name}`, e);
debugger;
done();
});
})
}
/**
* Takes a name, a callback, and any number of assets or promises. It executes the
* callback (presumably, the test) and reports the resulting surface to Gold.
* @param name {string}
* @param callback {Function}, has two params, the first is a CanvasKit.SkCanvas
* and the second is an array of results from the passed in assets or promises.
* If a given assetOrPromise was a string, the result will be an ArrayBuffer.
* @param assetsToFetchOrPromisesToWaitOn {string|Promise}. If a string, it will
* be treated as a url to fetch and return an ArrayBuffer with the contents as
* a result in the callback. Otherwise, the promise will be waited on and its
* result will be whatever the promise resolves to.
*/
const gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
_commonGM(it, false, name, callback, assetsToFetchOrPromisesToWaitOn);
}
/**
* fgm is like gm, except only tests declared with fgm, force_gm, or fit will be
* executed. This mimics the behavior of Jasmine.js.
*/
const fgm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
_commonGM(fit, false, name, callback, assetsToFetchOrPromisesToWaitOn);
}
/**
* force_gm is like gm, except only tests declared with fgm, force_gm, or fit will be
* executed. This mimics the behavior of Jasmine.js.
*/
const force_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
fgm(name, callback, assetsToFetchOrPromisesToWaitOn);
}
/**
* skip_gm does nothing. It is a convenient way to skip a test temporarily.
*/
const skip_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
console.log(`Skipping gm ${name}`);
// do nothing, skip the test for now
}
/**
* pause_gm is like fgm, except the test will not finish right away and clear,
* making it ideal for a human to manually inspect the results.
*/
const pause_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
_commonGM(fit, true, name, callback, assetsToFetchOrPromisesToWaitOn);
}
const _commonMultipleCanvasGM = (it, pause, name, callback) => {
it(`draws gm ${name} on both CanvasKit and using Canvas2D`, (done) => {
const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
skcanvas._config = 'software_canvas';
const realCanvas = document.getElementById('test');
realCanvas._config = 'html_canvas';
realCanvas.width = CANVAS_WIDTH;
realCanvas.height = CANVAS_HEIGHT;
if (pause) {
console.log('debugging canvaskit version');
callback(realCanvas);
callback(skcanvas);
const png = skcanvas.toDataURL();
const img = document.createElement('img');
document.body.appendChild(img);
img.src = png;
debugger;
return;
}
const promises = [];
for (const canvas of [skcanvas, realCanvas]) {
callback(canvas);
// canvas has .toDataURL (even though skcanvas is not a real Canvas)
// so this will work.
promises.push(reportCanvas(canvas, name, canvas._config));
}
Promise.all(promises).then(() => {
skcanvas.dispose();
done();
}).catch(reportError(done));
});
}
/**
* Takes a name and a callback. It executes the callback (presumably, the test)
* for both a CanvasKit.SkCanvas and a native Canvas2D. The result of both will be
* uploaded to Gold.
* @param name {string}
* @param callback {Function}, has one param, either a CanvasKit.SkCanvas or a native
* Canvas2D object.
*/
const multipleCanvasGM = (name, callback) => {
_commonMultipleCanvasGM(it, false, name, callback);
}
/**
* fmultipleCanvasGM is like multipleCanvasGM, except only tests declared with
* fmultipleCanvasGM, force_multipleCanvasGM, or fit will be executed. This
* mimics the behavior of Jasmine.js.
*/
const fmultipleCanvasGM = (name, callback) => {
_commonMultipleCanvasGM(fit, false, name, callback);
}
/**
* force_multipleCanvasGM is like multipleCanvasGM, except only tests declared
* with fmultipleCanvasGM, force_multipleCanvasGM, or fit will be executed. This
* mimics the behavior of Jasmine.js.
*/
const force_multipleCanvasGM = (name, callback) => {
fmultipleCanvasGM(name, callback);
}
/**
* pause_multipleCanvasGM is like fmultipleCanvasGM, except the test will not
* finish right away and clear, making it ideal for a human to manually inspect the results.
*/
const pause_multipleCanvasGM = (name, callback) => {
_commonMultipleCanvasGM(fit, true, name, callback);
}
/**
* skip_multipleCanvasGM does nothing. It is a convenient way to skip a test temporarily.
*/
const skip_multipleCanvasGM = (name, callback) => {
console.log(`Skipping multiple canvas gm ${name}`);
}
function reportSurface(surface, testname, done) {
// In docker, the webgl canvas is blank, but the surface has the pixel
// data. So, we copy it out and draw it to a normal canvas to take a picture.
// To be consistent across CPU and GPU, we just do it for all configurations
// (even though the CPU canvas shows up after flush just fine).
let pixels = surface.getCanvas().readPixels(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
pixels = new Uint8ClampedArray(pixels.buffer);
const imageData = new ImageData(pixels, CANVAS_WIDTH, CANVAS_HEIGHT);
const reportingCanvas = document.getElementById('report');
reportingCanvas.getContext('2d').putImageData(imageData, 0, 0);
reportCanvas(reportingCanvas, testname).then(() => {
// TODO(kjlubick): should we call surface.delete() here?
done();
}).catch(reportError(done));
}
function starPath(CanvasKit, X=128, Y=128, R=116) {
const p = new CanvasKit.SkPath();
p.moveTo(X + R, Y);
for (let i = 1; i < 8; i++) {
let a = 2.6927937 * i;
p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
}
p.close();
return p;
}