From 966c03a317ceab6b015e5f1fcd946cb5d5ad7678 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Thu, 6 Jan 2022 03:30:02 +0900 Subject: [PATCH] [chore] fix test flakiness (#7076) --- package.json | 4 +-- test/custom-elements/index.ts | 40 ++++++++++------------ test/helpers.ts | 51 +++++++++++++++++++++++++++-- test/runtime-puppeteer/index.ts | 46 +++++++++++++------------- test/runtime/index.ts | 8 +++-- test/server-side-rendering/index.ts | 25 +++++++------- 6 files changed, 109 insertions(+), 65 deletions(-) diff --git a/package.json b/package.json index ee6c781860..233772b8a2 100644 --- a/package.json +++ b/package.json @@ -83,8 +83,8 @@ }, "types": "types/runtime/index.d.ts", "scripts": { - "test": "mocha", - "test:unit": "mocha --require sucrase/register --recursive src/**/__test__.ts", + "test": "mocha --exit", + "test:unit": "mocha --require sucrase/register --recursive src/**/__test__.ts --exit", "quicktest": "mocha", "precoverage": "c8 mocha", "coverage": "c8 report --reporter=text-lcov > coverage.lcov && c8 report --reporter=html", diff --git a/test/custom-elements/index.ts b/test/custom-elements/index.ts index 35df156879..42b27eebd3 100644 --- a/test/custom-elements/index.ts +++ b/test/custom-elements/index.ts @@ -4,7 +4,7 @@ import * as http from 'http'; import { rollup } from 'rollup'; import virtual from '@rollup/plugin-virtual'; import puppeteer from 'puppeteer'; -import { addLineNumbers, loadConfig, loadSvelte } from '../helpers'; +import { addLineNumbers, loadConfig, loadSvelte, retryAsync, executeBrowserTest } from '../helpers'; import { deepEqual } from 'assert'; const page = ` @@ -17,8 +17,8 @@ const page = ` const assert = fs.readFileSync(`${__dirname}/assert.js`, 'utf-8'); describe('custom-elements', function() { + // Note: Increase the timeout in preparation for restarting Chromium due to SIGSEGV. this.timeout(10000); - let svelte; let server; let browser; @@ -44,12 +44,16 @@ describe('custom-elements', function() { }); } + async function launchPuppeteer() { + return await retryAsync(() => puppeteer.launch()); + } + before(async () => { svelte = loadSvelte(); console.log('[custom-element] Loaded Svelte'); server = await create_server(); console.log('[custom-element] Started server'); - browser = await puppeteer.launch(); + browser = await launchPuppeteer(); console.log('[custom-element] Launched puppeteer browser'); }); @@ -108,26 +112,7 @@ describe('custom-elements', function() { const result = await bundle.generate({ format: 'iife', name: 'test' }); code = result.output[0].code; - const page = await browser.newPage(); - - page.on('console', (type) => { - console[type._type](type._text); - }); - - page.on('error', error => { - console.log('>>> an error happened'); - console.error(error); - }); - - try { - await page.goto('http://localhost:6789'); - - const result = await page.evaluate(() => test(document.querySelector('main'))); - if (result) console.log(result); - } catch (err) { - console.log(addLineNumbers(code)); - throw err; - } finally { + function assertWarnings() { if (expected_warnings) { deepEqual(warnings.map(w => ({ code: w.code, @@ -138,6 +123,15 @@ describe('custom-elements', function() { })), expected_warnings); } } + + browser = await executeBrowserTest( + browser, + launchPuppeteer, + assertWarnings, + () => { + console.log(addLineNumbers(code)); + assertWarnings(); + }); }); }); }); diff --git a/test/helpers.ts b/test/helpers.ts index 101490d4dc..1ea0b19880 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -8,7 +8,7 @@ export const assert = (assert$1 as unknown) as typeof assert$1 & { htmlEqual: (a // for coverage purposes, we need to test source files, // but for sanity purposes, we need to test dist files -export function loadSvelte(test) { +export function loadSvelte(test: boolean = false) { process.env.TEST = test ? 'true' : ''; const resolved = require.resolve('../compiler.js'); @@ -55,7 +55,7 @@ export function cleanRequireCache() { const virtualConsole = new jsdom.VirtualConsole(); virtualConsole.sendTo(console); -const window = new jsdom.JSDOM('
', {virtualConsole}).window; +const window = new jsdom.JSDOM('
', { virtualConsole }).window; global.document = window.document; global.navigator = window.navigator; global.getComputedStyle = window.getComputedStyle; @@ -279,3 +279,50 @@ export function prettyPrintPuppeteerAssertionError(message) { assert.equal(match[1], match[2]); } } + +export async function retryAsync(fn: () => Promise, maxAttempts: number = 3, interval: number = 1000): Promise { + let attempts = 0; + while (attempts <= maxAttempts) { + try { + return await fn(); + } catch (err) { + if (++attempts >= maxAttempts) throw err; + await new Promise(resolve => setTimeout(resolve, interval)); + } + } +} + +// NOTE: Chromium may exit with SIGSEGV, so retry in that case +export async function executeBrowserTest(browser, launchPuppeteer: () => Promise, additionalAssertion: () => void, onError: (err: Error) => void) { + let count = 0; + do { + count++; + try { + const page = await browser.newPage(); + + page.on('console', (type) => { + console[type._type](type._text); + }); + + page.on('error', error => { + console.log('>>> an error happened'); + console.error(error); + }); + await page.goto('http://localhost:6789'); + const result = await page.evaluate(() => test(document.querySelector('main'))); + if (result) console.log(result); + additionalAssertion(); + await page.close(); + break; + } catch (err) { + if (count === 5 || browser.isConnected()) { + onError(err); + throw err; + } + console.debug(err.stack || err); + console.log('RESTARTING Chromium...'); + browser = await launchPuppeteer(); + } + } while (count <= 5); + return browser; +} diff --git a/test/runtime-puppeteer/index.ts b/test/runtime-puppeteer/index.ts index b700d47945..8e6f813de4 100644 --- a/test/runtime-puppeteer/index.ts +++ b/test/runtime-puppeteer/index.ts @@ -9,7 +9,9 @@ import { loadConfig, loadSvelte, mkdirp, - prettyPrintPuppeteerAssertionError + prettyPrintPuppeteerAssertionError, + retryAsync, + executeBrowserTest } from '../helpers'; import { deepEqual } from 'assert'; @@ -48,15 +50,21 @@ function create_server() { }); } +async function launchPuppeteer() { + return await retryAsync(() => puppeteer.launch()); +} + const assert = fs.readFileSync(`${__dirname}/assert.js`, 'utf-8'); -describe('runtime (puppeteer)', () => { +describe('runtime (puppeteer)', function() { + // Note: Increase the timeout in preparation for restarting Chromium due to SIGSEGV. + this.timeout(10000); before(async () => { svelte = loadSvelte(false); console.log('[runtime-puppeteer] Loaded Svelte'); server = await create_server(); console.log('[runtime-puppeteer] Started server'); - browser = await puppeteer.launch(); + browser = await launchPuppeteer(); console.log('[runtime-puppeteer] Launched puppeteer browser'); }); @@ -205,27 +213,7 @@ describe('runtime (puppeteer)', () => { const result = await bundle.generate({ format: 'iife', name: 'test' }); code = result.output[0].code; - const page = await browser.newPage(); - - page.on('console', (type) => { - console[type._type](type._text); - }); - - page.on('error', error => { - console.log('>>> an error happened'); - console.error(error); - }); - - try { - await page.goto('http://localhost:6789'); - - const result = await page.evaluate(() => test(document.querySelector('main'))); - if (result) console.log(result); - } catch (err) { - failed.add(dir); - prettyPrintPuppeteerAssertionError(err.message); - throw err; - } finally { + function assertWarnings() { if (config.warnings) { deepEqual(warnings.map(w => ({ code: w.code, @@ -240,6 +228,16 @@ describe('runtime (puppeteer)', () => { throw new Error('Received unexpected warnings'); } } + + browser = await executeBrowserTest( + browser, + launchPuppeteer, + assertWarnings, + (err) => { + failed.add(dir); + prettyPrintPuppeteerAssertionError(err.message); + assertWarnings(); + }); }); } diff --git a/test/runtime/index.ts b/test/runtime/index.ts index e777196ae2..4fc3880208 100644 --- a/test/runtime/index.ts +++ b/test/runtime/index.ts @@ -65,7 +65,7 @@ describe('runtime', () => { } const testName = `${dir} ${hydrate ? `(with hydration${from_ssr_html ? ' from ssr rendered html' : ''})` : ''}`; - (config.skip ? it.skip : solo ? it.only : it)(testName, () => { + (config.skip ? it.skip : solo ? it.only : it)(testName, (done) => { if (failed.has(dir)) { // this makes debugging easier, by only printing compiled output once throw new Error('skipping test, already failed'); @@ -96,7 +96,7 @@ describe('runtime', () => { glob('**/*.svelte', { cwd }).forEach(file => { if (file[0] === '_') return; - const dir = `${cwd}/_output/${hydrate ? 'hydratable' : 'normal'}`; + const dir = `${cwd}/_output/${hydrate ? 'hydratable' : 'normal'}`; const out = `${dir}/${file.replace(/\.svelte$/, '.js')}`; if (fs.existsSync(out)) { @@ -120,7 +120,7 @@ describe('runtime', () => { } }); - return Promise.resolve() + Promise.resolve() .then(() => { // hack to support transition tests clear_loops(); @@ -245,6 +245,7 @@ describe('runtime', () => { .catch(err => { // print a clickable link to open the directory err.stack += `\n\ncmd-click: ${path.relative(process.cwd(), cwd)}/main.svelte`; + done(); throw err; }) .then(() => { @@ -255,6 +256,7 @@ describe('runtime', () => { flush(); if (config.after_test) config.after_test(); + done(); }); }); } diff --git a/test/server-side-rendering/index.ts b/test/server-side-rendering/index.ts index cc3f20d637..f4175c73a6 100644 --- a/test/server-side-rendering/index.ts +++ b/test/server-side-rendering/index.ts @@ -52,21 +52,23 @@ describe('ssr', () => { throw new Error('Forgot to remove `solo: true` from test'); } - (solo ? it.only : it)(dir, () => { - dir = path.resolve(`${__dirname}/samples`, dir); + (solo ? it.only : it)(dir, (done) => { - cleanRequireCache(); + try { - const compileOptions = { - sveltePath, - ...config.compileOptions, - generate: 'ssr', - format: 'cjs' - }; + dir = path.resolve(`${__dirname}/samples`, dir); - require('../../register')(compileOptions); + cleanRequireCache(); + + const compileOptions = { + sveltePath, + ...config.compileOptions, + generate: 'ssr', + format: 'cjs' + }; + + require('../../register')(compileOptions); - try { const Component = require(`${dir}/main.svelte`).default; const expectedHtml = tryToReadFile(`${dir}/_expected.html`); @@ -132,6 +134,7 @@ describe('ssr', () => { throw err; } finally { set_current_component(null); + done(); } }); });