Skip to content

Commit 195eeff

Browse files
authored
fix(ci): report build script failure + fix missing logs (#8188)
1 parent c0aa66c commit 195eeff

File tree

4 files changed

+148
-63
lines changed

4 files changed

+148
-63
lines changed

package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,9 @@
5959
"start": "node ./scripts start",
6060
"export": "node ./scripts website export",
6161
"build-tests": "rollup -c ./rollup.test.config.js",
62-
"test": "node ./scripts test",
6362
"test:unit-browser": "npm run test -- -s unit -p 8080 -l -c ",
6463
"test:visual-browser": "npm run test -- -s visual -p 8080 -l -c ",
65-
"test:old_but_gold": "qunit --require ./test/node_test_setup.js test/lib test/unit",
66-
"test:visual:old_but_gold": "qunit test/node_test_setup.js test/lib test/visual",
64+
"test": "node ./scripts test",
6765
"test:coverage": "nyc --silent qunit test/node_test_setup.js test/lib test/unit",
6866
"test:visual:coverage": "nyc --silent --no-clean qunit test/node_test_setup.js test/lib test/visual",
6967
"coverage:report": "nyc report --reporter=lcov --reporter=text",

scripts/index.js

Lines changed: 130 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
11
#!/usr/bin/env node
2+
3+
/**
4+
* Dearest fabric maintainer 💗,
5+
* This file contains the cli logic, which governs most of the available commands fabric has to offer.
6+
*
7+
* 📢 **IMPORTANT**
8+
* CI uses these commands.
9+
* In order for CI to correctly report the result of the command, the process must receive a correct exit code
10+
* meaning that if you `spawn` a process, make sure to listen to the `exit` event and terminate the main process with the relevant code.
11+
* Failing to do so will make CI report a false positive 📉.
12+
*/
13+
14+
15+
216
const fs = require('fs-extra');
317
const os = require('os');
418
const _ = require('lodash');
@@ -121,13 +135,13 @@ inquirer.registerPrompt('test-selection', ICheckbox);
121135

122136

123137
function build(options = {}) {
124-
const args = ['rollup', '-c', options.watch ? '--watch' : ''];
138+
const cmd = ['rollup', '-c', options.watch ? '--watch' : ''].join(' ');
125139
let minDest;
126140
if (options.output && !options.fast) {
127141
const { name, base, ...rest } = path.parse(path.resolve(options.output));
128142
minDest = path.format({ name: `${name}.min`, ...rest });
129143
}
130-
return cp.spawn(args.join(' '), {
144+
const processOptions = {
131145
stdio: 'inherit',
132146
shell: true,
133147
cwd: wd,
@@ -138,7 +152,21 @@ function build(options = {}) {
138152
BUILD_OUTPUT: options.output,
139153
BUILD_MIN_OUTPUT: minDest
140154
},
141-
});
155+
}
156+
if (options.watch) {
157+
cp.spawn(cmd, processOptions);
158+
}
159+
else {
160+
try {
161+
cp.execSync(cmd, processOptions);
162+
} catch (error) {
163+
// minimal logging, no need for stack trace
164+
console.error(error.message);
165+
// inform ci
166+
process.exit(1);
167+
}
168+
}
169+
142170
}
143171

144172
function startWebsite() {
@@ -244,57 +272,106 @@ function exportToWebsite(options) {
244272
})
245273
}
246274

