Skip to content

Commit 01dd584

Browse files
authored
Add integration test for restore of file-based programs (#8470)
1 parent ac57445 commit 01dd584

File tree

9 files changed

+157
-26
lines changed

9 files changed

+157
-26
lines changed

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ To debug unit tests locally, press <kbd>F5</kbd> in VS Code with the "Launch Tes
6464
To debug integration tests
6565
1. Import the `csharp-test-profile.code-profile` in VSCode to setup a clean profile in which to run integration tests. This must be imported at least once to use the launch configurations (ensure the extensions are updated in the profile).
6666
2. Open any integration test file and <kbd>F5</kbd> launch with the correct launch configuration selected.
67-
- For integration tests inside `test/lsptoolshost`, use either `Launch Current File slnWithCsproj Integration Tests` or `[DevKit] Launch Current File slnWithCsproj Integration Tests` (to run tests using C# + C# Dev Kit)
67+
- For integration tests inside `test/lsptoolshost`, use either `[Roslyn] Run Current File Integration Test` or `[DevKit] Launch Current File Integration Tests` (to run tests using C# + C# Dev Kit)
6868
- For integration tests inside `test/razor`, use `[Razor] Run Current File Integration Test`
69-
- For integration tests inside `test/omnisharp`, use one of the `Omnisharp:` current file profiles
69+
- For integration tests inside `test/omnisharp`, use one of the `[O#] Run Current File Integration Test` current file profiles
7070

7171
These will allow you to actually debug the test, but the 'Razor integration tests' configuration does not.
7272

azure-pipelines.yml

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ stages:
7272
- stage:
7373
displayName: Test Linux (.NET 8)
7474
dependsOn: []
75+
variables:
76+
ROSLYN_SKIP_TEST_FILE_BASED_PROGRAMS: 'true'
7577
jobs:
7678
- template: azure-pipelines/test-matrix.yml
7779
parameters:
@@ -83,11 +85,13 @@ stages:
8385
pool:
8486
name: NetCore-Public
8587
demands: ImageOverride -equals 1es-ubuntu-2004-open
86-
containerName: mcr.microsoft.com/dotnet/sdk:8.0
88+
containerName: mcr.microsoft.com/dotnet/sdk:8.0-noble
8789

8890
- stage:
8991
displayName: Test Linux (.NET 9)
9092
dependsOn: []
93+
variables:
94+
ROSLYN_SKIP_TEST_FILE_BASED_PROGRAMS: 'true'
9195
jobs:
9296
- template: azure-pipelines/test-matrix.yml
9397
parameters:
@@ -99,11 +103,29 @@ stages:
99103
pool:
100104
name: NetCore-Public
101105
demands: ImageOverride -equals 1es-ubuntu-2004-open
102-
containerName: mcr.microsoft.com/dotnet/sdk:9.0
106+
containerName: mcr.microsoft.com/dotnet/sdk:9.0-noble
107+
108+
- stage:
109+
displayName: Test Linux (.NET 10)
110+
dependsOn: []
111+
jobs:
112+
- template: azure-pipelines/test-matrix.yml
113+
parameters:
114+
os: linux
115+
# Prefer the dotnet from the container.
116+
installDotNet: false
117+
testVSCodeVersion: $(testVSCodeVersion)
118+
installAdditionalLinuxDependencies: true
119+
pool:
120+
name: NetCore-Public
121+
demands: ImageOverride -equals 1es-ubuntu-2004-open
122+
containerName: mcr.microsoft.com/dotnet/sdk:10.0.100-rc.2-noble
103123

104124
- stage: Test_Windows_Stage
105125
displayName: Test Windows
106126
dependsOn: []
127+
variables:
128+
ROSLYN_SKIP_TEST_FILE_BASED_PROGRAMS: 'true'
107129
jobs:
108130
- template: azure-pipelines/test-matrix.yml
109131
parameters:
@@ -117,6 +139,8 @@ stages:
117139
- stage: Test_MacOS_Stage
118140
displayName: Test MacOS
119141
dependsOn: []
142+
variables:
143+
ROSLYN_SKIP_TEST_FILE_BASED_PROGRAMS: 'true'
120144
jobs:
121145
- template: azure-pipelines/test-matrix.yml
122146
parameters:

azure-pipelines/test-linux-docker-prereqs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ steps:
1313
# Installing the dependencies requires root, but the docker image we're using doesn't have root permissions (nor sudo)
1414
# We can exec as root from outside the container from the host machine.
1515
# Really we should create our own image with these pre-installed, but we'll need to figure out how to publish the image.
16-
- script: docker exec --user root $(containerId) bash -c 'apt-get update -y && apt-get install -y libglib2.0-0 libnss3 libatk-bridge2.0-dev libdrm2 libgtk-3-0 libgbm-dev libasound2 xvfb'
16+
- script: docker exec --user root $(containerId) bash -c 'apt-get update -y && apt-get install -y libglib2.0-0 libnss3 libatk-bridge2.0-dev libdrm2 libgtk-3-0 libgbm-dev libasound2t64 xvfb'
1717
displayName: 'Install additional Linux dependencies'
1818
target: host
1919
condition: eq(variables['Agent.OS'], 'Linux')

test/lsptoolshost/integrationTests/completion.integration.test.ts

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import * as vscode from 'vscode';
77
import * as path from 'path';
88
import { describe, beforeAll, beforeEach, afterAll, test, expect, afterEach } from '@jest/globals';
99
import testAssetWorkspace from './testAssets/testAssetWorkspace';
10-
import { activateCSharpExtension, closeAllEditorsAsync, openFileInWorkspaceAsync } from './integrationHelpers';
10+
import {
11+
activateCSharpExtension,
12+
closeAllEditorsAsync,
13+
getCompletionsAsync,
14+
openFileInWorkspaceAsync,
15+
} from './integrationHelpers';
1116

1217
describe(`Completion Tests`, () => {
1318
beforeAll(async () => {
@@ -70,23 +75,4 @@ describe(`Completion Tests`, () => {
7075
expect(methodOverrideLine).toContain('override void Method(NeedsImport n)');
7176
expect(methodOverrideImplLine).toContain('base.Method(n);');
7277
});
73-
74-
async function getCompletionsAsync(
75-
position: vscode.Position,
76-
triggerCharacter: string | undefined,
77-
completionsToResolve: number
78-
): Promise<vscode.CompletionList> {
79-
const activeEditor = vscode.window.activeTextEditor;
80-
if (!activeEditor) {
81-
throw new Error('No active editor');
82-
}
83-
84-
return await vscode.commands.executeCommand(
85-
'vscode.executeCompletionItemProvider',
86-
activeEditor.document.uri,
87-
position,
88-
triggerCharacter,
89-
completionsToResolve
90-
);
91-
}
9278
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import * as path from 'path';
8+
import testAssetWorkspace from './testAssets/testAssetWorkspace';
9+
import {
10+
activateCSharpExtension,
11+
closeAllEditorsAsync,
12+
getCompletionsAsync,
13+
openFileInWorkspaceAsync,
14+
revertActiveFile,
15+
waitForAllAsyncOperationsAsync,
16+
waitForExpectedResult,
17+
describeIfFileBasedPrograms,
18+
} from './integrationHelpers';
19+
import { beforeAll, beforeEach, afterAll, test, expect, afterEach } from '@jest/globals';
20+
import { CSharpExtensionExports } from '../../../src/csharpExtensionExports';
21+
22+
describeIfFileBasedPrograms(`File-based Programs Tests`, () => {
23+
let exports: CSharpExtensionExports;
24+
25+
beforeAll(async () => {
26+
process.env.RoslynWaiterEnabled = 'true';
27+
exports = await activateCSharpExtension();
28+
});
29+
30+
beforeEach(async () => {
31+
await openFileInWorkspaceAsync(path.join('src', 'scripts', 'app1.cs'));
32+
});
33+
34+
afterEach(async () => {
35+
await revertActiveFile();
36+
await closeAllEditorsAsync();
37+
});
38+
39+
afterAll(async () => {
40+
await testAssetWorkspace.cleanupWorkspace();
41+
});
42+
43+
test('Inserting package directive triggers a restore', async () => {
44+
await vscode.window.activeTextEditor!.edit((editBuilder) => {
45+
editBuilder.insert(new vscode.Position(0, 0), '#:package [email protected]');
46+
editBuilder.insert(new vscode.Position(1, 0), 'using Newton');
47+
});
48+
await vscode.window.activeTextEditor!.document.save();
49+
await waitForAllAsyncOperationsAsync(exports);
50+
51+
const position = new vscode.Position(1, 'using Newton'.length);
52+
await waitForExpectedResult<vscode.CompletionList>(
53+
async () => getCompletionsAsync(position, undefined, 10),
54+
10 * 1000,
55+
100,
56+
(completionItems) => expect(completionItems.items.map((item) => item.label)).toContain('Newtonsoft')
57+
);
58+
});
59+
});

test/lsptoolshost/integrationTests/integrationHelpers.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import { ServerState } from '../../../src/lsptoolshost/server/languageServerEven
1212
import testAssetWorkspace from './testAssets/testAssetWorkspace';
1313
import { EOL, platform } from 'os';
1414
import { describe, expect, test } from '@jest/globals';
15+
import { WaitForAsyncOperationsRequest } from './testHooks';
1516

16-
export async function activateCSharpExtension(): Promise<void> {
17+
export async function activateCSharpExtension(): Promise<CSharpExtensionExports> {
1718
const csharpExtension = vscode.extensions.getExtension<CSharpExtensionExports>('ms-dotnettools.csharp');
1819
if (!csharpExtension) {
1920
throw new Error('Failed to find installation of ms-dotnettools.csharp');
@@ -53,6 +54,8 @@ export async function activateCSharpExtension(): Promise<void> {
5354
if (shouldRestart) {
5455
await restartLanguageServer();
5556
}
57+
58+
return csharpExtension.exports;
5659
}
5760

5861
export function usingDevKit(): boolean {
@@ -113,6 +116,25 @@ export function isSlnWithGenerator(workspace: typeof vscode.workspace) {
113116
return isGivenSln(workspace, 'slnWithGenerator');
114117
}
115118

119+
export async function getCompletionsAsync(
120+
position: vscode.Position,
121+
triggerCharacter: string | undefined,
122+
completionsToResolve: number
123+
): Promise<vscode.CompletionList> {
124+
const activeEditor = vscode.window.activeTextEditor;
125+
if (!activeEditor) {
126+
throw new Error('No active editor');
127+
}
128+
129+
return await vscode.commands.executeCommand(
130+
'vscode.executeCompletionItemProvider',
131+
activeEditor.document.uri,
132+
position,
133+
triggerCharacter,
134+
completionsToResolve
135+
);
136+
}
137+
116138
export async function getCodeLensesAsync(): Promise<vscode.CodeLens[]> {
117139
const activeEditor = vscode.window.activeTextEditor;
118140
if (!activeEditor) {
@@ -278,6 +300,10 @@ export const testIfDevKit = testIf(usingDevKit());
278300
export const testIfNotMacOS = testIf(!isMacOS());
279301
export const testIfWindows = testIf(isWindows());
280302

303+
const runFileBasedProgramsTests = process.env['ROSLYN_SKIP_TEST_FILE_BASED_PROGRAMS'] !== 'true';
304+
console.log(`process.env.ROSLYN_SKIP_TEST_FILE_BASED_PROGRAMS: ${process.env.ROSLYN_SKIP_TEST_FILE_BASED_PROGRAMS}`);
305+
export const describeIfFileBasedPrograms = describeIf(runFileBasedProgramsTests);
306+
281307
function describeIf(condition: boolean) {
282308
return condition ? describe : describe.skip;
283309
}
@@ -299,3 +325,8 @@ function isWindows() {
299325
function isLinux() {
300326
return !(isMacOS() || isWindows());
301327
}
328+
329+
export async function waitForAllAsyncOperationsAsync(exports: CSharpExtensionExports): Promise<void> {
330+
const source = new vscode.CancellationTokenSource();
331+
await exports.experimental.sendServerRequest(WaitForAsyncOperationsRequest.type, { operations: [] }, source.token);
332+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<Project>
2+
<PropertyGroup>
3+
<!-- Integration tests need to cleanup artifacts between runs. Putting the artifacts under 'testAssets' will ensure this happens when assets are overwritten after test run. -->
4+
<ArtifactsPath>$(MSBuildThisFileDirectory)</ArtifactsPath>
5+
</PropertyGroup>
6+
</Project>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
3+
4+
Console.WriteLine("Hello World!");
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as lsp from 'vscode-languageserver-protocol';
7+
8+
export interface WaitForAsyncOperationsParams {
9+
/**
10+
* The operations to wait for.
11+
*/
12+
operations: string[];
13+
}
14+
15+
export interface WaitForAsyncOperationsResponse {} // eslint-disable-line @typescript-eslint/no-empty-object-type
16+
17+
export namespace WaitForAsyncOperationsRequest {
18+
export const method = 'workspace/waitForAsyncOperations';
19+
export const messageDirection: lsp.MessageDirection = lsp.MessageDirection.clientToServer;
20+
export const type = new lsp.RequestType<WaitForAsyncOperationsParams, WaitForAsyncOperationsResponse, void>(method);
21+
}

0 commit comments

Comments
 (0)