openvibers/openvibe.tools Commits
npm test no longer counts a skipped test as passed (roadmap WS-Q task 7, the rule of openvibe-shared/test-runner). test/run.js counted every app test that exited 0 as passed, so the three audio tests without ffmpeg and sdk-client without an OpenVibe.SDK checkout, which check nothing and exit 0, read as ok. A test now prints "<label>: skipped (<why>)" for what it could not run, and the runner lists it as skip with its reasons and does not count it as passed: the summary reads "52/55 test files passed, 3 skipped (…)", and only a run with nothing skipped says N/N; --strict (or OV_TEST_STRICT=1) makes a skip fail the run. The per-app runner stays (the syntax check, each test from its app directory, 180 s timeout); SKIP_RE and skipsIn are copied from the module because the root has no dependencies (each app pins openvibe-shared on its own, unchanged here). audio merge, audio jobs and audio hardening printed "SKIPPED (no ffmpeg on this machine)" and now print "skipped (…)"; sdk-client already printed the line. Without ffmpeg the three audio files are listed as skipped. npm test (Node 22, ffmpeg and the SDK checkout present): syntax 199/199, 55/55 test files passed.
Changes
@@@ -13,7 +13,7 @@ const { execFileSync } = require('child_process');const { startApp } = require('../../_shared/test/spawn');try { execFileSync('ffmpeg', ['-version'], { stdio: 'ignore' }); } catch {-- console.log('audio hardening: SKIPPED (no ffmpeg on this machine)');++ console.log('audio hardening: skipped (no ffmpeg on this machine)');process.exit(0);}@@@ -12,7 +12,7 @@ const { startApp } = require('../../_shared/test/spawn');const sleep = (ms) => new Promise(r => setTimeout(r, ms));try { execFileSync('ffmpeg', ['-version'], { stdio: 'ignore' }); } catch {-- console.log('audio jobs: SKIPPED (no ffmpeg on this machine)');++ console.log('audio jobs: skipped (no ffmpeg on this machine)');process.exit(0);}@@@ -12,7 +12,7 @@ const { startApp } = require('../../_shared/test/spawn');const sleep = (ms) => new Promise(r => setTimeout(r, ms));try { execFileSync('ffmpeg', ['-version'], { stdio: 'ignore' }); } catch {-- console.log('audio merge: SKIPPED (no ffmpeg on this machine)');++ console.log('audio merge: skipped (no ffmpeg on this machine)');process.exit(0);}@@@ -1,13 +1,37 @@'use strict';// npm test — every app's tests (apps/*/test/*.test.js, apps/_shared/test/*.test.js), each in its own// process from its app directory, after a syntax check of every server file. Run on Node 22.++//++// npm test # everything++// npm test -- audio/test # only files whose path contains one of the words++// npm test -- --strict # a skipped test fails the run too (or OV_TEST_STRICT=1)++//++// A test that cannot run something here prints `<label>: skipped (<why>)` (no ffmpeg, no OpenVibe.SDK++// checkout): that file is listed as `skip` with its reasons and not counted as passed, so the summary++// reads `60/62 test files passed, 2 skipped (…)`; only a run with nothing skipped says `N/N test files++// passed`. This is the rule of openvibe-shared/test-runner, copied here because this root runner has no++// dependencies of its own (each app pins openvibe-shared separately).const { spawnSync } = require('child_process');const fs = require('fs');const path = require('path');const ROOT = path.join(__dirname, '..');const APPS = path.join(ROOT, 'apps');--const only = process.argv.slice(2);++const strict = process.argv.includes('--strict') || process.env.OV_TEST_STRICT === '1';++const only = process.argv.slice(2).filter(a => !a.startsWith('-'));++++/** `<label>: skipped (<why>)` on a line of its own (SKIP_RE from openvibe-shared/test-runner). */++const SKIP_RE = /^[ \t]*[\w .,'()/+#-]{1,120}: skipped \((.+)\)[ \t]*$/gm;++++/** The skip lines in a test's output, in order, without repeats (skipsIn from openvibe-shared/test-runner). */++function skipsIn(output) {++ const out = [];++ for (const m of String(output || '').matchAll(SKIP_RE)) {++ const line = m[0].trim();++ if (!out.includes(line)) out.push(line);++ }++ return out;++}function walk(dir, out = []) {for (const e of fs.readdirSync(dir, { withFileTypes: true })) {@@@ -37,17 +61,26 @@ const tests = apps.flatMap(a => {}).filter(t => !only.length || only.some(o => t.file.includes(o)));const failed = [];++const skipped = [];for (const t of tests) {const name = path.relative(ROOT, t.file);const t0 = Date.now();const r = spawnSync(process.execPath, [t.file], { cwd: path.join(APPS, t.app), encoding: 'utf8', timeout: 180_000, env: { ...process.env, NODE_ENV: 'test' } });const ms = Date.now() - t0;-- if (r.status === 0) {++ const skips = r.status === 0 ? skipsIn(`${r.stdout}${r.stderr}`) : [];++ if (skips.length) {++ skipped.push({ name, skips });++ console.log(`skip ${name} (${ms} ms) ${skips.join('; ')}`);++ } else if (r.status === 0) {console.log(`ok ${name} (${ms} ms)${r.stdout.trim() ? ` — ${r.stdout.trim().split('\n').pop()}` : ''}`);} else {failed.push(name);console.error(`FAIL ${name} (${ms} ms)\n${r.stdout}${r.stderr}${r.error ? r.error.message : ''}`);}}--console.log(`\n${tests.length - failed.length}/${tests.length} test files passed${failed.length ? `; failed: ${failed.join(', ')}` : ''}`);--process.exit(failed.length || bad ? 1 : 0);++const passed = tests.length - failed.length - skipped.length;++console.log(`\n${passed}/${tests.length} test files passed`++ + (skipped.length ? `, ${skipped.length} skipped (${skipped.map(s => `${s.name}: ${s.skips.join('; ')}`).join(' | ')})` : '')++ + (failed.length ? `; failed: ${failed.join(', ')}` : '')++ + (strict && skipped.length ? ' — strict: skips fail the run' : ''));++process.exit(failed.length || bad || (strict && skipped.length) ? 1 : 0);
@@ -13,7 +13,7 @@ const { execFileSync } = require('child_process');
@@ -13,7 +13,7 @@ const { execFileSync } = require('child_process');
const { startApp } = require('../../_shared/test/spawn');
const { startApp } = require('../../_shared/test/spawn');
try { execFileSync('ffmpeg', ['-version'], { stdio: 'ignore' }); } catch {
try { execFileSync('ffmpeg', ['-version'], { stdio: 'ignore' }); } catch {
- console.log('audio hardening: SKIPPED (no ffmpeg on this machine)');
+ console.log('audio hardening: skipped (no ffmpeg on this machine)');
process.exit(0);
process.exit(0);
}
}
@@ -12,7 +12,7 @@ const { startApp } = require('../../_shared/test/spawn');
@@ -12,7 +12,7 @@ const { startApp } = require('../../_shared/test/spawn');
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
try { execFileSync('ffmpeg', ['-version'], { stdio: 'ignore' }); } catch {
try { execFileSync('ffmpeg', ['-version'], { stdio: 'ignore' }); } catch {
- console.log('audio jobs: SKIPPED (no ffmpeg on this machine)');
+ console.log('audio jobs: skipped (no ffmpeg on this machine)');
process.exit(0);
process.exit(0);
}
}
@@ -12,7 +12,7 @@ const { startApp } = require('../../_shared/test/spawn');
@@ -12,7 +12,7 @@ const { startApp } = require('../../_shared/test/spawn');
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
try { execFileSync('ffmpeg', ['-version'], { stdio: 'ignore' }); } catch {
try { execFileSync('ffmpeg', ['-version'], { stdio: 'ignore' }); } catch {
- console.log('audio merge: SKIPPED (no ffmpeg on this machine)');
+ console.log('audio merge: skipped (no ffmpeg on this machine)');
process.exit(0);
process.exit(0);
}
}
@@ -1,13 +1,37 @@
@@ -1,13 +1,37 @@
'use strict';
'use strict';
// npm test — every app's tests (apps/*/test/*.test.js, apps/_shared/test/*.test.js), each in its own
// npm test — every app's tests (apps/*/test/*.test.js, apps/_shared/test/*.test.js), each in its own
// process from its app directory, after a syntax check of every server file. Run on Node 22.
// process from its app directory, after a syntax check of every server file. Run on Node 22.
+//
+// npm test # everything
+// npm test -- audio/test # only files whose path contains one of the words
+// npm test -- --strict # a skipped test fails the run too (or OV_TEST_STRICT=1)
+//
+// A test that cannot run something here prints `<label>: skipped (<why>)` (no ffmpeg, no OpenVibe.SDK
+// checkout): that file is listed as `skip` with its reasons and not counted as passed, so the summary
+// reads `60/62 test files passed, 2 skipped (…)`; only a run with nothing skipped says `N/N test files
+// passed`. This is the rule of openvibe-shared/test-runner, copied here because this root runner has no
+// dependencies of its own (each app pins openvibe-shared separately).
const { spawnSync } = require('child_process');
const { spawnSync } = require('child_process');
const fs = require('fs');
const fs = require('fs');
const path = require('path');
const path = require('path');
const ROOT = path.join(__dirname, '..');
const ROOT = path.join(__dirname, '..');
const APPS = path.join(ROOT, 'apps');
const APPS = path.join(ROOT, 'apps');
-const only = process.argv.slice(2);
+const strict = process.argv.includes('--strict') || process.env.OV_TEST_STRICT === '1';
+const only = process.argv.slice(2).filter(a => !a.startsWith('-'));
+
+/** `<label>: skipped (<why>)` on a line of its own (SKIP_RE from openvibe-shared/test-runner). */
+const SKIP_RE = /^[ \t]*[\w .,'()/+#-]{1,120}: skipped \((.+)\)[ \t]*$/gm;
+
+/** The skip lines in a test's output, in order, without repeats (skipsIn from openvibe-shared/test-runner). */
+function skipsIn(output) {
+ const out = [];
+ for (const m of String(output || '').matchAll(SKIP_RE)) {
+ const line = m[0].trim();
+ if (!out.includes(line)) out.push(line);
+ }
+ return out;
+}
function walk(dir, out = []) {
function walk(dir, out = []) {
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
@@ -37,17 +61,26 @@ const tests = apps.flatMap(a => {
@@ -37,17 +61,26 @@ const tests = apps.flatMap(a => {
}).filter(t => !only.length || only.some(o => t.file.includes(o)));
}).filter(t => !only.length || only.some(o => t.file.includes(o)));
const failed = [];
const failed = [];
+const skipped = [];
for (const t of tests) {
for (const t of tests) {
const name = path.relative(ROOT, t.file);
const name = path.relative(ROOT, t.file);
const t0 = Date.now();
const t0 = Date.now();
const r = spawnSync(process.execPath, [t.file], { cwd: path.join(APPS, t.app), encoding: 'utf8', timeout: 180_000, env: { ...process.env, NODE_ENV: 'test' } });
const r = spawnSync(process.execPath, [t.file], { cwd: path.join(APPS, t.app), encoding: 'utf8', timeout: 180_000, env: { ...process.env, NODE_ENV: 'test' } });
const ms = Date.now() - t0;
const ms = Date.now() - t0;
- if (r.status === 0) {
+ const skips = r.status === 0 ? skipsIn(`${r.stdout}${r.stderr}`) : [];
+ if (skips.length) {
+ skipped.push({ name, skips });
+ console.log(`skip ${name} (${ms} ms) ${skips.join('; ')}`);
+ } else if (r.status === 0) {
console.log(`ok ${name} (${ms} ms)${r.stdout.trim() ? ` — ${r.stdout.trim().split('\n').pop()}` : ''}`);
console.log(`ok ${name} (${ms} ms)${r.stdout.trim() ? ` — ${r.stdout.trim().split('\n').pop()}` : ''}`);
} else {
} else {
failed.push(name);
failed.push(name);
console.error(`FAIL ${name} (${ms} ms)\n${r.stdout}${r.stderr}${r.error ? r.error.message : ''}`);
console.error(`FAIL ${name} (${ms} ms)\n${r.stdout}${r.stderr}${r.error ? r.error.message : ''}`);
}
}
}
}
-console.log(`\n${tests.length - failed.length}/${tests.length} test files passed${failed.length ? `; failed: ${failed.join(', ')}` : ''}`);
-process.exit(failed.length || bad ? 1 : 0);
+const passed = tests.length - failed.length - skipped.length;
+console.log(`\n${passed}/${tests.length} test files passed`
+ + (skipped.length ? `, ${skipped.length} skipped (${skipped.map(s => `${s.name}: ${s.skips.join('; ')}`).join(' | ')})` : '')
+ + (failed.length ? `; failed: ${failed.join(', ')}` : '')
+ + (strict && skipped.length ? ' — strict: skips fail the run' : ''));
+process.exit(failed.length || bad || (strict && skipped.length) ? 1 : 0);