diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..d7302d84 --- /dev/null +++ b/.npmignore @@ -0,0 +1,6 @@ +* +!lib/** +!bin/lab +!.npmignore +!.eslintignore +!README.md diff --git a/README.md b/README.md index 970f0e6e..c9141978 100755 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ global manipulation. Our goal with **lab** is to keep the execution engine as si - `-m`, `--timeout` - individual tests timeout in milliseconds (zero disables timeout). Defaults to 2 seconds. - `-M`, `--context-timeout` - default timeouts for before, after, beforeEach and afterEach in milliseconds. Disabled by default. - `-n`, `--linter` - specify linting program file path; default is `eslint`. +- `--lint-fix` - apply any fixes from the linter, requires `-L` or `--lint` to be enabled. Disabled by default. - `--lint-options` - specify options to pass to linting program. It must be a string that is JSON.parse(able). - `-o`, `--output` - file to write the report to, otherwise sent to stdout. - `-p`, `--parallel` - sets parallel execution as default test option. Defaults to serial execution. @@ -183,6 +184,24 @@ lab.experiment('with only', () => { }); ``` +The `test()` callback has a `note()` function attached to it that can be used to +attach notes to the test case. These notes are included in the console reporter +at the end of the output. For example, if you would like to add a note with the +current time, your test case may look like the following: + +```javascript +lab.test('attaches notes', (done) => { + + Code.expect(1 + 1).to.equal(2); + done.note(`The current time is ${Date.now()}`); + done(); +}); +``` + +Multiple notes can be appended for the same test case by simply calling `note()` +repeatedly. + + The `test()` callback provides a second `onCleanup` argument which is a function used to register a runtime cleanup function to be executed after the test completed. The cleanup function will execute even in the event of a timeout. Note that the cleanup function will be executed as-is without any timers and if it fails to call it's `next` argument, the runner will freeze. @@ -239,7 +258,7 @@ lab.experiment('math', { timeout: 1000 }, () => { The `script([options])` method takes an optional `options` argument where `options` is an object with the following optional keys: - `schedule` - if `false`, an automatic execution of the script is disabled. Automatic execution allows running lab test scripts directly with node without having to use the cli (e.g. `node test/script.js`). When using **lab** programmatically, this behavior is undesired and - can be turned off by setting `schedule` to `false`. Defaults to `true`. + can be turned off by setting `schedule` to `false`. If you need to see the output with schedule disabled you should set `output` to `process.stdout`. Defaults to `true`. - `cli` - allows setting command line options within the script. Note that the last script file loaded wins and usage of this is recommended only for temporarily changing the execution of tests. This option is useful for code working with an automatic test engine that run tests on commits. Setting this option has no effect when not using the CLI runner. For example setting `cli` to `{ ids: [1] }` will only execute @@ -348,6 +367,53 @@ if (typeof value === 'symbol') { ``` +## `.labrc.js` file + +**lab** supports a `.labrc.js` configuration file for centralizing lab settings. +The `.labrc.js` file can be located in the current working directory, any +directory that is the parent of the current working directory, or in the user's +home directory. The `.labrc.js` file needs to be able to be required by +Node.js. Therefore, either format it as a JSON file or with a `module.exports` +that exports an object with the keys that are the settings. + + +Below is an example of a `.labrc.js` file to enable linting and test coverage checking: + +```js +module.exports = { + coverage: true, + threshold: 90, + lint: true +}; +``` + +### `.labrc.js` setting precedent + +The `.labrc.js` file will override the **lab** default settings. Any options passed +to the **lab** runner will override the settings found in `.labrc.js`. For example, +assume you have the following `.labrc.js` file: + +```js +module.exports = { + coverage: true, + threshold: 100 +}; +``` + +If you need to reduce the coverage threshold for a single run, you can execute +**lab** as follows: + +```sh +lab -t 80 +``` + +### `.labrc.js` available settings + +The `.labrc.js` file supports configuration keys that are named with the long name +of the command line settings. Therefore, if you need to specify an assert +library, you would export a key named "assert" with the desired value. + + ## Extending the linter **lab** uses a shareable [eslint](http://eslint.org/) config, and a plugin containing several **hapi** specific linting rules. If you want to extend the default linter you must: @@ -359,12 +425,22 @@ if (typeof value === 'symbol') { Your project's eslint configuration will now extend the default **lab** configuration. ## Ignoring files in linting + Since [eslint](http://eslint.org/) is used to lint, you can create an `.eslintignore` containing paths to be ignored: ``` node_modules/* **/vendor/*.js ``` +## Only run linting + +In order to run linting and not to execute tests you can combine the `dry` run +flag with the `lint` flag. + +``` +lab -dL +``` + ## Running a custom linter If you would like to run a different linter, or even a custom version of eslint you should diff --git a/lib/cli.js b/lib/cli.js index e3fac10a..51fc225b 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -5,25 +5,30 @@ const Fs = require('fs'); const Path = require('path'); const Bossy = require('bossy'); +const FindRc = require('find-rc'); const Hoek = require('hoek'); const Coverage = require('./coverage'); const Pkg = require('../package.json'); const Runner = require('./runner'); const Transform = require('./transform'); const Utils = require('./utils'); +// .labrc configuration will be required if it exists // Declare internals const internals = {}; +internals.rcPath = FindRc('lab'); +internals.rc = internals.rcPath ? require(internals.rcPath) : {}; + exports.run = function () { const settings = internals.options(); settings.coveragePath = Path.join(process.cwd(), settings['coverage-path'] || ''); - settings.coverageExclude = ['node_modules', 'test']; + settings.coverageExclude = ['node_modules', 'test', 'test_runner']; if (settings['coverage-exclude']) { settings.coverageExclude = settings.coverageExclude.concat(settings['coverage-exclude']); } @@ -97,33 +102,36 @@ internals.traverse = function (paths, options) { testFiles = testFiles.concat(traverse(path)); }); + if (options.pattern && !testFiles.length) { + options.output.write('The pattern provided (-P or --pattern) didn\'t match any files.'); + process.exit(0); + } + testFiles = testFiles.map((path) => { return Path.resolve(path); }); const scripts = []; - if (testFiles.length) { - testFiles.forEach((file) => { + testFiles.forEach((file) => { - global._labScriptRun = false; - file = Path.resolve(file); - const pkg = require(file); - if (pkg.lab && - pkg.lab._root) { + global._labScriptRun = false; + file = Path.resolve(file); + const pkg = require(file); + if (pkg.lab && + pkg.lab._root) { - scripts.push(pkg.lab); + scripts.push(pkg.lab); - if (pkg.lab._cli) { - Utils.applyOptions(options, pkg.lab._cli); - } - } - else if (global._labScriptRun) { - options.output.write('The file: ' + file + ' includes a lab script that is not exported via exports.lab'); - return process.exit(1); + if (pkg.lab._cli) { + Utils.applyOptions(options, pkg.lab._cli); } - }); - } + } + else if (global._labScriptRun) { + options.output.write('The file: ' + file + ' includes a lab script that is not exported via exports.lab'); + return process.exit(1); + } + }); return scripts; }; @@ -135,174 +143,229 @@ internals.options = function () { assert: { alias: 'a', type: 'string', - description: 'specify an assertion library module path to require and make available under Lab.assertions' + description: 'specify an assertion library module path to require and make available under Lab.assertions', + default: null }, colors: { alias: 'C', type: 'boolean', - default: null, - description: 'enable color output (defaults to terminal capabilities)' + description: 'enable color output (defaults to terminal capabilities)', + default: null }, 'context-timeout': { alias: 'M', type: 'number', - description: 'timeout for before, after, beforeEach, afterEach in milliseconds' + description: 'timeout for before, after, beforeEach, afterEach in milliseconds', + default: null }, coverage: { alias: 'c', type: 'boolean', - description: 'enable code coverage analysis' + description: 'enable code coverage analysis', + default: null }, 'coverage-path': { type: 'string', - description: 'set code coverage path' + description: 'set code coverage path', + default: null }, 'coverage-exclude': { type: 'string', description: 'set code coverage excludes', - multiple: true + multiple: true, + default: null }, debug: { alias: 'D', type: 'boolean', - default: false, - description: 'print the stack during a domain error event' + description: 'print the stack during a domain error event', + default: null }, dry: { alias: 'd', type: 'boolean', - description: 'skip all tests (dry run)' + description: 'skip all tests (dry run)', + default: null }, environment: { alias: 'e', type: 'string', description: 'value to set NODE_ENV before tests', - default: 'test' + default: null }, flat: { alias: 'f', type: 'boolean', - description: 'prevent recursive collection of tests within the provided path' + description: 'prevent recursive collection of tests within the provided path', + default: null }, globals: { alias: ['I', 'ignore'], type: 'string', - description: 'ignore a list of globals for the leak detection (comma separated)' + description: 'ignore a list of globals for the leak detection (comma separated)', + default: null }, grep: { alias: 'g', type: 'string', - description: 'only run tests matching the given pattern which is internally compiled to a RegExp' + description: 'only run tests matching the given pattern which is internally compiled to a RegExp', + default: null }, help: { alias: 'h', type: 'boolean', - description: 'display usage options' + description: 'display usage options', + default: null }, id: { alias: 'i', type: 'range', - description: 'test identifier' + description: 'test identifier', + default: null }, leaks: { alias: 'l', type: 'boolean', - description: 'disable global variable leaks detection' + description: 'disable global variable leaks detection', + default: null }, lint: { alias: 'L', type: 'boolean', - description: 'enable linting' + description: 'enable linting', + default: null }, linter: { alias: 'n', type: 'string', description: 'linter path to use', - default: 'eslint' + default: null + }, + 'lint-fix': { + type: 'boolean', + description: 'apply any fixes from the linter.', + default: null }, 'lint-options': { type: 'string', - description: 'specify options to pass to linting program. It must be a string that is JSON.parse(able).' + description: 'specify options to pass to linting program. It must be a string that is JSON.parse(able).', + default: null }, 'lint-errors-threshold': { type: 'number', description: 'linter errors threshold in absolute value', - default: 0 + default: null }, 'lint-warnings-threshold': { type: 'number', description: 'linter warnings threshold in absolute value', - default: 0 + default: null }, output: { alias: 'o', type: 'string', description: 'file path to write test results', - multiple: true + multiple: true, + default: null }, parallel: { alias: 'p', type: 'boolean', - description: 'parallel test execution within each experiment' + description: 'parallel test execution within each experiment', + default: null }, pattern: { alias: 'P', type: 'string', - description: 'file pattern to use for locating tests' + description: 'file pattern to use for locating tests', + default: null }, reporter: { alias: 'r', type: 'string', description: 'reporter type [console, html, json, tap, lcov, clover, junit]', - default: 'console', - multiple: true + multiple: true, + default: null }, shuffle: { type: 'boolean', - description: 'shuffle script execution order' + description: 'shuffle script execution order', + default: null }, silence: { alias: 's', type: 'boolean', - description: 'silence test output' + description: 'silence test output', + default: null }, 'silent-skips': { alias: 'k', type: 'boolean', - description: 'don’t output skipped tests' + description: 'don’t output skipped tests', + default: null }, sourcemaps: { alias: ['S', 'sourcemaps'], type: 'boolean', - description: 'enable support for sourcemaps' + description: 'enable support for sourcemaps', + default: null }, threshold: { alias: 't', type: 'number', - description: 'code coverage threshold percentage' + description: 'code coverage threshold percentage', + default: null }, timeout: { alias: 'm', type: 'number', - description: 'timeout for each test in milliseconds' + description: 'timeout for each test in milliseconds', + default: null }, transform: { alias: ['T', 'transform'], type: 'string', - description: 'javascript file that exports an array of objects ie. [ { ext: ".js", transform: function (content, filename) { ... } } ]' + description: 'javascript file that exports an array of objects ie. [ { ext: ".js", transform: function (content, filename) { ... } } ]', + default: null }, verbose: { alias: 'v', type: 'boolean', - description: 'verbose test output' + description: 'verbose test output', + default: null }, version: { alias: 'V', type: 'boolean', - description: 'version information' + description: 'version information', + default: null } }; + const defaults = { + verbose: false, + paths: ['test'], + coverage: false, + debug: true, + dry: false, + environment: 'test', + flat: false, + leaks: true, + lint: false, + linter: 'eslint', + 'lint-fix': false, + 'lint-errors-threshold': 0, + 'lint-warnings-threshold': 0, + parallel: false, + reporter: 'console', + shuffle: false, + silence: false, + 'silent-skips': false, + sourcemaps: false, + timeout: 2000, + verbose: false + }; + const argv = Bossy.parse(definition); if (argv instanceof Error) { @@ -321,51 +384,55 @@ internals.options = function () { process.exit(0); } - const options = { - paths: argv._ ? [].concat(argv._) : ['test'] - }; - - if (argv.assert) { - options.assert = require(argv.assert); - require('./').assertions = options.assert; - } + const options = Utils.mergeOptions(defaults, internals.rc); + options.paths = argv._ ? [].concat(argv._) : options.paths; - const keys = ['coverage', 'coverage-path', 'coverage-exclude', 'colors', 'dry', 'debug', 'environment', 'flat', - 'grep', 'globals', 'timeout', 'parallel', 'pattern', 'reporter', 'threshold', 'context-timeout', 'shuffle', 'sourcemaps', - 'lint', 'linter', 'transform', 'lint-options', 'lint-errors-threshold', 'lint-warnings-threshold', 'silent-skips']; + const keys = ['assert', 'colors', 'context-timeout', 'coverage', 'coverage-exclude', + 'coverage-path', 'debug', 'dry', 'environment', 'flat', 'globals', 'grep', + 'lint', 'lint-errors-threshold', 'lint-fix', 'lint-options', 'lint-warnings-threshold', + 'linter', 'output', 'parallel', 'pattern', 'reporter', 'shuffle', 'silence', + 'silent-skips', 'sourcemaps', 'threshold', 'timeout', 'transform', 'verbose']; for (let i = 0; i < keys.length; ++i) { - if (argv.hasOwnProperty(keys[i]) && argv[keys[i]] !== undefined) { + if (argv.hasOwnProperty(keys[i]) && argv[keys[i]] !== undefined && argv[keys[i]] !== null) { options[keys[i]] = argv[keys[i]]; } } - options.environment = options.environment && options.environment.trim(); - options.leaks = !argv.leaks; - options.output = argv.output || process.stdout; - options.coverage = (options.coverage || options.threshold > 0 || options.reporter.indexOf('html') !== -1 || options.reporter.indexOf('lcov') !== -1 || options.reporter.indexOf('clover') !== -1); + if (typeof argv.leaks === 'boolean') { + options.leaks = !argv.leaks; + } - if (Array.isArray(options.reporter) && argv.output) { - if (!Array.isArray(argv.output) || options.output.length !== options.reporter.length) { + if (argv.id) { + options.ids = argv.id; + } + + if (Array.isArray(options.reporter) && options.output) { + if (!Array.isArray(options.output) || options.output.length !== options.reporter.length) { console.error(Bossy.usage(definition, 'lab [options] [path]')); process.exit(1); } } + if (!options.output) { + options.output = process.stdout; + } + + if (options.assert) { + options.assert = require(options.assert); + require('./').assertions = options.assert; + } + if (options.globals) { options.globals = options.globals.trim().split(','); } - if (argv.silence) { + if (options.silence) { options.progress = 0; } - else if (argv.verbose) { + else if (options.verbose) { options.progress = 2; } - if (argv.id) { - options.ids = argv.id; - } - options.pattern = options.pattern ? '.*' + options.pattern + '.*?' : ''; if (options.transform) { const transform = require(Path.resolve(options.transform)); @@ -381,6 +448,8 @@ internals.options = function () { options.pattern = new RegExp(options.pattern + '\\.(js)$'); } + options.coverage = (options.coverage || options.threshold > 0 || options.reporter.indexOf('html') !== -1 || options.reporter.indexOf('lcov') !== -1 || options.reporter.indexOf('clover') !== -1); + return options; }; diff --git a/lib/coverage.js b/lib/coverage.js index 39639aef..31a18fc1 100755 --- a/lib/coverage.js +++ b/lib/coverage.js @@ -49,7 +49,8 @@ exports.instrument = function (options) { internals.pattern = function (options) { const base = internals.escape(options.coveragePath || ''); - const excludes = options.coverageExclude ? [].concat(options.coverageExclude).map(internals.escape).join('|') : ''; + let excludes = options.coverageExclude ? [].concat(options.coverageExclude).map(internals.escape) : ''; + excludes = excludes ? excludes.map((exclude) => `${exclude}\\/`).join('|') : excludes; const regex = '^' + base + (excludes ? (base[base.length - 1] === '/' ? '' : '\\/') + '(?!' + excludes + ')' : ''); return new RegExp(regex); }; diff --git a/lib/leaks.js b/lib/leaks.js index 85d323af..89b5f3e2 100755 --- a/lib/leaks.js +++ b/lib/leaks.js @@ -73,7 +73,13 @@ exports.detect = function (customGlobals) { TypeError: true, URIError: true, Boolean: true, - Intl: true + Intl: true, + Map: true, + Promise: true, + Set: true, + Symbol: true, + WeakMap: true, + WeakSet: true }; if (customGlobals) { @@ -82,34 +88,10 @@ exports.detect = function (customGlobals) { } } - if (global.Promise) { - whitelist.Promise = true; - } - if (global.Proxy) { whitelist.Proxy = true; } - if (global.Symbol) { - whitelist.Symbol = true; - } - - if (global.Map) { - whitelist.Map = true; - } - - if (global.WeakMap) { - whitelist.WeakMap = true; - } - - if (global.Set) { - whitelist.Set = true; - } - - if (global.WeakSet) { - whitelist.WeakSet = true; - } - if (global.Reflect) { whitelist.Reflect = true; } diff --git a/lib/lint.js b/lib/lint.js index 0902e696..85448ac1 100755 --- a/lib/lint.js +++ b/lib/lint.js @@ -3,6 +3,7 @@ // Load modules const ChildProcess = require('child_process'); +const Fs = require('fs'); // Declare internals @@ -15,9 +16,19 @@ const internals = { exports.lint = function (settings, callback) { const linterPath = (settings.linter && settings.linter !== 'eslint') ? settings.linter : internals.linter; - const child = ChildProcess.fork(linterPath, - settings['lint-options'] ? [settings['lint-options']] : [], - { cwd: settings.lintingPath }); + + let linterOptions; + + try { + linterOptions = JSON.parse(settings['lint-options'] || '{}'); + } + catch (err) { + throw new Error('lint-options could not be parsed'); + } + + linterOptions.fix = settings['lint-fix']; + + const child = ChildProcess.fork(linterPath, [JSON.stringify(linterOptions)], { cwd: settings.lintingPath }); child.once('message', (message) => { child.kill(); @@ -43,6 +54,10 @@ exports.lint = function (settings, callback) { lint.totalWarnings = warnings; result.totalErrors += errors; result.totalWarnings += warnings; + + if (lint.fix) { + Fs.writeFileSync(lint.filename, lint.fix.output); + } }); result.total = result.totalErrors + result.totalWarnings; diff --git a/lib/linter/index.js b/lib/linter/index.js index bfd4fa26..4df7a23f 100755 --- a/lib/linter/index.js +++ b/lib/linter/index.js @@ -42,17 +42,26 @@ exports.lint = function () { return results.results.map((result) => { - return { - filename: result.filePath, - errors: result.messages.map((err) => { - - return { - line: err.line, - severity: err.severity === 1 ? 'WARNING' : 'ERROR', - message: err.ruleId + ' - ' + err.message - }; - }) + const transformed = { + filename: result.filePath }; + + if (result.hasOwnProperty('output')) { + transformed.fix = { + output: result.output + }; + } + + transformed.errors = result.messages.map((err) => { + + return { + line: err.line, + severity: err.severity === 1 ? 'WARNING' : 'ERROR', + message: err.ruleId + ' - ' + err.message + }; + }); + + return transformed; }); }; diff --git a/lib/reporters/console.js b/lib/reporters/console.js index c2fe6782..2be0c961 100755 --- a/lib/reporters/console.js +++ b/lib/reporters/console.js @@ -69,7 +69,7 @@ internals.Reporter.prototype.test = function (test) { // Verbose (Spec reporter) for (let i = 0; i < test.path.length; ++i) { - if (test.path[i] !== this.last[i]) { + if (test.path[i] !== this.last[i] || (i > 0 && test.path[i - 1] !== this.last[i - 1])) { this.report(internals.spacer(i * 2) + test.path[i] + '\n'); } } @@ -78,7 +78,7 @@ internals.Reporter.prototype.test = function (test) { const spacer = internals.spacer(test.path.length * 2); if (test.err) { - this.report(spacer + this.colors.red(asterisk + test.id + ') ' + test.relativeTitle) + '\n'); + this.report(spacer + this.colors.red(asterisk + ' ' + test.id + ') ' + test.relativeTitle) + '\n'); } else { const symbol = test.skipped || test.todo ? this.colors.magenta('-') : this.colors.green(check); @@ -136,6 +136,8 @@ internals.Reporter.prototype.end = function (notebook) { // Tests + const notes = notebook.tests.filter(internals.filterNotes); + const failures = notebook.tests.filter(internals.filterFailures); const skipped = notebook.tests.filter(internals.filterSkipped); @@ -340,6 +342,18 @@ internals.Reporter.prototype.end = function (notebook) { } } + if (notes.length) { + output += '\n\nTest notes:\n'; + notes.forEach((test) => { + + output += gray(test.relativeTitle) + '\n'; + test.notes.forEach((note) => { + + output += yellow(`\t* ${note}`); + }); + }); + } + output += '\n'; this.report(output); }; @@ -377,7 +391,7 @@ internals.colors = function (enabled) { const codes = { 'black': 0, - 'gray': 90, + 'gray': 92, 'red': 31, 'green': 32, 'yellow': 33, @@ -399,6 +413,12 @@ internals.colors = function (enabled) { }; +internals.filterNotes = function (test) { + + return test.notes && test.notes.length; +}; + + internals.filterFailures = function (test) { return !!test.err; diff --git a/lib/runner.js b/lib/runner.js index f7a302b9..d07887d5 100755 --- a/lib/runner.js +++ b/lib/runner.js @@ -45,13 +45,17 @@ internals.defaults = { globals: null, leaks: true, timeout: 2000, - output: false, // Stream.Writable or string (filename) + output: process.stdout, // Stream.Writable or string (filename) parallel: false, progress: 1, reporter: 'console', + shuffle: false, // schedule: true, threshold: 0, + + lint: false, + 'lint-fix': false, 'lint-errors-threshold': 0, 'lint-warnings-threshold': 0 }; @@ -60,6 +64,7 @@ internals.defaults = { exports.report = function (scripts, options, callback) { const settings = Utils.mergeOptions(internals.defaults, options); + settings.environment = settings.environment.trim(); const reporter = Reporters.generate(settings); const executeScripts = function (next) { @@ -256,7 +261,7 @@ internals.executeExperiments = function (experiments, state, skip, callback) { const previousDomains = state.domains; state.domains = []; - const skipExperiment = skip || experiment.options.skip; + const skipExperiment = skip || experiment.options.skip || !internals.experimentHasTests(experiment, state); const steps = [ function (next) { @@ -461,6 +466,36 @@ internals.executeTests = function (experiment, state, skip, callback) { }; +internals.experimentHasTests = function (experiment, state) { + + if (experiment.experiments.length) { + const experimentsHasTests = experiment.experiments.some((childExperiment) => { + + return internals.experimentHasTests(childExperiment, state); + }); + + if (experimentsHasTests) { + return true; + } + } + + const hasTests = experiment.tests.some((test) => { + + if ((state.filters.ids.length && state.filters.ids.indexOf(test.id) === -1) || + (state.filters.grep && !state.filters.grep.test(test.title))) { + + return false; + } + + if (!test.options.skip && test.fn) { + return true; + } + }); + + return hasTests; +}; + + internals.collectDeps = function (experiment, key) { const set = []; @@ -598,10 +633,18 @@ internals.protect = function (item, state, callback) { item.onCleanup = func; }; - const itemResult = item.fn.call(null, (err) => { + const done = (err) => { finish(err, 'done'); - }, onCleanup); + }; + + item.notes = []; + done.note = (note) => { + + item.notes.push(note); + }; + + const itemResult = item.fn.call(null, done, onCleanup); if (itemResult && itemResult.then instanceof Function) { diff --git a/package.json b/package.json index 735ce202..7b4f27d3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "lab", "description": "Test utility", - "version": "10.5.1", + "version": "10.9.0", "repository": "git://github.com/hapijs/lab", "main": "lib/index.js", "keywords": [ @@ -13,10 +13,11 @@ "dependencies": { "bossy": "3.x.x", "diff": "2.x.x", - "eslint": "2.9.x", + "eslint": "3.0.x", "eslint-config-hapi": "9.x.x", "eslint-plugin-hapi": "4.x.x", "espree": "3.x.x", + "find-rc": "3.0.x", "handlebars": "4.x.x", "hoek": "4.x.x", "items": "2.x.x", @@ -26,7 +27,7 @@ "source-map-support": "0.4.x" }, "devDependencies": { - "code": "2.x.x", + "code": "3.x.x", "cpr": "1.1.x", "eslint-plugin-markdown": "1.0.0-beta.2", "lab-event-reporter": "1.x.x", diff --git a/test/cli.js b/test/cli.js index 8be7e62c..7d5a3f24 100755 --- a/test/cli.js +++ b/test/cli.js @@ -86,6 +86,25 @@ describe('CLI', () => { }); }); + it('runs a single test and uses .labrc when found', (done) => { + + RunCli([Path.join(__dirname, 'cli_labrc', 'index.js')], (error, result) => { + + if (error) { + done(error); + } + + expect(result.errorOutput).to.equal(''); + expect(result.code).to.equal(0); + expect(result.output).to.contain('1 tests complete'); + expect(result.output).to.contain('sets environment from .labrc.js'); + expect(result.output).to.contain('Coverage: 100'); + expect(result.output).to.contain('Linting results'); + expect(result.output).to.not.contain('No global variable leaks detected'); + done(); + }, Path.join(__dirname, 'cli_labrc')); + }); + it('exits with code 1 after function throws', (done) => { RunCli(['test/cli_throws/throws.js'], (error, result) => { @@ -395,7 +414,7 @@ describe('CLI', () => { it('doesn\'t fail with coverage when no external file is being tested', (done) => { - RunCli(['test/cli/simple.js', '-t', '10'], (error, result) => { + RunCli(['test/cli/simple.js', '-t', '100'], (error, result) => { if (error) { done(error); @@ -793,6 +812,21 @@ describe('CLI', () => { }); }); + it('reports a warning when no files matching the pattern are found', (done) => { + + RunCli(['test/cli_pattern', '-m', '2000', '-a', 'code', '-P', 'nofiles'], (error, result) => { + + if (error) { + done(error); + } + + expect(result.errorOutput).to.equal(''); + expect(result.code).to.equal(0); + expect(result.output).to.contain('The pattern provided (-P or --pattern) didn\'t match any files.'); + done(); + }); + }); + it('only loads files matching pattern when pattern at beginning of name (-P)', (done) => { RunCli(['test/cli_pattern', '-m', '2000', '-a', 'code', '-P', 'file'], (error, result) => { diff --git a/test/cli_labrc/.labrc.js b/test/cli_labrc/.labrc.js new file mode 100644 index 00000000..afc0cb28 --- /dev/null +++ b/test/cli_labrc/.labrc.js @@ -0,0 +1,8 @@ +module.exports = { + coverage: true, + environment: 'labrc', + threshold: 10, + lint: true, + leaks: false, + verbose: true +}; diff --git a/test/cli_labrc/index.js b/test/cli_labrc/index.js new file mode 100644 index 00000000..ef91acd6 --- /dev/null +++ b/test/cli_labrc/index.js @@ -0,0 +1,29 @@ +'use strict'; + +// Load modules + +const Code = require('code'); +const _Lab = require('../../test_runner'); + + +// Declare internals + +const internals = {}; + + +// Test shortcuts + +const lab = exports.lab = _Lab.script(); +const describe = lab.describe; +const it = lab.it; +const expect = Code.expect; + + +describe('Test .labrc.js', () => { + + it('sets environment from .labrc.js', (done) => { + + expect(process.env.NODE_ENV).to.equal('labrc'); + done(); + }); +}); diff --git a/test/coverage.js b/test/coverage.js index 23bb5a24..acfa9f08 100755 --- a/test/coverage.js +++ b/test/coverage.js @@ -59,6 +59,16 @@ describe('Coverage', () => { done(); }); + it('measures coverage a file with test in the name', (done) => { + + const Test = require('./coverage/test-folder/test-name.js'); + Test.method(); + + const cov = Lab.coverage.analyze({ coveragePath: Path.join(__dirname, 'coverage/test-folder'), coverageExclude: ['test', 'node_modules'] }); + expect(cov.percent).to.equal(100); + done(); + }); + it('identifies lines with partial coverage when having external sourcemap', (done) => { const Test = require('./coverage/sourcemaps-external'); @@ -80,7 +90,7 @@ describe('Coverage', () => { } }); - expect(missedLines).to.deep.include([ + expect(missedLines).to.include([ { filename: 'test/coverage/while.js', lineNumber: '5', originalLineNumber: 11 }, { filename: 'test/coverage/while.js', lineNumber: '6', originalLineNumber: 12 } ]); @@ -109,7 +119,7 @@ describe('Coverage', () => { } }); - expect(missedLines).to.deep.include([ + expect(missedLines).to.include([ { filename: './while.js', lineNumber: '5', originalLineNumber: 11 }, { filename: './while.js', lineNumber: '6', originalLineNumber: 12 } ]); @@ -199,7 +209,7 @@ describe('Coverage', () => { it('should work with loop labels', (done) => { const Test = require('./coverage/loop-labels.js'); - expect(Test.method()).to.deep.equal([1, 0]); + expect(Test.method()).to.equal([1, 0]); const cov = Lab.coverage.analyze({ coveragePath: Path.join(__dirname, 'coverage/loop-labels') }); const source = cov.files[0].source; @@ -215,7 +225,7 @@ describe('Coverage', () => { } }); - expect(missedChunks).to.have.length(1).and.to.deep.equal([{ source: 'j < 1', miss: 'true' }]); + expect(missedChunks).to.have.length(1).and.to.equal([{ source: 'j < 1', miss: 'true' }]); done(); }); @@ -235,8 +245,8 @@ describe('Coverage', () => { const cov = Lab.coverage.analyze({ coveragePath: Path.join(__dirname, 'coverage/single-line-functions') }); const source = cov.files[0].source; const missedLines = Object.keys(source).filter((lineNumber) => source[lineNumber].miss); - expect(results).to.deep.equal([7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 5, 10, 5]); - expect(missedLines).to.deep.equal(['12', '15', '21', '27', '30', '33', '39', '46', '50', '53', '56']); + expect(results).to.equal([7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 5, 10, 5]); + expect(missedLines).to.equal(['12', '15', '21', '27', '30', '33', '39', '46', '50', '53', '56']); done(); }); @@ -249,7 +259,7 @@ describe('Coverage', () => { const source = cov.files[0].source; const missedLines = Object.keys(source).filter((lineNumber) => source[lineNumber].miss); expect(result).to.equal(7); - expect(missedLines).to.deep.equal(['19', '22']); + expect(missedLines).to.equal(['19', '22']); done(); }); @@ -270,7 +280,7 @@ describe('Coverage', () => { return file.filename; }); - expect(sorted).to.deep.equal(['/a/b', '/a/c', '/a/b/a', '/a/b/c', '/a/c/b']); + expect(sorted).to.equal(['/a/b', '/a/c', '/a/b/a', '/a/b/c', '/a/c/b']); done(); }); }); diff --git a/test/coverage/test-folder/test-name.js b/test/coverage/test-folder/test-name.js new file mode 100644 index 00000000..84e87723 --- /dev/null +++ b/test/coverage/test-folder/test-name.js @@ -0,0 +1,14 @@ +'use strict'; + +// Load modules + + +// Declare internals + +const internals = {}; + + +exports.method = function () { + + return; +}; diff --git a/test/index.js b/test/index.js index 8f52357b..bd143e7e 100755 --- a/test/index.js +++ b/test/index.js @@ -775,7 +775,7 @@ describe('Lab', () => { it('schedules automatic execution', { parallel: false }, (done) => { - const script = Lab.script(); + const script = Lab.script({ output: false }); script.experiment('test', () => { script.test('works', (testDone) => { diff --git a/test/leaks.js b/test/leaks.js index a3419515..e7b98de4 100755 --- a/test/leaks.js +++ b/test/leaks.js @@ -10,7 +10,7 @@ const Lab = require('../'); // Declare internals const internals = { - harmonyGlobals: ['Promise', 'Proxy', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet', 'Reflect'] + harmonyGlobals: ['Proxy', 'Reflect'] }; diff --git a/test/lint/eslint/fix/success.js b/test/lint/eslint/fix/success.js new file mode 100644 index 00000000..f590a5f7 --- /dev/null +++ b/test/lint/eslint/fix/success.js @@ -0,0 +1,14 @@ +'use strict'; + +// Load modules + + +// Declare internals + +const internals = {}; + + +exports.method = function (value) { + + return value; +}; diff --git a/test/linters.js b/test/linters.js index 758844dc..c27a0e1b 100755 --- a/test/linters.js +++ b/test/linters.js @@ -2,6 +2,7 @@ // Load modules +const Fs = require('fs'); const Path = require('path'); const _Lab = require('../test_runner'); const Code = require('code'); @@ -31,7 +32,7 @@ describe('Linters - eslint', () => { const checkedFile = eslintResults[0]; expect(checkedFile).to.include({ filename: Path.join(path, 'fail.js') }); - expect(checkedFile.errors).to.deep.include([ + expect(checkedFile.errors).to.include([ { line: 13, severity: 'ERROR', message: 'semi - Missing semicolon.' }, { line: 14, severity: 'WARNING', message: 'eol-last - Newline required at end of file but not found.' } ]); @@ -53,7 +54,7 @@ describe('Linters - eslint', () => { const checkedFile = eslintResults[0]; expect(checkedFile).to.include({ filename: Path.join(path, 'fail.js') }); - expect(checkedFile.errors).to.deep.include([ + expect(checkedFile.errors).to.include([ { line: 13, severity: 'ERROR', message: 'semi - Missing semicolon.' }, { line: 14, severity: 'WARNING', message: 'eol-last - Newline required at end of file but not found.' } ]); @@ -75,9 +76,9 @@ describe('Linters - eslint', () => { const checkedFile = eslintResults[0]; expect(checkedFile).to.include({ filename: Path.join(path, 'fail.js') }); - expect(checkedFile.errors).to.deep.include([ + expect(checkedFile.errors).to.include([ { line: 14, severity: 'ERROR', message: 'eol-last - Newline required at end of file but not found.' }]); - expect(checkedFile.errors).to.not.deep.include({ line: 8, severity: 'ERROR', message: 'no-unused-vars - internals is defined but never used' }); + expect(checkedFile.errors).to.not.include({ line: 8, severity: 'ERROR', message: 'no-unused-vars - internals is defined but never used' }); done(); }); }); @@ -151,6 +152,51 @@ describe('Linters - eslint', () => { done(); }); }); + + it('should fix lint rules when --lint-fix used', (done, onCleanup) => { + + const originalWriteFileSync = Fs.writeFileSync; + + onCleanup((next) => { + + Fs.writeFileSync = originalWriteFileSync; + next(); + }); + + Fs.writeFileSync = (path, output) => { + + expect(path).to.endWith('test/lint/eslint/fix/success.js'); + expect(output).to.endWith('\n\n return value;\n};\n'); + }; + + const path = Path.join(__dirname, 'lint', 'eslint', 'fix'); + Linters.lint({ lintingPath: path, linter: 'eslint', 'lint-fix': true }, (err, result) => { + + expect(err).to.not.exist(); + expect(result).to.include('lint'); + + const eslintResults = result.lint; + expect(eslintResults).to.have.length(1); + expect(eslintResults[0]).to.include({ + totalErrors: 0, + totalWarnings: 0 + }); + done(); + }); + }); + + it('should error on malformed lint-options', (done) => { + + const path = Path.join(__dirname, 'lint', 'eslint', 'fix'); + + const f = () => { + + Linters.lint({ lintingPath: path, linter: 'eslint', 'lint-options': '}' }, () => {}); + }; + + expect(f).to.throw('lint-options could not be parsed'); + done(); + }); }); describe('Linters - custom', () => { diff --git a/test/reporters.js b/test/reporters.js index aa68df62..30dbd0da 100755 --- a/test/reporters.js +++ b/test/reporters.js @@ -339,7 +339,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console' }, (err, code, output) => { + Lab.report(script, { reporter: 'console', output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(0); @@ -361,7 +361,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', colors: false }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(1); @@ -392,7 +392,7 @@ describe('Reporter', () => { script.test('a todo test'); }); - Lab.report(script, { reporter: 'console' }, (err, code, output) => { + Lab.report(script, { reporter: 'console', output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(0); @@ -417,7 +417,7 @@ describe('Reporter', () => { }); global.x1 = true; - Lab.report(script, { reporter: 'console', colors: false }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, output: false }, (err, code, output) => { delete global.x1; expect(err).to.not.exist(); @@ -435,13 +435,13 @@ describe('Reporter', () => { script.test('works', (finished) => { - expect(['a', 'b']).to.deep.equal(['a', 'c']); + expect(['a', 'b']).to.equal(['a', 'c']); finished(); }); }); global.x1 = true; - Lab.report(script, { reporter: 'console', colors: false }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, output: false }, (err, code, output) => { delete global.x1; expect(err).to.not.exist(); @@ -468,7 +468,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', colors: false, leaks: false }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, leaks: false, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(1); @@ -492,7 +492,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', colors: false, leaks: false }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, leaks: false, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(1); @@ -516,7 +516,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', colors: false, leaks: false }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, leaks: false, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(1); @@ -540,7 +540,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', colors: false, leaks: false }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, leaks: false, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(1); @@ -562,7 +562,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', colors: false }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(1); @@ -582,7 +582,7 @@ describe('Reporter', () => { script.test('works', (finished) => { }); }); - Lab.report(script, { reporter: 'console', colors: false, timeout: 1 }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, timeout: 1, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(1); @@ -625,7 +625,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', colors: false, 'context-timeout': 1 }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, 'context-timeout': 1, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(1); @@ -650,7 +650,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', colors: false, 'context-timeout': 1000 }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, 'context-timeout': 1000, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(0); @@ -670,7 +670,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', colors: false }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(1); @@ -681,6 +681,28 @@ describe('Reporter', () => { }); }); + it('generates a report with all notes displayed', (done) => { + + const script = Lab.script(); + script.experiment('test', () => { + + script.test('works', (finished) => { + + finished.note('This is a sweet feature'); + finished.note('Here is another note'); + finished(); + }); + }); + + Lab.report(script, { reporter: 'console', progress: 0, output: false }, (err, code, output) => { + + expect(err).not.to.exist(); + expect(output).to.contain('This is a sweet feature'); + expect(output).to.contain('Here is another note'); + done(); + }); + }); + it('generates a report without progress', (done) => { const script = Lab.script(); @@ -692,7 +714,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', progress: 0 }, (err, code, output) => { + Lab.report(script, { reporter: 'console', progress: 0, output: false }, (err, code, output) => { expect(err).not.to.exist(); expect(output).to.match(/^\u001b\[32m1 tests complete\u001b\[0m\nTest duration: \d+ ms\n\u001b\[32mNo global variable leaks detected\u001b\[0m\n\n$/); @@ -711,10 +733,74 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', progress: 2 }, (err, code, output) => { + Lab.report(script, { reporter: 'console', progress: 2, output: false }, (err, code, output) => { + + expect(err).not.to.exist(); + expect(output).to.match(/^test\n \u001b\[32m✔\u001b\[0m \u001b\[92m1\) works \(\d+ ms\)\u001b\[0m\n\n\n\u001b\[32m1 tests complete\u001b\[0m\nTest duration: \d+ ms\n\u001b\[32mNo global variable leaks detected\u001b\[0m\n\n$/); + done(); + }); + }); + + it('generates a report with verbose progress with experiments with same named tests', (done) => { + + const script = Lab.script(); + script.experiment('experiment', () => { + + script.experiment('sub experiment', () => { + + script.experiment('sub sub experiment', () => { + + script.test('works', (finished) => finished()); + }); + }); + + script.experiment('sub experiment', () => { + + script.experiment('sub sub experiment', () => { + + script.test('works', (finished) => finished()); + }); + }); + + script.experiment('sub experiment', () => { + + script.experiment('sub sub experiment 1', () => { + + script.experiment('sub sub sub experiment', () => { + + script.test('works', (finished) => finished()); + }); + }); + + script.experiment('sub sub experiment', () => { + + script.experiment('sub sub sub experiment', () => { + + script.test('works', (finished) => finished()); + }); + }); + }); + }); + + Lab.report(script, { reporter: 'console', progress: 2, output: false }, (err, code, output) => { + + expect(err).not.to.exist(); + expect(output).to.contain('4) works'); + done(); + }); + }); + + it('generates a report with verbose progress with the same test name and no wrapper experiment', (done) => { + + const script = Lab.script(); + script.test('works', (finished) => finished()); + script.test('works', (finished) => finished()); + + Lab.report(script, { reporter: 'console', progress: 2, output: false }, (err, code, output) => { expect(err).not.to.exist(); - expect(output).to.match(/^test\n \u001b\[32m✔\u001b\[0m \u001b\[90m1\) works \(\d+ ms\)\u001b\[0m\n\n\n\u001b\[32m1 tests complete\u001b\[0m\nTest duration: \d+ ms\n\u001b\[32mNo global variable leaks detected\u001b\[0m\n\n$/); + expect(output).to.contain('1) works'); + expect(output).to.contain('2) works'); done(); }); }); @@ -731,10 +817,10 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', progress: 2, assert: Code }, (err, code, output) => { + Lab.report(script, { reporter: 'console', progress: 2, assert: Code, output: false }, (err, code, output) => { expect(err).not.to.exist(); - expect(output).to.match(/^test\n \u001b\[32m✔\u001b\[0m \u001b\[90m1\) works \(\d+ ms and \d+ assertions\)\u001b\[0m\n\n\n\u001b\[32m1 tests complete\u001b\[0m\nTest duration: \d+ ms\nAssertions count\: \d+ \(verbosity\: \d+\.\d+\)\n\u001b\[32mNo global variable leaks detected\u001b\[0m\n\n$/); + expect(output).to.match(/^test\n \u001b\[32m✔\u001b\[0m \u001b\[92m1\) works \(\d+ ms and \d+ assertions\)\u001b\[0m\n\n\n\u001b\[32m1 tests complete\u001b\[0m\nTest duration: \d+ ms\nAssertions count\: \d+ \(verbosity\: \d+\.\d+\)\n\u001b\[32mNo global variable leaks detected\u001b\[0m\n\n$/); done(); }); }); @@ -753,7 +839,7 @@ describe('Reporter', () => { const oldPlatform = process.platform; Object.defineProperty(process, 'platform', { writable: true, value: 'win32' }); - Lab.report(script, { reporter: 'console', progress: 2 }, (err, code, output) => { + Lab.report(script, { reporter: 'console', progress: 2, output: false }, (err, code, output) => { process.platform = oldPlatform; expect(err).not.to.exist(); @@ -782,7 +868,7 @@ describe('Reporter', () => { script.test('a todo test'); }); - Lab.report(script, { reporter: 'console', 'silent-skips': true }, (err, code, output) => { + Lab.report(script, { reporter: 'console', 'silent-skips': true, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(0); @@ -814,7 +900,7 @@ describe('Reporter', () => { script.test('a todo test'); }); - Lab.report(script, { reporter: 'console', progress: 2, 'silent-skips': true }, (err, code, output) => { + Lab.report(script, { reporter: 'console', progress: 2, 'silent-skips': true, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(0); @@ -850,7 +936,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', coverage: true, coveragePath: Path.join(__dirname, './coverage/console') }, (err, code, output) => { + Lab.report(script, { reporter: 'console', coverage: true, coveragePath: Path.join(__dirname, './coverage/console'), output: false }, (err, code, output) => { expect(err).not.to.exist(); expect(output).to.contain('Coverage: 80.95% (4/21)'); @@ -893,7 +979,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', coverage: true, coveragePath: Path.join(__dirname, './coverage/sourcemaps-external'), sourcemaps: true }, (err, code, output) => { + Lab.report(script, { reporter: 'console', coverage: true, coveragePath: Path.join(__dirname, './coverage/sourcemaps-external'), sourcemaps: true, output: false }, (err, code, output) => { expect(err).not.to.exist(); expect(output).to.contain('test/coverage/sourcemaps-external.js missing coverage from file(s):'); @@ -916,7 +1002,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', coverage: true, coveragePath: Path.join(__dirname, './coverage/'), sourcemaps: true }, (err, code, output) => { + Lab.report(script, { reporter: 'console', coverage: true, coveragePath: Path.join(__dirname, './coverage/'), sourcemaps: true, output: false }, (err, code, output) => { expect(err).not.to.exist(); expect(output).to.not.contain('sourcemaps-covered'); @@ -953,7 +1039,7 @@ describe('Reporter', () => { } }); - Lab.report(script, { reporter: 'console', colors: false }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(1); @@ -983,11 +1069,11 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', colors: false, progress: 2 }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, progress: 2, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(1); - expect(output).to.match(/test\n ✔ 1\) works \(\d+ ms\)\n ✖2\) fails\n \- 3\) skips \(\d+ ms\)\n/); + expect(output).to.match(/test\n ✔ 1\) works \(\d+ ms\)\n ✖ 2\) fails\n \- 3\) skips \(\d+ ms\)\n/); done(); }); }); @@ -1011,7 +1097,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console' }, (err, code, output) => { + Lab.report(script, { reporter: 'console', output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(0); @@ -1032,7 +1118,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', colors: false }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, output: false }, (err, code, output) => { expect(err).not.to.exist(); const result = output.replace(/at.*\.js\:\d+\:\d+\)?/g, 'at '); @@ -1058,7 +1144,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', colors: false }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, output: false }, (err, code, output) => { expect(err).not.to.exist(); @@ -1088,7 +1174,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', colors: false }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, output: false }, (err, code, output) => { expect(err).not.to.exist(); @@ -1196,7 +1282,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', colors: false }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, output: false }, (err, code, output) => { expect(err).not.to.exist(); const result = output.replace(/at.*\.js\:\d+\:\d+\)?/g, 'at '); @@ -1228,7 +1314,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'console', colors: false }, (err, code, output) => { + Lab.report(script, { reporter: 'console', colors: false, output: false }, (err, code, output) => { expect(err).not.to.exist(); const result = output.replace(/at.*\.js\:\d+\:\d+\)?/g, 'at '); @@ -1263,7 +1349,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'json', lint: true, linter: 'eslint' }, (err, code, output) => { + Lab.report(script, { reporter: 'json', lint: true, linter: 'eslint', output: false }, (err, code, output) => { const result = JSON.parse(output); expect(err).to.not.exist(); @@ -1301,7 +1387,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'json' }, (err, code, output) => { + Lab.report(script, { reporter: 'json', output: false }, (err, code, output) => { const result = JSON.parse(output); expect(err).to.not.exist(); @@ -1326,7 +1412,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'json', coverage: true, coveragePath: Path.join(__dirname, './coverage/json') }, (err, code, output) => { + Lab.report(script, { reporter: 'json', coverage: true, coveragePath: Path.join(__dirname, './coverage/json'), output: false }, (err, code, output) => { expect(err).not.to.exist(); const result = JSON.parse(output); @@ -1352,7 +1438,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'html', coverage: true, coveragePath: Path.join(__dirname, './coverage/html') }, (err, code, output) => { + Lab.report(script, { reporter: 'html', coverage: true, coveragePath: Path.join(__dirname, './coverage/html'), output: false }, (err, code, output) => { expect(err).not.to.exist(); expect(output).to.contain('
'); @@ -1376,7 +1462,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'html', coverage: true, coveragePath: Path.join(__dirname, './coverage/sourcemaps-external'), sourcemaps: true }, (err, code, output) => { + Lab.report(script, { reporter: 'html', coverage: true, coveragePath: Path.join(__dirname, './coverage/sourcemaps-external'), sourcemaps: true, output: false }, (err, code, output) => { expect(err).not.to.exist(); expect(output).to.contain([ @@ -1417,7 +1503,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'html', coverage: true, coveragePath: Path.join(__dirname, './coverage/html-lint/'), lint: true, linter: 'eslint', lintingPath: Path.join(__dirname, './coverage/html-lint') }, (err, code, output) => { + Lab.report(script, { reporter: 'html', coverage: true, coveragePath: Path.join(__dirname, './coverage/html-lint/'), lint: true, linter: 'eslint', lintingPath: Path.join(__dirname, './coverage/html-lint'), output: false }, (err, code, output) => { expect(err).not.to.exist(); expect(output) @@ -1461,7 +1547,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'html', coverage: true, coveragePath: Path.join(__dirname, './coverage/html-lint/'), lint: true, linter: 'eslint', lintingPath: Path.join(__dirname, './coverage/html-lint'), 'lint-errors-threshold': 2, 'lint-warnings-threshold': 2 }, (err, code, output) => { + Lab.report(script, { reporter: 'html', coverage: true, coveragePath: Path.join(__dirname, './coverage/html-lint/'), lint: true, linter: 'eslint', lintingPath: Path.join(__dirname, './coverage/html-lint'), 'lint-errors-threshold': 2, 'lint-warnings-threshold': 2, output: false }, (err, code, output) => { expect(err).not.to.exist(); expect(output) @@ -1485,7 +1571,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'html', 'context-timeout': 1 }, (err, code, output) => { + Lab.report(script, { reporter: 'html', 'context-timeout': 1, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(1); @@ -1512,7 +1598,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'html', 'context-timeout': 1 }, (err, code, output) => { + Lab.report(script, { reporter: 'html', 'context-timeout': 1, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(1); @@ -1628,7 +1714,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'html', coveragePath: Path.join(__dirname, './coverage/html') }, (err, code, output) => { + Lab.report(script, { reporter: 'html', coveragePath: Path.join(__dirname, './coverage/html'), output: false }, (err, code, output) => { expect(err).not.to.exist(); expect(output).to.contain('Test Report'); @@ -1671,7 +1757,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'tap' }, (err, code, output) => { + Lab.report(script, { reporter: 'tap', output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(1); @@ -1714,7 +1800,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'junit' }, (err, code, output) => { + Lab.report(script, { reporter: 'junit', output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(1); @@ -1747,7 +1833,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'lcov', coverage: true }, (err, code, output) => { + Lab.report(script, { reporter: 'lcov', coverage: true, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(0); @@ -1776,7 +1862,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'lcov', coverage: false }, (err, code, output) => { + Lab.report(script, { reporter: 'lcov', coverage: false, output: false }, (err, code, output) => { expect(err).to.not.exist(); expect(code).to.equal(0); @@ -1812,7 +1898,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'clover', coverage: true, coveragePath: Path.join(__dirname, './coverage/clover') }, (err, code, output) => { + Lab.report(script, { reporter: 'clover', coverage: true, coveragePath: Path.join(__dirname, './coverage/clover'), output: false }, (err, code, output) => { expect(err).not.to.exist(); expect(output).to.contain('clover.test.coverage'); @@ -1852,7 +1938,7 @@ describe('Reporter', () => { const origCwd = process.cwd(); process.chdir(Path.join(__dirname, './coverage/')); - Lab.report(script, { reporter: 'clover', coverage: true, coveragePath: Path.join(__dirname, './coverage/clover') }, (err, code, output) => { + Lab.report(script, { reporter: 'clover', coverage: true, coveragePath: Path.join(__dirname, './coverage/clover'), output: false }, (err, code, output) => { expect(err).not.to.exist(); expect(output).to.not.contain('clover.test.coverage'); @@ -1894,7 +1980,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: 'clover', coverage: false, coveragePath: Path.join(__dirname, './coverage/clover') }, (err, code, output) => { + Lab.report(script, { reporter: 'clover', coverage: false, coveragePath: Path.join(__dirname, './coverage/clover'), output: false }, (err, code, output) => { expect(err).not.to.exist(); expect(output).to.not.contain('clover.test.coverage'); @@ -2031,7 +2117,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: reporter }, (err, code, output) => { + Lab.report(script, { reporter: reporter, output: false }, (err, code, output) => { expect(err).to.not.exist(); done(); @@ -2051,7 +2137,7 @@ describe('Reporter', () => { }); }); - Lab.report(script, { reporter: reporter }, (err, code, output) => { + Lab.report(script, { reporter: reporter, output: false }, (err, code, output) => { expect(err).to.not.exist(); done(); diff --git a/test/run_cli.js b/test/run_cli.js index 9d77c337..b432d096 100644 --- a/test/run_cli.js +++ b/test/run_cli.js @@ -11,7 +11,9 @@ const internals = { module.exports = (args, callback, root) => { - const cli = ChildProcess.spawn('node', [].concat(internals.labPath, args), { 'cwd' : root ? root : '.' }); + const childEnv = Object.assign({}, process.env); + delete childEnv.NODE_ENV; + const cli = ChildProcess.spawn('node', [].concat(internals.labPath, args), { env: childEnv, cwd : root || '.' }); let output = ''; let errorOutput = ''; let combinedOutput = ''; diff --git a/test/runner.js b/test/runner.js index 13fa2551..ba1e4847 100755 --- a/test/runner.js +++ b/test/runner.js @@ -678,6 +678,114 @@ describe('Runner', () => { }); }); + it('skips before function in non-run experiment', (done) => { + + const script = Lab.script(); + script.experiment('test', () => { + + script.experiment.skip('subexperiment1', () => { + + script.before((beforeDone) => { + + throw new Error(); + }); + + script.test('s1', (testDone) => testDone()); + }); + + script.experiment('subexperiment2', () => { + + script.before((beforeDone) => beforeDone()); + + script.test('s1', (testDone) => testDone()); + }); + }); + + Lab.execute(script, {}, null, (err, notebook) => { + + expect(err).not.to.exist(); + expect(notebook.tests).to.have.length(2); + expect(notebook.tests.filter((test) => test.skipped)).to.have.length(1); + expect(notebook.failures).to.equal(0); + done(); + }); + }); + + it('skips before function when not run through index', (done) => { + + const script = Lab.script(); + script.experiment('test', () => { + + script.experiment('subexperiment1', () => { + + script.before((beforeDone) => { + + throw new Error(); + }); + + script.test('s1', (testDone) => testDone()); + }); + + script.experiment('subexperiment2', () => { + + script.before((beforeDone) => beforeDone()); + + script.test('s1', (testDone) => testDone()); + }); + }); + + Lab.execute(script, { ids: [2] }, null, (err, notebook) => { + + expect(err).not.to.exist(); + expect(notebook.tests).to.have.length(1); + expect(notebook.failures).to.equal(0); + done(); + }); + }); + + it('skips before function when not run through index and in sub experiment', (done) => { + + const script = Lab.script(); + script.experiment('test', () => { + + script.experiment('subexperiment1', () => { + + script.before((beforeDone) => { + + throw new Error(); + }); + + script.experiment('sub sub experiment1', () => { + + script.before((beforeDone) => { + + throw new Error(); + }); + + script.test('s1', (testDone) => testDone()); + }); + }); + + script.experiment('subexperiment2', () => { + + script.experiment('sub subexperiment2', () => { + + script.before((beforeDone) => beforeDone()); + + script.test('s1', (testDone) => testDone()); + }); + }); + }); + + Lab.execute(script, { ids: [2] }, null, (err, notebook) => { + + expect(err).not.to.exist(); + expect(notebook.tests).to.have.length(1); + expect(notebook.failures).to.equal(0); + done(); + }); + }); + it('dry run', (done) => { const script = Lab.script(); @@ -803,7 +911,7 @@ describe('Runner', () => { Lab.execute(scripts, { dry: true, shuffle: true }, null, (err, notebook2) => { expect(err).not.to.exist(); - expect(notebook1.tests).to.not.deep.equal(notebook2.tests); + expect(notebook1.tests).to.not.equal(notebook2.tests); Math.random = random; done(); }); @@ -874,7 +982,7 @@ describe('Runner', () => { expect(err).to.not.exist(); expect(notebook.tests[0].err).to.equal('\'before\' action failed'); - expect(steps).to.deep.equal(['before']); + expect(steps).to.equal(['before']); done(); }); }); @@ -908,7 +1016,7 @@ describe('Runner', () => { expect(err).to.not.exist(); expect(notebook.tests[0].err).to.equal('\'before each\' action failed'); - expect(steps).to.deep.equal(['before']); + expect(steps).to.equal(['before']); done(); }); }); @@ -974,7 +1082,7 @@ describe('Runner', () => { Lab.execute(script, null, null, (err, notebook) => { expect(err).not.to.exist(); - expect(steps).to.deep.equal([ + expect(steps).to.equal([ 'outer beforeEach', 'first test', 'outer afterEach 1', @@ -1016,7 +1124,7 @@ describe('Runner', () => { Lab.execute(script, { parallel: true }, null, (err, notebook) => { expect(err).not.to.exist(); - expect(steps).to.deep.equal(['2', '1']); + expect(steps).to.equal(['2', '1']); done(); }); }); @@ -1046,7 +1154,7 @@ describe('Runner', () => { Lab.execute(script, { parallel: true }, null, (err, notebook) => { expect(err).not.to.exist(); - expect(steps).to.deep.equal(['1', '2']); + expect(steps).to.equal(['1', '2']); done(); }); }); @@ -1076,7 +1184,7 @@ describe('Runner', () => { Lab.execute(script, null, null, (err, notebook) => { expect(err).not.to.exist(); - expect(steps).to.deep.equal(['2', '1']); + expect(steps).to.equal(['2', '1']); done(); }); }); diff --git a/test/utils.js b/test/utils.js index 56ab0feb..af159108 100755 --- a/test/utils.js +++ b/test/utils.js @@ -35,7 +35,7 @@ describe('Utils', () => { }; const merged = Utils.mergeOptions(parent, child); - expect(merged).to.deep.equal({ a: 1, b: 3, c: 4 }); + expect(merged).to.equal({ a: 1, b: 3, c: 4 }); done(); }); @@ -47,7 +47,7 @@ describe('Utils', () => { }; const merged = Utils.mergeOptions(parent, null); - expect(merged).to.deep.equal({ a: 1, b: 2 }); + expect(merged).to.equal({ a: 1, b: 2 }); done(); }); @@ -59,7 +59,7 @@ describe('Utils', () => { }; const merged = Utils.mergeOptions(null, child); - expect(merged).to.deep.equal({ b: 3, c: 4 }); + expect(merged).to.equal({ b: 3, c: 4 }); done(); }); @@ -78,7 +78,7 @@ describe('Utils', () => { }; const merged = Utils.mergeOptions(parent, child, ['e', 'f']); - expect(merged).to.deep.equal({ a: 1, b: 3, c: 4 }); + expect(merged).to.equal({ a: 1, b: 3, c: 4 }); done(); }); @@ -97,7 +97,7 @@ describe('Utils', () => { }; Utils.applyOptions(parent, child); - expect(parent).to.deep.equal({ a: 1, b: 3, c: 4, e: 5, f: 6 }); + expect(parent).to.equal({ a: 1, b: 3, c: 4, e: 5, f: 6 }); done(); }); });