Skip to content

Commit 453935d

Browse files
authored
Merge pull request #6509 from davidwengier/RazorFormattingIntegrationTest
Create Razor integration test system, and formatting test
2 parents 343e944 + b9e7412 commit 453935d

23 files changed

+414
-125
lines changed

.vscode/launch.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,17 @@
323323
"generateOptionsSchema"
324324
],
325325
"cwd": "${workspaceFolder}"
326+
},
327+
{
328+
"type": "node",
329+
"request": "launch",
330+
"name": "Razor integration tests",
331+
"preLaunchTask": "build",
332+
"program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js",
333+
"args": [
334+
"test:razorintegration"
335+
],
336+
"cwd": "${workspaceFolder}"
326337
}
327338
]
328339
}

jest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const config: Config = {
88
projects: [
99
'<rootDir>/test/unitTests/jest.config.ts',
1010
'<rootDir>/test/integrationTests/jest.config.ts',
11+
'<rootDir>/test/razorIntegrationTests/jest.config.ts',
1112
'<rootDir>/test/razorTests/jest.config.ts',
1213
'<rootDir>/omnisharptest/omnisharpJestTests/jest.config.ts',
1314
],

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"test": "tsc -p ./ && gulp test",
6464
"test:integration": "tsc -p ./ && gulp test:integration",
6565
"test:razor": "tsc -p ./ && npm run compile:razorTextMate && gulp test:razor",
66+
"test:razorintegration": "tsc -p ./ && gulp test:razorintegration",
6667
"omnisharptest": "tsc -p ./ && gulp omnisharptest",
6768
"omnisharptest:unit": "tsc -p ./ && gulp omnisharptest:unit",
6869
"omnisharptest:feature": "tsc -p ./ && gulp omnisharptest:feature",

tasks/projectPaths.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,14 @@ export const omnisharpTestRootPath = path.join(rootPath, 'out', 'omnisharptest')
2323
export const omnisharpFeatureTestRunnerPath = path.join(omnisharpTestRootPath, 'runFeatureTests.js');
2424

2525
export const integrationTestAssetsRootPath = path.join(rootPath, 'test', 'integrationTests', 'testAssets');
26+
export const razorIntegrationTestAssetsRootPath = path.join(rootPath, 'test', 'razorIntegrationTests', 'testAssets');
2627

2728
export const testRootPath = path.join(rootPath, 'out', 'test');
2829
export const integrationTestRunnerPath = path.join(testRootPath, 'integrationTests', 'runIntegrationTests.js');
30+
export const razorIntegrationTestRunnerPath = path.join(
31+
testRootPath,
32+
'razorIntegrationTests',
33+
'runIntegrationTests.js'
34+
);
2935

3036
export const nodePath = path.join(process.env.NVM_BIN ? `${process.env.NVM_BIN}${path.sep}` : '', 'node');

tasks/testTasks.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
omnisharpTestRootPath,
1515
testRootPath,
1616
integrationTestRunnerPath,
17+
razorIntegrationTestAssetsRootPath,
18+
razorIntegrationTestRunnerPath,
1719
} from './projectPaths';
1820
import spawnNode from './spawnNode';
1921
import * as jest from 'jest';
@@ -45,6 +47,16 @@ gulp.task('test:razor', async () => {
4547
runJestTest(razorTestProjectName);
4648
});
4749