247-
248275
/**
249-
*
250-
* @param {'unit' | 'visual'} suite
251-
* @param {string[] | null} tests file paths
252-
* @param {{debug?:boolean,recreate?:boolean,verbose?:boolean,filter?:string}} [options]
276+
*
277+
* @returns {Promise<boolean | undefined>} true if some tests failed
253278
*/
254-
async function test(suite, tests, options = {}) {
255-
const port = options.port || suite === 'visual' ? 8081 : 8080;
279+
async function runTestem({ suite, port, launch, dev, processOptions, context } = {}) {
280+
port = port || suite === 'visual' ? 8081 : 8080;
256281
try {
257282
await killPort(port);
258283
} catch (error) {
259284

260285
}
261286

262-
const args = [
263-
'testem',
264-
!options.dev ? 'ci' : '',
287+
if (launch) {
288+
// open localhost
289+
const url = `http://localhost:${port}/`;
290+
const start = (os.platform() === 'darwin' ? 'open' : os.platform() === 'win32' ? 'start' : 'xdg-open');
291+
cp.exec([start, url].join(' '));
292+
}
293+
294+
const processCmdOptions = [
265295
'-p', port,
266296
'-f', `test/testem.${suite}.js`,
267-
'-l', options.context.map(_.upperFirst).join(',')
297+
'-l', context.map(_.upperFirst).join(',')
268298
];
269299

270-
cp.spawn(args.join(' '), {
271-
cwd: wd,
272-
env: {
273-
...process.env,
274-
TEST_FILES: (tests || []).join(','),
275-
NODE_CMD: ['qunit', 'test/node_test_setup.js', 'test/lib'].concat(tests || `test/${suite}`).join(' '),
276-
VERBOSE: Number(options.verbose),
277-
QUNIT_DEBUG_VISUAL_TESTS: Number(options.debug),
278-
QUNIT_RECREATE_VISUAL_REFS: Number(options.recreate),
279-
QUNIT_FILTER: options.filter,
280-
REPORT_FILE: options.out
281-
},
282-
shell: true,
283-
stdio: 'inherit',
284-
detached: options.dev
285-
})
286-
.on('exit', (code) => {
287-
// propagate failed exit code to the process for ci to fail
288-
// don't exit if tests passed - this is for parallel local testing
289-
code && process.exit(code);
300+
if (dev) {
301+
cp.spawn(['testem', ...processCmdOptions].join(' '), {
302+
...processOptions,
303+
detached: true
290304
});
305+
}
306+
else {
307+
try {
308+
cp.execSync(['testem', 'ci', ...processCmdOptions].join(' '), processOptions);
309+
} catch (error) {
310+
return true;
311+
}
312+
}
313+
}
291314

292-
if (options.launch) {
293-
// open localhost
294-
const url = `http://localhost:${port}/`;
295-
const start = (os.platform() === 'darwin' ? 'open' : os.platform() === 'win32' ? 'start' : 'xdg-open');
296-
cp.exec([start, url].join(' '));
315+
/**
316+
*
317+
* @param {'unit' | 'visual'} suite
318+
* @param {string[] | null} tests file paths
319+
* @param {{debug?:boolean,recreate?:boolean,verbose?:boolean,filter?:string}} [options]
320+
* @returns {Promise<boolean | undefined>} true if some tests failed
321+
*/
322+
async function test(suite, tests, options = {}) {
323+
let failed = false;
324+
const qunitEnv = {
325+
QUNIT_DEBUG_VISUAL_TESTS: Number(options.debug),
326+
QUNIT_RECREATE_VISUAL_REFS: Number(options.recreate),
327+
QUNIT_FILTER: options.filter,
328+
};
329+
const env = {
330+
...process.env,
331+
TEST_FILES: (tests || []).join(','),
332+
NODE_CMD: ['qunit', 'test/node_test_setup.js', 'test/lib'].concat(tests || `test/${suite}`).join(' '),
333+
VERBOSE: Number(options.verbose),
334+
REPORT_FILE: options.out
335+
};
336+
const browserContexts = options.context.filter(c => c !== 'node');
337+
338+
// temporary revert
339+
// run node tests directly with qunit
340+
if (options.context.includes('node')) {
341+
try {
342+
cp.execSync(env.NODE_CMD, {
343+
cwd: wd,
344+
env: {
345+
...env,
346+
// browser takes precendence in golden ref generation
347+
...(browserContexts.length === 0 ? qunitEnv : {})
348+
},
349+
shell: true,
350+
stdio: 'inherit',
351+
});
352+
} catch (error) {
353+
failed = true;
354+
}
355+
}
356+
357+
if (browserContexts.length > 0) {
358+
failed = await runTestem({
359+
...options,
360+
suite,
361+
processOptions: {
362+
cwd: wd,
363+
env: {
364+
...env,
365+
...qunitEnv
366+
},
367+
shell: true,
368+
stdio: 'inherit',
369+
},
370+
context: browserContexts
371+
}) || failed;
297372
}
373+
374+
return failed;
298375
}
299376

300377
/**
@@ -410,15 +487,14 @@ async function runIntreactiveTestSuite(options) {
410487
}
411488
return acc;
412489
}, { unit: [], visual: [] });
413-
_.reduce(tests, async (queue, files, suite) => {
414-
await queue;
490+
return Promise.all(_.map(tests, (files, suite) => {
415491
if (files === true) {
416492
return test(suite, null, options);
417493
}
418494
else if (Array.isArray(files) && files.length > 0) {
419495
return test(suite, files, options);
420496
}
421-
}, Promise.resolve());
497+
}));
422498
}
423499

424500
program
@@ -467,31 +543,36 @@ program
467543
.option('-a, --all', 'run all tests', false)
468544
.option('-d, --debug', 'debug visual tests by overriding refs (golden images) in case of visual changes', false)
469545
.option('-r, --recreate', 'recreate visual refs (golden images)', false)
470-
.option('-v, --verbose', 'log passing tests', false)
546+
.option('-v, --verbose', 'log passing tests', true)
547+
.option('--no-verbose', 'disable verbose logging')
471548
.option('-l, --launch', 'launch tests in the browser', false)
472549
.option('--dev', 'runs testem in `dev` mode, without a `ci` flag', false)
473550
.addOption(new commander.Option('-c, --context <context...>', 'context to test in').choices(['node', 'chrome', 'firefox']).default(['node']))
474551
.option('-p, --port')
475552
.option('-o, --out <out>', 'path to report test results to')
476553
.option('--clear-cache', 'clear CLI test cache', false)
477-
.action((options) => {
554+
.action(async (options) => {
478555
if (options.clearCache) {
479556
fs.removeSync(CLI_CACHE);
480557
}
481558
if (options.all) {
482559
options.suite = ['unit', 'visual'];
483560
}
561+
const results = [];
484562
if (options.suite) {
485-
_.reduce(options.suite, async (queue, suite) => {
486-
await queue;
563+
results.push(...await Promise.all(_.map(options.suite, (suite) => {
487564
return test(suite, null, options);
488-
}, Promise.resolve());
565+
})));
489566
}
490567
else if (options.file) {
491-
test(options.file.startsWith('visual') ? 'visual' : 'unit', [`test/${options.file}`], options);
568+
results.push(await test(options.file.startsWith('visual') ? 'visual' : 'unit', [`test/${options.file}`], options));
492569
}
493570
else {
494-
runIntreactiveTestSuite(options);
571+
results.push(...await runIntreactiveTestSuite(options));
572+
}
573+
if (_.some(results)) {
574+
// inform ci that tests have failed
575+
process.exit(1);
495576
}
496577
});
497578

test/node_test_setup.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@ var chalk = require('chalk');
33
var diff = require('deep-object-diff').diff;
44
var commander = require('commander');
55

6-
// commander.program
7-
// .option('-d, --debug', 'debug visual tests by overriding refs (golden images) in case of visual changes', false)
8-
// .option('-r, --recreate', 'recreate visual refs (golden images)', false)
9-
// .action(options => {
10-
// QUnit.debug = QUnit.debugVisual = options.debug;
11-
// QUnit.recreateVisualRefs = options.recreate;
12-
// }).parse(process.argv);
13-
// // for now accept an env variable because qunit doesn't allow passing unknown options
14-
// QUnit.debugVisual = Number(process.env.QUNIT_DEBUG_VISUAL_TESTS);
15-
// QUnit.recreateVisualRefs = Number(process.env.QUNIT_RECREATE_VISUAL_REFS);
16-
// QUnit.config.filter = process.env.QUNIT_FILTER;
6+
commander.program
7+
.option('-d, --debug', 'debug visual tests by overriding refs (golden images) in case of visual changes', false)
8+
.option('-r, --recreate', 'recreate visual refs (golden images)', false)
9+
.action(options => {
10+
QUnit.debug = QUnit.debugVisual = options.debug;
11+
QUnit.recreateVisualRefs = options.recreate;
12+
}).parse(process.argv);
13+
// for now accept an env variable because qunit doesn't allow passing unknown options
14+
QUnit.debugVisual = Number(process.env.QUNIT_DEBUG_VISUAL_TESTS);
15+
QUnit.recreateVisualRefs = Number(process.env.QUNIT_RECREATE_VISUAL_REFS);
16+
QUnit.config.filter = process.env.QUNIT_FILTER;
17+
18+
process.on('uncaughtException', QUnit.onUncaughtException);
1719

1820
global.fabric = require('../dist/fabric').fabric;
1921
global.pixelmatch = require('pixelmatch');

test/tests.mustache

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ QUnit.config.reorder = false;
1818
<script>
1919
QUnit.testDone(visualCallback.testDone.bind(visualCallback));
2020
21+
window.addEventListener('unhandledrejection', function (event) {
22+
QUnit.onUncaughtException(event.reason);
23+
});
24+
2125
function downloadGoldens() {
2226
const checkbox = document.getElementById('qunit-urlconfig-hidepassed');
2327
checkbox.checked && checkbox.click();

0 commit comments

Comments
 (0)