Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vdiff subcommands #113

Merged
merged 6 commits into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions bin/d2l-test-runner.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,34 @@
#!/usr/bin/env node
import { argv } from 'node:process';
import commandLineArgs from 'command-line-args';
import process from 'node:process';
import { runner } from '../src/server/cli/test-runner.js';

const options = await runner.getOptions(argv);
const { argv, stdout } = process;
const cli = commandLineArgs({ name: 'subcommand', defaultOption: true }, { stopAtFirstUnknown: true, argv });

await runner.start(options);
if (cli.subcommand === 'vdiff') {
const vdiff = commandLineArgs({ name: 'subcommand', defaultOption: true }, { stopAtFirstUnknown: true, argv: cli._unknown || [] });

if (!vdiff.subcommand) {
runTests();
} else if (vdiff.subcommand === 'golden') {
argv.splice(argv.findIndex(a => a === 'golden'), 1, '--golden');
stdout.write('\nGenerating vdiff goldens...\n');
runTests();
} else if (vdiff.subcommand === 'report') {
const { report } = await import('../src/server/cli/vdiff/report.js');
await report.start();
} else if (vdiff.subcommand === 'migrate') {
const { migrate } = await import('../src/server/cli/vdiff/migrate.js');
await migrate.start(vdiff._unknown);
} else {
stdout.write(`\nfatal: unknown subcomamnd: ${vdiff.subcommand}\n`);
}
} else {
runTests();
}

async function runTests() {
const options = await runner.getOptions(argv);
await runner.start(options);
}
15 changes: 0 additions & 15 deletions bin/d2l-test-vdiff-report.js

This file was deleted.

40 changes: 0 additions & 40 deletions bin/migrate-goldens.js

This file was deleted.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
},
"bin": {
"d2l-test-runner": "./bin/d2l-test-runner.js",
"d2l-test-vdiff-report": "./bin/d2l-test-vdiff-report.js",
"migrate-goldens": "./bin/migrate-goldens.js"
"dtr": "./bin/d2l-test-runner.js"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lol not sure I'll be able to use this without reading it as "define the relationship"

},
"author": "D2L Corporation",
"license": "Apache-2.0",
Expand Down
28 changes: 22 additions & 6 deletions src/server/cli/test-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,7 @@ async function getTestRunnerOptions(argv = []) {
},
{
name: 'golden',
type: Boolean,
description: 'Generate new golden screenshots. Ignored unless group is "vdiff".',
order: 14
type: Boolean
},
{
name: 'grep',
Expand All @@ -83,7 +81,7 @@ async function getTestRunnerOptions(argv = []) {
name: 'help',
type: Boolean,
description: 'Print usage information and exit',
order: 15
order: 14
},
{
name: 'open',
Expand Down Expand Up @@ -134,19 +132,37 @@ async function getTestRunnerOptions(argv = []) {
},
{
header: 'Usage',
content: 'd2l-test-runner [options]',
content: 'd2l-test-runner [options]\nd2l-test-runner <command> [options]\n',
},
{
header: 'Options',
optionList: optionDefinitions
.map(o => {
o.description += '\n';
const longAlias = optionDefinitions.find(clone => clone !== o && clone.longAlias === o.name)?.name;
if (longAlias) o.name += `, --${longAlias}`;
return o;
})
.filter(o => 'order' in o)
.sort((a, b) => (a.order > b.order ? 1 : -1))
},
{
header: 'Commands',
content: [{
example: 'vdiff',
desc: 'Run tests for the vdiff group'
},
{
example: 'vdiff report',
desc: 'Open the latest vdiff report'
},
{
example: 'vdiff golden',
desc: 'Generate new golden screenshots'
},
{
example: 'vdiff migrate [directory]',
desc: 'Migrate from @brightspace-ui/visual-diff. Restrict which goldens are migrated with a directory glob.'
}]
}
]);
process.stdout.write(help);
Expand Down
46 changes: 46 additions & 0 deletions src/server/cli/vdiff/migrate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env node
import { appendFile, mkdir, readFile, rename, rm } from 'node:fs/promises';
import { join, normalize, parse } from 'node:path';
import commandLineArgs from 'command-line-args';
import { glob } from 'glob';
import { PATHS } from '../../visual-diff-plugin.js';
import { stdout } from 'node:process';

