123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- 'use strict';
- const debug = require('debug')('egg-bin');
- const fs = require('fs');
- const path = require('path');
- const globby = require('globby');
- const Command = require('../command');
- const changed = require('jest-changed-files');
- class TestCommand extends Command {
- constructor(rawArgv) {
- super(rawArgv);
- this.usage = 'Usage: egg-bin test [files] [options]';
- this.options = {
- require: {
- description: 'require the given module',
- alias: 'r',
- type: 'array',
- },
- grep: {
- description: 'only run tests matching <pattern>',
- alias: 'g',
- type: 'array',
- },
- timeout: {
- description: 'set test-case timeout in milliseconds',
- alias: 't',
- type: 'number',
- },
- 'full-trace': {
- description: 'display the full stack trace',
- },
- changed: {
- description: 'only test with changed files and match ${cwd}/test/**/*.test.(js|ts)',
- alias: 'c',
- },
- 'dry-run': {
- type: 'boolean',
- description: 'whether show test command, no test will be executed',
- alias: 'd',
- },
- espower: {
- type: 'boolean',
- description: 'whether require intelli-espower-loader(js) or espower-typescript(ts) for power-assert',
- default: true,
- alias: 'e',
- },
- };
- }
- get description() {
- return 'Run test with mocha';
- }
- * run(context) {
- const opt = {
- env: Object.assign({
- NODE_ENV: 'test',
- }, context.env),
- execArgv: context.execArgv,
- };
- const mochaFile = require.resolve('mocha/bin/_mocha');
- const testArgs = yield this.formatTestArgs(context);
- if (!testArgs) return;
- if (context.argv['dry-run']) {
- debug('test with dry-run');
- console.log(mochaFile);
- console.log(testArgs.join('\n'));
- return;
- }
- debug('run test: %s %s', mochaFile, testArgs.join(' '));
- yield this.helper.forkNode(mochaFile, testArgs, opt);
- }
- /**
- * format test args then change it to array style
- * @param {Object} context - { cwd, argv, ...}
- * @return {Array} [ '--require=xxx', 'xx.test.js' ]
- * @protected
- */
- * formatTestArgs({ argv, debugOptions }) {
- const testArgv = Object.assign({}, argv);
- /* istanbul ignore next */
- testArgv.timeout = testArgv.timeout || process.env.TEST_TIMEOUT || 60000;
- testArgv.reporter = testArgv.reporter || process.env.TEST_REPORTER;
- // force exit
- testArgv.exit = true;
- // whether is debug mode, if pass --inspect then `debugOptions` is valid
- // others like WebStorm 2019 will pass NODE_OPTIONS, and egg-bin itself will be debug, so could detect `process.env.JB_DEBUG_FILE`.
- if (debugOptions || process.env.JB_DEBUG_FILE) {
- // --no-timeout
- testArgv.timeout = false;
- }
- // collect require
- let requireArr = testArgv.require || testArgv.r || [];
- /* istanbul ignore next */
- if (!Array.isArray(requireArr)) requireArr = [ requireArr ];
- // clean mocha stack, inspired by https://github.com/rstacruz/mocha-clean
- // [mocha built-in](https://github.com/mochajs/mocha/blob/master/lib/utils.js#L738) don't work with `[npminstall](https://github.com/cnpm/npminstall)`, so we will override it.
- if (!testArgv.fullTrace) requireArr.unshift(require.resolve('../mocha-clean'));
- requireArr.push(require.resolve('co-mocha'));
- if (requireArr.includes('intelli-espower-loader')) {
- console.warn('[egg-bin] don\'t need to manually require `intelli-espower-loader` anymore');
- } else if (testArgv.espower) {
- if (testArgv.typescript) {
- // espower-typescript must append after ts-node
- requireArr.push(testArgv.tscompiler, require.resolve('../espower-typescript'));
- } else {
- requireArr.push(require.resolve('intelli-espower-loader'));
- }
- }
- testArgv.require = requireArr;
- let pattern;
- // changed
- if (testArgv.changed) {
- pattern = yield this._getChangedTestFiles();
- if (!pattern.length) {
- console.log('No changed test files');
- return;
- }
- }
- if (!pattern) {
- // specific test files
- pattern = testArgv._.slice();
- }
- if (!pattern.length && process.env.TESTS) {
- pattern = process.env.TESTS.split(',');
- }
- // collect test files
- if (!pattern.length) {
- pattern = [ `test/**/*.test.${testArgv.typescript ? 'ts' : 'js'}` ];
- }
- pattern = pattern.concat([ '!test/fixtures', '!test/node_modules' ]);
- // expand glob and skip node_modules and fixtures
- const files = globby.sync(pattern);
- files.sort();
- if (files.length === 0) {
- console.log(`No test files found with ${pattern}`);
- return;
- }
- // auto add setup file as the first test file
- const setupFile = path.join(process.cwd(), `test/.setup.${testArgv.typescript ? 'ts' : 'js'}`);
- if (fs.existsSync(setupFile)) {
- files.unshift(setupFile);
- }
- testArgv._ = files;
- // remove alias
- testArgv.$0 = undefined;
- testArgv.r = undefined;
- testArgv.t = undefined;
- testArgv.g = undefined;
- testArgv.e = undefined;
- testArgv.typescript = undefined;
- testArgv['dry-run'] = undefined;
- testArgv.dryRun = undefined;
- return this.helper.unparseArgv(testArgv);
- }
- * _getChangedTestFiles() {
- const cwd = process.cwd();
- const res = yield changed.getChangedFilesForRoots([ cwd ]);
- const changedFiles = res.changedFiles;
- const files = [];
- for (const file of changedFiles) {
- // only find ${cwd}/test/**/*.test.(js|ts)
- if (file.startsWith(path.join(cwd, 'test')) && file.match(/\.test\.(js|ts)$/)) {
- files.push(file);
- }
- }
- return files;
- }
- }
- module.exports = TestCommand;
|