- Feature Name: Implementing "<test_binary> --list --format json"
- Start Date: 2023-02-08
- RFC PR: none yet (based however on discussions in #107307)
- Rust Issue: rust-lang/rust#107307
Summary
<test_binary> --format pretty|terse|json|junit
already exists. (See #49359)
As does <test_binary> --list
.
This proposal put both together i.e. <test_binary> --list --format json
for use by IDE test explorers / runners for superior user experience.
Until stabilization, a topic for an upcoming RFC, this will be available under -Zunstable-options
.
Details
Typical IDE test infras (in vscode or vside e.g.) do things in 2 phases (a) discovery (b) test execution.
While it is possible to do the above today for Rust in vscode, we do not have a good user experience. E.g. vscode-rust-test-adapter) implements a test window but there is no way to navigate back to the source code from there.
The reason is vscode-rust-test-adapter) uses output of "cargo --list" to build a list of tests across the workspace but it lacks the location of each test in the source code as well as whether it is ignored or not.
This is a pain for projects that contain even 10s of tests.
Motivation
During the discovery phase, IDE examines the workspace to list the tests in a test window (see e.g. vscode-rust-test-adapter)
Users then use that listing to run some or all of the tests - upon completion of which the test explorer nicely displays the outcome / timing / errors etc. Users also use this UX to start debugging the tests if necessary. See the Visual Studio IDE test experience.
This proposal is for implementing <test_binary> --list --format json
to spit out all the necessary information to provide the user with a superior testing experience within their IDEs.
Guide-level explanation
Today <test_binary> --list
spits out the following
D:\src\dpt\tests\x\a> .\target\debug\deps\a-ffd6346f1b390104.exe --list
p2dchecks::should_fail: test
p2dchecks::should_success: bench
p2dchecks::should_ignore: test
With this preRFC i.e. <test_binary> --list --format json
, we would get a machine readeable output with all necessary information during the discovery phase i.e. without running the tests.
D:\src\dpt\tests\x\a> .\target\debug\deps\a-ffd6346f1b390104.exe --list --format json
{ "type": "suite", "event": "discovery", "test_count": 2 }
{ "type": "test", "event": "discovered", "name": "p2dchecks::should_success", ignore: false, ignore_message: "", source_path: "src\\lib.rs", start_line: 54, start_col: 8, end_line: 54, end_col: 22 }
{ "type": "test", "event": "discovered", "name": "p2dchecks::should_fail", ignore: false, ignore_message: "", source_path: "src\\lib.rs", start_line: 59, start_col: 8, end_line: 59, end_col: 19 }
{ "type": "test", "event": "discovered", "name": "p2dchecks::should_ignore", ignore: true, ignore_message: "nyi", source_path: "src\\lib.rs", start_line: 64, start_col: 8, end_line: 64, end_col: 21 }
{ "type": "suite", "event": "completed", "total": 3, "ignored": 1 }
Note
- Only
--format json
will be implemented during the test discovery phase. i.e.<test_binary> --list --format pretty|terse|junit
will not work. - Until stabilization, a topic for an upcoming RFC, this will be available under
-Zunstable-options
.
Reference-level explanation
<test_binary> --format json
has been implemented as part of #49359.
This proposal, builds on the same json format and introduces the following:
name | type | description |
---|---|---|
discovery | event | This is psuedo-event as the listing happens in on-shot. It is being introduced to retain consistency with the overall json format |
discovered | event | - ditto - |
ignore | field | true | false indicating whether this test is marked with [#ignore] |
ignore_message | field | the message in the ignore attribute. e.g. we need to fix root cause. if the attribute is #[ignore = "we need to fix root cause."] |
start_line | field | start line of the test function identifier |
start_col | field | start column of the test function identifier |
end_line | field | end line of the test function identifier |
end_col | field | end column of the test function identifier |
Drawbacks
None I am aware of.
Rationale and alternatives
I have considered the following alternatives main things is none of these are cross platform solutions that will work across all IDEs (vi, sublime, vs, vscode etc.) without either degraded user experience or non-trivial work or both.
While the following are all fun hacking exercises, they are all net pain.
-
RLS /codelens does spit out this information. However test runners are often extensions that do not have access to the RLS channels spawned by the IDE. See an example of what I mean. It is still doable by spawning another RLS but that will degrade the user experience - more disk io and RAM consumption - as now 2 x RLS will be inaction per workspace.
-
Reading the machine code and using pdb reader API to location the source code.
This is also doable. We know the main method is just a call test_main_static as follows
test::test_main_static(&[&it_works, &it_works2])
. From there we can work out the address ofTestDescAndFn
and from there it'stestfn
member and use PDB reader to locate the source code information.But like seriously?
-
Parsing the source code myself. Also doable-ish kinda. It will require some parsing library for rust and it is made even more complicated by multiple parameterized test libraties existing in crates.io. e.g. test-case, rstest etc.
-
Making changes in another test runner instead of rust / cargo. Doable. And recommendation from rust team was nextest-rs. This doesn't work for me because it requires users to add another crate to their workspace - rust-analyzer.vs should work out of the box i.e. 0 friction, as should any other tool.
Prior art
Discuss prior art, both the good and the bad, in relation to this proposal. A few examples of what this can include are:
For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had?
Yes we can see the architecture of the Visual Studio IDE test experience extensibility.
Unresolved questions
What parts of the design do you expect to resolve through the RFC process before this gets merged?
Mainly soliciting feedback from users and other tool writers.
What parts of the design do you expect to resolve through the implementation of this feature before stabilization?
None.
What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC?
Not aware of any.
Future possibilities
Some test runners and IDE e.g. VS take this to the next level - they continuously run tests in the background as the user types in the code. See. WallabyJs / NCrunch / VS Live Testing.
As the users type in the code they are informed about the test failures, code coverage etc. This is super super useful for folks infected with Kent Beck style TDD. (I can personally vouch that any large project can be developed that way - but this technique has a learning curve and does require some skill)
No reason why we should not have a similar experience for Rust as well - and it is my aspiration to create one. But one step at a time.