Merge pull request #1393 from balena-io/cli-tests

Implement full command testing, beginning with "balena version"
This commit is contained in:
Lucian Buzzo 2019-08-13 09:38:32 +01:00 committed by GitHub
commit 8dd1106a44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 121 additions and 21 deletions

View File

@ -145,13 +145,14 @@ capitano.command(actions.push.push)
capitano.command(actions.join.join)
capitano.command(actions.leave.leave)
cli = capitano.parse(process.argv)
runCommand = ->
capitanoExecuteAsync = Promise.promisify(capitano.execute)
if cli.global?.help
capitanoExecuteAsync(command: "help #{cli.command ? ''}")
else
capitanoExecuteAsync(cli)
exports.run = (argv) ->
cli = capitano.parse(argv)
runCommand = ->
capitanoExecuteAsync = Promise.promisify(capitano.execute)
if cli.global?.help
capitanoExecuteAsync(command: "help #{cli.command ? ''}")
else
capitanoExecuteAsync(cli)
Promise.all([events.trackCommand(cli), runCommand()])
.catch(require('./errors').handleError)
Promise.all([events.trackCommand(cli), runCommand()])
.catch(require('./errors').handleError)

View File

@ -100,7 +100,11 @@ export function globalInit() {
// stream-to-promise will produce native promises if not for this module,
// which is likely to lead to errors as much of the CLI coffeescript code
// expects bluebird promises.
require('any-promise/register/bluebird');
// The registration is only run if it hasn't already happened (for example
// in a test case).
if (!(global as any)['@@any-promise/REGISTRATION']) {
require('any-promise/register/bluebird');
}
// check for CLI updates once a day
require('./utils/update').notify();

View File

@ -18,6 +18,7 @@
import { Main } from '@oclif/command';
import { ExitError } from '@oclif/errors';
import { AppOptions } from './app';
import { handleError } from './errors';
class CustomMain extends Main {
@ -34,9 +35,13 @@ class CustomMain extends Main {
/**
* oclif CLI entrypoint
*/
export function run(argv: string[]) {
CustomMain.run(argv.slice(2)).then(
require('@oclif/command/flush'),
export function run(command: string[], options: AppOptions) {
return CustomMain.run(command).then(
() => {
if (!options.noFlush) {
return require('@oclif/command/flush');
}
},
(error: Error) => {
// oclif sometimes exits with ExitError code 0 (not an error)
if (error instanceof ExitError && error.oclif.exit === 0) {

View File

@ -18,14 +18,19 @@ import { stripIndent } from 'common-tags';
import { exitWithExpectedError } from './utils/patterns';
export interface AppOptions {
// Prevent the default behaviour of flushing stdout after running a command
noFlush: boolean;
}
/**
* Simple command-line pre-parsing to choose between oclif or Capitano.
* @param argv process.argv
*/
function routeCliFramework(argv: string[]): void {
function routeCliFramework(argv: string[], options: AppOptions): void {
if (process.env.DEBUG) {
console.log(
`Debug: original argv0="${process.argv0}" argv=[${argv}] length=${
`[debug] original argv0="${process.argv0}" argv=[${argv}] length=${
argv.length
}`,
);
@ -65,11 +70,11 @@ function routeCliFramework(argv: string[]): void {
argv = [argv[0], argv[1], ...cmdSlice];
}
if (process.env.DEBUG) {
console.log(`Debug: new argv=[${argv}] length=${argv.length}`);
console.log(`[debug] new argv=[${argv}] length=${argv.length}`);
}
require('./app-oclif').run(argv);
return require('./app-oclif').run(cmdSlice, options);
} else {
require('./app-capitano');
return require('./app-capitano').run(cmdSlice);
}
}
@ -154,10 +159,10 @@ function isOclifCommand(argvSlice: string[]): [boolean, boolean] {
* CLI entrypoint, but see also `bin/balena` and `bin/balena-dev` which
* call this function.
*/
export function run(): void {
export function run(cliArgs = process.argv, options: AppOptions): void {
// globalInit() must be called very early on (before other imports) because
// it sets up Sentry error reporting, global HTTP proxy settings, balena-sdk
// shared options, and performs node version requirement checks.
require('./app-common').globalInit();
routeCliFramework(process.argv);
return routeCliFramework(cliArgs, options);
}

View File

@ -46,7 +46,7 @@
"package": "npm run build:fast && npm run build:standalone && npm run build:installer",
"release": "ts-node --type-check -P automation/tsconfig.json automation/run.ts release",
"pretest": "npm run build",
"test": "mocha -r ts-node/register tests/**/*.spec.ts",
"test": "mocha -r ts-node/register \"tests/**/*.spec.ts\"",
"test:fast": "npm run build:fast && npm run test",
"ci": "npm run test && catch-uncommitted",
"watch": "gulp watch",

48
tests/helpers.ts Normal file
View File

@ -0,0 +1,48 @@
import * as path from 'path';
import * as balenaCLI from '../build/app';
export const runCommand = async (cmd: string) => {
const preArgs = [process.argv[0], path.join(process.cwd(), 'bin', 'balena')];
const oldStdOut = process.stdout.write;
const oldStdErr = process.stderr.write;
const err: string[] = [];
const out: string[] = [];
// @ts-ignore
process.stdout.write = (log: string) => {
// Skip over debug messages
if (!log.startsWith('[debug]')) {
out.push(log);
}
oldStdOut(log);
};
// @ts-ignore
process.stderr.write = (log: string) => {
// Skip over debug messages
if (!log.startsWith('[debug]')) {
err.push(log);
}
oldStdErr(log);
};
try {
await balenaCLI.run(preArgs.concat(cmd.split(' ')), {
noFlush: true,
});
process.stdout.write = oldStdOut;
process.stderr.write = oldStdErr;
return {
err,
out,
};
} catch (err) {
process.stdout.write = oldStdOut;
process.stderr.write = oldStdErr;
throw err;
}
};

37
tests/version.spec.ts Normal file
View File

@ -0,0 +1,37 @@
import * as chai from 'chai';
import * as fs from 'fs';
import { runCommand } from './helpers';
const packageJSON = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
const nodeVersion = process.version.startsWith('v')
? process.version.slice(1)
: process.version;
describe('balena version', function() {
it('should print the installed version of the CLI', async () => {
const { out } = await runCommand('version');
chai.expect(out.join('')).to.equal(`${packageJSON.version}\n`);
});
it('should print additional version information with the -a flag', async () => {
const { out } = await runCommand('version -a');
chai.expect(out.join('')).to.equal(
`balena-cli version "${packageJSON.version}"
Node.js version "${nodeVersion}"
`,
);
});
it('should print version information as JSON with the the -j flag', async () => {
const { out } = await runCommand('version -j');
const json = JSON.parse(out.join(''));
chai.expect(json).to.deep.equal({
'balena-cli': packageJSON.version,
'Node.js': nodeVersion,
});
});
});