Skip to content

Commit 0cff7f8

Browse files
bors[bot]etaoins
andcommitted
Merge #1446
1446: Initial Visual Studio Code unit tests r=matklad a=etaoins As promised in #1439 this is an initial attempt at unit testing the VSCode extension. There are two separate parts to this: getting the test framework working and unit testing the code in #1439. The test framework nearly intact from the VSCode extension generator. The main thing missing was `test/index.ts` which acts as an entry point for Mocha. This was simply copied back in. I also needed to open the test VSCode instance inside a workspace as our file URI generation depends on a workspace being open. There are two ways to run the test framework: 1. Opening the extension's source in VSCode, pressing F5 and selecting the "Extensions Test" debug target. 2. Closing all copies of VSCode and running `npm test`. This is started from the command line but actually opens a temporary VSCode window to host the tests. This doesn't attempt to wire this up to CI. That requires running a headless X11 server which is a bit daunting. I'll assess the difficulty of that in a follow-up branch. This PR is at least helpful for local development without having to induce errors on a Rust project. For the actual tests this uses snapshots of `rustc` output from [a real Rust project](https://github.com/etaoins/arret) captured from the command line. Except for extracting the `message` object and reformatting they're copied verbatim into fixture JSON files. Only four different types of diagnostics are tested but they represent the main combinations of code actions and related information possible. They can be considered the happy path tests; as we encounter corner-cases we can introduce new tests fixtures. Co-authored-by: Ryan Cumming <[email protected]>
2 parents afd18db + 98ac62c commit 0cff7f8

File tree

12 files changed

+784
-61
lines changed

12 files changed

+784
-61
lines changed

docs/dev/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,25 @@ To work on the VS Code extension, launch code inside `editors/code` and use `F5`
9797
to launch/debug. To automatically apply formatter and linter suggestions, use
9898
`npm run fix`.
9999

100+
Tests are located inside `src/test` and are named `*.test.ts`. They use the
101+
[Mocha](https://mochajs.org) test framework and the builtin Node
102+
[assert](https://nodejs.org/api/assert.html) module. Unlike normal Node tests
103+
they must be hosted inside a VS Code instance. This can be done in one of two
104+
ways:
105+
106+
1. When `F5` debugging in VS Code select the `Extension Tests` configuration
107+
from the drop-down at the top of the Debug View. This will launch a temporary
108+
instance of VS Code. The test results will appear in the "Debug Console" tab
109+
of the primary VS Code instance.
110+
111+
2. Run `npm test` from the command line. Although this is initiated from the
112+
command line it is not headless; it will also launch a temporary instance of
113+
VS Code.
114+
115+
Due to the requirements of running the tests inside VS Code they are **not run
116+
on CI**. When making changes to the extension please ensure the tests are not
117+
broken locally before opening a Pull Request.
118+
100119
# Logging
101120

102121
Logging is done by both rust-analyzer and VS Code, so it might be tricky to

editors/code/.vscode/launch.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"request": "launch",
2121
"runtimeExecutable": "${execPath}",
2222
"args": [
23+
"${workspaceFolder}/src/test/",
2324
"--extensionDevelopmentPath=${workspaceFolder}",
2425
"--extensionTestsPath=${workspaceFolder}/out/test"
2526
],

editors/code/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"postinstall": "node ./node_modules/vscode/bin/install",
2424
"fix": "prettier **/*.{json,ts} --write && tslint --project . --fix",
2525
"lint": "tslint --project .",
26+
"test": "node node_modules/vscode/bin/test",
2627
"prettier": "prettier **/*.{json,ts}",
2728
"travis": "npm run compile && npm run lint && npm run prettier -- --list-different"
2829
},

editors/code/src/commands/cargo_watch.ts

Lines changed: 5 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@ import * as child_process from 'child_process';
22
import * as fs from 'fs';
33
import * as path from 'path';
44
import * as vscode from 'vscode';
5+
56
import { Server } from '../server';
67
import { terminate } from '../utils/processes';
78
import {
89
mapRustDiagnosticToVsCode,
910
RustDiagnostic
1011
} from '../utils/rust_diagnostics';
12+
import {
13+
areCodeActionsEqual,
14+
areDiagnosticsEqual
15+
} from '../utils/vscode_diagnostics';
1116
import { LineBuffer } from './line_buffer';
1217
import { StatusDisplay } from './watch_status';
1318

@@ -184,67 +189,6 @@ export class CargoWatchProvider
184189
this.statusDisplay.hide();
185190
}
186191

