Skip to content

Commit f456286

Browse files
committed
Add some basic support for running tests by line number instead of name
See #4021.
1 parent b9acedf commit f456286

16 files changed

Lines changed: 625 additions & 551 deletions

File tree

‎package-lock.json‎

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2423,6 +2423,17 @@
24232423
"default": true,
24242424
"markdownDescription": "Whether to show skipped tests in the test tree.",
24252425
"scope": "window"
2426+
},
2427+
"dart.testInvocationMode": {
2428+
"enum": [
2429+
"name",
2430+
"line"
2431+
],
2432+
"default": [
2433+
"name"
2434+
],
2435+
"description": "How to locate tests for execution. Using `name` is compatible with older versions of `package:test` but cannot handle some complex/dynamic test names.",
2436+
"scope": "window"
24262437
}
24272438
}
24282439
},
@@ -2865,7 +2876,8 @@
28652876
"semver": "^7.3.8",
28662877
"vscode-languageclient": "^8.0.2",
28672878
"ws": "^8.11.0",
2868-
"yaml": "^2.2.1"
2879+
"yaml": "^2.2.1",
2880+
"vscode-uri": "^3.0.7"
28692881
},
28702882
"devDependencies": {
28712883
"@types/glob": "^8.0.0",

‎src/extension/analysis/file_tracker_lsp.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { CancellationToken, TextDocument, Uri, workspace } from "vscode";
22
import { LanguageClient } from "vscode-languageclient/node";
3+
import { URI } from "vscode-uri";
34
import { FlutterOutline, FlutterOutlineParams, Outline, OutlineParams, PublishFlutterOutlineNotification, PublishOutlineNotification } from "../../shared/analysis/lsp/custom_protocol";
45
import { EventEmitter } from "../../shared/events";
56
import { IAmDisposable, Logger } from "../../shared/interfaces";
@@ -39,7 +40,7 @@ export class LspFileTracker implements IAmDisposable {
3940
this.watchPubspec();
4041
}
4142

42-
public getOutlineFor(file: { fsPath: string } | string): Outline | undefined {
43+
public getOutlineFor(file: URI | string): Outline | undefined {
4344
return this.outlines[fsPath(file)];
4445
}
4546

@@ -55,7 +56,7 @@ export class LspFileTracker implements IAmDisposable {
5556
}, 50, 5000, token);
5657
}
5758

58-
public getFlutterOutlineFor(file: { fsPath: string } | string): FlutterOutline | undefined {
59+
public getFlutterOutlineFor(file: URI | string): FlutterOutline | undefined {
5960
return this.flutterOutlines[fsPath(file)];
6061
}
6162

@@ -67,7 +68,7 @@ export class LspFileTracker implements IAmDisposable {
6768
}, 50, 5000, token);
6869
}
6970