50+
const razorIntegrationTestProjects = ['BasicRazorApp2_1'];
51+
for (const projectName of razorIntegrationTestProjects) {
52+
gulp.task(`test:razorintegration:${projectName}`, async () => runIntegrationTest(projectName, /* razor */ true));
53+
}
54+
55+
gulp.task(
56+
'test:razorintegration',
57+
gulp.series(razorIntegrationTestProjects.map((projectName) => `test:razorintegration:${projectName}`))
58+
);
59+
4860
gulp.task('omnisharptest:unit', async () => {
4961
const result = await spawnNode([
5062
mochaPath,
@@ -113,7 +125,7 @@ gulp.task(
113125
gulp.series(integrationTestProjects.map((projectName) => `test:integration:${projectName}`))
114126
);
115127

116-
gulp.task('test', gulp.series('test:unit', 'test:integration', 'test:razor'));
128+
gulp.task('test', gulp.series('test:unit', 'test:integration', 'test:razor', 'test:razorintegration'));
117129

118130
async function runOmnisharpIntegrationTest(testAssetName: string, engine: 'stdio' | 'lsp') {
119131
const workspaceFile = `omnisharp${engine === 'lsp' ? '_lsp' : ''}_${testAssetName}.code-workspace`;
@@ -145,22 +157,23 @@ async function runOmnisharpIntegrationTest(testAssetName: string, engine: 'stdio
145157
return result;
146158
}
147159

148-
async function runIntegrationTest(testAssetName: string) {
160+
async function runIntegrationTest(testAssetName: string, razor = false) {
149161
const workspacePath = path.join(
150-
integrationTestAssetsRootPath,
162+
razor ? razorIntegrationTestAssetsRootPath : integrationTestAssetsRootPath,
151163
testAssetName,
152164
'.vscode',
153165
`lsp_tools_host_${testAssetName}.code-workspace`
154166
);
155-
const codeTestsPath = path.join(testRootPath, 'integrationTests');
167+
const codeTestsPath = path.join(testRootPath, razor ? 'razorIntegrationTests' : 'integrationTests');
156168

157169
const env = {
158170
CODE_TESTS_WORKSPACE: workspacePath,
159171
CODE_EXTENSIONS_PATH: rootPath,
160172
EXTENSIONS_TESTS_PATH: path.join(codeTestsPath, 'index.js'),
161173
};
162174

163-
const result = await spawnNode([integrationTestRunnerPath, '--enable-source-maps'], { env, cwd: rootPath });
175+
const runnerPath = razor ? razorIntegrationTestRunnerPath : integrationTestRunnerPath;
176+
const result = await spawnNode([runnerPath, '--enable-source-maps'], { env, cwd: rootPath });
164177

165178
if (result.code === null || result.code > 0) {
166179
// Ensure that gulp fails when tests fail

test/integrationTests/index.ts

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,11 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import * as jest from 'jest';
7-
import { Config } from '@jest/types';
8-
import * as path from 'path';
6+
import { runIntegrationTests } from '../runIntegrationTests';
97
import { jestIntegrationTestProjectName } from './jest.config';
108

11-
async function runIntegrationTests() {
12-
const repoRoot = process.env.CODE_EXTENSIONS_PATH;
13-
if (!repoRoot) {
14-
throw new Error('CODE_EXTENSIONS_PATH not set.');
15-
}
16-
17-
const jestConfigPath = path.join(repoRoot, 'jest.config.ts');
18-
const jestConfig = {
19-
config: jestConfigPath,
20-
selectProjects: [jestIntegrationTestProjectName],
21-
// Since we're running tests in the actual vscode process we have to run them serially.
22-
runInBand: true,
23-
// Timeout cannot be overriden in the jest config file, so override here.
24-
testTimeout: 120000,
25-
verbose: true,
26-
} as Config.Argv;
27-
28-
let filter: string;
29-
if (process.env.TEST_FILE_FILTER) {
30-
// If we have just a file, run that with runTestsByPath.
31-
jestConfig.runTestsByPath = true;
32-
jestConfig.testMatch = [process.env.TEST_FILE_FILTER];
33-
filter = process.env.TEST_FILE_FILTER;
34-
} else {
35-
filter = jestIntegrationTestProjectName;
36-
}
37-
38-
const { results } = await jest.runCLI(jestConfig, [filter]);
39-
40-
if (!results.success) {
41-
throw new Error('Tests failed.');
42-
}
43-
}
44-
459
export async function run() {
4610
process.env.RUNNING_INTEGRATION_TESTS = 'true';
4711

48-
return runIntegrationTests();
12+
await runIntegrationTests(jestIntegrationTestProjectName);
4913
}

test/integrationTests/runIntegrationTests.ts

Lines changed: 2 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -3,86 +3,6 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import * as cp from 'child_process';
7-
import * as path from 'path';
8-
import { downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath, runTests } from '@vscode/test-electron';
9-
import { execChildProcess } from '../../src/common';
6+
import * as runner from '../runIntegrationTests';
107

11-
function getSln(workspacePath: string): string | undefined {
12-
if (workspacePath.endsWith('slnWithGenerator')) {
13-
return 'slnWithGenerator.sln';
14-
}
15-
return undefined;
16-
}
17-
18-
async function main() {
19-
try {
20-
const vscodeExecutablePath = await downloadAndUnzipVSCode('stable');
21-
const [cli, ...args] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath);
22-
23-
console.log('Display: ' + process.env.DISPLAY);
24-
25-
const result = cp.spawnSync(cli, [...args, '--install-extension', 'ms-dotnettools.vscode-dotnet-runtime'], {
26-
encoding: 'utf-8',
27-
stdio: 'inherit',
28-
});
29-
if (result.error) {
30-
throw new Error(`Failed to install the runtime extension: ${result.error}`);
31-
}
32-
33-
// The folder containing the Extension Manifest package.json
34-
// Passed to `--extensionDevelopmentPath`
35-
const extensionDevelopmentPath = process.env.CODE_EXTENSIONS_PATH;
36-
if (!extensionDevelopmentPath) {
37-
throw new Error('Environment variable CODE_EXTENSIONS_PATH is empty');
38-
}
39-
40-
// The path to the extension test runner script
41-
// Passed to --extensionTestsPath
42-
const extensionTestsPath = process.env.EXTENSIONS_TESTS_PATH;
43-
44-
if (!extensionTestsPath) {
45-
console.error('Empty extension tests path');
46-
process.exit(-1);
47-
}
48-
49-
// The integration tests expect that the workspace to run the
50-
// tests against is set in an environment variable.
51-
const workspacePath = process.env.CODE_TESTS_WORKSPACE;
52-
53-
if (!workspacePath) {
54-
console.error(`Empty workspace path`);
55-
process.exit(-1);
56-
}
57-
58-
console.log(`workspace path = '${workspacePath}'`);
59-
60-
const sln = getSln(workspacePath);
61-
if (sln) {
62-
// Run a build before the tests, to ensure that source generators are set up correctly
63-
if (!process.env.DOTNET_ROOT) {
64-
throw new Error('Environment variable DOTNET_ROOT is not set');
65-
}
66-
67-
const dotnetPath = path.join(process.env.DOTNET_ROOT, 'dotnet');
68-
await execChildProcess(`${dotnetPath} build ${sln}`, workspacePath);
69-
}
70-
71-
// Download VS Code, unzip it and run the integration test
72-
const exitCode = await runTests({
73-
extensionDevelopmentPath,
74-
extensionTestsPath,
75-
// Launch with info logging as anything else is way too verbose and will hide test results.
76-
launchArgs: [workspacePath, '-n', '--log', 'info'],
77-
extensionTestsEnv: process.env,
78-
});
79-
80-
process.exit(exitCode);
81-
} catch (err) {
82-
console.error(err);
83-
console.error('Failed to run tests');
84-
process.exit(1);
85-
}
86-
}
87-
88-
main();
8+
runner.main();
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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 path from 'path';
7+
import * as vscode from 'vscode';
8+
import * as jestLib from '@jest/globals';
9+
import testAssetWorkspace from './testAssets/testAssetWorkspace';
10+
import * as integrationHelpers from '../integrationTests/integrationHelpers';
11+
12+
function normalizeNewlines(original: string | undefined): string | undefined {
13+
if (!original) {
14+
return original;
15+
}
16+
17+
while (original.indexOf('\r\n') != -1) {
18+
original = original.replace('\r\n', '\n');
19+
}
20+
21+
return original;
22+
}
23+
24+
jestLib.describe(`Razor Formatting ${testAssetWorkspace.description}`, function () {
25+
jestLib.beforeAll(async function () {
26+
if (!integrationHelpers.isRazorWorkspace(vscode.workspace)) {
27+
return;
28+
}
29+
30+
const razorConfig = vscode.workspace.getConfiguration('razor');
31+
await razorConfig.update('format.enable', true);
32+
const htmlConfig = vscode.workspace.getConfiguration('html');
33+
await htmlConfig.update('format.enable', true);
34+
35+
await integrationHelpers.activateCSharpExtension();
36+
await integrationHelpers.openFileInWorkspaceAsync(path.join('Pages', 'BadlyFormatted.razor'));
37+
});
38+
39+
jestLib.afterAll(async () => {
40+
await testAssetWorkspace.cleanupWorkspace();
41+
});
42+
43+
jestLib.test('Document formatted correctly', async () => {
44+
if (!integrationHelpers.isRazorWorkspace(vscode.workspace)) {
45+
return;
46+
}
47+
48+
const activeDocument = vscode.window.activeTextEditor?.document.uri;
49+
if (!activeDocument) {
50+
throw new Error('No active document');
51+
}
52+
53+
const edits = <vscode.TextEdit[]>await vscode.commands.executeCommand(
54+
'vscode.executeFormatDocumentProvider',
55+
activeDocument,
56+
{
57+
insertSpaces: true,
58+
tabSize: 4,
59+
}
60+
);
61+
62+
jestLib.expect(edits).toBeDefined();
63+
64+
// It's much easier to verify the expected state of the document, than a bunch of edits
65+
const formatEdit = new vscode.WorkspaceEdit();
66+
formatEdit.set(activeDocument, edits);
67+
await vscode.workspace.applyEdit(formatEdit);
68+
69+
const contents = normalizeNewlines(vscode.window.activeTextEditor?.document.getText());
70+
jestLib.expect(contents).toBe(
71+
normalizeNewlines(`@page "/bad"
72+
73+
@code {
74+
private string _x = "";
75+
76+
private void M()
77+
{
78+
// hi there
79+
}
80+
}
81+
`)
82+
);
83+
});
84+
});

test/razorIntegrationTests/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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 { runIntegrationTests } from '../runIntegrationTests';
7+
import { jestIntegrationTestProjectName } from './jest.config';
8+
9+
export async function run() {
10+
process.env.RUNNING_INTEGRATION_TESTS = 'true';
11+
12+
await runIntegrationTests(jestIntegrationTestProjectName);
13+
}
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+
import type { Config } from 'jest';
6+
import { baseProjectConfig } from '../../baseJestConfig';
7+
8+
export const jestIntegrationTestProjectName = 'Razor Integration Tests';
9+
10+
/**
11+
* Defines a project configuration for jest integration tests.
12+
*/
13+
const integrationTestConfig: Config = {
14+
...baseProjectConfig,
15+
displayName: jestIntegrationTestProjectName,
16+
roots: ['<rootDir>'],
17+
testEnvironment: '<rootDir>/../integrationTests/jestSetup/vsCodeEnvironment.ts',
18+
setupFilesAfterEnv: ['<rootDir>/../integrationTests/jestSetup/vsCodeFramework.ts'],
19+
};
20+
21+
export default integrationTestConfig;

0 commit comments

Comments
 (0)