Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 3e03c5b

Browse files
committed
Add test suite & two ways to run it (via yarn, via VS Code)
1 parent 15cc7ac commit 3e03c5b

File tree

9 files changed

+928
-7
lines changed

9 files changed

+928
-7
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.token
2+
.vscode-test
23
node_modules
34
*.vsix
45
out

.vscode/launch.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
{
66
"version": "0.2.0",
77
"configurations": [
8+
89
{
910
"name": "Run Extension",
1011
"type": "extensionHost",
@@ -17,5 +18,17 @@
1718
],
1819
"preLaunchTask": "${defaultBuildTask}"
1920
},
21+
{
22+
"name": "Run Extension Tests",
23+
"type": "extensionHost",
24+
"request": "launch",
25+
"runtimeExecutable": "${execPath}",
26+
"args": [
27+
"--extensionDevelopmentPath=${workspaceFolder}",
28+
"--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
29+
],
30+
"outFiles": ["${workspaceFolder}/out/test/**/*.js"],
31+
"preLaunchTask": "${defaultBuildTask}"
32+
}
2033
]
2134
}

package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"bugs": {
1414
"url": "https://github.com/ruby-syntax-tree/vscode-syntax-tree/issues"
1515
},
16+
"packageManager": "yarn@1.22.19",
1617
"engines": {
1718
"vscode": "^1.66.0"
1819
},
@@ -76,18 +77,25 @@
7677
"colors": []
7778
},
7879
"scripts": {
80+
"clean": "rm -Rf out",
7981
"compile": "tsc -p ./",
8082
"package": "vsce package --yarn --githubBranch main",
8183
"publish": "vsce publish --yarn --githubBranch main",
8284
"vscode:prepublish": "yarn compile",
85+
"test": "node ./out/test/runTest.js",
8386
"watch": "tsc --watch -p ./"
8487
},
8588
"dependencies": {
8689
"vscode-languageclient": "8.0.2-next.5"
8790
},
8891
"devDependencies": {
92+
"@types/glob": "^7.1.1",
93+
"@types/mocha": "^9.1.1",
8994
"@types/node": "^18.0.0",
9095
"@types/vscode": "^1.68.0",
96+
"@vscode/test-electron": "^1.6.1",
97+
"glob": "^7.1.4",
98+
"mocha": "^9.1.1",
9199
"typescript": "^4.7.4",
92100
"vsce": "^2.9.2"
93101
}

src/test/runTest.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { runTests } from '@vscode/test-electron';
2+
import * as path from 'path';
3+
4+
import { USER_DATA_DIR, WORKSPACE_DIR } from './suite/setup';
5+
6+
async function main() {
7+
try {
8+
// The folder containing the Extension Manifest package.json
9+
// Passed to `--extensionDevelopmentPath`
10+
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
11+
12+
// The path to the extension test script
13+
// Passed to --extensionTestsPath
14+
const extensionTestsPath = path.resolve(__dirname, './suite/index');
15+
16+
// Download VS Code, unzip it and run the integration test
17+
await runTests({ extensionDevelopmentPath, extensionTestsPath, launchArgs: ['--disable-extensions', '--disable-gpu', '--user-data-dir', USER_DATA_DIR, WORKSPACE_DIR] });
18+
} catch (err) {
19+
console.error('Failed to run tests');
20+
process.exit(1);
21+
}
22+
}
23+
24+
main();

src/test/suite/automation.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import * as assert from 'assert';
2+
import * as path from 'path';
3+
import { TextEncoder } from 'util';
4+
5+
import { Range, Uri, commands, window, workspace } from 'vscode';
6+
7+
import { WORKSPACE_DIR } from './setup';
8+
9+
export async function closeAll() {
10+
await commands.executeCommand('workbench.action.closeAllEditors');
11+
12+
await commands.executeCommand('workbench.panel.output.focus');
13+
await commands.executeCommand('workbench.action.output.toggleOutput');
14+
await showOutputChannel();
15+
16+
// @see https://stackoverflow.com/questions/44733028/how-to-close-textdocument-in-vs-code
17+
const { visibleTextEditors: editors } = window;
18+
for (let i = 0; i < editors.length; i++) {
19+
const { document } = editors[i];
20+
await window.showTextDocument(document, { preview: true, preserveFocus: true });
21+
await editors[i].edit(cb => {
22+
cb.delete(new Range(0, 0, document.lineCount, 0));
23+
});
24+
await commands.executeCommand('workbench.action.closeActiveEditor');
25+
}
26+
await commands.executeCommand('workbench.action.output.toggleOutput');
27+
28+
// Set the focus back to something neutral
29+
await commands.executeCommand('workbench.explorer.fileView.focus');
30+
}
31+
32+
export async function createEditor(content: string, filename = 'test.rb') {
33+
const uri = Uri.file(`${WORKSPACE_DIR}${path.sep}${filename}`);
34+
await workspace.fs.writeFile(uri, new TextEncoder().encode(content));
35+
await window.showTextDocument(uri);
36+
assert.ok(window.activeTextEditor);
37+
assert.equal(window.activeTextEditor.document.getText(), content);
38+
return window.activeTextEditor;
39+
}
40+
41+
export function formatDocument() {
42+
return commands.executeCommand('editor.action.formatDocument', 'ruby-syntax-tree.vscode-syntax-tree');
43+
}
44+
45+
export function restart() {
46+
return commands.executeCommand('syntaxTree.restart');
47+
}
48+
49+
export function showOutputChannel(timeout = 15000) {
50+
// TODO: make use of await commands.executeCommand('workbench.panel.output.focus'); ??
51+
return new Promise<string>((c, e) => {
52+
const failSauce = new Error(`No active editor change within ${timeout}ms`)
53+
const timer = setTimeout(() => e(failSauce), timeout);
54+
const disposable = window.onDidChangeActiveTextEditor(e => {
55+
if (e) {
56+
clearTimeout(timer);
57+
disposable.dispose();
58+
c(e.document.getText())
59+
}
60+
})
61+
commands.executeCommand('syntaxTree.showOutputChannel');
62+
})
63+
}
64+
65+
export function start() {
66+
return commands.executeCommand('syntaxTree.start');
67+
}
68+
69+
export function stop() {
70+
return commands.executeCommand('syntaxTree.stop');
71+
}