70-
public supportsPubRunTest(file: { fsPath: string } | string): boolean | undefined {
71+
public supportsPubRunTest(file: URI | string): boolean | undefined {
7172
// Handle explicit flags.
7273
if (this.wsContext.config.supportsPackageTest === true)
7374
return true;

‎src/extension/commands/test.ts‎

Lines changed: 16 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@ import * as path from "path";
33
import * as vs from "vscode";
44
import { FlutterCapabilities } from "../../shared/capabilities/flutter";
55
import { noAction } from "../../shared/constants";
6-
import { TestStatus } from "../../shared/enums";
76
import { Logger } from "../../shared/interfaces";
87
import { GroupNode, SuiteData, SuiteNode, TestModel, TestNode, TreeNode } from "../../shared/test/test_model";
98
import { disposeAll, escapeDartString, generateTestNameFromFileName, uniq } from "../../shared/utils";
109
import { sortBy } from "../../shared/utils/array";
1110
import { fsPath, isWithinPath, mkDirRecursive } from "../../shared/utils/fs";
1211
import { TestOutlineInfo } from "../../shared/utils/outline_das";
13-
import { createTestFileAction, defaultTestFileContents, getLaunchConfig, TestName } from "../../shared/utils/test";
12+
import { TestSelection, createTestFileAction, defaultTestFileContents, getLaunchConfig, getTestSelectionForNodes, getTestSelectionForOutline } from "../../shared/utils/test";
1413
import { getLaunchConfigDefaultTemplate } from "../../shared/vscode/debugger";
1514
import { getAllProjectFolders } from "../../shared/vscode/utils";
1615
import { WorkspaceContext } from "../../shared/workspace";
@@ -24,7 +23,7 @@ const CAN_JUMP_BETWEEN_TEST_IMPLEMENTATION = "dart-code:canGoToTestOrImplementat
2423
export let isInTestFileThatHasImplementation = false;
2524
export let isInImplementationFileThatCanHaveTest = false;
2625

27-
export type SuiteList = [SuiteNode, TestName[]];
26+
export type SuiteList = [SuiteNode, TestSelection[]];
2827

2928
export class TestCommands implements vs.Disposable {
3029
private disposables: vs.Disposable[] = [];
@@ -33,8 +32,8 @@ export class TestCommands implements vs.Disposable {
3332
this.disposables.push(
3433
vs.commands.registerCommand("_dart.startDebuggingTestFromOutline", (test: TestOutlineInfo, launchTemplate: any | undefined) => this.startTestFromOutline(false, test, launchTemplate)),
3534
vs.commands.registerCommand("_dart.startWithoutDebuggingTestFromOutline", (test: TestOutlineInfo, launchTemplate: any | undefined) => this.startTestFromOutline(true, test, launchTemplate)),
36-
vs.commands.registerCommand("_dart.startDebuggingTestsFromVsTestController", (suiteData: SuiteData, treeNodes: Array<SuiteNode | GroupNode | TestNode>, suppressPrompts: boolean, testRun: vs.TestRun | undefined) => this.runTestsForNode(suiteData, this.getTestNamesForNodes(treeNodes), true, suppressPrompts, treeNodes.length === 1 && treeNodes[0] instanceof TestNode, undefined, testRun)),
37-
vs.commands.registerCommand("_dart.startWithoutDebuggingTestsFromVsTestController", (suiteData: SuiteData, treeNodes: Array<SuiteNode | GroupNode | TestNode>, suppressPrompts: boolean, testRun: vs.TestRun | undefined) => this.runTestsForNode(suiteData, this.getTestNamesForNodes(treeNodes), false, suppressPrompts, treeNodes.length === 1 && treeNodes[0] instanceof TestNode, undefined, testRun)),
35+
vs.commands.registerCommand("_dart.startDebuggingTestsFromVsTestController", (suiteData: SuiteData, treeNodes: Array<SuiteNode | GroupNode | TestNode>, suppressPrompts: boolean, testRun: vs.TestRun | undefined) => this.runTestsForNode(suiteData, treeNodes, true, suppressPrompts, treeNodes.length === 1 && treeNodes[0] instanceof TestNode, undefined, testRun)),
36+
vs.commands.registerCommand("_dart.startWithoutDebuggingTestsFromVsTestController", (suiteData: SuiteData, treeNodes: Array<SuiteNode | GroupNode | TestNode>, suppressPrompts: boolean, testRun: vs.TestRun | undefined) => this.runTestsForNode(suiteData, treeNodes, false, suppressPrompts, treeNodes.length === 1 && treeNodes[0] instanceof TestNode, undefined, testRun)),
3837
vs.commands.registerCommand("_dart.runAllTestsWithoutDebugging", (suites: SuiteNode[] | undefined, testRun: vs.TestRun | undefined, isRunningAll: boolean) => this.runAllTestsWithoutDebugging(suites, testRun, isRunningAll)),
3938
vs.commands.registerCommand("dart.goToTests", (resource: vs.Uri | undefined) => this.goToTestOrImplementationFile(resource), this),
4039
vs.commands.registerCommand("dart.goToTestOrImplementationFile", () => this.goToTestOrImplementationFile(), this),
@@ -112,15 +111,16 @@ export class TestCommands implements vs.Disposable {
112111
programPath: projectWithTests.tests[0],
113112
shouldRunSkippedTests: false,
114113
suppressPrompts: suites?.length !== 1,
115-
testNames: undefined,
116114
testRun,
115+
testSelection: undefined,
117116
token: undefined,
118117
useLaunchJsonTestTemplate: true,
119118
}))
120119
);
121120
}
122121

123-
private async runTestsForNode(suiteData: SuiteData, testNames: TestName[] | undefined, debug: boolean, suppressPrompts: boolean, runSkippedTests: boolean, token?: vs.CancellationToken, testRun?: vs.TestRun) {
122+
private async runTestsForNode(suiteData: SuiteData, nodes: TreeNode[], debug: boolean, suppressPrompts: boolean, runSkippedTests: boolean, token?: vs.CancellationToken, testRun?: vs.TestRun) {
123+
const testSelection = getTestSelectionForNodes(nodes);
124124
const programPath = fsPath(suiteData.path);
125125
const canRunSkippedTest = this.flutterCapabilities.supportsRunSkippedTests || !isInsideFlutterProject(vs.Uri.file(suiteData.path));
126126
const shouldRunSkippedTests = runSkippedTests && canRunSkippedTest;
@@ -131,21 +131,24 @@ export class TestCommands implements vs.Disposable {
131131
programPath,
132132
shouldRunSkippedTests,
133133
suppressPrompts,
134-
testNames,
135134
testRun,
135+
testSelection,
136136
token,
137137
useLaunchJsonTestTemplate: true,
138138
});
139139
}
140140

141-
private runTests({ programPath, debug, testNames, shouldRunSkippedTests, suppressPrompts, launchTemplate, testRun, token, useLaunchJsonTestTemplate }: TestLaunchInfo): Promise<boolean> {
141+
private runTests({ programPath, debug, testSelection, shouldRunSkippedTests, suppressPrompts, launchTemplate, testRun, token, useLaunchJsonTestTemplate }: TestLaunchInfo): Promise<boolean> {
142142
if (useLaunchJsonTestTemplate) {
143143
// Get the default Run/Debug template for running/debugging tests and use that as a base.
144144
const template = getLaunchConfigDefaultTemplate(vs.Uri.file(programPath), debug);
145145
if (template)
146146
launchTemplate = Object.assign({}, template, launchTemplate);
147147
}
148148

149+
// TODO(dantup): Implement this!
150+
const runTestsByLine = true /* supportsRunTestByLine */ && config.testInvocationMode === "line";
151+
149152
const subs: vs.Disposable[] = [];
150153
return new Promise<boolean>(async (resolve, reject) => {
151154
let testsName = path.basename(programPath);
@@ -157,7 +160,8 @@ export class TestCommands implements vs.Disposable {
157160
...getLaunchConfig(
158161
!debug,
159162
programPath,
160-
testNames,
163+
testSelection,
164+
runTestsByLine,
161165
shouldRunSkippedTests,
162166
launchTemplate,
163167
),
@@ -198,40 +202,6 @@ export class TestCommands implements vs.Disposable {
198202
});
199203
}
200204

201-
private getTestNamesForNodes(nodes: TreeNode[]): TestName[] | undefined {
202-
if (nodes.find((node) => node instanceof SuiteNode))
203-
return undefined;
204-
205-
return (nodes as Array<GroupNode | TestNode>)
206-
.filter((treeNode) => treeNode.name)
207-
.map((treeNode) => ({ name: treeNode.name!, isGroup: treeNode instanceof GroupNode }));
208-
}
209-
210-
private getTestNames(treeNode: TreeNode, onlyOfStatus?: TestStatus): TestName[] | undefined {
211-
// If we're getting all tests, we can just use the test name/group name (or undefined for suite) directly.
212-
if (onlyOfStatus === undefined) {
213-
if ((treeNode instanceof TestNode || treeNode instanceof GroupNode) && treeNode.name !== undefined)
214-
return [{ name: treeNode.name, isGroup: treeNode instanceof GroupNode }];
215-
216-
return undefined;
217-
}
218-
219-
// Otherwise, collect all descendant tests that are of the specified type.
220-
let names: TestName[] = [];
221-
if (treeNode instanceof SuiteNode || treeNode instanceof GroupNode) {
222-
for (const child of treeNode.children) {
223-
const childNames = this.getTestNames(child, onlyOfStatus);
224-
if (childNames)
225-
names = names.concat(childNames);
226-
}
227-
} else if (treeNode instanceof TestNode && treeNode.name !== undefined) {
228-
if (treeNode.status === onlyOfStatus)
229-
names.push({ name: treeNode.name, isGroup: treeNode instanceof GroupNode });
230-
}
231-
232-
return names;
233-
}
234-
235205
private startTestFromOutline(noDebug: boolean, test: TestOutlineInfo, launchTemplate: any | undefined) {
236206
const canRunSkippedTest = !test.isGroup && (this.flutterCapabilities.supportsRunSkippedTests || !isInsideFlutterProject(vs.Uri.file(test.file)));
237207
const shouldRunSkippedTests = canRunSkippedTest; // These are the same when running directly, since we always run skipped.
@@ -242,8 +212,8 @@ export class TestCommands implements vs.Disposable {
242212
programPath: test.file,
243213
shouldRunSkippedTests,
244214
suppressPrompts: false,
245-
testNames: [{ name: test.fullName, isGroup: test.isGroup }],
246215
testRun: undefined,
216+
testSelection: [getTestSelectionForOutline(test)],
247217
token: undefined,
248218
});
249219
}
@@ -352,7 +322,7 @@ export class TestCommands implements vs.Disposable {
352322
interface TestLaunchInfo {
353323
programPath: string;
354324
debug: boolean;
355-
testNames: TestName[] | undefined;
325+
testSelection: TestSelection[] | undefined;
356326
shouldRunSkippedTests: boolean;
357327
suppressPrompts: boolean;
358328
launchTemplate: any | undefined;

‎src/extension/config.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ class Config {
151151
get showSkippedTests(): boolean { return this.getConfig<boolean>("showSkippedTests", true); }
152152
get showTestCodeLens(): boolean { return this.getConfig<boolean>("showTestCodeLens", true); }
153153
get showTodos(): boolean | string[] { return this.getConfig<boolean | string[]>("showTodos", true); }
154+
// TODO(dantup): WIRE THIS UP!
155+
get testInvocationMode(): "name" | "line" { return this.getConfig<"name" | "line">("testInvocationMode", "name"); }
154156
get updateDevTools(): boolean { return this.getConfig<boolean>("updateDevTools", true); }
155157
get updateImportsOnRename(): boolean { return this.getConfig<boolean>("updateImportsOnRename", true); }
156158
get useLegacyAnalyzerProtocol(): boolean { return this.getConfig<boolean>("useLegacyAnalyzerProtocol", false); }

‎src/extension/extension.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { Analyzer } from "../shared/analyzer";
44
import { DartCapabilities } from "../shared/capabilities/dart";
55
import { DaemonCapabilities, FlutterCapabilities } from "../shared/capabilities/flutter";
66
import { vsCodeVersion } from "../shared/capabilities/vscode";
7-
import { dartPlatformName, flutterExtensionIdentifier, HAS_LAST_DEBUG_CONFIG, HAS_LAST_TEST_DEBUG_CONFIG, isMac, isWin, IS_LSP_CONTEXT, IS_RUNNING_LOCALLY_CONTEXT, platformDisplayName, PUB_OUTDATED_SUPPORTED_CONTEXT } from "../shared/constants";
7+
import { HAS_LAST_DEBUG_CONFIG, HAS_LAST_TEST_DEBUG_CONFIG, IS_LSP_CONTEXT, IS_RUNNING_LOCALLY_CONTEXT, PUB_OUTDATED_SUPPORTED_CONTEXT, dartPlatformName, flutterExtensionIdentifier, isMac, isWin, platformDisplayName } from "../shared/constants";
88
import { LogCategory } from "../shared/enums";
99
import { WebClient } from "../shared/fetch";
1010
import { DartWorkspaceContext, FlutterSdks, FlutterWorkspaceContext, IAmDisposable, IFlutterDaemon, Logger, Sdks, WritableWorkspaceConfig } from "../shared/interfaces";
11-
import { captureLogs, EmittingLogger, logToConsole, RingLog } from "../shared/logging";
11+
import { EmittingLogger, RingLog, captureLogs, logToConsole } from "../shared/logging";
1212
import { PubApi } from "../shared/pub/api";
1313
import { internalApiSymbol } from "../shared/symbols";
1414
import { TestSessionCoordinator } from "../shared/test/coordinator";
@@ -52,7 +52,7 @@ import { OpenInOtherEditorCommands } from "./commands/open_in_other_editors";
5252
import { PackageCommands } from "./commands/packages";
5353
import { RefactorCommands } from "./commands/refactor";
5454
import { SdkCommands } from "./commands/sdk";
55-
import { isInImplementationFileThatCanHaveTest, isInTestFileThatHasImplementation, TestCommands } from "./commands/test";
55+
import { TestCommands, isInImplementationFileThatCanHaveTest, isInTestFileThatHasImplementation } from "./commands/test";
5656
import { TypeHierarchyCommand } from "./commands/type_hierarchy";
5757
import { config } from "./config";
5858
import { DartTaskProvider } from "./dart/dart_task_provider";
@@ -62,7 +62,7 @@ import { FlutterIconDecorationsDas } from "./decorations/flutter_icon_decoration
6262
import { FlutterIconDecorationsLsp } from "./decorations/flutter_icon_decorations_lsp";
6363
import { FlutterUiGuideDecorationsDas } from "./decorations/flutter_ui_guides_decorations_das";
6464
import { FlutterUiGuideDecorationsLsp } from "./decorations/flutter_ui_guides_decorations_lsp";
65-
import { getExperiments, KnownExperiments } from "./experiments";
65+
import { KnownExperiments, getExperiments } from "./experiments";
6666
import { setUpDaemonMessageHandler } from "./flutter/daemon_message_handler";
6767
import { FlutterDaemon } from "./flutter/flutter_daemon";
6868
import { DasFlutterOutlineProvider, FlutterOutlineProvider, FlutterWidgetItem, LspFlutterOutlineProvider } from "./flutter/flutter_outline_view";

0 commit comments

Comments
 (0)