-
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
Conversation
|
Negative numbers should throw a user-friendly error. |
|
While |
|
The range syntax should work for the |
|
I’m not sure it makes sense at all to allow the range syntax when using |
a48a3b5 to
561d222
Compare
|
@sindresorhus I guess everything works as expected now, I moved the whole parser logic to a new file, and also added the support for ranges in That being said now we can expect a glob pattern such as There is only one option added to the There is no negative number allowed in a range string, which means /c.c @novemberborn |
novemberborn
left a comment
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.
Hi @qti3e, thanks for the PR!
The range syntax should work for the
filessetting in the AVA config too. I don’t see that documented.
@sindresorhus I don't see how this is useful. If you want to always run part of a test file, make a new test file. Our globbing logic is still really complex and I don't think we should add more complexity to it.
I’m not sure it makes sense at all to allow the range syntax when using
watch.
Agreed. Another argument for not allowing it in the configuration. Then, we can enforce this in lib/cli.js by looking for watch mode and the presence of ranges in the provided files.
Having reviewed the PR it definitely looks easier to implement and maintain if we only support this syntax on the CLI.
… that leads to a file not found status, which I believe is best to be addressed as part of #2158 (which I'm going to take right after this PR if that's ok)
- Due to the implementation using line numbers that are higher than the actual number of lines in the file will result in the last test being executed, it would be nice to have a way to get line numbers in the file without the overhead of reading the file from disk.
If we restrict this feature to files passed on the CLI, it's definitely legit to parse the file counting its lines.
lib/range.js
Outdated
| for (const file of files) { | ||
| const [actualFilename, rangeStr] = splitRangeStr(file); | ||
|
|
||
| if (any.has(actualFilename)) { |
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.
Did you consider merging the ranges?
lib/range.js
Outdated
|
|
||
| function parseRange(files) { | ||
| const any = new Set(); | ||
| const ranges = {}; |
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.
Let's use a Map instead.
lib/range.js
Outdated
| const union = require('array-union'); | ||
|
|
||
| function parseRange(files) { | ||
| const any = new Set(); |
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.
What does any mean? I can't quite tell from the logic below.
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.
@nov parseRange should work on both ranged paths and normal paths, imagine we have an input like ['a.js', 'a.js:4'] in this case the a.js path has a higher priority and should cancel out a.js, this set is used to store file paths that do not have a range syntax in them.
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.
I see. Should that maybe print an error instead? Or perhaps we could run only the selected lines and print a warning "ignoring other tests from a.js since you selected lines" (well not quite that, but you get the idea).
lib/range.js
Outdated
| const lines = rangeParser.parse(rangeStr); | ||
|
|
||
| if (ranges[actualFilename]) { | ||
| ranges[actualFilename] = union(ranges[actualFilename], lines); |
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.
Rather than using array-union you could concatenate the arrays, and then pass them through a [...newSet(lines)] at the very end to get unique values out.
lib/range.js
Outdated
| continue; | ||
| } | ||
|
|
||
| const lines = rangeParser.parse(rangeStr); |
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.
Why isn't the range string parsed in splitRangeStr()?
That function could be rename to parseFileSelection(), returning a file and extracted lines, and then this parseRange() function could be renamed to processFileSelections() or something. Maybe rename the range module too.
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.
Because of how this implementation handles globs I needed to have the range part as a string, and of course, we need another function to parse that part.
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.
Right, so this will be a lot simpler without globs 😄
lib/runner.js
Outdated
| for (const jsCallsite of callsites()) { | ||
| const callsite = sourceMapSupport.wrapCallSite(jsCallsite); | ||
| const fileName = callsite.getFileName(); | ||
| if (fileName === this.file) { |
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.
I imagine this will break when using AVA with pre-compiled test files. The selected file may be build/test.js but source-map-support may translate the callsite to src/test.js. You should check the files before the source map is applied.
This also needs a comment to explain why we need to check the files in the first place (because the test() may be invoked in a helper file — which is its own edge case that I wouldn't know how to deal with).
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.
We can't check the file before the source map is applied after the translation line numbers are all changed, (a.js:5 might end up running line 6.)
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.
We can't check the file before the source map is applied after the translation line numbers are all changed, (
a.js:5might end up running line 6.)
Yes I get that. My point though is that applying the source map may change the fileName of the call site.
lib/runner.js
Outdated
| // get the last line of the previous test. | ||
| const range = [lastCallsiteLineNumber, callsiteLineNumber - 1]; | ||
| const includedInRange = this.lines.some(n => n >= range[0] && n <= range[1]); | ||
| lastMetadata.exclusive = this.match.length === 0 ? |
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.
exclusive will be true if the test is declared using .only(). But, crucially, tests can also be skipped.
I think the ranges should narrow down from the initial test selection. If there are exclusive tests, then only those that match the ranges should be run. If there are no exclusive tests, then skipped tests should never be run.
lib/runner.js
Outdated
| } | ||
|
|
||
| lastCallsiteLineNumber = callsiteLineNumber; | ||
| lastMetadata = metadata; |
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.
Rather than tracking the last metedata object and callsite, perhaps you could add the callsite number to the metadata, and then adjust the test selection once all tests have been declared?
lib/runner.js
Outdated
|
|
||
| if (lastMetadata) { | ||
| // `callsiteLineNumber` is the beginning of the current test, -1 to | ||
| // get the last line of the previous test. |
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.
This does include whitespace, but I think that's OK for now.
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.
I don't think there is a way to handle this case unless you parse the source file to it's AST and then extract when the function call actually ends (this information is not in the callsite data.)
|
@novemberborn Please let me know your final decision about dropping ranged globs. |
I agree. I didn't think that through when first commenting.
👍 |
I think that should be handled here. It's clearly not a valid filename, so we should give the user a helpful and relevant error message that it's an invalid range. |
|
@novemberborn PTAL, also please restart that Travis task. |
novemberborn
left a comment
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.
Very nice, I think we're getting there @qti3e!
There is no negative number allowed in a range string, which means test.js:-4 is treated as a file with the name test.js:-4 rather than line -4 in test.js and well that leads to a file not found status
I think that should be handled here. It's clearly not a valid filename, so we should give the user a helpful and relevant error message that it's an invalid range.
I agree with this.
| 10 }); | ||
| ``` | ||
|
|
||
| > Please note that using any number more than the maximum line-number will execute the last test. |
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:
Note that ranges beyond the last line of the file will select the last test.
|
|
||
| > 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. |
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.
How about:
You can combine line ranges with the
--matchoption.
| continue; | ||
| } | ||
|
|
||
| ranges.set(actualFilename, ranges.get(actualFilename).concat(lines)); |
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.
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)
}| function parseFileSelections(files) { | ||
| const ranges = new Map(); | ||
| const ignored = []; | ||
| const filenames = []; |
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.
Let's make this a set, too.
| const filenames = []; | ||
|
|
||
| for (const file of files) { | ||
| const [actualFilename, lines] = parseFileSelection(file); |
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.
With filenames a set, you can add the actualFilename to it here, regardless of any other condition.
| for (const jsCallsite of callsites()) { | ||
| const callsite = sourceMapSupport.wrapCallSite(jsCallsite); | ||
| const fileName = callsite.getFileName(); | ||
| if (jsCallsite === this.file || fileName === this.file) { |
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.
Did you mean jsCallsite.getFileName()?
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.
| if (this.lines.length > 0) { | ||
| let callsiteLineNumber; | ||
|
|
||
| for (const jsCallsite of callsites()) { |
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.
Let's rename jsCallsite to callsite.
| let callsiteLineNumber; | ||
|
|
||
| for (const jsCallsite of callsites()) { | ||
| const callsite = sourceMapSupport.wrapCallSite(jsCallsite); |
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.
And rename this to wrapped.
| scheduledStart = true; | ||
| process.nextTick(() => { | ||
| this.callsiteLineNumbers.push(Infinity); | ||
| this.callsiteLineNumbers.sort((a, b) => a - b); |
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.
Let's do this work inside the start() method instead.
We should deduplicate the line numbers as well. It's an edge case but you could specify two tests on the same line.
| const filename = path.resolve(this.options.resolveTestsFrom, file); | ||
| ranges.set(filename, ranges.get(file)); | ||
| return filename; | ||
| }); |
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.
Why not do this processing in the CLI instead?
|
Closing due to inactivity. @qti3e I do hope you'll find the time to come back to this! |
🦄 Fixes: #2138
There are just a few edges case that I found it useful to have your comments on :)
:...is actually part of the file name, (test.js:5a valid filename on Linux at least)!?Also, there is definitely room for tests to be improved, I couldn't actually get very useful information from
runStatus.statsfor what I had in mind, (name of executed tests) if the current test is not enough I'd be happy to hear about a way to get that information from the API.IssueHunt Summary