Skip to content

Commit 91c3912

Browse files
authored
Retry tests and remove sleep (#239)
1 parent 4aaa12e commit 91c3912

File tree

5 files changed

+126
-140
lines changed

5 files changed

+126
-140
lines changed

.vscode/launch.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
"name": "Extension Tests",
2424
"type": "extensionHost",
2525
"request": "launch",
26+
"env": {
27+
"CURSORLESS_TEST": "true"
28+
},
2629
"args": [
2730
"--disable-extension",
2831
"asvetliakov.vscode-neovim",

src/editDisplayUtils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ const getPendingEditDecorationTime = () =>
1313
.get<number>("pendingEditDecorationTime")!;
1414

1515
export async function decorationSleep() {
16+
if (process.env.CURSORLESS_TEST != null) {
17+
return;
18+
}
19+
1620
await sleep(getPendingEditDecorationTime());
1721
}
1822

src/extension.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,14 @@ import { TestCase } from "./TestCase";
1313
import { ThatMark } from "./ThatMark";
1414
import { Clipboard } from "./Clipboard";
1515
import { TestCaseRecorder } from "./TestCaseRecorder";
16+
import { getParseTreeApi } from "./getExtensionApi";
1617

1718
export async function activate(context: vscode.ExtensionContext) {
1819
const fontMeasurements = new FontMeasurements(context);
1920
await fontMeasurements.calculate();
2021
const decorations = new Decorations(fontMeasurements);
2122

22-
const parseTreeExtension = vscode.extensions.getExtension("pokey.parse-tree");
23-
24-
if (parseTreeExtension == null) {
25-
throw new Error("Depends on pokey.parse-tree extension");
26-
}
27-
28-
const { getNodeAtLocation } = await parseTreeExtension.activate();
23+
const { getNodeAtLocation } = await getParseTreeApi();
2924

3025
var isActive = vscode.workspace
3126
.getConfiguration("cursorless")

src/getExtensionApi.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as vscode from "vscode";
2+
import { ThatMark } from "./ThatMark";
3+
import NavigationMap from "./NavigationMap";
4+
import { SyntaxNode } from "web-tree-sitter";
5+
6+
export interface CursorlessApi {
7+
thatMark: ThatMark;
8+
sourceMark: ThatMark;
9+
navigationMap: NavigationMap;
10+
addDecorations: () => void;
11+
}
12+
13+
export interface ParseTreeApi {
14+
getNodeAtLocation(location: vscode.Location): SyntaxNode;
15+
loadLanguage: (languageId: string) => Promise<boolean>;
16+
}
17+
18+
export async function getExtensionApi<T>(extensionId: string) {
19+
const extension = vscode.extensions.getExtension(extensionId);
20+
21+
if (extension == null) {
22+
throw new Error(`Could not get ${extensionId} extension`);
23+
}
24+
25+
return (await extension.activate()) as T;
26+
}
27+
28+
export const getCursorlessApi = () =>
29+
getExtensionApi<CursorlessApi>("pokey.cursorless");
30+
31+
export const getParseTreeApi = () =>
32+
getExtensionApi<ParseTreeApi>("pokey.parse-tree");

src/test/suite/recorded.test.ts

Lines changed: 85 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import * as path from "path";
44
import * as yaml from "js-yaml";
55
import * as vscode from "vscode";
66
import { TestCaseFixture } from "../../TestCase";
7-
import { ThatMark } from "../../ThatMark";
87
import NavigationMap from "../../NavigationMap";
98
import * as sinon from "sinon";
109
import { Clipboard } from "../../Clipboard";
@@ -16,6 +15,7 @@ import {
1615
} from "../../toPlainObject";
1716
import { walkFilesSync } from "./walkSync";
1817
import { enableDebugLog } from "../../debug";
18+
import { getCursorlessApi, getParseTreeApi } from "../../getExtensionApi";
1919

2020
function createPosition(position: PositionPlainObject) {
2121
return new vscode.Position(position.line, position.character);
@@ -28,6 +28,8 @@ function createSelection(selection: SelectionPlainObject): vscode.Selection {
2828
}
2929

3030
suite("recorded test cases", async function () {
31+
this.timeout("100s");
32+
this.retries(3);
3133
const directory = path.join(
3234
__dirname,
3335
"../../../src/test/suite/fixtures/recorded"
@@ -39,139 +41,89 @@ suite("recorded test cases", async function () {
3941
sinon.restore();
4042
});
4143

42-
let lastLanguageId: string;
43-
44-
files.forEach(async (file) => {
45-
test(file.split(".")[0], async function () {
46-
this.timeout(100000);
47-
const cursorless = vscode.extensions.getExtension("pokey.cursorless");
48-
49-
if (cursorless == null) {
50-
throw new Error("Could not get cursorless extension");
51-
}
52-
53-
const cursorlessApi: {
54-
thatMark: ThatMark;
55-
sourceMark: ThatMark;
56-
navigationMap: NavigationMap;
57-
addDecorations: () => void;
58-
} = await cursorless.activate();
59-
const buffer = await fsp.readFile(file);
60-
const fixture = yaml.load(buffer.toString()) as TestCaseFixture;
61-
const excludeFields: string[] = [];
62-
63-
await vscode.commands.executeCommand("workbench.action.closeAllEditors");
64-
const document = await vscode.workspace.openTextDocument({
65-
language: fixture.languageId,
66-
content: fixture.initialState.documentContents,
67-
});
68-
const editor = await vscode.window.showTextDocument(document);
69-
70-
// Sleep on changing language is necessary otherwise the tree sitter
71-
// will throw an exception on getNodeAtLocation()
72-
if (lastLanguageId !== document.languageId) {
73-
if (lastLanguageId != null) {
74-
await new Promise((resolve) => setTimeout(resolve, 200));
75-
}
76-
lastLanguageId = document.languageId;
77-
}
78-
79-
editor.selections = fixture.initialState.selections.map(createSelection);
80-
81-
if (fixture.initialState.thatMark) {
82-
const initialThatMark = fixture.initialState.thatMark.map((mark) => ({
83-
selection: createSelection(mark),
84-
editor,
85-
}));
86-
cursorlessApi.thatMark.set(initialThatMark);
87-
}
88-
if (fixture.initialState.sourceMark) {
89-
const initialSourceMark = fixture.initialState.sourceMark.map(
90-
(mark) => ({
91-
selection: createSelection(mark),
92-
editor,
93-
})
94-
);
95-
cursorlessApi.sourceMark.set(initialSourceMark);
96-
}
97-
98-
if (fixture.initialState.clipboard) {
99-
let mockClipboard = fixture.initialState.clipboard;
100-
sinon.replace(Clipboard, "readText", async () => mockClipboard);
101-
sinon.replace(Clipboard, "writeText", async (value: string) => {
102-
mockClipboard = value;
103-
});
104-
} else {
105-
excludeFields.push("clipboard");
106-
}
107-
108-
// Wait for cursorless to set up decorations
109-
cursorlessApi.addDecorations();
110-
111-
// Assert that recorded decorations are present
112-
const assertDecorations = () => {
113-
Object.entries(fixture.marks).forEach(([key, token]) => {
114-
const { color, character } = NavigationMap.splitKey(key);
115-
const currentToken = cursorlessApi.navigationMap.getToken(
116-
color,
117-
character
118-
);
119-
assert(
120-
currentToken != null,
121-
`Mark "${color} ${character}" not found`
122-
);
123-
assert.deepStrictEqual(rangeToPlainObject(currentToken.range), token);
124-
});
125-
};
126-
127-
// Tried three times, sleep 100ms between each
128-
await tryAndRetry(assertDecorations, 3, 100);
129-
130-
const returnValue = await vscode.commands.executeCommand(
131-
"cursorless.command",
132-
fixture.spokenForm,
133-
fixture.command.actionName,
134-
fixture.command.partialTargets,
135-
...fixture.command.extraArgs
136-
);
137-
138-
// TODO Visible ranges are not asserted, see:
139-
// https://github.com/pokey/cursorless-vscode/issues/160
140-
const { visibleRanges, ...resultState } = await takeSnapshot(
141-
cursorlessApi.thatMark,
142-
cursorlessApi.sourceMark,
143-
excludeFields
144-
);
145-
146-
assert.deepStrictEqual(
147-
resultState,
148-
fixture.finalState,
149-
"Unexpected final state"
150-
);
151-
152-
assert.deepStrictEqual(
153-
returnValue,
154-
fixture.returnValue,
155-
"Unexpected return value"
156-
);
157-
});
158-
});
44+
files.forEach((file) => test(file.split(".")[0], () => runTest(file)));
15945
});
16046

161-
async function tryAndRetry(
162-
callback: () => void,
163-
numberOfThries: number,
164-
sleepTime: number
165-
) {
166-
while (true) {
167-
try {
168-
return callback();
169-
} catch (error) {
170-
if (numberOfThries === 0) {
171-
throw error;
172-
}
173-
numberOfThries--;
174-
await new Promise((resolve) => setTimeout(resolve, sleepTime));
175-
}
47+
async function runTest(file: string) {
48+
const buffer = await fsp.readFile(file);
49+
const fixture = yaml.load(buffer.toString()) as TestCaseFixture;
50+
const excludeFields: string[] = [];
51+
52+
const cursorlessApi = await getCursorlessApi();
53+
const parseTreeApi = await getParseTreeApi();
54+
55+
await vscode.commands.executeCommand("workbench.action.closeAllEditors");
56+
const document = await vscode.workspace.openTextDocument({
57+
language: fixture.languageId,
58+
content: fixture.initialState.documentContents,
59+
});
60+
const editor = await vscode.window.showTextDocument(document);
61+
62+
await parseTreeApi.loadLanguage(document.languageId);
63+
64+
editor.selections = fixture.initialState.selections.map(createSelection);
65+
66+
if (fixture.initialState.thatMark) {
67+
const initialThatMark = fixture.initialState.thatMark.map((mark) => ({
68+
selection: createSelection(mark),
69+
editor,
70+
}));
71+
cursorlessApi.thatMark.set(initialThatMark);
72+
}
73+
if (fixture.initialState.sourceMark) {
74+
const initialSourceMark = fixture.initialState.sourceMark.map((mark) => ({
75+
selection: createSelection(mark),
76+
editor,
77+
}));
78+
cursorlessApi.sourceMark.set(initialSourceMark);
79+
}
80+
81+
if (fixture.initialState.clipboard) {
82+
let mockClipboard = fixture.initialState.clipboard;
83+
sinon.replace(Clipboard, "readText", async () => mockClipboard);
84+
sinon.replace(Clipboard, "writeText", async (value: string) => {
85+
mockClipboard = value;
86+
});
87+
} else {
88+
excludeFields.push("clipboard");
17689
}
90+
91+
// Wait for cursorless to set up decorations
92+
cursorlessApi.addDecorations();
93+
94+
// Assert that recorded decorations are present
95+
Object.entries(fixture.marks).forEach(([key, token]) => {
96+
const { color, character } = NavigationMap.splitKey(key);
97+
const currentToken = cursorlessApi.navigationMap.getToken(color, character);
98+
assert(currentToken != null, `Mark "${color} ${character}" not found`);
99+
assert.deepStrictEqual(rangeToPlainObject(currentToken.range), token);
100+
});
101+
102+
const returnValue = await vscode.commands.executeCommand(
103+
"cursorless.command",
104+
fixture.spokenForm,
105+
fixture.command.actionName,
106+
fixture.command.partialTargets,
107+
...fixture.command.extraArgs
108+
);
109+
110+
// TODO Visible ranges are not asserted, see:
111+
// https://github.com/pokey/cursorless-vscode/issues/160
112+
const { visibleRanges, ...resultState } = await takeSnapshot(
113+
cursorlessApi.thatMark,
114+
cursorlessApi.sourceMark,
115+
excludeFields
116+
);
117+
118+
assert.deepStrictEqual(
119+
resultState,
120+
fixture.finalState,
121+
"Unexpected final state"
122+
);
123+
124+
assert.deepStrictEqual(
125+
returnValue,
126+
fixture.returnValue,
127+
"Unexpected return value"
128+
);
177129
}

0 commit comments

Comments
 (0)