From ee594c7e36342531c10ad25c1fd14fe4346a7945 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sat, 4 Jun 2022 11:30:07 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=93=A6=20NEW:=20Support=20run=20test?= =?UTF-8?q?=20with=20node:test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ```bash egg-bin node-test ``` --- .eslintrc | 8 +- README.md | 76 ++++++++-- lib/cmd/node-test.js | 144 +++++++++++++++++++ lib/cmd/test.js | 14 +- lib/utils.js | 17 +++ package.json | 1 + test/egg-bin.test.js | 18 +-- test/fixtures/node-test/test/foo.test.js | 74 ++++++++++ test/fixtures/node-test/test/success.test.js | 14 ++ test/node-test.test.js | 19 +++ 10 files changed, 350 insertions(+), 35 deletions(-) create mode 100644 lib/cmd/node-test.js create mode 100644 lib/utils.js create mode 100644 test/fixtures/node-test/test/foo.test.js create mode 100644 test/fixtures/node-test/test/success.test.js create mode 100644 test/node-test.test.js diff --git a/.eslintrc b/.eslintrc index c799fe53..9ed89cb5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,3 +1,9 @@ { - "extends": "eslint-config-egg" + "extends": "eslint-config-egg", + "rules": { + /** + * @see http://eslint.org/docs/rules/strict + */ + "strict": [ "off" ] + } } diff --git a/README.md b/README.md index c82430d9..968f3a9c 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ [download-image]: https://img.shields.io/npm/dm/egg-bin.svg?style=flat-square [download-url]: https://npmjs.org/package/egg-bin - egg developer tool, extends [common-bin]. --- @@ -25,7 +24,7 @@ egg developer tool, extends [common-bin]. ## Install ```bash -$ npm i egg-bin --save-dev +npm i egg-bin --save-dev ``` ## Usage @@ -58,7 +57,7 @@ All the commands support these specific v8 options: - `--es_staging` ```bash -$ egg-bin [command] --debug --es_staging +egg-bin [command] --debug --es_staging ``` if `process.env.NODE_DEBUG_OPTION` is provided (WebStorm etc), will use it as debug options. @@ -68,7 +67,7 @@ if `process.env.NODE_DEBUG_OPTION` is provided (WebStorm etc), will use it as de Start dev cluster on `local` env, it will start a master, an agent and a worker. ```bash -$ egg-bin dev +egg-bin dev ``` ##### options @@ -91,7 +90,7 @@ automatically detect the protocol, use the new `inspector` when the targeted run if running without `VSCode` or `WebStorm`, we will use [inspector-proxy](https://github.com/whxaxes/inspector-proxy) to proxy worker debug, so you don't need to worry about reload. ```bash -$ egg-bin debug --debug-port=9229 --proxy=9999 +egg-bin debug --debug-port=9229 --proxy=9999 ``` ##### options @@ -99,7 +98,6 @@ $ egg-bin debug --debug-port=9229 --proxy=9999 - all `egg-bin dev` options is accepted. - `--proxy=9999` worker debug proxy port. - ### test Using [mocha] to run test. @@ -107,7 +105,7 @@ Using [mocha] to run test. [power-assert] is the default `assert` library, and [intelli-espower-loader] will be auto required. ```bash -$ egg-bin test [files] [options] +egg-bin test [files] [options] ``` - `files` is optional, default to `test/**/*.test.js` @@ -128,14 +126,14 @@ test You can pass any mocha argv. - `--require` require the given module -- `--grep` only run tests matching +- `--grep` only run tests matching `` - `--timeout` milliseconds, default to 60000 - `--full-trace` display the full stack trace, default to false. - `--typescript` / `--ts` enable typescript support, default to `false`. - `--changed` / `-c` only test changed test files(test files means files that match `${pwd}/test/**/*.test.(js|ts)`) -- `--dry-run` / `-d` whether dry-run the test command, just show the command +- `--dry-run` / `-d` whether dry-run the test command, just show the command - `--espower` / `-e` whether auto require intelli-espower-loader(js) or espower-typescript(ts) for power-assert, default to `true`. -- see more at https://mochajs.org/#usage +- see more at #### environment @@ -159,6 +157,55 @@ The test timeout can set by `TEST_TIMEOUT` env, default is `60000` ms. TEST_TIMEOUT=2000 egg-bin test ``` +### node-test + +Using [node:test] to run test. + +[power-assert] is the default `assert` library, and [intelli-espower-loader] will be auto required. + +```bash +egg-bin node-test [files] [options] +``` + +- `files` is optional, default to `test/**/*.test.js` +- `test/fixtures`, `test/node_modules` is always exclude. + +#### auto require `test/.setup.js` + +If `test/.setup.js` file exists, it will be auto require as the first test file. + +```js +test + ├── .setup.js + └── foo.test.js +``` + +#### node:test options + +- `--test-only` configures the test runner to only execute top level tests that have the only option set + +#### environment + +Environment is also support, will use it if options not provide. + +You can set `TESTS` env to set the tests directory, it support [glob] grammar. + +```bash +TESTS=test/a.test.js egg-bin node-test +``` + +And the reporter can set by the `TEST_REPORTER` env, default is `tap`. + +```bash +TEST_REPORTER=doc egg-bin node-test +``` + +The test timeout can set by `TEST_TIMEOUT` env, default is `60000` ms. + +```bash +TEST_TIMEOUT=2000 egg-bin node-test +``` + ### cov Using [c8] to run code coverage, it support all test params above. @@ -173,8 +220,10 @@ You can pass any mocha argv. - `--prerequire` prerequire files for coverage instrument, you can use this options if load files slowly when call `mm.app` or `mm.cluster` - `--typescript` / `--ts` enable typescript support, default to `false`, if true, will auto add `.ts` extension and ignore `typings` and `d.ts`. - `--c8` c8 instruments passthrough. you can use this to overwrite egg-bin's default c8 instruments and add additional ones. + > > - egg-bin have some default instruments passed to c8 like `-r` and `--temp-directory` > - `egg-bin cov --c8="-r teamcity -r text" --c8-report=true` + > - `--c8-report` use c8 to report coverage, c8 uses native V8 coverage, make sure you're running Node.js >= 10.12.0, default to `false`. - also support all test params above. @@ -184,7 +233,7 @@ You can pass any mocha argv. You can set `COV_EXCLUDES` env to add dir ignore coverage. ```bash -$ COV_EXCLUDES="app/plugins/c*,app/autocreate/**" egg-bin cov +COV_EXCLUDES="app/plugins/c*,app/autocreate/**" egg-bin cov ``` ### pkgfiles @@ -192,7 +241,7 @@ $ COV_EXCLUDES="app/plugins/c*,app/autocreate/**" egg-bin cov Generate `pkg.files` automatically before npm publish, see [ypkgfiles] for detail ```bash -$ egg-bin pkgfiles +egg-bin pkgfiles ``` ### autod @@ -200,7 +249,7 @@ $ egg-bin pkgfiles Generate `pkg.dependencies` and `pkg.devDependencies` automatically, see [autod] for detail ```bash -$ egg-bin autod +egg-bin autod ``` ## Custom egg-bin for your team @@ -293,6 +342,7 @@ This project follows the git-contributor [spec](https://github.com/xudafeng/git- [mocha]: https://mochajs.org +[node:test]: https://nodejs.org/api/test.html [glob]: https://github.com/isaacs/node-glob [nsp]: https://npmjs.com/nsp [iron-node]: https://github.com/s-a/iron-node diff --git a/lib/cmd/node-test.js b/lib/cmd/node-test.js new file mode 100644 index 00000000..f4371250 --- /dev/null +++ b/lib/cmd/node-test.js @@ -0,0 +1,144 @@ +'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 { getChangedTestFiles } = require('../utils'); + +class NodeTestCommand extends Command { + constructor(rawArgv) { + super(rawArgv); + this.usage = 'Usage: egg-bin node-test [files] [options]'; + this.options = { + require: { + description: 'require the given module', + alias: 'r', + type: 'array', + }, + grep: { + description: 'only run tests matching ', + alias: 'g', + type: 'array', + }, + timeout: { + description: 'set test-case timeout in milliseconds', + alias: 't', + type: 'number', + }, + changed: { + description: 'only test with changed files and match ${cwd}/test/**/*.test.(js|ts)', + alias: 'c', + }, + }; + } + + get description() { + return 'Run test with node:test'; + } + + async run(context) { + const opt = { + env: Object.assign({ + NODE_ENV: 'test', + }, context.env), + execArgv: context.execArgv, + }; + const testArgs = await this.formatTestArgs(context); + if (!testArgs) return; + + if (parseInt(process.version.split('.')[0].substring(1)) < 18) { + // using user land test module + // https://github.com/nodejs/node-core-test + const nodeTestBin = require.resolve('test/bin/node--test'); + debug('run test: %s %s', nodeTestBin, testArgs.join(' ')); + await this.helper.forkNode(nodeTestBin, testArgs, opt); + return; + } + testArgs.unshift('--test'); + debug('run test: %s %s', process.execPath, testArgs.join(' ')); + await this.helper.spawn(process.execPath, testArgs, opt); + } + + /** + * format test args then change it to array style + * @param {Object} context - { cwd, argv, ...} + * @param context.argv + * @return {Array} [ '--require=xxx', 'xx.test.js' ] + * @protected + */ + async formatTestArgs({ argv }) { + 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; + + // collect require + let requireArr = testArgv.require || testArgv.r || []; + /* istanbul ignore next */ + if (!Array.isArray(requireArr)) requireArr = [ requireArr ]; + + testArgv.require = requireArr; + + let pattern; + // changed + if (testArgv.changed) { + pattern = await 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['dry-run'] = undefined; + testArgv.dryRun = undefined; + // not support tscompiler for now + testArgv.tscompiler = undefined; + + return this.helper.unparseArgv(testArgv); + } + + async _getChangedTestFiles() { + return await getChangedTestFiles(process.cwd()); + } +} + +module.exports = NodeTestCommand; diff --git a/lib/cmd/test.js b/lib/cmd/test.js index ed49d693..64daa09d 100644 --- a/lib/cmd/test.js +++ b/lib/cmd/test.js @@ -4,8 +4,8 @@ const debug = require('debug')('egg-bin'); const fs = require('fs'); const path = require('path'); const globby = require('globby'); -const changed = require('jest-changed-files'); const Command = require('../command'); +const { getChangedTestFiles } = require('../utils'); class TestCommand extends Command { constructor(rawArgv) { @@ -176,17 +176,7 @@ class TestCommand extends Command { } async _getChangedTestFiles() { - const cwd = process.cwd(); - const res = await 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; + return await getChangedTestFiles(process.cwd()); } } diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 00000000..469dcc4e --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,17 @@ +const path = require('path'); +const changed = require('jest-changed-files'); + +module.exports = { + async getChangedTestFiles(dir) { + const res = await changed.getChangedFilesForRoots([ dir ]); + const changedFiles = res.changedFiles; + const files = []; + for (const file of changedFiles) { + // only find ${dir}/test/**/*.test.(js|ts) + if (file.startsWith(path.join(dir, 'test')) && file.match(/\.test\.(js|ts)$/)) { + files.push(file); + } + } + return files; + }, +}; diff --git a/package.json b/package.json index ae635dcf..d0251370 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "power-assert": "^1.6.1", "semver": "^7.3.7", "source-map-support": "^0.5.21", + "test": "^3.0.0", "test-exclude": "^6.0.0", "ts-node": "^10.8.0", "ypkgfiles": "^1.6.0" diff --git a/test/egg-bin.test.js b/test/egg-bin.test.js index 4c2ba2c0..f49fc69d 100644 --- a/test/egg-bin.test.js +++ b/test/egg-bin.test.js @@ -8,28 +8,28 @@ describe('test/egg-bin.test.js', () => { const cwd = path.join(__dirname, 'fixtures/test-files'); describe('global options', () => { - it('should show version', done => { - coffee.fork(eggBin, [ '--version' ], { cwd }) + it('should show version', () => { + return coffee.fork(eggBin, [ '--version' ], { cwd }) // .debug() .expect('stdout', /\d+\.\d+\.\d+/) .expect('code', 0) - .end(done); + .end(); }); - it('should show help', done => { - coffee.fork(eggBin, [ '--help' ], { cwd }) + it('should show help', () => { + return coffee.fork(eggBin, [ '--help' ], { cwd }) // .debug() .expect('stdout', /Usage: .*egg-bin.* \[command] \[options]/) .expect('code', 0) - .end(done); + .end(); }); - it('should show help when command not exists', done => { - coffee.fork(eggBin, [ 'not-exists' ], { cwd }) + it('should show help when command not exists', () => { + return coffee.fork(eggBin, [ 'not-exists' ], { cwd }) // .debug() .expect('stdout', /Usage: .*egg-bin.* \[command] \[options]/) .expect('code', 0) - .end(done); + .end(); }); }); }); diff --git a/test/fixtures/node-test/test/foo.test.js b/test/fixtures/node-test/test/foo.test.js new file mode 100644 index 00000000..e7fbc4a0 --- /dev/null +++ b/test/fixtures/node-test/test/foo.test.js @@ -0,0 +1,74 @@ +'use strict'; + +let test; +try { + test = require('node:test'); +} catch { + test = require('test'); +} +const assert = require('assert'); + +test('node-test subtest 1', async t => { + await t.test('should work', () => { + assert(true); + }); + + await t.test('should fail', () => { + assert(1 === 0); + }); +}); + +test('node-test subtest 2', async t => { + await t.test('should work', () => { + assert(true); + }); + + await t.test('should fail', () => { + assert(1 === 0); + }); +}); + +test('synchronous passing test', () => { + // This test passes because it does not throw an exception. + assert.strictEqual(1, 1); +}); + +test('synchronous failing test', () => { + // This test fails because it throws an exception. + assert.strictEqual(1, 2); +}); + +test('asynchronous passing test', async () => { + // This test passes because the Promise returned by the async + // function is not rejected. + assert.strictEqual(1, 1); +}); + +test('asynchronous failing test', async () => { + // This test fails because the Promise returned by the async + // function is rejected. + assert.strictEqual(1, 2); +}); + +test('failing test using Promises', () => { + // Promises can be used directly as well. + return new Promise((resolve, reject) => { + setImmediate(() => { + reject(new Error('this will cause the test to fail')); + }); + }); +}); + +test('callback passing test', (_, done) => { + // done() is the callback function. When the setImmediate() runs, it invokes + // done() with no arguments. + setImmediate(done); +}); + +test('callback failing test', (_, done) => { + // When the setImmediate() runs, done() is invoked with an Error object and + // the test fails. + setImmediate(() => { + done(new Error('callback failure')); + }); +}); diff --git a/test/fixtures/node-test/test/success.test.js b/test/fixtures/node-test/test/success.test.js new file mode 100644 index 00000000..efb66b2f --- /dev/null +++ b/test/fixtures/node-test/test/success.test.js @@ -0,0 +1,14 @@ +'use strict'; + +let test; +try { + test = require('node:test'); +} catch { + test = require('test'); +} +const assert = require('assert'); + +test('synchronous passing test', () => { + // This test passes because it does not throw an exception. + assert.strictEqual(1, 1); +}); diff --git a/test/node-test.test.js b/test/node-test.test.js new file mode 100644 index 00000000..fbdd0b85 --- /dev/null +++ b/test/node-test.test.js @@ -0,0 +1,19 @@ +'use strict'; + +const path = require('path'); +const coffee = require('coffee'); + +describe('test/node-test.test.js', () => { + const eggBin = require.resolve('../bin/egg-bin.js'); + const cwd = path.join(__dirname, 'fixtures/node-test'); + + it('should run with node-test mode work', () => { + return coffee.fork(eggBin, [ 'node-test' ], { cwd }) + // .debug() + .expect('stdout', /# tests 2/) + .expect('stdout', /# pass 1/) + .expect('stdout', /# fail 1/) + .expect('code', 1) + .end(); + }); +}); From 89275f0d97bb1219665920f9766b5b7e2d61b487 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sat, 4 Jun 2022 12:37:41 +0800 Subject: [PATCH 2/5] f --- .eslintrc | 8 +------- lib/cmd/node-test.js | 2 +- lib/cmd/test.js | 4 ++-- package.json | 4 ++-- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/.eslintrc b/.eslintrc index 9ed89cb5..c799fe53 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,9 +1,3 @@ { - "extends": "eslint-config-egg", - "rules": { - /** - * @see http://eslint.org/docs/rules/strict - */ - "strict": [ "off" ] - } + "extends": "eslint-config-egg" } diff --git a/lib/cmd/node-test.js b/lib/cmd/node-test.js index f4371250..dfce7c3b 100644 --- a/lib/cmd/node-test.js +++ b/lib/cmd/node-test.js @@ -64,7 +64,7 @@ class NodeTestCommand extends Command { /** * format test args then change it to array style * @param {Object} context - { cwd, argv, ...} - * @param context.argv + * @param {Object} context.argv node-test arguments * @return {Array} [ '--require=xxx', 'xx.test.js' ] * @protected */ diff --git a/lib/cmd/test.js b/lib/cmd/test.js index 64daa09d..3c3a0af6 100644 --- a/lib/cmd/test.js +++ b/lib/cmd/test.js @@ -77,8 +77,8 @@ class TestCommand extends Command { /** * format test args then change it to array style * @param {Object} context - { cwd, argv, ...} - * @param context.argv - * @param context.debugOptions + * @param {Object} context.argv test arguments + * @param {Object} context.debugOptions debug options * @return {Array} [ '--require=xxx', 'xx.test.js' ] * @protected */ diff --git a/package.json b/package.json index d0251370..d634c37f 100644 --- a/package.json +++ b/package.json @@ -54,14 +54,14 @@ "enzyme": "^2.0.0", "esbuild-register": "^2.5.0", "eslint": "^8.16.0", - "eslint-config-egg": "^11.1.0", + "eslint-config-egg": "^12.0.0", "git-contributor": "^1.0.10", "jsdom": "^8.0.1", "mm": "^3.2.0", "react": "^0.14.7", "react-addons-test-utils": "^0.14.7", "react-dom": "^0.14.7", - "typescript": "^3" + "typescript": "^4.7.2" }, "repository": { "type": "git", From 504c9cf7d2b1b0b3ce9d94894f5f0860dc4926d6 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sat, 4 Jun 2022 21:09:00 +0800 Subject: [PATCH 3/5] f --- README.md | 42 ++++++- lib/cmd/cov.js | 19 ++-- lib/cmd/node-test-cov.js | 134 +++++++++++++++++++++++ lib/cmd/node-test.js | 19 ++-- lib/utils.js | 30 +++++ package.json | 1 - test/fixtures/node-test/test/foo.test.js | 3 +- test/fixtures/node-test/utils.js | 3 + test/node-test-cov.test.js | 20 ++++ 9 files changed, 242 insertions(+), 29 deletions(-) create mode 100644 lib/cmd/node-test-cov.js create mode 100644 test/fixtures/node-test/utils.js create mode 100644 test/node-test-cov.test.js diff --git a/README.md b/README.md index 968f3a9c..f4ea60ce 100644 --- a/README.md +++ b/README.md @@ -180,10 +180,12 @@ test └── foo.test.js ``` -#### node:test options +#### node-test options - `--test-only` configures the test runner to only execute top level tests that have the only option set +TBD: TypeScript not support yet + #### environment Environment is also support, will use it if options not provide. @@ -208,11 +210,11 @@ TEST_TIMEOUT=2000 egg-bin node-test ### cov -Using [c8] to run code coverage, it support all test params above. +Using [mocha] and [c8] to run code coverage, it support all test params above. Coverage reporter will output text-summary, json and lcov. -#### options +#### cov options You can pass any mocha argv. @@ -224,11 +226,11 @@ You can pass any mocha argv. > - egg-bin have some default instruments passed to c8 like `-r` and `--temp-directory` > - `egg-bin cov --c8="-r teamcity -r text" --c8-report=true` > -- `--c8-report` use c8 to report coverage, c8 uses native V8 coverage, make sure you're running Node.js >= 10.12.0, default to `false`. +- `--c8-report` use c8 to report coverage, c8 uses native V8 coverage, default to `false`. - also support all test params above. -#### environment +#### cov environment You can set `COV_EXCLUDES` env to add dir ignore coverage. @@ -236,6 +238,36 @@ You can set `COV_EXCLUDES` env to add dir ignore coverage. COV_EXCLUDES="app/plugins/c*,app/autocreate/**" egg-bin cov ``` +### node-test-cov + +Using [node:test] and [c8] to run code coverage, it support all test params above. + +Coverage reporter will output text-summary, json and lcov. + +#### node-test-cov options + +You can pass any [node:test] argv. + +- `-x` add dir ignore coverage, support multiple argv +- `--prerequire` prerequire files for coverage instrument, you can use this options if load files slowly when call `mm.app` or `mm.cluster` +- `--typescript` / `--ts` enable typescript support, default to `false`, if true, will auto add `.ts` extension and ignore `typings` and `d.ts`. +- `--c8` c8 instruments passthrough. you can use this to overwrite egg-bin's default c8 instruments and add additional ones. + > + > - egg-bin have some default instruments passed to c8 like `-r` and `--temp-directory` + > - `egg-bin cov --c8="-r teamcity -r text" --c8-report=true` + > +- `--c8-report` use c8 to report coverage, c8 uses native V8 coverage, default to `false`. + +- also support all node-test params above. + +#### node-test-cov environment + +You can set `COV_EXCLUDES` env to add dir ignore coverage. + +```bash +COV_EXCLUDES="app/plugins/c*,app/autocreate/**" egg-bin node-test-cov +``` + ### pkgfiles Generate `pkg.files` automatically before npm publish, see [ypkgfiles] for detail diff --git a/lib/cmd/cov.js b/lib/cmd/cov.js index e8ab97b0..cf3131f4 100644 --- a/lib/cmd/cov.js +++ b/lib/cmd/cov.js @@ -4,9 +4,9 @@ const debug = require('debug')('egg-bin'); const path = require('path'); const fs = require('fs/promises'); -const testExclude = require('test-exclude'); - const Command = require('./test'); +const { defaultExcludes } = require('../utils'); + const EXCLUDES = Symbol('cov#excludes'); /* istanbul ignore next */ @@ -38,12 +38,7 @@ class CovCommand extends Command { }; // you can add ignore dirs here - this[EXCLUDES] = new Set([ - 'example/', - 'examples/', - 'mocks**/', - 'docs/', - ].concat(testExclude.defaultExclude)); + this[EXCLUDES] = new Set(defaultExcludes); } get description() { @@ -93,7 +88,7 @@ class CovCommand extends Command { await fs.rm(coverageDir, { force: true, recursive: true }); const covArgs = await this.getCovArgs(context); if (!covArgs) return; - debug('covArgs: %j', covArgs); + debug('run cov: %s %s', cli, covArgs.join(' ')); await this.helper.forkNode(cli, covArgs, opt); } @@ -135,10 +130,14 @@ class CovCommand extends Command { } const testArgs = await this.formatTestArgs(context); if (!testArgs) return; - covArgs.push(require.resolve('mocha/bin/_mocha')); + covArgs.push(...this.getTestCommandAndArgs()); covArgs = covArgs.concat(testArgs); return covArgs; } + + getTestCommandAndArgs() { + return [ require.resolve('mocha/bin/_mocha') ]; + } } module.exports = CovCommand; diff --git a/lib/cmd/node-test-cov.js b/lib/cmd/node-test-cov.js new file mode 100644 index 00000000..a2a404b7 --- /dev/null +++ b/lib/cmd/node-test-cov.js @@ -0,0 +1,134 @@ +const debug = require('debug')('egg-bin'); +const path = require('path'); +const fs = require('fs/promises'); +const NodeTestCommand = require('./node-test'); +const { getNodeTestCommandAndArgs, defaultExcludes } = require('../utils'); + +const EXCLUDES = Symbol('cov#excludes'); + +class NodeTestCovCommand extends NodeTestCommand { + constructor(argv) { + super(argv); + this.usage = 'Usage: egg-bin node-test-cov'; + this.options = { + x: { + description: 'istanbul coverage ignore, one or more fileset patterns', + type: 'string', + }, + prerequire: { + description: 'prerequire files for coverage instrument', + type: 'boolean', + }, + c8: { + description: 'c8 instruments passthrough', + type: 'string', + default: '--temp-directory ./node_modules/.c8_output -r text-summary -r json-summary -r json -r lcov', + }, + }; + + // you can add ignore dirs here + this[EXCLUDES] = new Set(defaultExcludes); + } + + get description() { + return 'Run test with coverage'; + } + + async run(context) { + const { cwd, argv, execArgv, env } = context; + if (argv.prerequire) { + env.EGG_BIN_PREREQUIRE = 'true'; + } + delete argv.prerequire; + + // ignore coverage + if (argv.x) { + if (Array.isArray(argv.x)) { + for (const exclude of argv.x) { + this.addExclude(exclude); + } + } else { + this.addExclude(argv.x); + } + argv.x = undefined; + } + const excludes = (process.env.COV_EXCLUDES && process.env.COV_EXCLUDES.split(',')) || []; + for (const exclude of excludes) { + this.addExclude(exclude); + } + + const opt = { + cwd, + execArgv, + env: Object.assign({ + NODE_ENV: 'test', + EGG_TYPESCRIPT: context.argv.typescript, + }, env), + }; + + // https://github.com/eggjs/egg/issues/3930 + if (context.argv.typescript) { + opt.env.SPAWN_WRAP_SHIM_ROOT = path.join(cwd, 'node_modules'); + } + const cli = require.resolve('c8/bin/c8.js'); + const outputDir = path.join(cwd, 'node_modules/.c8_output'); + await fs.rm(outputDir, { force: true, recursive: true }); + const coverageDir = path.join(cwd, 'coverage'); + await fs.rm(coverageDir, { force: true, recursive: true }); + const covArgs = await this.getCovArgs(context); + if (!covArgs) return; + debug('run cov: %s %s', cli, covArgs.join(' ')); + await this.helper.forkNode(cli, covArgs, opt); + } + + /** + * add istanbul coverage ignore + * @param {String} exclude - glob pattern + */ + addExclude(exclude) { + this[EXCLUDES].add(exclude); + } + /** + * get coverage args + * @param {Object} context - { cwd, argv, ...} + * @return {Array} args for c8 + * @protected + */ + async getCovArgs(context) { + let covArgs = [ + // '--show-process-tree', + ]; + + // typescript support + if (context.argv.typescript) { + covArgs.push('--extension', '.ts'); + this.addExclude('typings/'); + this.addExclude('**/*.d.ts'); + } + + // c8 args passthrough + const passthroughArgs = context.argv.c8; + context.argv['c8-report'] = undefined; + context.argv.c8 = undefined; + if (passthroughArgs) { + covArgs = covArgs.concat(passthroughArgs.split(' ')); + } + console.log(this[EXCLUDES]); + for (const exclude of this[EXCLUDES]) { + covArgs.push('-x'); + covArgs.push(exclude); + } + const testArgs = await this.formatTestArgs(context); + if (!testArgs) return; + covArgs.push(...this.getTestCommandAndArgs()); + covArgs = covArgs.concat(testArgs); + return covArgs; + } + + getTestCommandAndArgs() { + const { command, args } = getNodeTestCommandAndArgs(); + return [ command, ...args ]; + } +} + +module.exports = NodeTestCovCommand; diff --git a/lib/cmd/node-test.js b/lib/cmd/node-test.js index dfce7c3b..b3108cc9 100644 --- a/lib/cmd/node-test.js +++ b/lib/cmd/node-test.js @@ -1,11 +1,9 @@ -'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 { getChangedTestFiles } = require('../utils'); +const { getChangedTestFiles, getNodeTestCommandAndArgs } = require('../utils'); class NodeTestCommand extends Command { constructor(rawArgv) { @@ -48,17 +46,14 @@ class NodeTestCommand extends Command { const testArgs = await this.formatTestArgs(context); if (!testArgs) return; - if (parseInt(process.version.split('.')[0].substring(1)) < 18) { - // using user land test module - // https://github.com/nodejs/node-core-test - const nodeTestBin = require.resolve('test/bin/node--test'); - debug('run test: %s %s', nodeTestBin, testArgs.join(' ')); - await this.helper.forkNode(nodeTestBin, testArgs, opt); + const { command, args } = getNodeTestCommandAndArgs(); + testArgs.unshift(...args); + debug('run test: %s %s', command, testArgs.join(' ')); + if (command.endsWith('.js')) { + await this.helper.forkNode(command, testArgs, opt); return; } - testArgs.unshift('--test'); - debug('run test: %s %s', process.execPath, testArgs.join(' ')); - await this.helper.spawn(process.execPath, testArgs, opt); + await this.helper.spawn(command, testArgs, opt); } /** diff --git a/lib/utils.js b/lib/utils.js index 469dcc4e..82cad14b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -14,4 +14,34 @@ module.exports = { } return files; }, + + getNodeTestCommandAndArgs() { + if (parseInt(process.version.split('.')[0].substring(1)) < 18) { + // using user land test module + // https://github.com/nodejs/node-core-test + return { + command: require.resolve('test/bin/node--test'), + args: [], + }; + } + return { + command: process.execPath, + args: [ '--test' ], + }; + }, + + get defaultExcludes() { + return [ + 'example/', + 'examples/', + 'mocks**/', + 'docs/', + // https://github.com/JaKXz/test-exclude/blob/620a7be412d4fc2070d50f0f63e3228314066fc9/index.js#L73 + 'test/**', + 'test{,-*}.js', + '**/*.test.js', + '**/__tests__/**', + '**/node_modules/**', + ]; + }, }; diff --git a/package.json b/package.json index d634c37f..91ac569b 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "semver": "^7.3.7", "source-map-support": "^0.5.21", "test": "^3.0.0", - "test-exclude": "^6.0.0", "ts-node": "^10.8.0", "ypkgfiles": "^1.6.0" }, diff --git a/test/fixtures/node-test/test/foo.test.js b/test/fixtures/node-test/test/foo.test.js index e7fbc4a0..a0766e24 100644 --- a/test/fixtures/node-test/test/foo.test.js +++ b/test/fixtures/node-test/test/foo.test.js @@ -7,6 +7,7 @@ try { test = require('test'); } const assert = require('assert'); +const utils = require('../utils'); test('node-test subtest 1', async t => { await t.test('should work', () => { @@ -30,7 +31,7 @@ test('node-test subtest 2', async t => { test('synchronous passing test', () => { // This test passes because it does not throw an exception. - assert.strictEqual(1, 1); + assert.strictEqual(utils.foo(), 'bar'); }); test('synchronous failing test', () => { diff --git a/test/fixtures/node-test/utils.js b/test/fixtures/node-test/utils.js new file mode 100644 index 00000000..44143004 --- /dev/null +++ b/test/fixtures/node-test/utils.js @@ -0,0 +1,3 @@ +exports.foo = () => { + return 'bar'; +}; diff --git a/test/node-test-cov.test.js b/test/node-test-cov.test.js new file mode 100644 index 00000000..197c3be2 --- /dev/null +++ b/test/node-test-cov.test.js @@ -0,0 +1,20 @@ +'use strict'; + +const path = require('path'); +const coffee = require('coffee'); + +describe('test/node-test-cov.test.js', () => { + const eggBin = require.resolve('../bin/egg-bin.js'); + const cwd = path.join(__dirname, 'fixtures/node-test'); + + it('should run with node-test mode work', () => { + return coffee.fork(eggBin, [ 'node-test-cov' ], { cwd }) + // .debug() + .expect('stdout', /# tests 2/) + .expect('stdout', /# pass 1/) + .expect('stdout', /# fail 1/) + .expect('stdout', /Statements {3}: 100% \( 3\/3 \)/) + .expect('code', 1) + .end(); + }); +}); From 5c8372b668b56fc653e7a5988a59f9461e7eb102 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sat, 4 Jun 2022 21:17:25 +0800 Subject: [PATCH 4/5] f --- README.md | 10 ---------- lib/cmd/node-test.js | 7 ------- test/fixtures/node-test/test/.setup.js | 1 + 3 files changed, 1 insertion(+), 17 deletions(-) create mode 100644 test/fixtures/node-test/test/.setup.js diff --git a/README.md b/README.md index f4ea60ce..f839a9df 100644 --- a/README.md +++ b/README.md @@ -170,16 +170,6 @@ egg-bin node-test [files] [options] - `files` is optional, default to `test/**/*.test.js` - `test/fixtures`, `test/node_modules` is always exclude. -#### auto require `test/.setup.js` - -If `test/.setup.js` file exists, it will be auto require as the first test file. - -```js -test - ├── .setup.js - └── foo.test.js -``` - #### node-test options - `--test-only` configures the test runner to only execute top level tests that have the only option set diff --git a/lib/cmd/node-test.js b/lib/cmd/node-test.js index b3108cc9..a3b52978 100644 --- a/lib/cmd/node-test.js +++ b/lib/cmd/node-test.js @@ -1,6 +1,4 @@ const debug = require('debug')('egg-bin'); -const fs = require('fs'); -const path = require('path'); const globby = require('globby'); const Command = require('../command'); const { getChangedTestFiles, getNodeTestCommandAndArgs } = require('../utils'); @@ -110,11 +108,6 @@ class NodeTestCommand extends Command { 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 diff --git a/test/fixtures/node-test/test/.setup.js b/test/fixtures/node-test/test/.setup.js new file mode 100644 index 00000000..0bf481dd --- /dev/null +++ b/test/fixtures/node-test/test/.setup.js @@ -0,0 +1 @@ +console.log('setup file will not support on node-test'); From 9c81845bcbeccd7bc672f2a159ecfbb31d774008 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sat, 4 Jun 2022 21:34:04 +0800 Subject: [PATCH 5/5] f --- test/ts.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ts.test.js b/test/ts.test.js index 97a7abcb..6bbd0fef 100644 --- a/test/ts.test.js +++ b/test/ts.test.js @@ -81,10 +81,10 @@ describe('test/ts.test.js', () => { }); it('should cov app in cluster mod', () => { - // skip on darwin and node v16 + // skip on darwin // https://github.com/eggjs/egg-bin/runs/6735190362?check_suite_focus=true // [agent_worker] receive disconnect event on child_process fork mode, exiting with code:110 - if (process.platform === 'darwin' && process.version.includes('v16.')) return; + if (process.platform === 'darwin') return; cwd = path.join(__dirname, './fixtures/example-ts-cluster'); return coffee.fork(eggBin, [ 'cov', '--ts' ], { cwd }) .debug()