-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Add ability to run a test residing on a given line #2181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ Translations: [Français](https://github.com/avajs/ava-docs/blob/master/fr_FR/do | |
| $ npx ava --help | ||
|
|
||
| Usage | ||
| ava [<file> ...] | ||
| ava [<file[:line]> ...] | ||
|
|
||
| Options | ||
| --watch, -w Re-run tests when tests and source files change | ||
|
|
@@ -27,6 +27,7 @@ $ npx ava --help | |
| Examples | ||
| ava | ||
| ava test.js test2.js | ||
| ava test.js:5 test2.js:4-5,9 | ||
| ava test-*.js | ||
| ava test | ||
|
|
||
|
|
@@ -125,6 +126,48 @@ test(function foo(t) { | |
| }); | ||
| ``` | ||
|
|
||
| ## Running tests residing on a given line | ||
|
|
||
| Sometimes during development it's helpful to run a test that is on a specific line. AVA allows you to run tests based on their location in the source code. | ||
|
|
||
| Match the test on line `5`: | ||
|
|
||
| ```console | ||
| npx ava test.js:5 | ||
| ``` | ||
|
|
||
| It is also possible to use a range: | ||
|
|
||
| ```console | ||
| npx ava test.js:5-10 | ||
| ``` | ||
|
|
||
| Using multiple ranges is also possible: | ||
|
|
||
| ```console | ||
| npx ava test.js:5-10,18,10 | ||
| ``` | ||
|
|
||
| To run a test you can use any line from where you call `test` until your next call to the `test` function, as an example you can use any of the lines from 3 to 6 in the following code to run `foo`. | ||
|
|
||
| ```js | ||
| 1 import test from 'ava'; | ||
| 2 | ||
| 3 test('foo', t => { | ||
| 4 t.pass(); | ||
| 5 }); | ||
| 6 | ||
| 7 test('bar', async t => { | ||
| 8 const bar = Promise.resolve('bar'); | ||
| 9 t.is(await bar, 'bar'); | ||
| 10 }); | ||
| ``` | ||
|
|
||
| > Please note that using any number more than the maximum line-number will execute the last test. | ||
|
|
||
| Using `--match` beside this feature will only match the tests on the given area. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about:
|
||
|
|
||
|
|
||
| ## Resetting AVA's cache | ||
|
|
||
| AVA caches the compiled test and helper files. It automatically recompiles these files when you change them. AVA tries its best to detect changes to your Babel configuration files, plugins and presets. If it seems like your latest Babel configuration isn't being applied, however, you can run AVA with the `--reset-cache` flag to reset AVA's cache. If set, all files in the `node_modules/.cache/ava` directory are deleted. Run AVA as normal to apply your new Babel configuration. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,7 +51,12 @@ class Api extends Emittery { | |
| } | ||
|
|
||
| async run(files = [], runtimeOptions = {}) { | ||
| files = files.map(file => path.resolve(this.options.resolveTestsFrom, file)); | ||
| const ranges = new Map(runtimeOptions.ranges || []); | ||
| files = files.map(file => { | ||
| const filename = path.resolve(this.options.resolveTestsFrom, file); | ||
| ranges.set(filename, ranges.get(file)); | ||
| return filename; | ||
| }); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not do this processing in the CLI instead? |
||
|
|
||
| const apiOptions = this.options; | ||
|
|
||
|
|
@@ -223,7 +228,8 @@ class Api extends Emittery { | |
| ...apiOptions, | ||
| recordNewSnapshots: !isCi, | ||
| // If we're looking for matches, run every single test process in exclusive-only mode | ||
| runOnlyExclusive: apiOptions.match.length > 0 || runtimeOptions.runOnlyExclusive === true | ||
| runOnlyExclusive: apiOptions.match.length > 0 || runtimeOptions.runOnlyExclusive === true, | ||
| lines: ranges.get(file) || [] | ||
| }; | ||
| if (precompilation) { | ||
| options.cacheDir = precompilation.cacheDir; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ const meow = require('meow'); | |
| const Promise = require('bluebird'); | ||
| const isCi = require('is-ci'); | ||
| const loadConfig = require('./load-config'); | ||
| const rangeParser = require('./range'); | ||
|
|
||
| // Bluebird specific | ||
| Promise.longStackTraces(); | ||
|
|
@@ -35,7 +36,7 @@ exports.run = async () => { // eslint-disable-line complexity | |
|
|
||
| const cli = meow(` | ||
| Usage | ||
| ava [<file> ...] | ||
| ava [<file[:line]> ...] | ||
|
|
||
| Options | ||
| --watch, -w Re-run tests when tests and source files change | ||
|
|
@@ -56,6 +57,7 @@ exports.run = async () => { // eslint-disable-line complexity | |
| Examples | ||
| ava | ||
| ava test.js test2.js | ||
| ava test.js:5 test2.js:4-5,9 | ||
| ava test-*.js | ||
| ava test | ||
|
|
||
|
|
@@ -280,7 +282,14 @@ exports.run = async () => { // eslint-disable-line complexity | |
| }); | ||
| watcher.observeStdin(process.stdin); | ||
| } else { | ||
| const runStatus = await api.run(files); | ||
| const {filenames, ranges, ignored} = rangeParser.parseFileSelections(files); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think about executing this when |
||
| if (ignored.length > 0) { | ||
| for (const file of ignored) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to add the length check. |
||
| console.error(`Ignoring other tests from ${file} since you selected lines.`); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can't write to the console from here, it messes with the reporters. I think for now we can use |
||
| } | ||
| } | ||
|
|
||
| const runStatus = await api.run(filenames, {ranges}); | ||
| process.exitCode = runStatus.suggestExitCode({matching: match.length > 0}); | ||
| reporter.endRun(); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| 'use strict'; | ||
| const rangeParser = require('parse-numeric-range'); | ||
|
|
||
| function parseFileSelections(files) { | ||
| const ranges = new Map(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| const ignored = []; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be a set, so you don't need to dedupe it later. |
||
| const filenames = []; | ||
novemberborn marked this conversation as resolved.
Show resolved
Hide resolved
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's make this a set, too. |
||
|
|
||
| for (const file of files) { | ||
| const [actualFilename, lines] = parseFileSelection(file); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With |
||
|
|
||
| if (!ranges.has(actualFilename)) { | ||
| ranges.set(actualFilename, []); | ||
| filenames.push(actualFilename); | ||
| } | ||
|
|
||
| if (lines.length === 0 && ranges.get(actualFilename).length > 0) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's move this up, and change the condition to |
||
| ignored.push(file); | ||
| continue; | ||
| } | ||
|
|
||
| ranges.set(actualFilename, ranges.get(actualFilename).concat(lines)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We now need to set the first value or otherwise modify the arary: if (!ranges.has(actualFilename)) {
ranges.set(actualFilename, lines)
} else {
ranges.get(actualFileName).push(...lines)
} |
||
| } | ||
|
|
||
| for (const file of filenames) { | ||
| ranges.set(file, [...new Set(ranges.get(file))]); | ||
| } | ||
|
|
||
| return { | ||
| filenames, | ||
| ranges, | ||
| ignored: [...new Set(ignored)] | ||
| }; | ||
| } | ||
|
|
||
| exports.parseFileSelections = parseFileSelections; | ||
|
|
||
| const rangeRegExp = /^(\d+(-\d+)?,?)+$/; | ||
|
|
||
| function parseFileSelection(file) { | ||
| const colonIndex = file.lastIndexOf(':'); | ||
| const mightHaveRange = colonIndex > -1 && file[colonIndex + 1] !== '\\'; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the On the other hand, given that a range must start with a digit, do we really need this clause?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That regex I'm using might be a bit heavy so I use a fast and dirty check first and store the result in
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not too fussed about performance overheads, presumably you only run this with a handful of ranges so it'll be really hard to tell what's faster or slower.
Are you saying the test is not necessary, or it is because of Windows?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can look at this from when this check was not there https://travis-ci.org/avajs/ava/builds/556309917
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, so let's add a comment explaining this handles file paths on Windows that do not include ranges. |
||
| const rangeStr = file.slice(colonIndex + 1); | ||
| const actualFilename = file.slice(0, colonIndex); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't really a filename though, it's still a file path. Let's rename this throughout. |
||
|
|
||
| if (mightHaveRange && rangeRegExp.test(rangeStr)) { | ||
| return [actualFilename, rangeParser.parse(rangeStr)]; | ||
| } | ||
|
|
||
| return [file, []]; | ||
| } | ||
|
|
||
| exports.parseFileSelection = parseFileSelection; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| 'use strict'; | ||
| const Emittery = require('emittery'); | ||
| const matcher = require('matcher'); | ||
| const callsites = require('callsites'); | ||
| const sourceMapSupport = require('source-map-support'); | ||
| const ContextRef = require('./context-ref'); | ||
| const createChain = require('./create-chain'); | ||
| const snapshotManager = require('./snapshot-manager'); | ||
|
|
@@ -15,13 +17,15 @@ class Runner extends Emittery { | |
| this.failWithoutAssertions = options.failWithoutAssertions !== false; | ||
| this.file = options.file; | ||
| this.match = options.match || []; | ||
| this.lines = options.lines || []; | ||
| this.projectDir = options.projectDir; | ||
| this.recordNewSnapshots = options.recordNewSnapshots === true; | ||
| this.runOnlyExclusive = options.runOnlyExclusive === true; | ||
| this.serial = options.serial === true; | ||
| this.snapshotDir = options.snapshotDir; | ||
| this.updateSnapshots = options.updateSnapshots; | ||
|
|
||
| this.callsiteLineNumbers = []; | ||
| this.activeRunnables = new Set(); | ||
| this.boundCompareTestSnapshot = this.compareTestSnapshot.bind(this); | ||
| this.interrupted = false; | ||
|
|
@@ -52,11 +56,29 @@ class Runner extends Emittery { | |
| if (!scheduledStart) { | ||
| scheduledStart = true; | ||
| process.nextTick(() => { | ||
| this.callsiteLineNumbers.push(Infinity); | ||
| this.callsiteLineNumbers.sort((a, b) => a - b); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's do this work inside the We should deduplicate the line numbers as well. It's an edge case but you could specify two tests on the same line. |
||
| hasStarted = true; | ||
| this.start(); | ||
| }); | ||
| } | ||
|
|
||
| if (this.lines.length > 0) { | ||
| let callsiteLineNumber; | ||
|
|
||
| for (const jsCallsite of callsites()) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's rename |
||
| const callsite = sourceMapSupport.wrapCallSite(jsCallsite); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And rename this to |
||
| const fileName = callsite.getFileName(); | ||
| if (jsCallsite === this.file || fileName === this.file) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you mean I don't think this needs to be compared to the source-map converted filename. You can't pass those on the CLI since you need to provide the file that AVA can actually load. |
||
| callsiteLineNumber = callsite.getLineNumber(); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| metadata.callsiteLineNumber = callsiteLineNumber; | ||
| this.callsiteLineNumbers.push(callsiteLineNumber); | ||
| } | ||
|
|
||
| const specifiedTitle = typeof args[0] === 'string' ? | ||
| args.shift() : | ||
| undefined; | ||
|
|
@@ -347,10 +369,21 @@ class Runner extends Emittery { | |
| } | ||
|
|
||
| async start() { | ||
| const isIgnoredByLineSelection = task => { | ||
| if (this.lines.length === 0) { | ||
| return false; | ||
| } | ||
|
|
||
| const lineNumber = task.metadata.callsiteLineNumber; | ||
| const endingLineNumber = this.callsiteLineNumbers[this.callsiteLineNumbers.indexOf(lineNumber) + 1] - 1; | ||
|
|
||
| return !this.lines.some(line => line >= lineNumber && line <= endingLineNumber); | ||
| }; | ||
|
|
||
| const concurrentTests = []; | ||
| const serialTests = []; | ||
| for (const task of this.tasks.serial) { | ||
| if (this.runOnlyExclusive && !task.metadata.exclusive) { | ||
| if (isIgnoredByLineSelection(task) || (this.runOnlyExclusive && !task.metadata.exclusive)) { | ||
| continue; | ||
| } | ||
|
|
||
|
|
@@ -368,7 +401,7 @@ class Runner extends Emittery { | |
| } | ||
|
|
||
| for (const task of this.tasks.concurrent) { | ||
| if (this.runOnlyExclusive && !task.metadata.exclusive) { | ||
| if (isIgnoredByLineSelection(task) || (this.runOnlyExclusive && !task.metadata.exclusive)) { | ||
| continue; | ||
| } | ||
|
|
||
|
|
@@ -390,7 +423,7 @@ class Runner extends Emittery { | |
| } | ||
|
|
||
| for (const task of this.tasks.todo) { | ||
| if (this.runOnlyExclusive && !task.metadata.exclusive) { | ||
| if (isIgnoredByLineSelection(task) || (this.runOnlyExclusive && !task.metadata.exclusive)) { | ||
| continue; | ||
| } | ||
|
|
||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import test from '../..'; | ||
|
|
||
| test('foo', t => { | ||
| t.pass(); | ||
| }); | ||
|
|
||
| test('unicorn', t => { | ||
| t.pass(); | ||
| }); | ||
|
|
||
| test('rainbow', t => { | ||
| t.is(1, 1); | ||
| t.pass(); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't make this a quote. Use italics instead: