Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
bd19287
add test resource
RikkiGibson Jul 29, 2025
1305e21
WIP: Integration test for file-based programs restore scenario
RikkiGibson Jul 29, 2025
e0dea11
Merge remote-tracking branch 'origin/main' into dev/rigibson/integrat…
RikkiGibson Jul 31, 2025
3458d52
add D.B.props
RikkiGibson Jul 31, 2025
d4b5997
cleanup
RikkiGibson Jul 31, 2025
f9be157
inspect details of notifications from server
RikkiGibson Jul 31, 2025
5fd1d9c
use 'waitForAllAsyncOperationsAsync'
RikkiGibson Aug 7, 2025
45e4d45
simplify
RikkiGibson Aug 7, 2025
fa315bc
fix lint errors
RikkiGibson Aug 7, 2025
02e435c
Merge remote-tracking branch 'origin/main' into dev/rigibson/integrat…
RikkiGibson Aug 7, 2025
0ef2931
WIP: bump roslyn version
RikkiGibson Aug 10, 2025
5a32bb2
rekick CI after adding package to feeds
RikkiGibson Aug 10, 2025
ad92e62
fix lint errors
RikkiGibson Aug 10, 2025
2239641
Merge branch 'dev/rigibson/integration-test-restore' of https://githu…
RikkiGibson Aug 10, 2025
e4e236a
Merge remote-tracking branch 'origin/main' into dev/rigibson/integrat…
RikkiGibson Aug 12, 2025
2fd2774
enable waiter in restore tests via env var
RikkiGibson Aug 12, 2025
3beee8e
I installed eslint finally
RikkiGibson Aug 12, 2025
3054919
condition test on new enough sdk
RikkiGibson Aug 12, 2025
94a2175
includePreviewVersions
RikkiGibson Aug 12, 2025
386f63e
use exact version
RikkiGibson Aug 12, 2025
f31eeaa
remove unnecessary(?) flag
RikkiGibson Aug 12, 2025
4fb723a
dot dot dot...
RikkiGibson Aug 12, 2025
b9637ba
update package name
RikkiGibson Aug 12, 2025
2493d9d
Explicitly get ubuntu noble numbat containers
RikkiGibson Aug 12, 2025
eb24923
Merge remote-tracking branch 'upstream/main' into dev/rigibson/integr…
RikkiGibson Oct 28, 2025
3c5d899
fix yml. fix skip test variable.
RikkiGibson Oct 28, 2025
77781dc
fix container name
RikkiGibson Oct 28, 2025
cc3d74f
fix for I bet the last time
RikkiGibson Oct 28, 2025
9d742a3
Move env var in pipeline
RikkiGibson Oct 28, 2025
7cf3de2
try to inspect what is happening in CI
RikkiGibson Oct 28, 2025
78c2213
try different name
RikkiGibson Oct 28, 2025
3e60d31
use template parameter
RikkiGibson Oct 28, 2025
c2f35aa
fix name
RikkiGibson Oct 28, 2025
dd46cce
try to deal with azdo env var weirdness
RikkiGibson Oct 28, 2025
b78119a
finish rename
RikkiGibson Oct 28, 2025
0baa5ab
rename test file
RikkiGibson Oct 28, 2025
885011d
More skips
RikkiGibson Oct 29, 2025
a56489b
Merge branch 'main' into dev/rigibson/integration-test-restore
RikkiGibson Oct 29, 2025
c030821
Merge branch 'main' into dev/rigibson/integration-test-restore
RikkiGibson Oct 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ To debug unit tests locally, press <kbd>F5</kbd> in VS Code with the "Launch Tes
To debug integration tests
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).
2. Open any integration test file and <kbd>F5</kbd> launch with the correct launch configuration selected.
- 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)
- 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)
- For integration tests inside `test/razor`, use `[Razor] Run Current File Integration Test`
- For integration tests inside `test/omnisharp`, use one of the `Omnisharp:` current file profiles
- For integration tests inside `test/omnisharp`, use one of the `[O#] Run Current File Integration Test` current file profiles

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

Expand Down
28 changes: 26 additions & 2 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ stages:
- stage:
displayName: Test Linux (.NET 8)
dependsOn: []
variables:
ROSLYN_SKIP_TEST_FILE_BASED_PROGRAMS: 'true'
jobs:
- template: azure-pipelines/test-matrix.yml
parameters:
Expand All @@ -83,11 +85,13 @@ stages:
pool:
name: NetCore-Public
demands: ImageOverride -equals 1es-ubuntu-2004-open
containerName: mcr.microsoft.com/dotnet/sdk:8.0
containerName: mcr.microsoft.com/dotnet/sdk:8.0-noble

