Skip to content

Commit 444aca3

Browse files
author
Dave Bartolomeo
committed
Implement QL Test support (using odasa for now)
The PR contains the initial implementing of QL Test support in CodeQL for Visual Studio Code. Because QL Test support isn't quite ready in the CLI yet, this PR uses `odasa` to run the tests for now. As CLI support comes online, it should be straightforward to swap out the implementation to use the CLI. The treeview UI for the tests is implemented via the `hbenl.vscode-test-explorer` extension. This extension is open source, and appears to be actively maintained. It's used by a couple dozen existing extensions for tests for various languages. The extension doesn't really do anything on its own, so taking it as a dependency isn't introducing any unwanted UI clutter. Note that I did have to remove the `--disable-extensions` argument from `launch.json`, because otherwise the test explorer extension gets disabled, preventing our own extension from loading. The UI will display a root node for each QL pack that contains tests, with the actual test directories and files as descendants of that root node. We consider only those QL packs in the workspace; QL packs on the default CodeQL search path are ignored. We use `codeql resolve qlpacks` to find the packs, and then watch all `qlpack.yml` files in the workspace for changes in order to refresh the pack discovery when necessary. Ideally, we'd have the CLI return a set of path patterns to watch, but for now the current implementation works fine. To discover the tests within a given pack, we walk the pack's directory tree manually for now, until the relevant CLI command is available. Because we do not yet have a mechanism in `qlpack.yml` to specify whether or not the pack contains tests, we assume that any pack whose name ends with "-tests" to contain nothing but tests, and any other pack to contain no tests. This is sufficient for the tests in the QL repo. As with QL pack discovery, we watch the file system for changes in `.ql` and `.qlref` files in order to refresh the tree of tests if anything changes. To actually run the tests, we just invoke `odasa qltest` with the appropriate arguments. This code is pretty much a straight copy-and-paste from the repo where I've had a private version of QL Test support for several months. Once we can run tests via the CLI, this will all be deleted. The `test-ui.ts` file implements a couple of additional commands for the context menu of the test treeview. You can accept the output of a failing test (copying the `.actual` file to the `.expected` file), and you can bring up a diff view of the `.expected` and `.actual` files). This PR includes a couple of related utility classes. `UIService` makes it a little easier to implement a service that handles VS Code commands. `Discovery` is a base class that handles most of the work that is shared between the different kinds of discovery that we do, like avoiding running multiple discovery operations simultaneously if we get a storm of file change notifications.
1 parent 4287f4d commit 444aca3

15 files changed

Lines changed: 1161 additions & 21 deletions

File tree

.vscode/launch.json

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
"request": "launch",
99
"runtimeExecutable": "${execPath}",
1010
"args": [
11-
"--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql",
12-
"--disable-extensions"
11+
"--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql"
1312
],
1413
"stopOnEntry": false,
1514
"sourceMaps": true,
@@ -29,8 +28,7 @@
2928
"runtimeExecutable": "${execPath}",
3029
"args": [
3130
"--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql",
32-
"--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/test",
33-
"--disable-extensions"
31+
"--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/test"
3432
],
3533
"stopOnEntry": false,
3634
"sourceMaps": true,
@@ -51,8 +49,7 @@
5149
"runtimeExecutable": "${execPath}",
5250
"args": [
5351
"--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql",
54-
"--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/no-workspace/index",
55-
"--disable-extensions"
52+
"--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/no-workspace/index"
5653
],
5754
"stopOnEntry": false,
5855
"sourceMaps": true,
@@ -70,7 +67,7 @@
7067
"args": [
7168
"--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql",
7269
"--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/minimal-workspace/index",
73-
"${workspaceRoot}/extensions/ql-vscode/test/data",
70+
"${workspaceRoot}/extensions/ql-vscode/test/data"
7471
],
7572
"stopOnEntry": false,
7673
"sourceMaps": true,

