1 // The size of the golden images (DMs)
2 const CANVAS_WIDTH = 600;
3 const CANVAS_HEIGHT = 600;
5 const _commonGM = (it, pause, name, callback, assetsToFetchOrPromisesToWaitOn) => {
6 const fetchPromises = [];
7 for (const assetOrPromise of assetsToFetchOrPromisesToWaitOn) {
8 // https://stackoverflow.com/a/9436948
9 if (typeof assetOrPromise === 'string' || assetOrPromise instanceof String) {
10 const newPromise = fetchWithRetries(assetOrPromise)
11 .then((response) => response.arrayBuffer())
16 fetchPromises.push(newPromise);
17 } else if (typeof assetOrPromise.then === 'function') {
18 fetchPromises.push(assetOrPromise);
20 throw 'Neither a string nor a promise ' + assetOrPromise;
23 it('draws gm '+name, (done) => {
24 const surface = CanvasKit.MakeCanvasSurface('test');
25 expect(surface).toBeTruthy('Could not make surface');
30 // if fetchPromises is empty, the returned promise will
31 // resolve right away and just call the callback.
32 Promise.all(fetchPromises).then((values) => {
34 // If callback returns a promise, the chained .then
36 return callback(surface.getCanvas(), values, surface);
38 console.log(`gm ${name} failed with error`, e);
39 expect(e).toBeFalsy();
46 reportSurface(surface, name, null);
47 console.error('pausing due to pause_gm being invoked');
49 reportSurface(surface, name, done);
52 console.log(`could not load assets for gm ${name}`, e);
59 const fetchWithRetries = (url) => {
60 const MAX_ATTEMPTS = 3;
61 const DELAY_AFTER_FAILURE = 1000;
63 return new Promise((resolve, reject) => {
65 const attemptFetch = () => {
67 fetch(url).then((resp) => resolve(resp))
69 if (attempts < MAX_ATTEMPTS) {
70 console.warn(`got error in fetching ${url}, retrying`, err);
73 console.error(`got error in fetching ${url} even after ${attempts} attempts`, err);
78 const retryAfterDelay = () => {
81 }, DELAY_AFTER_FAILURE);
89 * Takes a name, a callback, and any number of assets or promises. It executes the
90 * callback (presumably, the test) and reports the resulting surface to Gold.
91 * @param name {string}
92 * @param callback {Function}, has two params, the first is a CanvasKit.Canvas
93 * and the second is an array of results from the passed in assets or promises.
94 * If a given assetOrPromise was a string, the result will be an ArrayBuffer.
95 * @param assetsToFetchOrPromisesToWaitOn {string|Promise}. If a string, it will
96 * be treated as a url to fetch and return an ArrayBuffer with the contents as
97 * a result in the callback. Otherwise, the promise will be waited on and its
98 * result will be whatever the promise resolves to.
100 const gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
101 _commonGM(it, false, name, callback, assetsToFetchOrPromisesToWaitOn);
105 * fgm is like gm, except only tests declared with fgm, force_gm, or fit will be
106 * executed. This mimics the behavior of Jasmine.js.
108 const fgm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
109 _commonGM(fit, false, name, callback, assetsToFetchOrPromisesToWaitOn);
113 * force_gm is like gm, except only tests declared with fgm, force_gm, or fit will be
114 * executed. This mimics the behavior of Jasmine.js.
116 const force_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
117 fgm(name, callback, assetsToFetchOrPromisesToWaitOn);
121 * skip_gm does nothing. It is a convenient way to skip a test temporarily.
123 const skip_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
124 console.log(`Skipping gm ${name}`);
125 // do nothing, skip the test for now
129 * pause_gm is like fgm, except the test will not finish right away and clear,
130 * making it ideal for a human to manually inspect the results.
132 const pause_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
133 _commonGM(fit, true, name, callback, assetsToFetchOrPromisesToWaitOn);
136 const _commonMultipleCanvasGM = (it, pause, name, callback) => {
137 it(`draws gm ${name} on both CanvasKit and using Canvas2D`, (done) => {
138 const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
139 skcanvas._config = 'software_canvas';
140 const realCanvas = document.getElementById('test');
141 realCanvas._config = 'html_canvas';
142 realCanvas.width = CANVAS_WIDTH;
143 realCanvas.height = CANVAS_HEIGHT;
146 console.log('debugging canvaskit version');
147 callback(realCanvas);
149 const png = skcanvas.toDataURL();
150 const img = document.createElement('img');
151 document.body.appendChild(img);
159 for (const canvas of [skcanvas, realCanvas]) {
161 // canvas has .toDataURL (even though skcanvas is not a real Canvas)
162 // so this will work.
163 promises.push(reportCanvas(canvas, name, canvas._config));
165 Promise.all(promises).then(() => {
168 }).catch(reportError(done));
173 * Takes a name and a callback. It executes the callback (presumably, the test)
174 * for both a CanvasKit.Canvas and a native Canvas2D. The result of both will be
176 * @param name {string}
177 * @param callback {Function}, has one param, either a CanvasKit.Canvas or a native
180 const multipleCanvasGM = (name, callback) => {
181 _commonMultipleCanvasGM(it, false, name, callback);
185 * fmultipleCanvasGM is like multipleCanvasGM, except only tests declared with
186 * fmultipleCanvasGM, force_multipleCanvasGM, or fit will be executed. This
187 * mimics the behavior of Jasmine.js.
189 const fmultipleCanvasGM = (name, callback) => {
190 _commonMultipleCanvasGM(fit, false, name, callback);
194 * force_multipleCanvasGM is like multipleCanvasGM, except only tests declared
195 * with fmultipleCanvasGM, force_multipleCanvasGM, or fit will be executed. This
196 * mimics the behavior of Jasmine.js.
198 const force_multipleCanvasGM = (name, callback) => {
199 fmultipleCanvasGM(name, callback);
203 * pause_multipleCanvasGM is like fmultipleCanvasGM, except the test will not
204 * finish right away and clear, making it ideal for a human to manually inspect the results.
206 const pause_multipleCanvasGM = (name, callback) => {
207 _commonMultipleCanvasGM(fit, true, name, callback);
211 * skip_multipleCanvasGM does nothing. It is a convenient way to skip a test temporarily.
213 const skip_multipleCanvasGM = (name, callback) => {
214 console.log(`Skipping multiple canvas gm ${name}`);
218 function reportSurface(surface, testname, done) {
219 // Sometimes, the webgl canvas is blank, but the surface has the pixel
220 // data. So, we copy it out and draw it to a normal canvas to take a picture.
221 // To be consistent across CPU and GPU, we just do it for all configurations
222 // (even though the CPU canvas shows up after flush just fine).
223 let pixels = surface.getCanvas().readPixels(0, 0, {
225 height: CANVAS_HEIGHT,
226 colorType: CanvasKit.ColorType.RGBA_8888,
227 alphaType: CanvasKit.AlphaType.Unpremul,
228 colorSpace: CanvasKit.ColorSpace.SRGB,
231 throw 'Could not get pixels for test '+testname;
233 pixels = new Uint8ClampedArray(pixels.buffer);
234 const imageData = new ImageData(pixels, CANVAS_WIDTH, CANVAS_HEIGHT);
236 const reportingCanvas = document.getElementById('report');
237 if (!reportingCanvas) {
238 throw 'Reporting canvas not found';
240 reportingCanvas.getContext('2d').putImageData(imageData, 0, 0);
244 reportCanvas(reportingCanvas, testname).then(() => {
247 }).catch(reportError(done));
251 function starPath(CanvasKit, X=128, Y=128, R=116) {
252 const p = new CanvasKit.Path();
254 for (let i = 1; i < 8; i++) {
255 let a = 2.6927937 * i;
256 p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));