src/test/suite/index.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as assert from 'assert';
2+
import { before, beforeEach } from 'mocha';
3+
4+
import * as auto from './automation';
5+
import { TIMEOUT_MS } from './setup';
6+
7+
// can directly import extension if needed
8+
// import * as myExtension from '../../extension';
9+
10+
const UNFORMATTED = `class Foo; def bar; puts 'baz'; end; end`;
11+
12+
const FORMATTED = `class Foo
13+
def bar
14+
puts "baz"
15+
end
16+
end
17+
`;
18+
19+
suite('Syntax Tree', () => {
20+
suite('commands', () => {
21+
beforeEach(async () => {
22+
await auto.stop();
23+
await auto.closeAll();
24+
});
25+
26+
test('showOutputChannel', async () => {
27+
const text = await auto.showOutputChannel();
28+
assert.match(text, /^Starting language server/m);
29+
})
30+
31+
test('start', async () => {
32+
await auto.start();
33+
const text = await auto.showOutputChannel();
34+
// HACK: with no way to clear the channel, this is a flakey assertion
35+
assert.match(text, /^Starting language server/m);
36+
});
37+
38+
test('stop', async () => {
39+
await auto.start();
40+
await auto.stop();
41+
const text = await auto.showOutputChannel();
42+
// HACK: with no way to clear the channel, this is a flakey assertion
43+
assert.match(text, /^Stopping language server/m);
44+
})
45+
});
46+
47+
// TODO: figure out re-entrancy issue in CI (prolly two LanguageClient instances at once?)
48+
test.skip('Format Document', async () => {
49+
await auto.closeAll();
50+
const editor = await auto.createEditor(UNFORMATTED);
51+
await auto.formatDocument();
52+
assert.equal(editor.document.getText(), FORMATTED);
53+
});
54+
});

src/test/suite/index.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import * as path from 'path';
2+
import * as Mocha from 'mocha';
3+
import * as glob from 'glob';
4+
5+
import { TIMEOUT_MS } from './setup';
6+
7+
export function run(): Promise<void> {
8+
const mocha = new Mocha({
9+
asyncOnly: true,
10+
color: true,
11+
forbidOnly: !!process.env.CI,
12+
slow: TIMEOUT_MS / 4,
13+
timeout: TIMEOUT_MS,
14+
ui: 'tdd'
15+
});
16+
17+
const testsRoot = path.resolve(__dirname, '..');
18+
19+
return new Promise((c, e) => {
20+
glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
21+
if (err) {
22+
return e(err);
23+
}
24+
25+
// Add files to the test suite
26+
files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
27+
28+
try {
29+
// Run the mocha test
30+
mocha.run(failures => {
31+
if (failures > 0) {
32+
// Let the cameras roll for a bit & make sure we capture the error
33+
if (process.env.CI) {
34+
setTimeout(() => e(new Error(`${failures} tests failed; pausing for dramatic effect.`)), 3000);
35+
} else {
36+
e(new Error(`${failures} tests failed.`));
37+
}
38+
} else {
39+
c();
40+
}
41+
});
42+
} catch (err) {
43+
console.error(err);
44+
e(err);
45+
}
46+
});
47+
});
48+
}

src/test/suite/setup.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as fs from 'fs';
2+
import * as os from 'os';
3+
import * as path from 'path';
4+
5+
// Share scratch dir between runner and Code process via clever env trick
6+
const inheritedScratchDir = process.env.RUBY_SYNTAX_TREE_TEST_SCRATCH_DIR;
7+
const SCRATCH_DIR = inheritedScratchDir || fs.mkdtempSync(`${os.tmpdir()}${path.sep}vscode-syntax-tree-`);
8+
process.env.RUBY_SYNTAX_TREE_TEST_SCRATCH_DIR = SCRATCH_DIR;
9+
10+
/// How long to wait for each test case before marking it as a failure.
11+
/// Outside of CI, give the dev 5 minutes to play with debuggers, etc!
12+
export const TIMEOUT_MS = process.env.CI ? 30000 : 300000;
13+
14+
/// Holds profile, settings, etc - to give us a clean slate every time
15+
/// & avoid polluting developer's real profile
16+
export const USER_DATA_DIR = path.join(SCRATCH_DIR, 'user-data');
17+
18+
/// Holds text documents that we author during tests.
19+
export const WORKSPACE_DIR = path.join(SCRATCH_DIR, 'workspace');
20+
21+
if (!inheritedScratchDir) { // We're the parent; adulting is hard!
22+
fs.mkdirSync(USER_DATA_DIR);
23+
fs.mkdirSync(WORKSPACE_DIR);
24+
console.log('Scratch folder:', SCRATCH_DIR);
25+
}

0 commit comments

Comments
 (0)