187-
function areDiagnosticsEqual(
188-
left: vscode.Diagnostic,
189-
right: vscode.Diagnostic
190-
): boolean {
191-
return (
192-
left.source === right.source &&
193-
left.severity === right.severity &&
194-
left.range.isEqual(right.range) &&
195-
left.message === right.message
196-
);
197-
}
198-
199-
function areCodeActionsEqual(
200-
left: vscode.CodeAction,
201-
right: vscode.CodeAction
202-
): boolean {
203-
if (
204-
left.kind !== right.kind ||
205-
left.title !== right.title ||
206-
!left.edit ||
207-
!right.edit
208-
) {
209-
return false;
210-
}
211-
212-
const leftEditEntries = left.edit.entries();
213-
const rightEditEntries = right.edit.entries();
214-
215-
if (leftEditEntries.length !== rightEditEntries.length) {
216-
return false;
217-
}
218-
219-
for (let i = 0; i < leftEditEntries.length; i++) {
220-
const [leftUri, leftEdits] = leftEditEntries[i];
221-
const [rightUri, rightEdits] = rightEditEntries[i];
222-
223-
if (leftUri.toString() !== rightUri.toString()) {
224-
return false;
225-
}
226-
227-
if (leftEdits.length !== rightEdits.length) {
228-
return false;
229-
}
230-
231-
for (let j = 0; j < leftEdits.length; j++) {
232-
const leftEdit = leftEdits[j];
233-
const rightEdit = rightEdits[j];
234-
235-
if (!leftEdit.range.isEqual(rightEdit.range)) {
236-
return false;
237-
}
238-
239-
if (leftEdit.newText !== rightEdit.newText) {
240-
return false;
241-
}
242-
}
243-
}
244-
245-
return true;
246-
}
247-
248192
interface CargoArtifact {
249193
reason: string;
250194
package_id: string;
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
{
2+
"message": "this argument is passed by reference, but would be more efficient if passed by value",
3+
"code": {
4+
"code": "clippy::trivially_copy_pass_by_ref",
5+
"explanation": null
6+
},
7+
"level": "warning",
8+
"spans": [
9+
{
10+
"file_name": "compiler/mir/tagset.rs",
11+
"byte_start": 941,
12+
"byte_end": 946,
13+
"line_start": 42,
14+
"line_end": 42,
15+
"column_start": 24,
16+
"column_end": 29,
17+
"is_primary": true,
18+
"text": [
19+
{
20+
"text": " pub fn is_disjoint(&self, other: Self) -> bool {",
21+
"highlight_start": 24,
22+
"highlight_end": 29
23+
}
24+
],
25+
"label": null,
26+
"suggested_replacement": null,
27+
"suggestion_applicability": null,
28+
"expansion": null
29+
}
30+
],
31+
"children": [
32+
{
33+
"message": "lint level defined here",
34+
"code": null,
35+
"level": "note",
36+
"spans": [
37+
{
38+
"file_name": "compiler/lib.rs",
39+
"byte_start": 8,
40+
"byte_end": 19,
41+
"line_start": 1,
42+
"line_end": 1,
43+
"column_start": 9,
44+
"column_end": 20,
45+
"is_primary": true,
46+
"text": [
47+
{
48+
"text": "#![warn(clippy::all)]",
49+
"highlight_start": 9,
50+
"highlight_end": 20
51+
}
52+
],
53+
"label": null,
54+
"suggested_replacement": null,
55+
"suggestion_applicability": null,
56+
"expansion": null
57+
}
58+
],
59+
"children": [],
60+
"rendered": null
61+
},
62+
{
63+
"message": "#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]",
64+
"code": null,
65+
"level": "note",
66+
"spans": [],
67+
"children": [],
68+
"rendered": null
69+
},
70+
{
71+
"message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref",
72+
"code": null,
73+
"level": "help",
74+
"spans": [],
75+
"children": [],
76+
"rendered": null
77+
},
78+
{
79+
"message": "consider passing by value instead",
80+
"code": null,
81+
"level": "help",
82+
"spans": [
83+
{
84+
"file_name": "compiler/mir/tagset.rs",
85+
"byte_start": 941,
86+
"byte_end": 946,
87+
"line_start": 42,
88+
"line_end": 42,
89+
"column_start": 24,
90+
"column_end": 29,
91+
"is_primary": true,
92+
"text": [
93+
{
94+
"text": " pub fn is_disjoint(&self, other: Self) -> bool {",
95+
"highlight_start": 24,
96+
"highlight_end": 29
97+
}
98+
],
99+
"label": null,
100+
"suggested_replacement": "self",
101+
"suggestion_applicability": "Unspecified",
102+
"expansion": null
103+
}
104+
],
105+
"children": [],
106+
"rendered": null
107+
}
108+
],
109+
"rendered": "warning: this argument is passed by reference, but would be more efficient if passed by value\n --> compiler/mir/tagset.rs:42:24\n |\n42 | pub fn is_disjoint(&self, other: Self) -> bool {\n | ^^^^^ help: consider passing by value instead: `self`\n |\nnote: lint level defined here\n --> compiler/lib.rs:1:9\n |\n1 | #![warn(clippy::all)]\n | ^^^^^^^^^^^\n = note: #[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref\n\n"
110+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"message": "method `next` has an incompatible type for trait",
3+
"code": {
4+
"code": "E0053",
5+
"explanation": "\nThe parameters of any trait method must match between a trait implementation\nand the trait definition.\n\nHere are a couple examples of this error:\n\n```compile_fail,E0053\ntrait Foo {\n fn foo(x: u16);\n fn bar(&self);\n}\n\nstruct Bar;\n\nimpl Foo for Bar {\n // error, expected u16, found i16\n fn foo(x: i16) { }\n\n // error, types differ in mutability\n fn bar(&mut self) { }\n}\n```\n"
6+
},
7+
"level": "error",
8+
"spans": [
9+
{
10+
"file_name": "compiler/ty/list_iter.rs",
11+
"byte_start": 1307,
12+
"byte_end": 1350,
13+
"line_start": 52,
14+
"line_end": 52,
15+
"column_start": 5,
16+
"column_end": 48,
17+
"is_primary": true,
18+
"text": [
19+
{
20+
"text": " fn next(&self) -> Option<&'list ty::Ref<M>> {",
21+
"highlight_start": 5,
22+
"highlight_end": 48
23+
}
24+
],
25+
"label": "types differ in mutability",
26+
"suggested_replacement": null,
27+
"suggestion_applicability": null,
28+
"expansion": null
29+
}
30+
],
31+
"children": [
32+
{
33+
"message": "expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>`",
34+
"code": null,
35+
"level": "note",
36+
"spans": [],
37+
"children": [],
38+
"rendered": null
39+
}
40+
],
41+
"rendered": "error[E0053]: method `next` has an incompatible type for trait\n --> compiler/ty/list_iter.rs:52:5\n |\n52 | fn next(&self) -> Option<&'list ty::Ref<M>> {\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ in mutability\n |\n = note: expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>`\n\n"
42+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
{
2+
"message": "this function takes 2 parameters but 3 parameters were supplied",
3+
"code": {
4+
"code": "E0061",
5+
"explanation": "\nThe number of arguments passed to a function must match the number of arguments\nspecified in the function signature.\n\nFor example, a function like:\n\n```\nfn f(a: u16, b: &str) {}\n```\n\nMust always be called with exactly two arguments, e.g., `f(2, \"test\")`.\n\nNote that Rust does not have a notion of optional function arguments or\nvariadic functions (except for its C-FFI).\n"
6+
},
7+
"level": "error",
8+
"spans": [
9+
{
10+
"file_name": "compiler/ty/select.rs",
11+
"byte_start": 8787,
12+
"byte_end": 9241,
13+
"line_start": 219,
14+
"line_end": 231,
15+
"column_start": 5,
16+
"column_end": 6,
17+
"is_primary": false,
18+
"text": [
19+
{
20+
"text": " pub fn add_evidence(",
21+
"highlight_start": 5,
22+
"highlight_end": 25
23+
},
24+
{
25+
"text": " &mut self,",
26+
"highlight_start": 1,
27+
"highlight_end": 19
28+
},
29+
{
30+
"text": " target_poly: &ty::Ref<ty::Poly>,",
31+
"highlight_start": 1,
32+
"highlight_end": 41
33+
},
34+
{
35+
"text": " evidence_poly: &ty::Ref<ty::Poly>,",
36+
"highlight_start": 1,
37+
"highlight_end": 43
38+
},
39+
{
40+
"text": " ) {",
41+
"highlight_start": 1,
42+
"highlight_end": 8
43+
},
44+
{
45+
"text": " match target_poly {",
46+
"highlight_start": 1,
47+
"highlight_end": 28
48+
},
49+
{
50+
"text": " ty::Ref::Var(tvar, _) => self.add_var_evidence(tvar, evidence_poly),",
51+
"highlight_start": 1,
52+
"highlight_end": 81
53+
},
54+
{
55+
"text": " ty::Ref::Fixed(target_ty) => {",
56+
"highlight_start": 1,
57+
"highlight_end": 43
58+
},
59+
{
60+
"text": " let evidence_ty = evidence_poly.resolve_to_ty();",
61+
"highlight_start": 1,
62+
"highlight_end": 65
63+
},
64+
{
65+
"text": " self.add_evidence_ty(target_ty, evidence_poly, evidence_ty)",
66+
"highlight_start": 1,
67+
"highlight_end": 76
68+
},
69+
{
70+
"text": " }",
71+
"highlight_start": 1,
72+
"highlight_end": 14
73+
},
74+
{
75+
"text": " }",
76+
"highlight_start": 1,
77+
"highlight_end": 10
78+
},
79+
{
80+
"text": " }",
81+
"highlight_start": 1,
82+
"highlight_end": 6
83+
}
84+
],
85+
"label": "defined here",
86+
"suggested_replacement": null,
87+
"suggestion_applicability": null,
88+
"expansion": null
89+
},
90+
{
91+
"file_name": "compiler/ty/select.rs",
92+
"byte_start": 4045,
93+
"byte_end": 4057,
94+
"line_start": 104,
95+
"line_end": 104,
96+
"column_start": 18,
97+
"column_end": 30,
98+
"is_primary": true,
99+
"text": [
100+
{
101+
"text": " self.add_evidence(target_fixed, evidence_fixed, false);",
102+
"highlight_start": 18,
103+
"highlight_end": 30
104+
}
105+
],
106+
"label": "expected 2 parameters",
107+
"suggested_replacement": null,
108+
"suggestion_applicability": null,
109+
"expansion": null
110+
}
111+
],
112+
"children": [],
113+
"rendered": "error[E0061]: this function takes 2 parameters but 3 parameters were supplied\n --> compiler/ty/select.rs:104:18\n |\n104 | self.add_evidence(target_fixed, evidence_fixed, false);\n | ^^^^^^^^^^^^ expected 2 parameters\n...\n219 | / pub fn add_evidence(\n220 | | &mut self,\n221 | | target_poly: &ty::Ref<ty::Poly>,\n222 | | evidence_poly: &ty::Ref<ty::Poly>,\n... |\n230 | | }\n231 | | }\n | |_____- defined here\n\n"
114+
}

0 commit comments

Comments
 (0)