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