async function start(argv = []) {
const { pattern = './**' } = commandLineArgs({ name: 'pattern', type: String, defaultOption: true }, { partial: true, argv });
const oldSuffix = 'screenshots/ci/golden';
const dirs = await glob(`${pattern}/${oldSuffix}`, { ignore: 'node_modules/**', posix: true });
let fileCount = 0;

const gitignore = await readFile('.gitignore', { encoding: 'UTF8' }).catch(() => '');
if (!new RegExp(`${PATHS.VDIFF_ROOT}/(\n|$)`).test(gitignore)) {
const newline = gitignore.endsWith('\n') ? '' : '\n';
await appendFile('.gitignore', `${newline}${PATHS.VDIFF_ROOT}/\n`);
}

await Promise.all(dirs.map(async dir => {
const files = await glob(`${dir}/*/*.png`, { posix: true });

await Promise.all(files.map(async file => {
fileCount += 1;
const { base: name, dir } = parse(file);
const dirName = parse(dir).name;

const newName = name
.replace(/^d2l-/, '')
.replace(new RegExp(`^${dirName}-`), '');

const newDir = dir.replace(`${oldSuffix}/${dirName}`, `${PATHS.GOLDEN}/${dirName}/chromium`);

await mkdir(newDir, { recursive: true });
return rename(file, join(newDir, newName));
}));
return rm(normalize(join(dir, '..', '..')), { recursive: true });
}));

stdout.write(`\nMigrated ${fileCount} ${fileCount === 1 ? 'golden' : 'goldens'} found in ${dirs.length} test ${dirs.length === 1 ? 'directory' : 'directories'}\n`);
}

export const migrate = {
start
};
19 changes: 19 additions & 0 deletions src/server/cli/vdiff/report.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env node
import { PATHS } from '../../visual-diff-plugin.js';
import { startDevServer } from '@web/dev-server';

export const report = {
start() {
return startDevServer({
config: {
nodeResolve: false,
open: `./${PATHS.REPORT_ROOT}/`,
rootDir: `${PATHS.VDIFF_ROOT}`,
preserveSymlinks: false,
watch: true
},
readCliArgs: false,
readFileConfig: false
});
}
};
58 changes: 56 additions & 2 deletions test/bin/d2l-test-runner.test.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,72 @@
import { assert, restore, stub } from 'sinon';
import { argv } from 'node:process';
import { expect } from 'chai';
import { migrate } from '../../src/server/cli/vdiff/migrate.js';
import process from 'node:process';
import { report } from '../../src/server/cli/vdiff/report.js';
import { runner } from '../../src/server/cli/test-runner.js';

const { argv, stdout } = process;
Copy link
Contributor Author

@bearfriend bearfriend Aug 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importing process and destructuring allows us to modify argv globally to test different inputs.


const run = async() => {
await import(`../../bin/d2l-test-runner.js?${Math.random()}`);
};

describe('d2l-test-runner', () => {

afterEach(() => {
restore();
});

it('starts test runner with options', async() => {
const opts = { my: 'options' };
const optionsStub = stub(runner, 'getOptions').returns(opts);
const startStub = stub(runner, 'start');
await import('../../bin/d2l-test-runner.js');
await run();

assert.calledOnceWithExactly(optionsStub, argv);
assert.calledOnceWithExactly(startStub, opts);

restore();
});

it('starts report server', async() => {
const reportStub = stub(report, 'start');
const optionsStub = stub(runner, 'getOptions');
const startStub = stub(runner, 'start');

argv.splice(0, argv.length, 'fake-node', 'fake-test-runner', 'vdiff', 'report');
await run();

assert.calledOnce(reportStub);
assert.notCalled(optionsStub);
assert.notCalled(startStub);
});

it('generates goldens', async() => {
const optionsStub = stub(runner, 'getOptions');
const startStub = stub(runner, 'start');
const stdoutStub = stub(stdout, 'write');

argv.splice(0, argv.length, 'fake-node', 'fake-test-runner', 'vdiff', 'golden');
await run();

expect(argv).to.deep.equal(['fake-node', 'fake-test-runner', 'vdiff', '--golden']);
assert.calledOnceWithExactly(optionsStub, argv);
assert.calledOnce(startStub);
assert.calledOnceWithExactly(stdoutStub, '\nGenerating vdiff goldens...\n');
});

it('starts migration', async() => {
const migrateStub = stub(migrate, 'start');
const optionsStub = stub(runner, 'getOptions');
const startStub = stub(runner, 'start');

argv.splice(0, argv.length, 'fake-node', 'fake-test-runner', 'vdiff', 'migrate', './test/**/dir');
await run();

assert.calledOnceWithExactly(migrateStub, ['./test/**/dir']);
assert.notCalled(optionsStub);
assert.notCalled(startStub);
});

});