- stage:
displayName: Test Linux (.NET 9)
dependsOn: []
variables:
ROSLYN_SKIP_TEST_FILE_BASED_PROGRAMS: 'true'
jobs:
- template: azure-pipelines/test-matrix.yml
parameters:
Expand All @@ -99,11 +103,29 @@ stages:
pool:
name: NetCore-Public
demands: ImageOverride -equals 1es-ubuntu-2004-open
containerName: mcr.microsoft.com/dotnet/sdk:9.0
containerName: mcr.microsoft.com/dotnet/sdk:9.0-noble

- stage:
displayName: Test Linux (.NET 10)
dependsOn: []
jobs:
- template: azure-pipelines/test-matrix.yml
parameters:
os: linux
# Prefer the dotnet from the container.
installDotNet: false
testVSCodeVersion: $(testVSCodeVersion)
installAdditionalLinuxDependencies: true
pool:
name: NetCore-Public
demands: ImageOverride -equals 1es-ubuntu-2004-open
containerName: mcr.microsoft.com/dotnet/sdk:10.0.100-rc.2-noble

- stage: Test_Windows_Stage
displayName: Test Windows
dependsOn: []
variables:
ROSLYN_SKIP_TEST_FILE_BASED_PROGRAMS: 'true'
jobs:
- template: azure-pipelines/test-matrix.yml
parameters:
Expand All @@ -117,6 +139,8 @@ stages:
- stage: Test_MacOS_Stage
displayName: Test MacOS
dependsOn: []
variables:
ROSLYN_SKIP_TEST_FILE_BASED_PROGRAMS: 'true'
jobs:
- template: azure-pipelines/test-matrix.yml
parameters:
Expand Down
2 changes: 1 addition & 1 deletion azure-pipelines/test-linux-docker-prereqs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ steps:
# Installing the dependencies requires root, but the docker image we're using doesn't have root permissions (nor sudo)
# We can exec as root from outside the container from the host machine.
# Really we should create our own image with these pre-installed, but we'll need to figure out how to publish the image.
- 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'
- 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'
displayName: 'Install additional Linux dependencies'
target: host
condition: eq(variables['Agent.OS'], 'Linux')
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import * as vscode from 'vscode';
import * as path from 'path';
import { describe, beforeAll, beforeEach, afterAll, test, expect, afterEach } from '@jest/globals';
import testAssetWorkspace from './testAssets/testAssetWorkspace';
import { activateCSharpExtension, closeAllEditorsAsync, openFileInWorkspaceAsync } from './integrationHelpers';
import {
activateCSharpExtension,
closeAllEditorsAsync,
getCompletionsAsync,
openFileInWorkspaceAsync,
} from './integrationHelpers';

describe(`Completion Tests`, () => {
beforeAll(async () => {
Expand Down Expand Up @@ -70,23 +75,4 @@ describe(`Completion Tests`, () => {
expect(methodOverrideLine).toContain('override void Method(NeedsImport n)');
expect(methodOverrideImplLine).toContain('base.Method(n);');
});

async function getCompletionsAsync(
position: vscode.Position,
triggerCharacter: string | undefined,
completionsToResolve: number
): Promise<vscode.CompletionList> {
const activeEditor = vscode.window.activeTextEditor;
if (!activeEditor) {
throw new Error('No active editor');
}

return await vscode.commands.executeCommand(
'vscode.executeCompletionItemProvider',
activeEditor.document.uri,
position,
triggerCharacter,
completionsToResolve
);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import * as path from 'path';
import testAssetWorkspace from './testAssets/testAssetWorkspace';
import {
activateCSharpExtension,
closeAllEditorsAsync,
getCompletionsAsync,
openFileInWorkspaceAsync,
revertActiveFile,
sleep,
waitForAllAsyncOperationsAsync,
waitForExpectedResult,
} from './integrationHelpers';
import { describe, beforeAll, beforeEach, afterAll, test, expect, afterEach } from '@jest/globals';
import { CSharpExtensionExports } from '../../../src/csharpExtensionExports';

const doRunSuite = process.env['ROSLYN_SKIP_TEST_FILE_BASED_PROGRAMS'] !== 'true';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#environment-variables

System and user-defined variables (except secret variables) also get injected as environment variables for your platform. When variables convert into environment variables, variable names become uppercase, and periods turn into underscores. For example, the variable name any.variable becomes the variable name $ANY_VARIABLE.

Linux env vars are case sensitive, so, the env. lookup is case sensitive. Since we were not using an uppercase name to access the env var, we weren't seeing it in the test.

Figuring this out took an unreasonable amount of time.

console.log(`process.env.ROSLYN_SKIP_TEST_FILE_BASED_PROGRAMS: ${process.env.ROSLYN_SKIP_TEST_FILE_BASED_PROGRAMS}`);
console.log(`doRunSuite: ${doRunSuite}`);
(doRunSuite ? describe : describe.skip)(`File-based Programs Tests`, () => {
let exports: CSharpExtensionExports;

beforeAll(async () => {
process.env.RoslynWaiterEnabled = 'true';
exports = await activateCSharpExtension();
});

beforeEach(async () => {
await openFileInWorkspaceAsync(path.join('src', 'scripts', 'app1.cs'));
});

afterEach(async () => {
await revertActiveFile();
await closeAllEditorsAsync();
});

afterAll(async () => {
await testAssetWorkspace.cleanupWorkspace();
});

test('Inserting package directive triggers a restore', async () => {
await sleep(1);
await vscode.window.activeTextEditor!.edit((editBuilder) => {
editBuilder.insert(new vscode.Position(0, 0), '#:package [email protected]');
});
await vscode.window.activeTextEditor!.document.save();
await waitForAllAsyncOperationsAsync(exports);

const position = new vscode.Position(1, 'using Newton'.length);
await waitForExpectedResult<vscode.CompletionList>(
async () => getCompletionsAsync(position, undefined, 10),
10 * 1000,
100,
(completionItems) => expect(completionItems.items.map((item) => item.label)).toContain('Newtonsoft')
);
});
});
29 changes: 28 additions & 1 deletion test/lsptoolshost/integrationTests/integrationHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import { ServerState } from '../../../src/lsptoolshost/server/languageServerEven
import testAssetWorkspace from './testAssets/testAssetWorkspace';
import { EOL, platform } from 'os';
import { describe, expect, test } from '@jest/globals';
import { WaitForAsyncOperationsRequest } from './testHooks';

export async function activateCSharpExtension(): Promise<void> {
export async function activateCSharpExtension(): Promise<CSharpExtensionExports> {
const csharpExtension = vscode.extensions.getExtension<CSharpExtensionExports>('ms-dotnettools.csharp');
if (!csharpExtension) {
throw new Error('Failed to find installation of ms-dotnettools.csharp');
Expand Down Expand Up @@ -53,6 +54,8 @@ export async function activateCSharpExtension(): Promise<void> {
if (shouldRestart) {
await restartLanguageServer();
}

return csharpExtension.exports;
}

export function usingDevKit(): boolean {
Expand Down Expand Up @@ -113,6 +116,25 @@ export function isSlnWithGenerator(workspace: typeof vscode.workspace) {
return isGivenSln(workspace, 'slnWithGenerator');
}

export async function getCompletionsAsync(
position: vscode.Position,
triggerCharacter: string | undefined,
completionsToResolve: number
): Promise<vscode.CompletionList> {
const activeEditor = vscode.window.activeTextEditor;
if (!activeEditor) {
throw new Error('No active editor');
}

return await vscode.commands.executeCommand(
'vscode.executeCompletionItemProvider',
activeEditor.document.uri,
position,
triggerCharacter,
completionsToResolve
);
}

export async function getCodeLensesAsync(): Promise<vscode.CodeLens[]> {
const activeEditor = vscode.window.activeTextEditor;
if (!activeEditor) {
Expand Down Expand Up @@ -299,3 +321,8 @@ function isWindows() {
function isLinux() {
return !(isMacOS() || isWindows());
}

export async function waitForAllAsyncOperationsAsync(exports: CSharpExtensionExports): Promise<void> {
const source = new vscode.CancellationTokenSource();
await exports.experimental.sendServerRequest(WaitForAsyncOperationsRequest.type, { operations: [] }, source.token);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project>
<PropertyGroup>
<!-- Integration tests need to cleanup artifacts between runs. Putting the artifacts under 'testAssets' will ensure this happens when assets are overwritten after test run. -->
<ArtifactsPath>$(MSBuildThisFileDirectory)</ArtifactsPath>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

using Newton;

Console.WriteLine("Hello World!");
21 changes: 21 additions & 0 deletions test/lsptoolshost/integrationTests/testHooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as lsp from 'vscode-languageserver-protocol';

export interface WaitForAsyncOperationsParams {
/**
* The operations to wait for.
*/
operations: string[];
}

export interface WaitForAsyncOperationsResponse {} // eslint-disable-line @typescript-eslint/no-empty-object-type

export namespace WaitForAsyncOperationsRequest {
export const method = 'workspace/waitForAsyncOperations';
export const messageDirection: lsp.MessageDirection = lsp.MessageDirection.clientToServer;
export const type = new lsp.RequestType<WaitForAsyncOperationsParams, WaitForAsyncOperationsResponse, void>(method);
}