common/config/rush/shrinkwrap.yaml

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ dependencies:
6767
vscode-jsonrpc: 4.0.0
6868
vscode-languageclient: 5.2.1
6969
vscode-test: 1.2.0
70+
vscode-test-adapter-api: 1.7.0
71+
vscode-test-adapter-util: 0.7.0
7072
webpack: 4.39.1
7173
webpack-cli: 3.3.6
7274
packages:
@@ -6122,6 +6124,21 @@ packages:
61226124
dev: false
61236125
resolution:
61246126
integrity: sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A==
6127+
/vscode-test-adapter-api/1.7.0:
6128+
dev: false
6129+
engines:
6130+
vscode: ^1.23.0
6131+
resolution:
6132+
integrity: sha512-X0rTcoDhDBmpmJuev2C5+GHGZD41nmcRYoSe7iw5e9/aIPTOFve1T1F5x9gb+zXoNQnkXSDibyMkeHDKtIkqCg==
6133+
/vscode-test-adapter-util/0.7.0:
6134+
dependencies:
6135+
tslib: 1.10.0
6136+
vscode-test-adapter-api: 1.7.0
6137+
dev: false
6138+
engines:
6139+
vscode: ^1.24.0
6140+
resolution:
6141+
integrity: sha512-eAsB8koXct5JytvUcV62wLEBCQfsoclauzMLEFT6H0qBr1h8LyRc+dGDcs48pO28yFOo6VV+5AwCRLxTKh7TzQ==
61256142
/vscode-test/1.2.0:
61266143
dependencies:
61276144
http-proxy-agent: 2.1.0
@@ -6433,7 +6450,7 @@ packages:
64336450
child-process-promise: 2.2.1
64346451
fs-extra: 8.1.0
64356452
glob: 7.1.4
6436-
glob-promise: 3.4.0
6453+
glob-promise: /glob-promise/3.4.0/glob@7.1.4
64376454
gulp: 4.0.2
64386455
gulp-sourcemaps: 2.6.5
64396456
gulp-typescript: /gulp-typescript/5.0.1/typescript@3.7.2
@@ -6449,7 +6466,7 @@ packages:
64496466
dev: false
64506467
name: '@rush-temp/build-tasks'
64516468
resolution:
6452-
integrity: sha512-ta2kXnX7phnKrO7rxdJl5A9Vtd8B4RDyoae3vhdI1d+COeITaXDd9xdPxo8lvduPSJTw2+HnzOgOu2pMAKSjTw==
6469+
integrity: sha512-WBog5Gepo348LYeu7FAZMORJpgx0CNFM+IjgLcKNFYnN9YOCRQogUkRL7ZmBt8WXoew1S5OyVltmEM8XPy8i+w==
64536470
tarball: 'file:projects/build-tasks.tgz'
64546471
version: 0.0.0
64556472
'file:projects/semmle-bqrs.tgz':
@@ -6461,7 +6478,7 @@ packages:
64616478
dev: false
64626479
name: '@rush-temp/semmle-bqrs'
64636480
resolution:
6464-
integrity: sha512-24GdnvMbGfQIWMfgDhift+kYJDnG7dX03NrpX4ajZ2rckteysvq2/K7XI1OXGvUuqrt3m0/+GRDHpSI9XKDJJA==
6481+
integrity: sha512-ufPu8zLXf9JvPCRycWLiFTDb5rZ7bqxQZuiFjy1DAxnatEG5VJITPSXwDFVc11qpjJpaFd4hI+4QtOda7d5zww==
64656482
tarball: 'file:projects/semmle-bqrs.tgz'
64666483
version: 0.0.0
64676484
'file:projects/semmle-io-node.tgz':
@@ -6473,7 +6490,7 @@ packages:
64736490
dev: false
64746491
name: '@rush-temp/semmle-io-node'
64756492
resolution:
6476-
integrity: sha512-Bj0ax/bASrHV7tamOuXZZdd3UOB4NBKdjdszIRaDvDRTu8RlEst+TVoUhkfy30qb2/6ePp3/juOJyyiBJN7u8Q==
6493+
integrity: sha512-jB3C3WWEI991Kr3knPKUwqqNi040WmYCubLJJG7AK1nz3V1YjmyLHIAdjqwOgDNXYKCQPC6tlaEgljbs2Q/kIQ==
64776494
tarball: 'file:projects/semmle-io-node.tgz'
64786495
version: 0.0.0
64796496
'file:projects/semmle-io.tgz':
@@ -6484,7 +6501,7 @@ packages:
64846501
dev: false
64856502
name: '@rush-temp/semmle-io'
64866503
resolution:
6487-
integrity: sha512-NtyviDSevxbd+hj4J66LucOzo8LU2hJ1Jh0eHw0Qu3tRZPUT8HcQlseyy29AvZR8n8eppfEZiAm/JdiHfmRPMA==
6504+
integrity: sha512-6DFvjDclWTihDToSf31Hh+wQNhLGkA37l4QajeW/w6gS4NHjSrFL1qBlS7dLUY80VC/8nQJH9foe3r6dfEfQYw==
64886505
tarball: 'file:projects/semmle-io.tgz'
64896506
version: 0.0.0
64906507
'file:projects/semmle-vscode-utils.tgz':
@@ -6496,19 +6513,20 @@ packages:
64966513
dev: false
64976514
name: '@rush-temp/semmle-vscode-utils'
64986515
resolution:
6499-
integrity: sha512-5y5r8SDoN9Fp44naC9gUe8rOexeckXg2T0h9QCJAIcEgnFqOxzRc6Rv9gbMUStFKNh+rFlvmYmgPAdg5QkfgUg==
6516+
integrity: sha512-yE5S1wsnrsJ8lTt9O9ALedlvH37M9sWQha7sL5iQ3P6dn2KsyUItGsJDnFoh2f0wy3TpZuj3p/KTEDBbgjXBGg==
65006517
tarball: 'file:projects/semmle-vscode-utils.tgz'
65016518
version: 0.0.0
65026519
'file:projects/typescript-config.tgz':
65036520
dev: false
65046521
name: '@rush-temp/typescript-config'
65056522
resolution:
6506-
integrity: sha512-XuUIySaNoooIduvehnlKYaHqZJmmQoCqB1RtKhNszjCYZaSSJAnKVucViWBf5oNLKSNP7NchrD7gcoBlQ3xYvw==
6523+
integrity: sha512-kSFyvKy63jUHFVXQEzALiYfsTdn7J+Y7PcqtUVo9GndU5b5Xh3rBpVbZD1QN8+y8GfT0m/sdZZQVyH0h+On11Q==
65076524
tarball: 'file:projects/typescript-config.tgz'
65086525
version: 0.0.0
65096526
'file:projects/vscode-codeql.tgz':
65106527
dependencies:
65116528
'@types/chai': 4.1.7
6529+
'@types/child-process-promise': 2.2.1
65126530
'@types/classnames': 2.2.9
65136531
'@types/fs-extra': 8.0.0
65146532
'@types/glob': 7.1.1
@@ -6559,13 +6577,15 @@ packages:
65596577
vscode-jsonrpc: 4.0.0
65606578
vscode-languageclient: 5.2.1
65616579
vscode-test: 1.2.0
6580+
vscode-test-adapter-api: 1.7.0
6581+
vscode-test-adapter-util: 0.7.0
65626582
webpack: 4.39.1
65636583
webpack-cli: /webpack-cli/3.3.6/webpack@4.39.1
65646584
xml2js: 0.4.19
65656585
dev: false
65666586
name: '@rush-temp/vscode-codeql'
65676587
resolution:
6568-
integrity: sha512-DE97bdxda65gVLZne73QzBpj2hyCbyzvQiRZxrJqDP1rkF62EGNohBSmlEQs8H2Jp8hxh5RhPhm/yUx70G7KEA==
6588+
integrity: sha512-1zj1CO19sanU2olFV3Kwf9OrG+92KO25WR6K1vnVHmScMj8H7aegRrjwyNeHi/IzxXDCAp68CZK+d4vEWBEa+g==
65696589
tarball: 'file:projects/vscode-codeql.tgz'
65706590
version: 0.0.0
65716591
registry: 'https://registry.npmjs.org/'
@@ -6640,5 +6660,7 @@ specifiers:
66406660
vscode-jsonrpc: ^4.0.0
66416661
vscode-languageclient: ^5.2.1
66426662
vscode-test: ^1.0.0
6663+
vscode-test-adapter-api: ~1.7.0
6664+
vscode-test-adapter-util: ~0.7.0
66436665
webpack: ^4.38.0
66446666
webpack-cli: ^3.3.2

