Skip to content

Commit 1bffae5

Browse files
authoredJan 7, 2025··
Added performance tests (#2697)
The tests should pass once #2700 is merged This is the result from running it locally on my desktop. ``` Performance: 10202 lines JSON Remove token 24 ms √ Remove token (108ms) Select character 14 ms √ Select character (122ms) Select word 10 ms √ Select word (113ms) Select token 8 ms √ Select token (110ms) Select identifier 7 ms √ Select identifier (115ms) Select line 40 ms √ Select line (141ms) Select sentence 25 ms √ Select sentence (126ms) Select paragraph 5 ms √ Select paragraph (104ms) Select document 8 ms √ Select document (115ms) Select nonWhitespaceSequence 8 ms √ Select nonWhitespaceSequence (96ms) Select string 247 ms √ Select string (353ms) Select map 167 ms √ Select map (248ms) Select collectionKey 135 ms √ Select collectionKey (223ms) Select value 142 ms √ Select value (222ms) Select boundedParagraph 11822 ms √ Select boundedParagraph (11906ms) Select boundedNonWhitespaceSequence 13749 ms √ Select boundedNonWhitespaceSequence (13837ms) Select surroundingPair.any 20893 ms √ Select surroundingPair.any (21039ms) Select surroundingPair.curlyBrackets 365 ms √ Select surroundingPair.curlyBrackets (483ms) 18 passing (50s) ``` ## Checklist - [x] I have added [tests](https://www.cursorless.org/docs/contributing/test-case-recorder/) - [-] I have updated the [docs](https://github.com/cursorless-dev/cursorless/tree/main/docs) and [cheatsheet](https://github.com/cursorless-dev/cursorless/tree/main/cursorless-talon/src/cheatsheet) - [-] I have not broken the cheatsheet
1 parent d113d88 commit 1bffae5

File tree

1 file changed

+150
-0
lines changed

1 file changed

+150
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import {
2+
asyncSafety,
3+
type ActionDescriptor,
4+
type ScopeType,
5+
type SimpleScopeTypeType,
6+
} from "@cursorless/common";
7+
import { openNewEditor, runCursorlessCommand } from "@cursorless/vscode-common";
8+
import assert from "assert";
9+
import * as vscode from "vscode";
10+
import { endToEndTestSetup } from "../endToEndTestSetup";
11+
12+
const testData = generateTestData(100);
13+
14+
const textBasedThresholdMs = 100;
15+
const parseTreeThresholdMs = 500;
16+
const surroundingPairThresholdMs = 500;
17+
18+
suite("Performance", async function () {
19+
endToEndTestSetup(this);
20+
21+
let previousTitle = "";
22+
23+
// Before each test, print the test title. This is done we have the test
24+
// title before the test run time / duration.
25+
this.beforeEach(function () {
26+
const title = this.currentTest!.title;
27+
if (title !== previousTitle) {
28+
console.log(` ${title}`);
29+
previousTitle = title;
30+
}
31+
});
32+
33+
test(
34+
"Remove token",
35+
asyncSafety(() => removeToken(textBasedThresholdMs)),
36+
);
37+
38+
const fixtures: [SimpleScopeTypeType | ScopeType, number][] = [
39+
// Text based
40+
["character", textBasedThresholdMs],
41+
["word", textBasedThresholdMs],
42+
["token", textBasedThresholdMs],
43+
["identifier", textBasedThresholdMs],
44+
["line", textBasedThresholdMs],
45+
["sentence", textBasedThresholdMs],
46+
["paragraph", textBasedThresholdMs],
47+
["document", textBasedThresholdMs],
48+
["nonWhitespaceSequence", textBasedThresholdMs],
49+
// Parse tree based
50+
["string", parseTreeThresholdMs],
51+
["map", parseTreeThresholdMs],
52+
["collectionKey", parseTreeThresholdMs],
53+
["value", parseTreeThresholdMs],
54+
// Text based, but utilizes surrounding pair
55+
["boundedParagraph", surroundingPairThresholdMs],
56+
["boundedNonWhitespaceSequence", surroundingPairThresholdMs],
57+
["collectionItem", surroundingPairThresholdMs],
58+
// Surrounding pair
59+
[{ type: "surroundingPair", delimiter: "any" }, surroundingPairThresholdMs],
60+
[
61+
{ type: "surroundingPair", delimiter: "curlyBrackets" },
62+
surroundingPairThresholdMs,
63+
],
64+
];
65+
66+
for (const [scope, threshold] of fixtures) {
67+
const [scopeType, title] = getScopeTypeAndTitle(scope);
68+
test(
69+
`Select ${title}`,
70+
asyncSafety(() => selectScopeType(scopeType, threshold)),
71+
);
72+
}
73+
});
74+
75+
async function removeToken(thresholdMs: number) {
76+
await testPerformance(thresholdMs, {
77+
name: "remove",
78+
target: {
79+
type: "primitive",
80+
modifiers: [{ type: "containingScope", scopeType: { type: "token" } }],
81+
},
82+
});
83+
}
84+
85+
async function selectScopeType(scopeType: ScopeType, thresholdMs: number) {
86+
await testPerformance(thresholdMs, {
87+
name: "setSelection",
88+
target: {
89+
type: "primitive",
90+
modifiers: [{ type: "containingScope", scopeType }],
91+
},
92+
});
93+
}
94+
95+
async function testPerformance(thresholdMs: number, action: ActionDescriptor) {
96+
const editor = await openNewEditor(testData, { languageId: "json" });
97+
// This is the position of the last json key in the document
98+
const position = new vscode.Position(editor.document.lineCount - 3, 5);
99+
const selection = new vscode.Selection(position, position);
100+
editor.selections = [selection];
101+
editor.revealRange(selection);
102+
103+
const start = performance.now();
104+
105+
await runCursorlessCommand({
106+
version: 7,
107+
usePrePhraseSnapshot: false,
108+
action,
109+
});
110+
111+
const duration = Math.round(performance.now() - start);
112+
113+
console.log(` ${duration} ms`);
114+
115+
assert.ok(
116+
duration < thresholdMs,
117+
`Duration ${duration}ms exceeds threshold ${thresholdMs}ms`,
118+
);
119+
}
120+
121+
function getScopeTypeAndTitle(
122+
scope: SimpleScopeTypeType | ScopeType,
123+
): [ScopeType, string] {
124+
if (typeof scope === "string") {
125+
return [{ type: scope }, scope];
126+
}
127+
switch (scope.type) {
128+
case "surroundingPair":
129+
return [scope, `${scope.type}.${scope.delimiter}`];
130+
}
131+
throw Error(`Unexpected scope type: ${scope.type}`);
132+
}
133+
134+
/**
135+
* Generate a large JSON object with n-keys, each with n-values.
136+
* {
137+
* "0": { "0": "value", ..., "n-1": "value" },
138+
* ...
139+
* "n-1": { "0": "value", ..., "n-1": "value" }
140+
* }
141+
*/
142+
function generateTestData(n: number): string {
143+
const value = Object.fromEntries(
144+
new Array(n).fill("").map((_, i) => [i.toString(), "value"]),
145+
);
146+
const obj = Object.fromEntries(
147+
new Array(n).fill("").map((_, i) => [i.toString(), value]),
148+
);
149+
return JSON.stringify(obj, null, 2);
150+
}

0 commit comments

Comments
 (0)
Please sign in to comment.