Skip to content

Commit a75fb00

Browse files
authored
Fix CI failures by adding sleep backoff (#870)
* Standardize suite setup * Add sleepWithBackoff * Fix deleted token * Tweak * Kick CI * Add warning to current sleep * Improve update fixtures sleep * Typo * Docs * More docs
1 parent 471e074 commit a75fb00

15 files changed

+103
-99
lines changed

src/test/suite/backwardCompatibility.test.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
import * as assert from "assert";
22
import * as vscode from "vscode";
3-
import * as sinon from "sinon";
43
import { getCursorlessApi } from "../../util/getExtensionApi";
54
import { openNewEditor } from "../openNewEditor";
5+
import { standardSuiteSetup } from "./standardSuiteSetup";
66

77
suite("Backward compatibility", async function () {
8-
this.timeout("100s");
9-
this.retries(3);
10-
11-
teardown(() => {
12-
sinon.restore();
13-
});
8+
standardSuiteSetup(this);
149

1510
test("Backward compatibility", runTest);
1611
});

src/test/suite/breakpoints.test.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
import * as assert from "assert";
22
import * as vscode from "vscode";
3-
import * as sinon from "sinon";
43
import { getCursorlessApi } from "../../util/getExtensionApi";
54
import { openNewEditor } from "../openNewEditor";
5+
import { standardSuiteSetup } from "./standardSuiteSetup";
66

77
suite("breakpoints", async function () {
8-
this.timeout("100s");
9-
this.retries(3);
8+
standardSuiteSetup(this);
109

1110
setup(() => {
1211
removeBreakpoints();
1312
});
1413

15-
teardown(() => {
16-
sinon.restore();
17-
});
18-
1914
suiteTeardown(() => {
2015
removeBreakpoints();
2116
});

src/test/suite/crossCellsSetSelection.test.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
import * as assert from "assert";
22
import * as vscode from "vscode";
3-
import * as sinon from "sinon";
43
import { getCursorlessApi } from "../../util/getExtensionApi";
54
import { openNewNotebookEditor } from "../openNewEditor";
6-
import sleep from "../../util/sleep";
5+
import { sleepWithBackoff, standardSuiteSetup } from "./standardSuiteSetup";
76

87
// Check that setSelection is able to focus the correct cell
98
suite("Cross-cell set selection", async function () {
10-
this.timeout("100s");
11-
this.retries(3);
12-
13-
teardown(() => {
14-
sinon.restore();
15-
});
9+
standardSuiteSetup(this);
1610

1711
test("Cross-cell set selection", runTest);
1812
});
@@ -24,7 +18,7 @@ async function runTest() {
2418

2519
// FIXME: There seems to be some timing issue when you create a notebook
2620
// editor
27-
await sleep(1000);
21+
await sleepWithBackoff(1000);
2822

2923
await graph.hatTokenMap.addDecorations();
3024

src/test/suite/editNewCell.test.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
import * as assert from "assert";
22
import * as vscode from "vscode";
3-
import * as sinon from "sinon";
43
import { getCursorlessApi } from "../../util/getExtensionApi";
5-
import { openNewNotebookEditor } from "../openNewEditor";
6-
import sleep from "../../util/sleep";
74
import { getCellIndex } from "../../util/notebook";
5+
import { openNewNotebookEditor } from "../openNewEditor";
86
import { getPlainNotebookContents } from "../util/notebook";
7+
import { sleepWithBackoff, standardSuiteSetup } from "./standardSuiteSetup";
98

109
// Check that setSelection is able to focus the correct cell
1110
suite("Edit new cell", async function () {
12-
this.timeout("100s");
13-
this.retries(3);
14-
15-
teardown(() => {
16-
sinon.restore();
17-
});
11+
standardSuiteSetup(this);
1812

1913
test("drink cell", () =>
2014
runTest("drink cell", "editNewLineBefore", 0, ["", "hello"]));
@@ -33,7 +27,7 @@ async function runTest(
3327

3428
// FIXME: There seems to be some timing issue when you create a notebook
3529
// editor
36-
await sleep(1000);
30+
await sleepWithBackoff(1000);
3731

3832
await graph.hatTokenMap.addDecorations();
3933

src/test/suite/fold.test.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import * as assert from "assert";
22
import * as vscode from "vscode";
3-
import * as sinon from "sinon";
43
import { openNewEditor } from "../openNewEditor";
4+
import { standardSuiteSetup } from "./standardSuiteSetup";
55

66
suite("fold", async function () {
7-
this.timeout("100s");
8-
this.retries(3);
9-
10-
teardown(() => {
11-
sinon.restore();
12-
});
7+
standardSuiteSetup(this);
138

149
test("fold made", foldMade);
1510
test("unfold made", unfoldMade);

src/test/suite/followLink.test.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
import * as assert from "assert";
2-
import * as vscode from "vscode";
3-
import * as sinon from "sinon";
42
import * as os from "os";
3+
import * as vscode from "vscode";
54
import { openNewEditor } from "../openNewEditor";
65
import { getFixturePath } from "../util/getFixturePaths";
6+
import { standardSuiteSetup } from "./standardSuiteSetup";
77

88
suite("followLink", async function () {
9-
this.timeout("100s");
10-
this.retries(3);
11-
12-
teardown(() => {
13-
sinon.restore();
14-
});
9+
standardSuiteSetup(this);
1510

1611
test("follow definition", followDefinition);
1712
test("follow link", followLink);

src/test/suite/groupByDocument.test.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
import * as assert from "assert";
22
import * as vscode from "vscode";
3-
import * as sinon from "sinon";
4-
import { getCursorlessApi } from "../../util/getExtensionApi";
53
import HatTokenMap from "../../core/HatTokenMap";
4+
import { getCursorlessApi } from "../../util/getExtensionApi";
5+
import { standardSuiteSetup } from "./standardSuiteSetup";
66

77
suite("Group by document", async function () {
8-
this.timeout("100s");
9-
this.retries(3);
10-
11-
teardown(() => {
12-
sinon.restore();
13-
});
8+
standardSuiteSetup(this);
149

1510
test("Group by document", runTest);
1611
});

src/test/suite/intraCellSetSelection.test.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
import * as assert from "assert";
22
import * as vscode from "vscode";
3-
import * as sinon from "sinon";
43
import { getCursorlessApi } from "../../util/getExtensionApi";
54
import { openNewNotebookEditor } from "../openNewEditor";
6-
import sleep from "../../util/sleep";
5+
import { sleepWithBackoff, standardSuiteSetup } from "./standardSuiteSetup";
76

87
// Check that setSelection is able to focus the correct cell
98
suite("Within cell set selection", async function () {
10-
this.timeout("100s");
11-
this.retries(3);
12-
13-
teardown(() => {
14-
sinon.restore();
15-
});
9+
standardSuiteSetup(this);
1610

1711
test("Within cell set selection", runTest);
1812
});
@@ -24,7 +18,7 @@ async function runTest() {
2418

2519
// FIXME: There seems to be some timing issue when you create a notebook
2620
// editor
27-
await sleep(1000);
21+
await sleepWithBackoff(1000);
2822

2923
await graph.hatTokenMap.addDecorations();
3024

src/test/suite/prePhraseSnapshot.test.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import * as assert from "assert";
22
import * as vscode from "vscode";
3-
import * as sinon from "sinon";
4-
import { getCursorlessApi } from "../../util/getExtensionApi";
53
import { selectionToPlainObject } from "../../testUtil/toPlainObject";
4+
import { getCursorlessApi } from "../../util/getExtensionApi";
65
import { mockPrePhraseGetVersion } from "../mockPrePhraseGetVersion";
76
import { openNewEditor } from "../openNewEditor";
7+
import { standardSuiteSetup } from "./standardSuiteSetup";
88

99
/**
1010
* The selections we expect when the pre-phrase snapshot is used
@@ -17,12 +17,7 @@ const snapshotExpectedSelections = [new vscode.Selection(0, 6, 0, 11)];
1717
const noSnapshotExpectedSelections = [new vscode.Selection(1, 6, 1, 11)];
1818

1919
suite("Pre-phrase snapshots", async function () {
20-
this.timeout("100s");
21-
this.retries(3);
22-
23-
teardown(() => {
24-
sinon.restore();
25-
});
20+
standardSuiteSetup(this);
2621

2722
test("Pre-phrase snapshot; single phrase", () =>
2823
runTest(true, false, snapshotExpectedSelections));

src/test/suite/recorded.test.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as assert from "assert";
22
import { promises as fsp } from "fs";
33
import * as yaml from "js-yaml";
4-
import * as sinon from "sinon";
54
import * as vscode from "vscode";
65
import HatTokenMap from "../../core/HatTokenMap";
76
import { ReadOnlyHatMap } from "../../core/IndividualHatMap";
@@ -22,10 +21,11 @@ import {
2221
} from "../../testUtil/toPlainObject";
2322
import { Clipboard } from "../../util/Clipboard";
2423
import { getCursorlessApi } from "../../util/getExtensionApi";
25-
import sleep from "../../util/sleep";
2624
import { openNewEditor } from "../openNewEditor";
2725
import asyncSafety from "../util/asyncSafety";
2826
import { getRecordedTestPaths } from "../util/getFixturePaths";
27+
import shouldUpdateFixtures from "./shouldUpdateFixtures";
28+
import { sleepWithBackoff, standardSuiteSetup } from "./standardSuiteSetup";
2929

3030
function createPosition(position: PositionPlainObject) {
3131
return new vscode.Position(position.line, position.character);
@@ -38,12 +38,7 @@ function createSelection(selection: SelectionPlainObject): vscode.Selection {
3838
}
3939

4040
suite("recorded test cases", async function () {
41-
this.timeout("100s");
42-
this.retries(5);
43-
44-
teardown(() => {
45-
sinon.restore();
46-
});
41+
standardSuiteSetup(this);
4742

4843
suiteSetup(async () => {
4944
// Necessary because opening a notebook opens the panel for some reason
@@ -77,7 +72,7 @@ async function runTest(file: string) {
7772
);
7873

7974
if (fixture.postEditorOpenSleepTimeMs != null) {
80-
await sleep(fixture.postEditorOpenSleepTimeMs);
75+
await sleepWithBackoff(fixture.postEditorOpenSleepTimeMs);
8176
}
8277

8378
editor.selections = fixture.initialState.selections.map(createSelection);
@@ -138,7 +133,7 @@ async function runTest(file: string) {
138133
);
139134

140135
if (fixture.postCommandSleepTimeMs != null) {
141-
await sleep(fixture.postCommandSleepTimeMs);
136+
await sleepWithBackoff(fixture.postCommandSleepTimeMs);
142137
}
143138

144139
const marks =
@@ -170,7 +165,7 @@ async function runTest(file: string) {
170165
? undefined
171166
: testDecorationsToPlainObject(graph.editStyles.testDecorations);
172167

173-
if (process.env.CURSORLESS_TEST_UPDATE_FIXTURES === "true") {
168+
if (shouldUpdateFixtures()) {
174169
const outputFixture = {
175170
...fixture,
176171
finalState: resultState,

src/test/suite/scroll.test.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import * as assert from "assert";
22
import * as vscode from "vscode";
3-
import * as sinon from "sinon";
43
import { openNewEditor } from "../openNewEditor";
4+
import { standardSuiteSetup } from "./standardSuiteSetup";
55

66
suite("scroll", async function () {
7-
this.timeout("100s");
8-
this.retries(3);
9-
10-
teardown(() => {
11-
sinon.restore();
12-
});
7+
standardSuiteSetup(this);
138

149
test("top whale", topWhale);
1510
test("bottom whale", bottomWhale);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Used to check weather the update fixtures launch config was used.
3+
* @returns `true` if developer used to the update fixtures launch config
4+
*/
5+
export default function shouldUpdateFixtures() {
6+
return process.env.CURSORLESS_TEST_UPDATE_FIXTURES === "true";
7+
}

src/test/suite/standardSuiteSetup.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Context } from "mocha";
2+
import * as sinon from "sinon";
3+
import sleep from "../../util/sleep";
4+
import shouldUpdateFixtures from "./shouldUpdateFixtures";
5+
6+
/**
7+
* The number of times the current test has been retried. Will be 0 the first
8+
* time the test runs and increase by 1 each time the test fails and needs to be
9+
* rerun.
10+
*/
11+
let retryCount = -1;
12+
13+
/**
14+
* The title of the previously run test. Used to keep track of
15+
* {@link retryCount}.
16+
*/
17+
let previousTestTitle = "";
18+
19+
export function standardSuiteSetup(suite: Mocha.Suite) {
20+
suite.timeout("100s");
21+
suite.retries(5);
22+
23+
teardown(() => {
24+
sinon.restore();
25+
});
26+
27+
setup(async function (this: Context) {
28+
const title = this.test!.fullTitle();
29+
retryCount = title === previousTestTitle ? retryCount + 1 : 0;
30+
previousTestTitle = title;
31+
});
32+
}
33+
34+
/**
35+
* Sleep function for use in tests that will be retried. Doubles the amount of
36+
* time it sleeps each time a test is run, starting from {@link ms} / 4.
37+
*
38+
* If the developer used the update fixtures launch config, we sleep for {@link ms} *
39+
* 2 every time so that they don't get spurious updates to fixtures due to not
40+
* sleeping enough.
41+
* @param ms The baseline number of milliseconds to sleep.
42+
* @returns A promise that will resolve when the sleep is over
43+
*/
44+
export function sleepWithBackoff(ms: number) {
45+
const timeToSleep = shouldUpdateFixtures()
46+
? ms * 2
47+
: ms * Math.pow(2, retryCount - 2);
48+
49+
return sleep(timeToSleep);
50+
}

src/test/suite/toggleDecorations.test.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
import * as assert from "assert";
22
import * as vscode from "vscode";
3-
import * as sinon from "sinon";
43
import { getCursorlessApi } from "../../util/getExtensionApi";
54
import { openNewEditor } from "../openNewEditor";
5+
import { standardSuiteSetup } from "./standardSuiteSetup";
66

77
suite("toggle decorations", async function () {
8-
this.timeout("100s");
9-
this.retries(3);
10-
11-
teardown(() => {
12-
sinon.restore();
13-
});
8+
standardSuiteSetup(this);
149

1510
test("toggle decorations", () => runTest());
1611
});

src/util/sleep.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
11
import { promisify } from "util";
22

3-
export default promisify(setTimeout);
3+
/**
4+
* Sleep function that returns a promise that resolves when the sleep is
5+
* complete.
6+
*
7+
* WARNING: Please do not use this function in tests, because we retry our tests
8+
* on failure, and this function will sleep the same amount of time every time
9+
* the test is retried. Prefer {@link sleepWithBackoff} instead.
10+
*/
11+
const sleep = promisify(setTimeout);
12+
13+
export default sleep;

0 commit comments

Comments
 (0)