Skip to content

Commit 3b5db9a

Browse files
authored
Merge pull request #6621 from dibarbet/jest_report_ci
Report jest test results to AzDo
2 parents 981538c + 8221f9d commit 3b5db9a

File tree

8 files changed

+152
-20
lines changed

8 files changed

+152
-20
lines changed

.vscode/launch.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
"${workspaceFolder}/**",
4242
"!**/node_modules/**"
4343
],
44-
"preLaunchTask": "buildDev"
44+
"preLaunchTask": "buildDev",
45+
"internalConsoleOptions": "openOnSessionStart"
4546
},
4647
{
4748
"name": "Launch Current File BasicRazorApp2_1 Integration Tests",
@@ -69,7 +70,8 @@
6970
"${workspaceFolder}/**",
7071
"!**/node_modules/**"
7172
],
72-
"preLaunchTask": "buildDev"
73+
"preLaunchTask": "buildDev",
74+
"internalConsoleOptions": "openOnSessionStart"
7375
},
7476
{
7577
"name": "Omnisharp: Launch Current File Integration Tests",

azure-pipelines/test-omnisharp.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ steps:
1414
env:
1515
DISPLAY: :99.0
1616

17+
- task: PublishTestResults@2
18+
condition: succeededOrFailed()
19+
displayName: 'Publish Test Results'
20+
inputs:
21+
testResultsFormat: 'JUnit'
22+
testResultsFiles: '*junit.xml'
23+
searchFolder: '$(Build.SourcesDirectory)/out'
24+
publishRunAttachments: true
25+
mergeTestResults: true
26+
testRunTitle: OmniSharp $(Agent.JobName) (Attempt $(System.JobAttempt))
27+
1728
- task: PublishPipelineArtifact@1
1829
condition: failed()
1930
displayName: 'Upload integration test logs'

azure-pipelines/test.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,17 @@ jobs:
3636
env:
3737
DISPLAY: :99.0
3838

39+
- task: PublishTestResults@2
40+
condition: succeededOrFailed()
41+
displayName: 'Publish Test Results'
42+
inputs:
43+
testResultsFormat: 'JUnit'
44+
testResultsFiles: '*junit.xml'
45+
searchFolder: '$(Build.SourcesDirectory)/out'
46+
publishRunAttachments: true
47+
mergeTestResults: true
48+
testRunTitle: $(Agent.JobName) (Attempt $(System.JobAttempt))
49+
3950
- task: PublishPipelineArtifact@1
4051
condition: failed()
4152
displayName: 'Upload integration test logs'

jest.config.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,23 @@ const config: Config = {
1414
'<rootDir>/omnisharptest/omnisharpUnitTests/jest.config.ts',
1515
'<rootDir>/omnisharptest/omnisharpIntegrationTests/jest.config.ts',
1616
],
17+
// Reporters are a global jest configuration property and cannot be set in the project jest config.
18+
// This configuration will create a 'junit.xml' file in the output directory, no matter which test project is running.
19+
// In order to not overwrite test results in CI, we configure a unique output file name in the gulp testTasks.
20+
reporters: [
21+
'default',
22+
[
23+
'jest-junit',
24+
{
25+
outputDirectory: '<rootDir>/out/',
26+
reportTestSuiteErrors: 'true',
27+
// Azure DevOps does not display test suites (it ignores them entirely).
28+
// So we have to put all the info in the test case name so the UI shows anything relatively useful.
29+
// See https://github.com/microsoft/azure-pipelines-tasks/issues/7659
30+
titleTemplate: '{filename} / {suitename} / {title}',
31+
},
32+
],
33+
],
1734
};
1835

1936
export default config;

package-lock.json

Lines changed: 75 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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
"@typescript-eslint/eslint-plugin": "^5.61.0",
131131
"@typescript-eslint/parser": "^5.61.0",
132132
"@vscode/test-electron": "2.3.4",
133+
"@vscode/vsce": "2.21.0",
133134
"archiver": "5.3.0",
134135
"del": "3.0.0",
135136
"eslint": "^8.43.0",
@@ -147,6 +148,7 @@
147148
"gulp": "4.0.2",
148149
"jest": "^29.6.2",
149150
"jest-cli": "^29.6.4",
151+
"jest-junit": "^16.0.0",
150152
"js-yaml": ">=3.13.1",
151153
"minimatch": "3.0.5",
152154
"mock-http-server": "1.4.2",
@@ -159,7 +161,6 @@
159161
"ts-node": "9.1.1",
160162
"typescript": "^5.1.6",
161163
"unzipper": "0.10.11",
162-
"@vscode/vsce": "2.21.0",
163164
"vscode-oniguruma": "^1.6.1",
164165
"vscode-textmate": "^6.0.0",
165166
"vscode-uri": "^3.0.7",

tasks/testTasks.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ gulp.task('test:razor', async () => {
2121

2222
const razorIntegrationTestProjects = ['BasicRazorApp2_1'];
2323
for (const projectName of razorIntegrationTestProjects) {
24-
gulp.task(`test:razorintegration:${projectName}`, async () => runIntegrationTest(projectName, /* razor */ true));
24+
gulp.task(`test:razorintegration:${projectName}`, async () =>
25+
runIntegrationTest(projectName, 'razorIntegrationTests', `Razor Test Integration ${projectName}`)
26+
);
2527
}
2628

2729
gulp.task(
@@ -41,10 +43,10 @@ const omnisharpIntegrationTestProjects = ['singleCsproj', 'slnWithCsproj', 'slnF
4143

4244
for (const projectName of omnisharpIntegrationTestProjects) {
4345
gulp.task(`omnisharptest:integration:${projectName}:stdio`, async () =>
44-
runOmnisharpJestIntegrationTest(projectName, 'stdio')
46+
runOmnisharpJestIntegrationTest(projectName, 'stdio', `OmniSharp Test Integration ${projectName} STDIO}`)
4547
);
4648
gulp.task(`omnisharptest:integration:${projectName}:lsp`, async () =>
47-
runOmnisharpJestIntegrationTest(projectName, 'lsp')
49+
runOmnisharpJestIntegrationTest(projectName, 'lsp', `OmniSharp Test Integration ${projectName} LSP}`)
4850
);
4951
gulp.task(
5052
`omnisharptest:integration:${projectName}`,
@@ -73,7 +75,9 @@ gulp.task('test:unit', async () => {
7375

7476
const integrationTestProjects = ['slnWithCsproj'];
7577
for (const projectName of integrationTestProjects) {
76-
gulp.task(`test:integration:${projectName}`, async () => runIntegrationTest(projectName));
78+
gulp.task(`test:integration:${projectName}`, async () =>
79+
runIntegrationTest(projectName, 'integrationTests', `Test Integration ${projectName}`)
80+
);
7781
}
7882

7983
gulp.task(
@@ -83,7 +87,7 @@ gulp.task(
8387

8488
gulp.task('test', gulp.series('test:unit', 'test:integration', 'test:razor', 'test:razorintegration'));
8589

86-
async function runOmnisharpJestIntegrationTest(testAssetName: string, engine: 'stdio' | 'lsp') {
90+
async function runOmnisharpJestIntegrationTest(testAssetName: string, engine: 'stdio' | 'lsp', suiteName: string) {
8791
const workspaceFile = `omnisharp${engine === 'lsp' ? '_lsp' : ''}_${testAssetName}.code-workspace`;
8892
const testFolder = path.join('omnisharptest', 'omnisharpIntegrationTests');
8993

@@ -96,26 +100,28 @@ async function runOmnisharpJestIntegrationTest(testAssetName: string, engine: 's
96100
CODE_DISABLE_EXTENSIONS: 'true',
97101
};
98102

99-
await runJestIntegrationTest(testAssetName, testFolder, workspaceFile, env);
103+
await runJestIntegrationTest(testAssetName, testFolder, workspaceFile, suiteName, env);
100104
}
101105

102-
async function runIntegrationTest(testAssetName: string, razor = false) {
106+
async function runIntegrationTest(testAssetName: string, testFolderName: string, suiteName: string) {
103107
const vscodeWorkspaceFileName = `lsp_tools_host_${testAssetName}.code-workspace`;
104-
const testFolder = path.join('test', razor ? 'razorIntegrationTests' : 'integrationTests');
105-
return await runJestIntegrationTest(testAssetName, testFolder, vscodeWorkspaceFileName);
108+
const testFolder = path.join('test', testFolderName);
109+
return await runJestIntegrationTest(testAssetName, testFolder, vscodeWorkspaceFileName, suiteName);
106110
}
107111

108112
/**
109113
* Runs jest based integration tests.
110114
* @param testAssetName the name of the test asset
111115
* @param testFolderName the relative path (from workspace root)
112116
* @param workspaceFileName the name of the vscode workspace file to use.
117+
* @param suiteName a unique name for the test suite being run.
113118
* @param env any environment variables needed.
114119
*/
115120
async function runJestIntegrationTest(
116121
testAssetName: string,
117122
testFolderName: string,
118123
workspaceFileName: string,
124+
suiteName: string,
119125
env: NodeJS.ProcessEnv = {}
120126
) {
121127
// Test assets are always in a testAssets folder inside the integration test folder.
@@ -143,6 +149,10 @@ async function runJestIntegrationTest(
143149
env.CODE_EXTENSIONS_PATH = rootPath;
144150
env.EXTENSIONS_TESTS_PATH = vscodeRunnerPath;
145151

152+
// Configure the file and suite name in CI to avoid having multiple test runs stomp on each other.
153+
env.JEST_JUNIT_OUTPUT_NAME = getJUnitFileName(suiteName);
154+
env.JEST_SUITE_NAME = suiteName;
155+
146156
const result = await spawnNode([launcherPath, '--enable-source-maps'], { env, cwd: rootPath });
147157

148158
if (result.code === null || result.code > 0) {
@@ -154,6 +164,8 @@ async function runJestIntegrationTest(
154164
}
155165

156166
async function runJestTest(project: string) {
167+
process.env.JEST_JUNIT_OUTPUT_NAME = getJUnitFileName(project);
168+
process.env.JEST_SUITE_NAME = project;
157169
const configPath = path.join(rootPath, 'jest.config.ts');
158170
const { results } = await jest.runCLI(
159171
{
@@ -168,3 +180,7 @@ async function runJestTest(project: string) {
168180
throw new Error('Tests failed.');
169181
}
170182
}
183+
184+
function getJUnitFileName(suiteName: string) {
185+
return `${suiteName.replaceAll(' ', '_')}_junit.xml`;
186+
}

test/runIntegrationTests.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,18 @@ export async function runIntegrationTests(projectName: string) {
2424
verbose: true,
2525
} as Config.Argv;
2626

27-
let filter: string;
2827
if (process.env.TEST_FILE_FILTER) {
29-
// If we have just a file, run that with runTestsByPath.
30-
jestConfig.runTestsByPath = true;
28+
// If we have just a file, run that with an explicit match.
3129
jestConfig.testMatch = [process.env.TEST_FILE_FILTER];
32-
filter = process.env.TEST_FILE_FILTER;
33-
} else {
34-
filter = projectName;
3530
}
3631

37-
const { results } = await jest.runCLI(jestConfig, [filter]);
32+
const { results } = await jest.runCLI(jestConfig, [projectName]);
3833

3934
if (!results.success) {
40-
throw new Error('Tests failed.');
35+
console.log('Tests failed.');
4136
}
37+
38+
// Explicitly exit the process - VSCode likes to write a bunch of cancellation errors to the console after this
39+
// which make it look like the tests always fail. We're done with the tests at this point, so just exit.
40+
process.exit(results.success ? 0 : 1);
4241
}

0 commit comments

Comments
 (0)