1cb93a386Sopenharmony_ci// The size of the golden images (DMs)
2cb93a386Sopenharmony_ciconst CANVAS_WIDTH = 600;
3cb93a386Sopenharmony_ciconst CANVAS_HEIGHT = 600;
4cb93a386Sopenharmony_ci
5cb93a386Sopenharmony_ciconst _commonGM = (it, pause, name, callback, assetsToFetchOrPromisesToWaitOn) => {
6cb93a386Sopenharmony_ci    const fetchPromises = [];
7cb93a386Sopenharmony_ci    for (const assetOrPromise of assetsToFetchOrPromisesToWaitOn) {
8cb93a386Sopenharmony_ci        // https://stackoverflow.com/a/9436948
9cb93a386Sopenharmony_ci        if (typeof assetOrPromise === 'string' || assetOrPromise instanceof String) {
10cb93a386Sopenharmony_ci            const newPromise = fetchWithRetries(assetOrPromise)
11cb93a386Sopenharmony_ci                .then((response) => response.arrayBuffer())
12cb93a386Sopenharmony_ci                .catch((err) => {
13cb93a386Sopenharmony_ci                    console.error(err);
14cb93a386Sopenharmony_ci                    throw err;
15cb93a386Sopenharmony_ci                });
16cb93a386Sopenharmony_ci            fetchPromises.push(newPromise);
17cb93a386Sopenharmony_ci        } else if (typeof assetOrPromise.then === 'function') {
18cb93a386Sopenharmony_ci            fetchPromises.push(assetOrPromise);
19cb93a386Sopenharmony_ci        } else {
20cb93a386Sopenharmony_ci            throw 'Neither a string nor a promise ' + assetOrPromise;
21cb93a386Sopenharmony_ci        }
22cb93a386Sopenharmony_ci    }
23cb93a386Sopenharmony_ci    it('draws gm '+name, (done) => {
24cb93a386Sopenharmony_ci        const surface = CanvasKit.MakeCanvasSurface('test');
25cb93a386Sopenharmony_ci        expect(surface).toBeTruthy('Could not make surface');
26cb93a386Sopenharmony_ci        if (!surface) {
27cb93a386Sopenharmony_ci            done();
28cb93a386Sopenharmony_ci            return;
29cb93a386Sopenharmony_ci        }
30cb93a386Sopenharmony_ci        // if fetchPromises is empty, the returned promise will
31cb93a386Sopenharmony_ci        // resolve right away and just call the callback.
32cb93a386Sopenharmony_ci        Promise.all(fetchPromises).then((values) => {
33cb93a386Sopenharmony_ci            try {
34cb93a386Sopenharmony_ci                // If callback returns a promise, the chained .then
35cb93a386Sopenharmony_ci                // will wait for it.
36cb93a386Sopenharmony_ci                return callback(surface.getCanvas(), values, surface);
37cb93a386Sopenharmony_ci            } catch (e) {
38cb93a386Sopenharmony_ci                console.log(`gm ${name} failed with error`, e);
39cb93a386Sopenharmony_ci                expect(e).toBeFalsy();
40cb93a386Sopenharmony_ci                debugger;
41cb93a386Sopenharmony_ci                done();
42cb93a386Sopenharmony_ci            }
43cb93a386Sopenharmony_ci        }).then(() => {
44cb93a386Sopenharmony_ci            surface.flush();
45cb93a386Sopenharmony_ci            if (pause) {
46cb93a386Sopenharmony_ci                reportSurface(surface, name, null);
47cb93a386Sopenharmony_ci                console.error('pausing due to pause_gm being invoked');
48cb93a386Sopenharmony_ci            } else {
49cb93a386Sopenharmony_ci                reportSurface(surface, name, done);
50cb93a386Sopenharmony_ci            }
51cb93a386Sopenharmony_ci        }).catch((e) => {
52cb93a386Sopenharmony_ci            console.log(`could not load assets for gm ${name}`, e);
53cb93a386Sopenharmony_ci            debugger;
54cb93a386Sopenharmony_ci            done();
55cb93a386Sopenharmony_ci        });
56cb93a386Sopenharmony_ci    })
57cb93a386Sopenharmony_ci};
58cb93a386Sopenharmony_ci
59cb93a386Sopenharmony_ciconst fetchWithRetries = (url) => {
60cb93a386Sopenharmony_ci    const MAX_ATTEMPTS = 3;
61cb93a386Sopenharmony_ci    const DELAY_AFTER_FAILURE = 1000;
62cb93a386Sopenharmony_ci
63cb93a386Sopenharmony_ci    return new Promise((resolve, reject) => {
64cb93a386Sopenharmony_ci        let attempts = 0;
65cb93a386Sopenharmony_ci        const attemptFetch = () => {
66cb93a386Sopenharmony_ci            attempts++;
67cb93a386Sopenharmony_ci            fetch(url).then((resp) => resolve(resp))
68cb93a386Sopenharmony_ci                .catch((err) => {
69cb93a386Sopenharmony_ci                    if (attempts < MAX_ATTEMPTS) {
70cb93a386Sopenharmony_ci                        console.warn(`got error in fetching ${url}, retrying`, err);
71cb93a386Sopenharmony_ci                        retryAfterDelay();
72cb93a386Sopenharmony_ci                    } else {
73cb93a386Sopenharmony_ci                        console.error(`got error in fetching ${url} even after ${attempts} attempts`, err);
74cb93a386Sopenharmony_ci                        reject(err);
75cb93a386Sopenharmony_ci                    }
76cb93a386Sopenharmony_ci                });
77cb93a386Sopenharmony_ci        };
78cb93a386Sopenharmony_ci        const retryAfterDelay = () => {
79cb93a386Sopenharmony_ci            setTimeout(() => {
80cb93a386Sopenharmony_ci                attemptFetch();
81cb93a386Sopenharmony_ci            }, DELAY_AFTER_FAILURE);
82cb93a386Sopenharmony_ci        }
83cb93a386Sopenharmony_ci        attemptFetch();
84cb93a386Sopenharmony_ci    });
85cb93a386Sopenharmony_ci
86cb93a386Sopenharmony_ci}
87cb93a386Sopenharmony_ci
88cb93a386Sopenharmony_ci/**
89cb93a386Sopenharmony_ci * Takes a name, a callback, and any number of assets or promises. It executes the
90cb93a386Sopenharmony_ci * callback (presumably, the test) and reports the resulting surface to Gold.
91cb93a386Sopenharmony_ci * @param name {string}
92cb93a386Sopenharmony_ci * @param callback {Function}, has two params, the first is a CanvasKit.Canvas
93cb93a386Sopenharmony_ci *    and the second is an array of results from the passed in assets or promises.
94cb93a386Sopenharmony_ci *    If a given assetOrPromise was a string, the result will be an ArrayBuffer.
95cb93a386Sopenharmony_ci * @param assetsToFetchOrPromisesToWaitOn {string|Promise}. If a string, it will
96cb93a386Sopenharmony_ci *    be treated as a url to fetch and return an ArrayBuffer with the contents as
97cb93a386Sopenharmony_ci *    a result in the callback. Otherwise, the promise will be waited on and its
98cb93a386Sopenharmony_ci *    result will be whatever the promise resolves to.
99cb93a386Sopenharmony_ci */
100cb93a386Sopenharmony_ciconst gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
101cb93a386Sopenharmony_ci    _commonGM(it, false, name, callback, assetsToFetchOrPromisesToWaitOn);
102cb93a386Sopenharmony_ci};
103cb93a386Sopenharmony_ci
104cb93a386Sopenharmony_ci/**
105cb93a386Sopenharmony_ci *  fgm is like gm, except only tests declared with fgm, force_gm, or fit will be
106cb93a386Sopenharmony_ci *  executed. This mimics the behavior of Jasmine.js.
107cb93a386Sopenharmony_ci */
108cb93a386Sopenharmony_ciconst fgm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
109cb93a386Sopenharmony_ci    _commonGM(fit, false, name, callback, assetsToFetchOrPromisesToWaitOn);
110cb93a386Sopenharmony_ci};
111cb93a386Sopenharmony_ci
112cb93a386Sopenharmony_ci/**
113cb93a386Sopenharmony_ci *  force_gm is like gm, except only tests declared with fgm, force_gm, or fit will be
114cb93a386Sopenharmony_ci *  executed. This mimics the behavior of Jasmine.js.
115cb93a386Sopenharmony_ci */
116cb93a386Sopenharmony_ciconst force_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
117cb93a386Sopenharmony_ci    fgm(name, callback, assetsToFetchOrPromisesToWaitOn);
118cb93a386Sopenharmony_ci};
119cb93a386Sopenharmony_ci
120cb93a386Sopenharmony_ci/**
121cb93a386Sopenharmony_ci *  skip_gm does nothing. It is a convenient way to skip a test temporarily.
122cb93a386Sopenharmony_ci */
123cb93a386Sopenharmony_ciconst skip_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
124cb93a386Sopenharmony_ci    console.log(`Skipping gm ${name}`);
125cb93a386Sopenharmony_ci    // do nothing, skip the test for now
126cb93a386Sopenharmony_ci};
127cb93a386Sopenharmony_ci
128cb93a386Sopenharmony_ci/**
129cb93a386Sopenharmony_ci *  pause_gm is like fgm, except the test will not finish right away and clear,
130cb93a386Sopenharmony_ci *  making it ideal for a human to manually inspect the results.
131cb93a386Sopenharmony_ci */
132cb93a386Sopenharmony_ciconst pause_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
133cb93a386Sopenharmony_ci    _commonGM(fit, true, name, callback, assetsToFetchOrPromisesToWaitOn);
134cb93a386Sopenharmony_ci};
135cb93a386Sopenharmony_ci
136cb93a386Sopenharmony_ciconst _commonMultipleCanvasGM = (it, pause, name, callback) => {
137cb93a386Sopenharmony_ci    it(`draws gm ${name} on both CanvasKit and using Canvas2D`, (done) => {
138cb93a386Sopenharmony_ci        const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
139cb93a386Sopenharmony_ci        skcanvas._config = 'software_canvas';
140cb93a386Sopenharmony_ci        const realCanvas = document.getElementById('test');
141cb93a386Sopenharmony_ci        realCanvas._config = 'html_canvas';
142cb93a386Sopenharmony_ci        realCanvas.width = CANVAS_WIDTH;
143cb93a386Sopenharmony_ci        realCanvas.height = CANVAS_HEIGHT;
144cb93a386Sopenharmony_ci
145cb93a386Sopenharmony_ci        if (pause) {
146cb93a386Sopenharmony_ci            console.log('debugging canvaskit version');
147cb93a386Sopenharmony_ci            callback(realCanvas);
148cb93a386Sopenharmony_ci            callback(skcanvas);
149cb93a386Sopenharmony_ci            const png = skcanvas.toDataURL();
150cb93a386Sopenharmony_ci            const img = document.createElement('img');
151cb93a386Sopenharmony_ci            document.body.appendChild(img);
152cb93a386Sopenharmony_ci            img.src = png;
153cb93a386Sopenharmony_ci            debugger;
154cb93a386Sopenharmony_ci            return;
155cb93a386Sopenharmony_ci        }
156cb93a386Sopenharmony_ci
157cb93a386Sopenharmony_ci        const promises = [];
158cb93a386Sopenharmony_ci
159cb93a386Sopenharmony_ci        for (const canvas of [skcanvas, realCanvas]) {
160cb93a386Sopenharmony_ci            callback(canvas);
161cb93a386Sopenharmony_ci            // canvas has .toDataURL (even though skcanvas is not a real Canvas)
162cb93a386Sopenharmony_ci            // so this will work.
163cb93a386Sopenharmony_ci            promises.push(reportCanvas(canvas, name, canvas._config));
164cb93a386Sopenharmony_ci        }
165cb93a386Sopenharmony_ci        Promise.all(promises).then(() => {
166cb93a386Sopenharmony_ci            skcanvas.dispose();
167cb93a386Sopenharmony_ci            done();
168cb93a386Sopenharmony_ci        }).catch(reportError(done));
169cb93a386Sopenharmony_ci    });
170cb93a386Sopenharmony_ci};
171cb93a386Sopenharmony_ci
172cb93a386Sopenharmony_ci/**
173cb93a386Sopenharmony_ci * Takes a name and a callback. It executes the callback (presumably, the test)
174cb93a386Sopenharmony_ci * for both a CanvasKit.Canvas and a native Canvas2D. The result of both will be
175cb93a386Sopenharmony_ci * uploaded to Gold.
176cb93a386Sopenharmony_ci * @param name {string}
177cb93a386Sopenharmony_ci * @param callback {Function}, has one param, either a CanvasKit.Canvas or a native
178cb93a386Sopenharmony_ci *    Canvas2D object.
179cb93a386Sopenharmony_ci */
180cb93a386Sopenharmony_ciconst multipleCanvasGM = (name, callback) => {
181cb93a386Sopenharmony_ci    _commonMultipleCanvasGM(it, false, name, callback);
182cb93a386Sopenharmony_ci};
183cb93a386Sopenharmony_ci
184cb93a386Sopenharmony_ci/**
185cb93a386Sopenharmony_ci *  fmultipleCanvasGM is like multipleCanvasGM, except only tests declared with
186cb93a386Sopenharmony_ci *  fmultipleCanvasGM, force_multipleCanvasGM, or fit will be executed. This
187cb93a386Sopenharmony_ci *  mimics the behavior of Jasmine.js.
188cb93a386Sopenharmony_ci */
189cb93a386Sopenharmony_ciconst fmultipleCanvasGM = (name, callback) => {
190cb93a386Sopenharmony_ci    _commonMultipleCanvasGM(fit, false, name, callback);
191cb93a386Sopenharmony_ci};
192cb93a386Sopenharmony_ci
193cb93a386Sopenharmony_ci/**
194cb93a386Sopenharmony_ci *  force_multipleCanvasGM is like multipleCanvasGM, except only tests declared
195cb93a386Sopenharmony_ci *  with fmultipleCanvasGM, force_multipleCanvasGM, or fit will be executed. This
196cb93a386Sopenharmony_ci *  mimics the behavior of Jasmine.js.
197cb93a386Sopenharmony_ci */
198cb93a386Sopenharmony_ciconst force_multipleCanvasGM = (name, callback) => {
199cb93a386Sopenharmony_ci    fmultipleCanvasGM(name, callback);
200cb93a386Sopenharmony_ci};
201cb93a386Sopenharmony_ci
202cb93a386Sopenharmony_ci/**
203cb93a386Sopenharmony_ci *  pause_multipleCanvasGM is like fmultipleCanvasGM, except the test will not
204cb93a386Sopenharmony_ci *  finish right away and clear, making it ideal for a human to manually inspect the results.
205cb93a386Sopenharmony_ci */
206cb93a386Sopenharmony_ciconst pause_multipleCanvasGM = (name, callback) => {
207cb93a386Sopenharmony_ci    _commonMultipleCanvasGM(fit, true, name, callback);
208cb93a386Sopenharmony_ci};
209cb93a386Sopenharmony_ci
210cb93a386Sopenharmony_ci/**
211cb93a386Sopenharmony_ci *  skip_multipleCanvasGM does nothing. It is a convenient way to skip a test temporarily.
212cb93a386Sopenharmony_ci */
213cb93a386Sopenharmony_ciconst skip_multipleCanvasGM = (name, callback) => {
214cb93a386Sopenharmony_ci    console.log(`Skipping multiple canvas gm ${name}`);
215cb93a386Sopenharmony_ci};
216cb93a386Sopenharmony_ci
217cb93a386Sopenharmony_ci
218cb93a386Sopenharmony_cifunction reportSurface(surface, testname, done) {
219cb93a386Sopenharmony_ci    // In docker, the webgl canvas is blank, but the surface has the pixel
220cb93a386Sopenharmony_ci    // data. So, we copy it out and draw it to a normal canvas to take a picture.
221cb93a386Sopenharmony_ci    // To be consistent across CPU and GPU, we just do it for all configurations
222cb93a386Sopenharmony_ci    // (even though the CPU canvas shows up after flush just fine).
223cb93a386Sopenharmony_ci    let pixels = surface.getCanvas().readPixels(0, 0, {
224cb93a386Sopenharmony_ci        width: CANVAS_WIDTH,
225cb93a386Sopenharmony_ci        height: CANVAS_HEIGHT,
226cb93a386Sopenharmony_ci        colorType: CanvasKit.ColorType.RGBA_8888,
227cb93a386Sopenharmony_ci        alphaType: CanvasKit.AlphaType.Unpremul,
228cb93a386Sopenharmony_ci        colorSpace: CanvasKit.ColorSpace.SRGB,
229cb93a386Sopenharmony_ci    });
230cb93a386Sopenharmony_ci    if (!pixels) {
231cb93a386Sopenharmony_ci        throw 'Could not get pixels for test '+testname;
232cb93a386Sopenharmony_ci    }
233cb93a386Sopenharmony_ci    pixels = new Uint8ClampedArray(pixels.buffer);
234cb93a386Sopenharmony_ci    const imageData = new ImageData(pixels, CANVAS_WIDTH, CANVAS_HEIGHT);
235cb93a386Sopenharmony_ci
236cb93a386Sopenharmony_ci    const reportingCanvas = document.getElementById('report');
237cb93a386Sopenharmony_ci    if (!reportingCanvas) {
238cb93a386Sopenharmony_ci        throw 'Reporting canvas not found';
239cb93a386Sopenharmony_ci    }
240cb93a386Sopenharmony_ci    reportingCanvas.getContext('2d').putImageData(imageData, 0, 0);
241cb93a386Sopenharmony_ci    if (!done) {
242cb93a386Sopenharmony_ci        return;
243cb93a386Sopenharmony_ci    }
244cb93a386Sopenharmony_ci    reportCanvas(reportingCanvas, testname).then(() => {
245cb93a386Sopenharmony_ci        surface.delete();
246cb93a386Sopenharmony_ci        done();
247cb93a386Sopenharmony_ci    }).catch(reportError(done));
248cb93a386Sopenharmony_ci}
249cb93a386Sopenharmony_ci
250cb93a386Sopenharmony_ci
251cb93a386Sopenharmony_cifunction starPath(CanvasKit, X=128, Y=128, R=116) {
252cb93a386Sopenharmony_ci    const p = new CanvasKit.Path();
253cb93a386Sopenharmony_ci    p.moveTo(X + R, Y);
254cb93a386Sopenharmony_ci    for (let i = 1; i < 8; i++) {
255cb93a386Sopenharmony_ci      let a = 2.6927937 * i;
256cb93a386Sopenharmony_ci      p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
257cb93a386Sopenharmony_ci    }
258cb93a386Sopenharmony_ci    p.close();
259cb93a386Sopenharmony_ci    return p;
260cb93a386Sopenharmony_ci}
261