mirror of https://github.com/miraclx/freyr-js
revamp test infra (#303)
This commit is contained in:
parent
cabf965a39
commit
5f939e2b85
|
|
@ -9,6 +9,9 @@ on:
|
|||
schedule:
|
||||
- cron: '0 0 * * WED,SAT' # 00:00 on Wednesdays and Saturdays, weekly.
|
||||
|
||||
env:
|
||||
DOCKER_ARGS: "--user root"
|
||||
|
||||
jobs:
|
||||
up-to-date:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -101,9 +104,7 @@ jobs:
|
|||
docker image ls -a
|
||||
|
||||
- name: Spotify - Download Track
|
||||
run: |
|
||||
DOCKER_ARGS="-v $(pwd)/stage:/data --user root" \
|
||||
npm run test -- --docker freyr-dev spotify.track
|
||||
run: npm test -- --docker freyr-dev spotify.track
|
||||
|
||||
spotify-album:
|
||||
if: ${{ always() }}
|
||||
|
|
@ -132,9 +133,7 @@ jobs:
|
|||
docker image ls -a
|
||||
|
||||
- name: Spotify - Download Album
|
||||
run: |
|
||||
DOCKER_ARGS="-v $(pwd)/stage:/data --user root" \
|
||||
npm run test -- --docker freyr-dev spotify.album
|
||||
run: npm test -- --docker freyr-dev spotify.album
|
||||
|
||||
spotify-artist:
|
||||
if: ${{ always() }}
|
||||
|
|
@ -163,9 +162,7 @@ jobs:
|
|||
docker image ls -a
|
||||
|
||||
- name: Spotify - Download Album
|
||||
run: |
|
||||
DOCKER_ARGS="-v $(pwd)/stage:/data --user root" \
|
||||
npm run test -- --docker freyr-dev spotify.artist
|
||||
run: npm test -- --docker freyr-dev spotify.artist
|
||||
|
||||
spotify-playlist:
|
||||
if: ${{ always() }}
|
||||
|
|
@ -194,9 +191,7 @@ jobs:
|
|||
docker image ls -a
|
||||
|
||||
- name: Spotify - Download Playlist
|
||||
run: |
|
||||
DOCKER_ARGS="-v $(pwd)/stage:/data --user root" \
|
||||
npm run test -- --docker freyr-dev spotify.playlist
|
||||
run: npm test -- --docker freyr-dev spotify.playlist
|
||||
|
||||
apple-music:
|
||||
if: false
|
||||
|
|
@ -231,9 +226,7 @@ jobs:
|
|||
docker image ls -a
|
||||
|
||||
- name: Apple Music - Download Track
|
||||
run: |
|
||||
DOCKER_ARGS="-v $(pwd)/stage:/data --user root" \
|
||||
npm run test -- --docker freyr-dev apple_music.track
|
||||
run: npm test -- --docker freyr-dev apple_music.track
|
||||
|
||||
apple-music-album:
|
||||
if: ${{ always() }}
|
||||
|
|
@ -262,9 +255,7 @@ jobs:
|
|||
docker image ls -a
|
||||
|
||||
- name: Apple Music - Download Album
|
||||
run: |
|
||||
DOCKER_ARGS="-v $(pwd)/stage:/data --user root" \
|
||||
npm run test -- --docker freyr-dev apple_music.album
|
||||
run: npm test -- --docker freyr-dev apple_music.album
|
||||
|
||||
apple-music-artist:
|
||||
if: ${{ always() }}
|
||||
|
|
@ -293,9 +284,7 @@ jobs:
|
|||
docker image ls -a
|
||||
|
||||
- name: Apple Music - Download Artist
|
||||
run: |
|
||||
DOCKER_ARGS="-v $(pwd)/stage:/data --user root" \
|
||||
npm run test -- --docker freyr-dev apple_music.artist
|
||||
run: npm test -- --docker freyr-dev apple_music.artist
|
||||
|
||||
apple-music-playlist:
|
||||
if: ${{ always() }}
|
||||
|
|
@ -324,9 +313,7 @@ jobs:
|
|||
docker image ls -a
|
||||
|
||||
- name: Apple Music - Download Playlist
|
||||
run: |
|
||||
DOCKER_ARGS="-v $(pwd)/stage:/data --user root" \
|
||||
npm run test -- --docker freyr-dev apple_music.playlist
|
||||
run: npm test -- --docker freyr-dev apple_music.playlist
|
||||
|
||||
deezer:
|
||||
if: false
|
||||
|
|
@ -361,9 +348,7 @@ jobs:
|
|||
docker image ls -a
|
||||
|
||||
- name: Deezer - Download Track
|
||||
run: |
|
||||
DOCKER_ARGS="-v $(pwd)/stage:/data --user root" \
|
||||
npm run test -- --docker freyr-dev deezer.track
|
||||
run: npm test -- --docker freyr-dev deezer.track
|
||||
|
||||
deezer-album:
|
||||
if: ${{ always() }}
|
||||
|
|
@ -392,9 +377,7 @@ jobs:
|
|||
docker image ls -a
|
||||
|
||||
- name: Deezer - Download Album
|
||||
run: |
|
||||
DOCKER_ARGS="-v $(pwd)/stage:/data --user root" \
|
||||
npm run test -- --docker freyr-dev deezer.album
|
||||
run: npm test -- --docker freyr-dev deezer.album
|
||||
|
||||
deezer-artist:
|
||||
if: ${{ always() }}
|
||||
|
|
@ -423,9 +406,7 @@ jobs:
|
|||
docker image ls -a
|
||||
|
||||
- name: Deezer - Download Artist
|
||||
run: |
|
||||
DOCKER_ARGS="-v $(pwd)/stage:/data --user root" \
|
||||
npm run test -- --docker freyr-dev deezer.artist
|
||||
run: npm test -- --docker freyr-dev deezer.artist
|
||||
|
||||
deezer-playlist:
|
||||
if: ${{ always() }}
|
||||
|
|
@ -454,9 +435,7 @@ jobs:
|
|||
docker image ls -a
|
||||
|
||||
- name: Deezer - Download Playlist
|
||||
run: |
|
||||
DOCKER_ARGS="-v $(pwd)/stage:/data --user root" \
|
||||
npm run test -- --docker freyr-dev deezer.playlist
|
||||
run: npm test -- --docker freyr-dev deezer.playlist
|
||||
|
||||
|
||||
docker-publish:
|
||||
|
|
|
|||
18
TEST.md
18
TEST.md
|
|
@ -6,34 +6,40 @@ freyr is bundled with its own flexibly customizable test runner.
|
|||
- To run all tests
|
||||
|
||||
```console
|
||||
npm run test -- --all
|
||||
npm test -- --all
|
||||
```
|
||||
|
||||
- To run just Spotify tests
|
||||
|
||||
```console
|
||||
npm run test -- spotify
|
||||
npm test -- spotify
|
||||
```
|
||||
|
||||
- To run just Apple Music artist tests
|
||||
|
||||
```console
|
||||
npm run test -- apple_music.artist
|
||||
npm test -- apple_music.artist
|
||||
```
|
||||
|
||||
- You can use a custom test suite (see the [default suite](https://github.com/miraclx/freyr-js/blob/master/test/default.json) for an example)
|
||||
|
||||
```console
|
||||
npm run test -- --all --suite ./special_cases.json
|
||||
npm test -- --all --suite ./special_cases.json
|
||||
```
|
||||
|
||||
- And optionally, you can run the tests inside a freyr docker container
|
||||
|
||||
```console
|
||||
npm run test -- deezer --docker freyr-dev:latest
|
||||
npm test -- deezer --docker freyr-dev:latest
|
||||
```
|
||||
|
||||
## `npm run test -- --help`
|
||||
- You can customize the working directory for storing the tracks and logs
|
||||
|
||||
```console
|
||||
npm test -- spotify.track --name run-1 --stage ./test-runs
|
||||
```
|
||||
|
||||
## `npm test -- --help`
|
||||
|
||||
```console
|
||||
freyr-test
|
||||
|
|
|
|||
4
cli.js
4
cli.js
|
|
@ -736,7 +736,7 @@ async function init(packageJson, queries, options) {
|
|||
data.store.get('progressBar').print(opts.retryMessage({ref: data.index + 1, ...data}));
|
||||
else {
|
||||
if (!options.bar) logger.write('\x1b[G\x1b[K');
|
||||
logger.write(opts.retryMessage(data), '\n');
|
||||
logger.write(opts.retryMessage({ref: data.index + 1, ...data}), '\n');
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -807,7 +807,7 @@ async function init(packageJson, queries, options) {
|
|||
if (options.bar) barGen.print(data);
|
||||
else {
|
||||
logger.write('\x1b[G\x1b[K');
|
||||
logger.write(data, '\n');
|
||||
logger.write({ref: `${i}[${data.index + 1}]`, ...data}, '\n');
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ const removeCallbacks = [];
|
|||
|
||||
const open = promisify(fs.open);
|
||||
const close = promisify(fs.close);
|
||||
const exists = promisify(fs.exists);
|
||||
const unlink = promisify(fs.unlink);
|
||||
|
||||
function garbageCollector() {
|
||||
|
|
@ -31,7 +30,6 @@ export default async function genFile(opts) {
|
|||
opts = opts || {};
|
||||
if (opts.filename) {
|
||||
opts.tmpdir = opts.tmpdir || tmpdir();
|
||||
if (!(await exists(opts.tmpdir))) throw new Error('tmpdir does not exist');
|
||||
const dir = join(opts.tmpdir, opts.dirname || '.');
|
||||
await mkdirp(dir);
|
||||
const name = join(dir, opts.filename);
|
||||
|
|
|
|||
191
test/index.js
191
test/index.js
|
|
@ -1,13 +1,16 @@
|
|||
import fs from 'fs';
|
||||
import url from 'url';
|
||||
import path from 'path';
|
||||
import util from 'util';
|
||||
import {randomUUID} from 'crypto';
|
||||
import {tmpdir} from 'os';
|
||||
import {randomBytes} from 'crypto';
|
||||
import {PassThrough} from 'stream';
|
||||
import {spawn} from 'child_process';
|
||||
import {relative, join, resolve} from 'path';
|
||||
|
||||
import fileMgr from '../src/file_mgr.js';
|
||||
|
||||
const exists = util.promisify(fs.exists);
|
||||
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
function sed(fn) {
|
||||
|
|
@ -52,40 +55,65 @@ async function pRetry(tries, fn) {
|
|||
return result;
|
||||
}
|
||||
|
||||
async function run_tests(stage, args) {
|
||||
let docker_image, i;
|
||||
if ((docker_image = !!~(i = args.indexOf('--docker'))) && !(docker_image = args.splice(i, 2)[1]))
|
||||
function short_path(path) {
|
||||
let a = resolve(path);
|
||||
let b = relative(process.cwd(), path);
|
||||
if (!['..', '/'].some(c => b.startsWith(c))) b = `./${b}`;
|
||||
return a.length < b.length ? a : b;
|
||||
}
|
||||
|
||||
const default_stage = join(tmpdir(), 'freyr-test');
|
||||
|
||||
async function run_tests(suite, args, i) {
|
||||
let docker_image;
|
||||
if (~(i = args.indexOf('--docker')) && !(docker_image = args.splice(i, 2)[1]))
|
||||
throw new Error('`--docker` requires an image name');
|
||||
let stage_name = randomBytes(6).toString('hex');
|
||||
if (~(i = args.indexOf('--name')) && !(stage_name = args.splice(i, 2)[1])) throw new Error('`--name` requires a stage name');
|
||||
let stage_path = default_stage;
|
||||
if (~(i = args.indexOf('--stage')) && !(stage_path = args.splice(i, 2)[1])) throw new Error('`--stage` requires a path');
|
||||
stage_path = resolve(join(stage_path, stage_name));
|
||||
let force, clean;
|
||||
if ((force = !!~(i = args.indexOf('--force')))) args.splice(i, 1);
|
||||
if ((clean = !!~(i = args.indexOf('--clean')))) args.splice(i, 1);
|
||||
if (await exists(stage_path))
|
||||
if (!force) throw new Error(`stage path [${stage_path}] already exists`);
|
||||
else if (clean) fs.rmdirSync(stage_path);
|
||||
|
||||
let is_gha = 'GITHUB_ACTIONS' in process.env && process.env['GITHUB_ACTIONS'] === 'true';
|
||||
if (~(i = args.indexOf('--all'))) args = Object.keys(stage);
|
||||
|
||||
let tests = args;
|
||||
if (~(i = args.indexOf('--all'))) args.splice(i, 1), (tests = Object.keys(suite));
|
||||
let invalidArg;
|
||||
if ((invalidArg = args.find(arg => arg.startsWith('-')))) throw new Error(`Invalid argument: ${invalidArg}`);
|
||||
|
||||
for (let [i, arg] of args.entries()) {
|
||||
let [service, type] = arg.split('.');
|
||||
if (!(service in stage)) throw new Error(`Invalid service: ${service}`);
|
||||
if (!tests.length) return noService;
|
||||
|
||||
for (let [i, test] of tests.entries()) {
|
||||
let [service, type] = test.split('.');
|
||||
if (!(service in suite)) throw new Error(`Invalid service: ${service}`);
|
||||
if (!type) {
|
||||
args.splice(i + 1, 0, ...Object.keys(stage[service]).map(type => `${arg}.${type}`));
|
||||
tests.splice(i + 1, 0, ...Object.keys(suite[service]).map(type => `${test}.${type}`));
|
||||
continue;
|
||||
}
|
||||
|
||||
let {uri, filter = [], expect} = stage[service][type];
|
||||
let {uri, filter = [], expect} = suite[service][type];
|
||||
|
||||
let test_stage_path = join(stage_path, test);
|
||||
|
||||
let preargs = ['--no-logo', '--no-header', '--no-bar'];
|
||||
if (is_gha) preargs.push('--no-auth');
|
||||
let child_args = [...preargs, uri, ...filter.map(f => `--filter=${f}`)];
|
||||
if (force) preargs.push('--force');
|
||||
let child_args = [...preargs, ...filter.map(f => `--filter=${f}`)];
|
||||
|
||||
let unmetExpectations = new Error('One or more expectations failed');
|
||||
|
||||
let child_id = randomUUID();
|
||||
|
||||
await pRetry(3, async (attempt, lastErr, abort) => {
|
||||
if (attempt > 1 && lastErr !== unmetExpectations) abort();
|
||||
|
||||
let logFile = await fileMgr({
|
||||
filename: `${service}-${type}-${attempt}.log`,
|
||||
dirname: path.join('freyr-test', child_id),
|
||||
tmpdir: test_stage_path,
|
||||
keep: true,
|
||||
mode: fs.constants.W_OK,
|
||||
});
|
||||
|
|
@ -117,8 +145,15 @@ async function run_tests(stage, args) {
|
|||
let child, handler;
|
||||
|
||||
if (!docker_image) {
|
||||
child = spawn('node', [path.relative(process.cwd(), path.join(__dirname, '..', 'cli.js')), ...child_args]);
|
||||
child = spawn('node', [
|
||||
short_path(join(__dirname, '..', 'cli.js')),
|
||||
...child_args,
|
||||
'--directory',
|
||||
short_path(test_stage_path),
|
||||
uri,
|
||||
]);
|
||||
} else {
|
||||
let child_id = `${test}.${stage_name}`;
|
||||
let extra_docker_args = process.env['DOCKER_ARGS'] ? process.env['DOCKER_ARGS'].split(' ') : [];
|
||||
child = spawn('docker', [
|
||||
'run',
|
||||
|
|
@ -130,8 +165,11 @@ async function run_tests(stage, args) {
|
|||
child_id,
|
||||
'--network',
|
||||
'host',
|
||||
'--volume',
|
||||
`${test_stage_path}:/data`,
|
||||
docker_image,
|
||||
...child_args,
|
||||
uri,
|
||||
]);
|
||||
process.on('SIGINT', (handler = () => (spawn('docker', ['kill', child_id]), process.off('SIGINT', handler))));
|
||||
}
|
||||
|
|
@ -205,69 +243,74 @@ async function run_tests(stage, args) {
|
|||
}
|
||||
|
||||
let errorCauses = Symbol('ErrorCauses');
|
||||
let noService = Symbol('noService');
|
||||
|
||||
function main() {
|
||||
let args = process.argv.slice(2);
|
||||
async function main(args) {
|
||||
let suite, test_suite, i;
|
||||
|
||||
let stage, test_suite, i;
|
||||
if (~(i = args.indexOf('--suite')) && !(test_suite = args.splice(i, 2)[1])) throw new Error('`--suite` requires a file path');
|
||||
|
||||
if ((test_suite = !!~(i = args.indexOf('--suite'))) && !(test_suite = args.splice(i, 2)[1]))
|
||||
throw new Error('`--suite` requires a file path');
|
||||
suite = JSON.parse(fs.readFileSync(test_suite || join(__dirname, 'default.json')));
|
||||
|
||||
try {
|
||||
stage = JSON.parse(fs.readFileSync(test_suite || path.join(__dirname, 'default.json')));
|
||||
} catch (e) {
|
||||
(i = ''), (stage = JSON.parse(fs.readFileSync(path.join(__dirname, 'default.json'))));
|
||||
console.error("\x1b[33mCouldn't read test suite file\x1b[0m\n", e.message, '\n');
|
||||
if (!['-h', '--help'].some(args.includes.bind(args))) {
|
||||
try {
|
||||
if (noService !== (await run_tests(suite, args))) return;
|
||||
} catch (err) {
|
||||
console.error('An error occurred!');
|
||||
if (errorCauses in err) {
|
||||
let causes = err[errorCauses];
|
||||
delete err[errorCauses];
|
||||
console.error('', err);
|
||||
for (let cause of causes) console.error('', cause);
|
||||
} else console.error('', err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!args.length || i === '' || args.includes('--help') || args.includes('-h')) {
|
||||
console.log('freyr-test');
|
||||
console.log('----------');
|
||||
console.log('Usage: freyr-test [options] [<SERVICE>[.<TYPE>]...]');
|
||||
console.log();
|
||||
console.log('Utility for testing the Freyr CLI');
|
||||
console.log();
|
||||
console.log('Options:');
|
||||
console.log();
|
||||
console.log(` SERVICE ${Object.keys(stage).join(' / ')}`);
|
||||
console.log(` TYPE ${[...new Set(Object.values(stage).flatMap(s => Object.keys(s)))].join(' / ')}`);
|
||||
console.log();
|
||||
console.log(' --all run all tests');
|
||||
console.log(' --suite <SUITE> use a specific test suite (json)');
|
||||
console.log(' --docker <IMAGE> run tests in a docker container');
|
||||
console.log(' --help show this help message');
|
||||
console.log();
|
||||
console.log('Enviroment Variables:');
|
||||
console.log();
|
||||
console.log(' DOCKER_ARGS arguments to pass to `docker run`');
|
||||
console.log();
|
||||
console.log('Example:');
|
||||
console.log();
|
||||
console.log(' $ freyr-test --all');
|
||||
console.log(' runs all tests');
|
||||
console.log();
|
||||
console.log(' $ freyr-test spotify');
|
||||
console.log(' runs all Spotify tests');
|
||||
console.log();
|
||||
console.log(' $ freyr-test apple_music.album');
|
||||
console.log(' tests downloading an Apple Music album');
|
||||
console.log();
|
||||
console.log(' $ freyr-test spotify.track deezer.artist');
|
||||
console.log(' tests downloading a Spotify track and Deezer artist');
|
||||
return;
|
||||
}
|
||||
|
||||
run_tests(stage, args).catch(err => {
|
||||
console.error('An error occurred!');
|
||||
if (errorCauses in err) {
|
||||
let causes = err[errorCauses];
|
||||
delete err[errorCauses];
|
||||
console.error('', err);
|
||||
for (let cause of causes) console.error('', cause);
|
||||
} else console.error('', err);
|
||||
process.exit(1);
|
||||
});
|
||||
console.log('freyr-test');
|
||||
console.log('----------');
|
||||
console.log('Usage: freyr-test [options] [<SERVICE>[.<TYPE>]...]');
|
||||
console.log();
|
||||
console.log('Utility for testing the Freyr CLI');
|
||||
console.log();
|
||||
console.log('Options:');
|
||||
console.log();
|
||||
console.log(` SERVICE ${Object.keys(suite).join(' / ')}`);
|
||||
console.log(` TYPE ${[...new Set(Object.values(suite).flatMap(s => Object.keys(s)))].join(' / ')}`);
|
||||
console.log();
|
||||
console.log(' --all run all tests');
|
||||
console.log(' --suite <SUITE> use a specific test suite (json)');
|
||||
console.log(' --docker <IMAGE> run tests in a docker container');
|
||||
console.log(' --name <NAME> name for this test run (defaults to a random hex string)');
|
||||
console.log(` --stage <PATH> directory to stage this test (default: ${default_stage})`);
|
||||
console.log(' --force force overwriting of existing staged files');
|
||||
console.log(' --clean (when --force is used) clean existing stage before reusing it');
|
||||
console.log(' --help show this help message');
|
||||
console.log();
|
||||
console.log('Enviroment Variables:');
|
||||
console.log();
|
||||
console.log(' DOCKER_ARGS arguments to pass to `docker run`');
|
||||
console.log();
|
||||
console.log('Example:');
|
||||
console.log();
|
||||
console.log(' $ freyr-test --all');
|
||||
console.log(' runs all tests');
|
||||
console.log();
|
||||
console.log(' $ freyr-test spotify');
|
||||
console.log(' runs all Spotify tests');
|
||||
console.log();
|
||||
console.log(' $ freyr-test apple_music.album');
|
||||
console.log(' tests downloading an Apple Music album');
|
||||
console.log();
|
||||
console.log(' $ freyr-test spotify.track deezer.artist');
|
||||
console.log(' tests downloading a Spotify track and Deezer artist');
|
||||
console.log();
|
||||
console.log(' $ freyr-test spotify.track --stage ./stage --name test-run');
|
||||
console.log(' downloads the Spotify test track in ./stage/test-run/spotify.track with logs');
|
||||
}
|
||||
|
||||
main();
|
||||
function _start() {
|
||||
main(process.argv.slice(2));
|
||||
}
|
||||
|
||||
_start();
|
||||
|
|
|
|||
Loading…
Reference in New Issue