extensions/ql-vscode/package.json

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
"categories": [
1919
"Programming Languages"
2020
],
21+
"extensionDependencies": [
22+
"hbenl.vscode-test-explorer"
23+
],
2124
"activationEvents": [
2225
"onLanguage:ql",
2326
"onView:codeQLDatabases",
@@ -95,14 +98,20 @@
9598
"description": "Number of threads for running queries."
9699
},
97100
"codeQL.runningQueries.timeout": {
98-
"type": ["integer", "null"],
101+
"type": [
102+
"integer",
103+
"null"
104+
],
99105
"default": null,
100106
"minimum": 0,
101107
"maximum": 2147483647,
102108
"description": "Timeout (in seconds) for running queries. Leave blank or set to zero for no timeout."
103109
},
104110
"codeQL.runningQueries.memory": {
105-
"type": ["integer", "null"],
111+
"type": [
112+
"integer",
113+
"null"
114+
],
106115
"default": null,
107116
"minimum": 1024,
108117
"description": "Memory (in MB) to use for running queries. Leave blank for CodeQL to choose a suitable value based on your system's available memory."
@@ -111,6 +120,22 @@
111120
"type": "boolean",
112121
"default": false,
113122
"description": "Enable debug logging and tuple counting when running CodeQL queries. This information is useful for debugging query performance."
123+
},
124+
"codeQL.tests.odasaDistributionPath": {
125+
"scope": "window",
126+
"type": "string",
127+
"default": "",
128+
"description": "Location of the ODASA distribution"
129+
},
130+
"codeQL.tests.odasaLicensePath": {
131+
"scope": "window",
132+
"type": "string",
133+
"description": "Location of the directory containing the ODASA license file"
134+
},
135+
"codeQL.tests.numberOfThreads": {
136+
"scope": "window",
137+
"type": "integer",
138+
"description": "Number of threads to use for CodeQL test execution"
114139
}
115140
}
116141
},
@@ -170,6 +195,14 @@
170195
{
171196
"command": "codeQLQueryHistory.itemClicked",
172197
"title": "Query History Item"
198+
},
199+
{
200+
"command": "codeQLTests.showOutputDifferences",
201+
"title": "CodeQL: Show Test Output Differences"
202+
},
203+
{
204+
"command": "codeQLTests.acceptOutput",
205+
"title": "CodeQL: Accept Test Output"
173206
}
174207
],
175208
"menus": {
@@ -205,6 +238,16 @@
205238
"command": "codeQLQueryHistory.removeHistoryItem",
206239
"group": "9_qlCommands",
207240
"when": "view == codeQLQueryHistory"
241+
},
242+
{
243+
"command": "codeQLTests.showOutputDifferences",
244+
"group": "qltest@1",
245+
"when": "view == test-explorer && viewItem == testWithSource"
246+
},
247+
{
248+
"command": "codeQLTests.acceptOutput",
249+
"group": "qltest@2",
250+
"when": "view == test-explorer && viewItem == testWithSource"
208251
}
209252
],
210253
"explorer/context": [
@@ -298,6 +341,7 @@
298341
"format": "tsfmt -r"
299342
},
300343
"dependencies": {
344+
"child-process-promise": "^2.2.1",
301345
"classnames": "~2.2.6",
302346
"fs-extra": "^8.1.0",
303347
"glob-promise": "^3.4.0",
@@ -310,10 +354,13 @@
310354
"tmp": "^0.1.0",
311355
"unzipper": "~0.10.5",
312356
"vscode-jsonrpc": "^4.0.0",
313-
"vscode-languageclient": "^5.2.1"
357+
"vscode-languageclient": "^5.2.1",
358+
"vscode-test-adapter-api": "~1.7.0",
359+
"vscode-test-adapter-util": "~0.7.0"
314360
},
315361
"devDependencies": {
316362
"@types/chai": "^4.1.7",
363+
"@types/child-process-promise": "^2.2.1",
317364
"@types/classnames": "~2.2.9",
318365
"@types/fs-extra": "^8.0.0",
319366
"@types/glob": "^7.1.1",
@@ -333,7 +380,6 @@
333380
"@types/xml2js": "~0.4.4",
334381
"build-tasks": "^0.0.1",
335382
"chai": "^4.2.0",
336-
"child-process-promise": "^2.2.1",
337383
"css-loader": "~3.1.0",
338384
"glob": "^7.1.4",
339385
"gulp": "^4.0.2",

extensions/ql-vscode/src/cli.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ export interface SourceInfo {
6767
sourceLocationPrefix: string;
6868
}
6969

70+
/**
71+
* The expected output of `codeql resolve qlpacks`.
72+
*/
73+
export interface ResolvedQLPacks {
74+
[index: string]: string[];
75+
}
76+
7077
/**
7178
* This class manages a cli server started by `codeql execute cli-server` to
7279
* run commands without the overhead of starting a new java
@@ -303,6 +310,21 @@ export class CodeQLCliServer implements Disposable {
303310
return await this.runJsonCodeQlCliCommand<QuerySetup>(['resolve', 'library-path'], subcommandArgs, "Resolving library paths");
304311
}
305312

313+
/**
314+
* Finds all available QL packs.
315+
* @param workspaces The current open workspaces
316+
* @param searchPath Overrides the default QL pack search path
317+
*/
318+
async resolveQLPacks(workspaces: string[], searchPath?: string[]): Promise<ResolvedQLPacks> {
319+
const subcommandArgs = [
320+
'--additional-packs', workspaces.join(path.delimiter)
321+
];
322+
if (searchPath !== undefined) {
323+
subcommandArgs.push('--search-path', searchPath.join(path.delimiter));
324+
}
325+
return await this.runJsonCodeQlCliCommand<ResolvedQLPacks>(['resolve', 'qlpacks'], subcommandArgs, 'Resolving QL packs');
326+
}
327+
306328
/**
307329
* Gets the metadata for a query.
308330
* @param queryPath The path to the query.

0 commit comments

Comments
 (0)