Add unit tests for query history and remote queries

Adds some tests for reading in the history and manipulating.
There are some more tests to come later. Maybe in another PR, maybe in
this one.

Note that this PR uses a new node 16 API String.prototype.replaceAll.
I think this is ok since vscode ships with node 16. If this causes
problems, I can separate to a different PR and we can discuss there.
This commit is contained in:
Andrew Eisenberg
2022-02-23 16:47:59 -08:00
parent f59012862e
commit 4a928f1298
39 changed files with 1982 additions and 62 deletions

View File

@@ -55,7 +55,7 @@
"@types/jszip": "~3.1.6",
"@types/mocha": "^9.0.0",
"@types/nanoid": "^3.0.0",
"@types/node": "^12.14.1",
"@types/node": "^16.11.25",
"@types/node-fetch": "~2.5.2",
"@types/proxyquire": "~1.3.28",
"@types/react": "^17.0.2",
@@ -1002,11 +1002,6 @@
"@types/node": "*"
}
},
"node_modules/@types/glob/node_modules/@types/node": {
"version": "14.0.23",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz",
"integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw=="
},
"node_modules/@types/google-protobuf": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.7.2.tgz",
@@ -1220,10 +1215,9 @@
}
},
"node_modules/@types/node": {
"version": "12.19.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.4.tgz",
"integrity": "sha512-o3oj1bETk8kBwzz1WlO6JWL/AfAA3Vm6J1B3C9CsdxHYp7XgPiH7OEXPUbZTndHlRaIElrANkQfe6ZmfJb3H2w==",
"dev": true
"version": "16.11.25",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.25.tgz",
"integrity": "sha512-NrTwfD7L1RTc2qrHQD4RTTy4p0CO2LatKBEKEds3CaVuhoM/+DJzmWZl5f+ikR8cm8F5mfJxK+9rQq07gRiSjQ=="
},
"node_modules/@types/node-fetch": {
"version": "2.5.7",
@@ -1291,12 +1285,6 @@
"@types/node": "*"
}
},
"node_modules/@types/semver/node_modules/@types/node": {
"version": "14.0.23",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz",
"integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw==",
"dev": true
},
"node_modules/@types/sinon": {
"version": "7.5.2",
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.2.tgz",
@@ -13912,13 +13900,6 @@
"requires": {
"@types/minimatch": "*",
"@types/node": "*"
},
"dependencies": {
"@types/node": {
"version": "14.0.23",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz",
"integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw=="
}
}
},
"@types/glob-stream": {
@@ -14109,10 +14090,9 @@
}
},
"@types/node": {
"version": "12.19.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.4.tgz",
"integrity": "sha512-o3oj1bETk8kBwzz1WlO6JWL/AfAA3Vm6J1B3C9CsdxHYp7XgPiH7OEXPUbZTndHlRaIElrANkQfe6ZmfJb3H2w==",
"dev": true
"version": "16.11.25",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.25.tgz",
"integrity": "sha512-NrTwfD7L1RTc2qrHQD4RTTy4p0CO2LatKBEKEds3CaVuhoM/+DJzmWZl5f+ikR8cm8F5mfJxK+9rQq07gRiSjQ=="
},
"@types/node-fetch": {
"version": "2.5.7",
@@ -14178,14 +14158,6 @@
"dev": true,
"requires": {
"@types/node": "*"
},
"dependencies": {
"@types/node": {
"version": "14.0.23",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz",
"integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw==",
"dev": true
}
}
},
"@types/sinon": {

View File

@@ -1064,7 +1064,7 @@
"@types/jszip": "~3.1.6",
"@types/mocha": "^9.0.0",
"@types/nanoid": "^3.0.0",
"@types/node": "^12.14.1",
"@types/node": "^16.11.25",
"@types/node-fetch": "~2.5.2",
"@types/proxyquire": "~1.3.28",
"@types/react": "^17.0.2",

View File

@@ -1,11 +0,0 @@
/**
* The npm library jszip is designed to work in both the browser and
* node. Consequently its typings @types/jszip refers to both node
* types like `Buffer` (which don't exist in the browser), and browser
* types like `Blob` (which don't exist in node). Instead of sticking
* all of `dom` in `compilerOptions.lib`, it suffices just to put in a
* stub definition of the type `Blob` here so that compilation
* succeeds.
*/
declare type Blob = string;

View File

@@ -403,7 +403,7 @@ export class CodeQLCliServer implements Disposable {
try {
if (cancellationToken !== undefined) {
cancellationRegistration = cancellationToken.onCancellationRequested(_e => {
tk(child.pid);
tk(child.pid || 0);
});
}
if (logger !== undefined) {

View File

@@ -83,3 +83,17 @@ export abstract class DisposableObject implements Disposable {
}
}
}
/**
* A simple disposable object that does nothing other than contain a list of disposable objects.
* This is useful for implementing a `Disposable` that owns other disposable objects.
*/
export class DisposableBucket extends DisposableObject {
/**
* Add a disposable object to this bucket.
* @param obj The object to add.
*/
public push<T extends Disposable>(obj: T): T {
return super.push(obj);
}
}

View File

@@ -574,7 +574,7 @@ export class QueryHistoryManager extends DisposableObject {
const current = this.treeDataProvider.getCurrent();
if (current !== undefined) {
await this.treeView.reveal(current, { select: true });
await this._onWillOpenQueryItem.fire(current);
this._onWillOpenQueryItem.fire(current);
}
}
@@ -655,7 +655,7 @@ export class QueryHistoryManager extends DisposableObject {
async handleItemClicked(
singleItem: QueryHistoryInfo,
multiSelect: QueryHistoryInfo[]
multiSelect: QueryHistoryInfo[] = []
) {
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
@@ -676,8 +676,10 @@ export class QueryHistoryManager extends DisposableObject {
// show original query file on double click
await this.handleOpenQuery(finalSingleItem, [finalSingleItem]);
} else {
// show results on single click
await this._onWillOpenQueryItem.fire(finalSingleItem);
// show results on single click only if query is completed successfully.
if (finalSingleItem.status === QueryStatus.Completed) {
await this._onWillOpenQueryItem.fire(finalSingleItem);
}
}
}
@@ -718,7 +720,7 @@ export class QueryHistoryManager extends DisposableObject {
async handleShowQueryText(
singleItem: QueryHistoryInfo,
multiSelect: QueryHistoryInfo[]
multiSelect: QueryHistoryInfo[] = []
) {
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);

View File

@@ -234,7 +234,7 @@ export class QueryServerClient extends DisposableObject {
}
get serverProcessPid(): number {
return this.serverProcess!.child.pid;
return this.serverProcess!.child.pid || 0;
}
async sendRequest<P, R, E, RO>(type: RequestType<WithProgressId<P>, R, E, RO>, parameter: P, token?: CancellationToken, progress?: (res: ProgressMessage) => void): Promise<R> {

View File

@@ -106,7 +106,8 @@ export class AnalysesResultsManager {
const resultsForQuery = this.internalGetAnalysesResults(queryId);
resultsForQuery.push(analysisResults);
this.analysesResults.set(queryId, resultsForQuery);
void publishResults(resultsForQuery);
void publishResults([...resultsForQuery]);
const pos = resultsForQuery.length - 1;
let artifactPath;
try {
@@ -116,16 +117,23 @@ export class AnalysesResultsManager {
throw new Error(`Could not download the analysis results for ${analysis.nwo}: ${e.message}`);
}
let newAnaysisResults: AnalysisResults;
if (path.extname(artifactPath) === '.sarif') {
const queryResults = await this.readResults(artifactPath);
analysisResults.results = queryResults;
analysisResults.status = 'Completed';
newAnaysisResults = {
...analysisResults,
results: queryResults,
status: 'Completed'
};
} else {
void this.logger.log('Cannot download results. Only alert and path queries are fully supported.');
analysisResults.status = 'Failed';
newAnaysisResults = {
...analysisResults,
status: 'Failed'
};
}
void publishResults(resultsForQuery);
resultsForQuery[pos] = newAnaysisResults;
void publishResults([...resultsForQuery]);
}
private async readResults(filePath: string): Promise<QueryResult[]> {

View File

@@ -0,0 +1,11 @@
/**
* @name MRVA Integration test 1
* @kind problem
* @problem.severity warning
* @id javascript/integration-test-1
*/
import javascript
from MemberDeclaration md
where md.getName() = "dispose"
select md, "Dispose method"

View File

@@ -0,0 +1,11 @@
/**
* @name MRVA Integration test 2
* @kind problem
* @problem.severity warning
* @id javascript/integration-test-2
*/
import javascript
from MemberDeclaration md
where md.getName() = "refresh"
select md, "Refresh method"

View File

@@ -0,0 +1,16 @@
"md","col1"
"dispose ... ();\n }","Dispose method"
"readonl ... > void;","Dispose method"
"async d ... }\n }","Dispose method"
"dispose(): any;","Dispose method"
"public ... }\n }","Dispose method"
"dispose: () => void;","Dispose method"
"dispose ... ');\n }","Dispose method"
"dispose ... ();\n }","Dispose method"
"public ... ();\n }","Dispose method"
"readonl ... > void;","Dispose method"
"dispose(): unknown","Dispose method"
"dispose ... inonSpy","Dispose method"
"dispose ... inonSpy","Dispose method"
"dispose ... inonSpy","Dispose method"
"dispose ... inonSpy","Dispose method"
1 md col1
2 dispose ... ();\n } Dispose method
3 readonl ... > void; Dispose method
4 async d ... }\n } Dispose method
5 dispose(): any; Dispose method
6 public ... }\n } Dispose method
7 dispose: () => void; Dispose method
8 dispose ... ');\n } Dispose method
9 dispose ... ();\n } Dispose method
10 public ... ();\n } Dispose method
11 readonl ... > void; Dispose method
12 dispose(): unknown Dispose method
13 dispose ... inonSpy Dispose method
14 dispose ... inonSpy Dispose method
15 dispose ... inonSpy Dispose method
16 dispose ... inonSpy Dispose method

View File

@@ -0,0 +1,19 @@
## github/vscode-codeql
| - | Message |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
| [dispose &#46;&#46;&#46; &#40;&#41;;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/cli.ts#L211) | Dispose method |
| [readonl &#46;&#46;&#46; &#62; void;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/extension.ts#L166) | Dispose method |
| [async d &#46;&#46;&#46; &#125;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/logging.ts#L151) | Dispose method |
| [dispose&#40;&#41;: any;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/pure/disposable-object.ts#L5) | Dispose method |
| [public &#46;&#46;&#46; &#125;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/pure/disposable-object.ts#L65) | Dispose method |
| [dispose: &#40;&#41; =&#62; void;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/query-results.ts#L54) | Dispose method |
| [dispose &#46;&#46;&#46; '&#41;;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/queryserver-client.ts#L32) | Dispose method |
| [dispose &#46;&#46;&#46; &#40;&#41;;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/telemetry.ts#L129) | Dispose method |
| [public &#46;&#46;&#46; &#40;&#41;;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/test-ui.ts#L54) | Dispose method |
| [readonl &#46;&#46;&#46; &#62; void;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/run-queries.ts#L327) | Dispose method |
| [dispose&#40;&#41;: unknown](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/vscode-tests/no-workspace/helpers.test.ts#L150) | Dispose method |
| [dispose &#46;&#46;&#46; inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L12) | Dispose method |
| [dispose &#46;&#46;&#46; inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L13) | Dispose method |
| [dispose &#46;&#46;&#46; inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L14) | Dispose method |
| [dispose &#46;&#46;&#46; inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L15) | Dispose method |

View File

@@ -0,0 +1,16 @@
"md","col1"
"dispose ... ();\n }","Dispose method"
"readonl ... > void;","Dispose method"
"async d ... }\n }","Dispose method"
"dispose(): any;","Dispose method"
"public ... }\n }","Dispose method"
"dispose: () => void;","Dispose method"
"dispose ... ');\n }","Dispose method"
"dispose ... ();\n }","Dispose method"
"public ... ();\n }","Dispose method"
"readonl ... > void;","Dispose method"
"dispose(): unknown","Dispose method"
"dispose ... inonSpy","Dispose method"
"dispose ... inonSpy","Dispose method"
"dispose ... inonSpy","Dispose method"
"dispose ... inonSpy","Dispose method"
1 md col1
2 dispose ... ();\n } Dispose method
3 readonl ... > void; Dispose method
4 async d ... }\n } Dispose method
5 dispose(): any; Dispose method
6 public ... }\n } Dispose method
7 dispose: () => void; Dispose method
8 dispose ... ');\n } Dispose method
9 dispose ... ();\n } Dispose method
10 public ... ();\n } Dispose method
11 readonl ... > void; Dispose method
12 dispose(): unknown Dispose method
13 dispose ... inonSpy Dispose method
14 dispose ... inonSpy Dispose method
15 dispose ... inonSpy Dispose method
16 dispose ... inonSpy Dispose method

View File

@@ -0,0 +1,19 @@
## github/vscode-codeql
| - | Message |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
| [dispose &#46;&#46;&#46; &#40;&#41;;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/cli.ts#L211) | Dispose method |
| [readonl &#46;&#46;&#46; &#62; void;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/extension.ts#L166) | Dispose method |
| [async d &#46;&#46;&#46; &#125;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/logging.ts#L151) | Dispose method |
| [dispose&#40;&#41;: any;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/pure/disposable-object.ts#L5) | Dispose method |
| [public &#46;&#46;&#46; &#125;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/pure/disposable-object.ts#L65) | Dispose method |
| [dispose: &#40;&#41; =&#62; void;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/query-results.ts#L54) | Dispose method |
| [dispose &#46;&#46;&#46; '&#41;;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/queryserver-client.ts#L32) | Dispose method |
| [dispose &#46;&#46;&#46; &#40;&#41;;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/telemetry.ts#L129) | Dispose method |
| [public &#46;&#46;&#46; &#40;&#41;;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/test-ui.ts#L54) | Dispose method |
| [readonl &#46;&#46;&#46; &#62; void;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/run-queries.ts#L327) | Dispose method |
| [dispose&#40;&#41;: unknown](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/vscode-tests/no-workspace/helpers.test.ts#L150) | Dispose method |
| [dispose &#46;&#46;&#46; inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L12) | Dispose method |
| [dispose &#46;&#46;&#46; inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L13) | Dispose method |
| [dispose &#46;&#46;&#46; inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L14) | Dispose method |
| [dispose &#46;&#46;&#46; inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L15) | Dispose method |

View File

@@ -0,0 +1,29 @@
{
"executionEndTime": 1645645080281,
"analysisSummaries": [
{
"nwo": "github/vscode-codeql",
"resultCount": 15,
"fileSizeInBytes": 191025,
"downloadLink": {
"id": "171543249",
"urlPath": "/repos/dsp-testing/qc-run2/actions/artifacts/171543249",
"innerFilePath": "results.sarif",
"queryId": "MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx"
}
},
{
"nwo": "other/hucairz",
"resultCount": 15,
"fileSizeInBytes": 191025,
"downloadLink": {
"id": "11111111",
"urlPath": "/repos/dsp-testing/qc-run2/actions/artifacts/11111111",
"innerFilePath": "results.sarif",
"queryId": "MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx"
}
}
],
"analysisFailures": [],
"queryId": "MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx"
}

View File

@@ -0,0 +1,17 @@
{
"queryName": "MRVA Integration test 1",
"queryFilePath": "PLACEHOLDER/q0.ql",
"queryText": "/**\n * @name MRVA Integration test 1\n * @kind problem\n * @problem.severity warning\n * @id javascript/integration-test-1\n */\nimport javascript\n\nfrom MemberDeclaration md\nwhere md.getName() = \"dispose\"\nselect md, \"Dispose method\"\n",
"controllerRepository": {
"owner": "dsp-testing",
"name": "qc-run2"
},
"repositories": [
{
"owner": "github",
"name": "vscode-codeql"
}
],
"executionStartTime": 1645644967533,
"actionsWorkflowRunId": 1889315769
}

View File

@@ -0,0 +1,6 @@
"md","col1"
"refresh ... d);\n }","Refresh method"
"refresh ... <void>;","Refresh method"
"public ... }\n }","Refresh method"
"public ... }\n }","Refresh method"
"refresh ... d);\n }","Refresh method"
1 md col1
2 refresh ... d);\n } Refresh method
3 refresh ... <void>; Refresh method
4 public ... }\n } Refresh method
5 public ... }\n } Refresh method
6 refresh ... d);\n } Refresh method

View File

@@ -0,0 +1,9 @@
## github/vscode-codeql
| - | Message |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- |
| [refresh &#46;&#46;&#46; d&#41;;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/astViewer.ts#L58) | Refresh method |
| [refresh &#46;&#46;&#46; &#60;void&#62;;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/databases.ts#L234) | Refresh method |
| [public &#46;&#46;&#46; &#125;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/databases.ts#L354) | Refresh method |
| [public &#46;&#46;&#46; &#125;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/discovery.ts#L21) | Refresh method |
| [refresh &#46;&#46;&#46; d&#41;;&#92;n &#125;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/query-history.ts#L268) | Refresh method |

View File

@@ -0,0 +1,18 @@
{
"executionEndTime": 1645645150738,
"analysisSummaries": [
{
"nwo": "github/vscode-codeql",
"resultCount": 5,
"fileSizeInBytes": 81237,
"downloadLink": {
"id": "171544171",
"urlPath": "/repos/dsp-testing/qc-run2/actions/artifacts/171544171",
"innerFilePath": "results.sarif",
"queryId": "MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN"
}
}
],
"analysisFailures": [],
"queryId": "MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN"
}

View File

@@ -0,0 +1,17 @@
{
"queryName": "MRVA Integration test 2",
"queryFilePath": "PLACEHOLDER/q1.ql",
"queryText": "/**\n * @name MRVA Integration test 2\n * @kind problem\n * @problem.severity warning\n * @id javascript/integration-test-2\n */\nimport javascript\n\nfrom MemberDeclaration md\nwhere md.getName() = \"refresh\"\nselect md, \"Refresh method\"\n",
"controllerRepository": {
"owner": "dsp-testing",
"name": "qc-run2"
},
"repositories": [
{
"owner": "github",
"name": "vscode-codeql"
}
],
"executionStartTime": 1645644973911,
"actionsWorkflowRunId": 1889316048
}

View File

@@ -0,0 +1,53 @@
{
"version": 1,
"queries": [
{
"t": "remote",
"status": "Completed",
"completed": true,
"queryId": "MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx",
"label": "MRVA Integration test 1",
"remoteQuery": {
"queryName": "MRVA Integration test 1",
"queryFilePath": "PLACEHOLDER/q0.ql",
"queryText": "/**\n * @name MRVA Integration test 1\n * @kind problem\n * @problem.severity warning\n * @id javascript/integration-test-1\n */\nimport javascript\n\nfrom MemberDeclaration md\nwhere md.getName() = \"dispose\"\nselect md, \"Dispose method\"\n",
"controllerRepository": {
"owner": "dsp-testing",
"name": "qc-run2"
},
"repositories": [
{
"owner": "github",
"name": "vscode-codeql"
}
],
"executionStartTime": 1645644967533,
"actionsWorkflowRunId": 1889315769
}
},
{
"t": "remote",
"status": "Completed",
"completed": true,
"queryId": "MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN",
"label": "MRVA Integration test 2",
"remoteQuery": {
"queryName": "MRVA Integration test 2",
"queryFilePath": "PLACEHOLDER/q1.ql",
"queryText": "/**\n * @name MRVA Integration test 2\n * @kind problem\n * @problem.severity warning\n * @id javascript/integration-test-2\n */\nimport javascript\n\nfrom MemberDeclaration md\nwhere md.getName() = \"refresh\"\nselect md, \"Refresh method\"\n",
"controllerRepository": {
"owner": "dsp-testing",
"name": "qc-run2"
},
"repositories": [
{
"owner": "github",
"name": "vscode-codeql"
}
],
"executionStartTime": 1645644973911,
"actionsWorkflowRunId": 1889316048
}
}
]
}

View File

@@ -18,7 +18,7 @@ import { slurpQueryHistory, splatQueryHistory } from '../../query-serialization'
chai.use(chaiAsPromised);
const expect = chai.expect;
describe.only('query-results', () => {
describe('query-results', () => {
let disposeSpy: sinon.SinonSpy;
let onDidChangeQueryHistoryConfigurationSpy: sinon.SinonSpy;
let mockConfig: QueryHistoryConfig;

View File

@@ -0,0 +1,342 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import * as sinon from 'sinon';
import * as chai from 'chai';
import 'mocha';
import 'sinon-chai';
import * as chaiAsPromised from 'chai-as-promised';
import { CancellationToken, ExtensionContext, Uri, window, workspace } from 'vscode';
import { QueryHistoryConfig } from '../../config';
import { DatabaseManager } from '../../databases';
import { tmpDir } from '../../helpers';
import { QueryHistoryManager } from '../../query-history';
import { QueryServerClient } from '../../queryserver-client';
import { DisposableBucket } from '../../pure/disposable-object';
import { Credentials } from '../../authentication';
import { AnalysesResultsManager } from '../../remote-queries/analyses-results-manager';
import { RemoteQueryResult } from '../../remote-queries/shared/remote-query-result';
import { walk } from '../test-helpers';
import { testDisposeHandler } from '../test-dispose-handler';
chai.use(chaiAsPromised);
const expect = chai.expect;
/**
* Tests for remote queries and how they interact with the query history manager.
*/
describe('Remote queries and query history manager', function() {
const EXTENSION_PATH = path.join(__dirname, '../../../');
const STORAGE_DIR = Uri.file(path.join(tmpDir.name, 'remote-queries')).fsPath;
const asyncNoop = async () => { /** noop */ };
let sandbox: sinon.SinonSandbox;
let qhm: QueryHistoryManager;
let rawQueryHistory: any;
let remoteQueryResult0: RemoteQueryResult;
let remoteQueryResult1: RemoteQueryResult;
let disposables: DisposableBucket;
let showTextDocumentSpy: sinon.SinonSpy;
let openTextDocumentSpy: sinon.SinonSpy;
beforeEach(() => {
copyHistoryState();
});
afterEach(() => {
deleteHistoryState();
});
beforeEach(() => {
sandbox = sinon.createSandbox();
disposables = new DisposableBucket();
rawQueryHistory = fs.readJSONSync(path.join(STORAGE_DIR, 'workspace-query-history.json')).queries;
remoteQueryResult0 = fs.readJSONSync(path.join(STORAGE_DIR, 'queries', rawQueryHistory[0].queryId, 'query-result.json'));
remoteQueryResult1 = fs.readJSONSync(path.join(STORAGE_DIR, 'queries', rawQueryHistory[1].queryId, 'query-result.json'));
qhm = new QueryHistoryManager(
{} as QueryServerClient,
{} as DatabaseManager,
STORAGE_DIR,
{
globalStorageUri: Uri.file(STORAGE_DIR),
extensionPath: EXTENSION_PATH
} as ExtensionContext,
{
onDidChangeConfiguration: () => new DisposableBucket(),
} as unknown as QueryHistoryConfig,
asyncNoop
);
disposables.push(qhm);
showTextDocumentSpy = sandbox.spy(window, 'showTextDocument');
openTextDocumentSpy = sandbox.spy(workspace, 'openTextDocument');
});
afterEach(() => {
disposables.dispose(testDisposeHandler);
sandbox.restore();
});
it('should read query history', async () => {
const spy = sandbox.spy();
disposables.push(qhm.onDidAddQueryItem(spy));
await qhm.readQueryHistory();
// Should have added the query history. Contents are directly from the file
expect(spy.getCall(0).args[0]).to.deep.eq(rawQueryHistory[0]);
expect(spy.getCall(1).args[0]).to.deep.eq(rawQueryHistory[1]);
expect(spy.callCount).to.eq(2);
expect(qhm.treeDataProvider.allHistory[0]).to.deep.eq(rawQueryHistory[0]);
expect(qhm.treeDataProvider.allHistory[1]).to.deep.eq(rawQueryHistory[1]);
expect(qhm.treeDataProvider.allHistory.length).to.eq(2);
});
it('should remove and then a query from history', async () => {
await qhm.readQueryHistory();
const addSpy = sandbox.spy();
disposables.push(qhm.onDidAddQueryItem(addSpy));
const removeSpy = sandbox.spy();
disposables.push(qhm.onDidRemoveQueryItem(removeSpy));
// Remove the first query
await qhm.handleRemoveHistoryItem(qhm.treeDataProvider.allHistory[0]);
expect(removeSpy.getCall(0).args[0]).to.deep.eq(rawQueryHistory[0]);
expect(removeSpy.callCount).to.eq(1);
expect(addSpy.callCount).to.eq(0);
expect(qhm.treeDataProvider.allHistory).to.deep.eq(rawQueryHistory.slice(1));
// Add it back
qhm.addQuery(rawQueryHistory[0]);
expect(removeSpy.callCount).to.eq(1);
expect(addSpy.getCall(0).args[0]).to.deep.eq(rawQueryHistory[0]);
expect(addSpy.callCount).to.eq(1);
expect(qhm.treeDataProvider.allHistory).to.deep.eq([rawQueryHistory[1], rawQueryHistory[0]]);
});
it('should remove two queries from history', async () => {
await qhm.readQueryHistory();
const addSpy = sandbox.spy();
disposables.push(qhm.onDidAddQueryItem(addSpy));
const removeSpy = sandbox.spy();
disposables.push(qhm.onDidRemoveQueryItem(removeSpy));
// Remove the both queries
// Just for fun, let's do it in reverse order
await qhm.handleRemoveHistoryItem(undefined!, [qhm.treeDataProvider.allHistory[1], qhm.treeDataProvider.allHistory[0]]);
expect(removeSpy.getCall(0).args[0]).to.deep.eq(rawQueryHistory[1]);
expect(removeSpy.getCall(1).args[0]).to.deep.eq(rawQueryHistory[0]);
expect(qhm.treeDataProvider.allHistory).to.deep.eq([]);
expect(removeSpy.callCount).to.eq(2);
// also, both queries should be removed from on disk storage
expect(fs.readJSONSync(path.join(STORAGE_DIR, 'workspace-query-history.json'))).to.deep.eq({
version: 1,
queries: []
});
});
it('should handle a click', async () => {
await qhm.readQueryHistory();
const openSpy = sandbox.spy();
disposables.push(qhm.onWillOpenQueryItem(openSpy));
await qhm.handleItemClicked(qhm.treeDataProvider.allHistory[0], []);
expect(openSpy.getCall(0).args[0]).to.deep.eq(rawQueryHistory[0]);
});
it('should get the query text', async () => {
await qhm.readQueryHistory();
await qhm.handleShowQueryText(qhm.treeDataProvider.allHistory[0], []);
expect(showTextDocumentSpy).to.have.been.calledOnce;
expect(openTextDocumentSpy).to.have.been.calledOnce;
const uri: Uri = openTextDocumentSpy.getCall(0).args[0];
expect(uri.scheme).to.eq('codeql');
const params = new URLSearchParams(uri.query);
expect(params.get('isQuickEval')).to.eq('false');
expect(params.get('queryText')).to.eq(rawQueryHistory[0].remoteQuery.queryText);
});
describe('AnalysisResultsManager', () => {
let mockCredentials: any;
let mockOctokit: any;
let mockLogger: any;
let arm: AnalysesResultsManager;
beforeEach(() => {
mockOctokit = {
request: sandbox.stub()
};
mockCredentials = {
getOctokit: () => mockOctokit
};
mockLogger = {
log: sandbox.spy()
};
sandbox.stub(Credentials, 'initialize').resolves(mockCredentials);
arm = new AnalysesResultsManager(
{} as ExtensionContext,
path.join(STORAGE_DIR, 'queries'),
mockLogger
);
});
it('should avoid downloading an analysis result', async () => {
// because the analysis result is already in on disk, it should not be downloaded
const publisher = sandbox.spy();
const analysisSummary = remoteQueryResult0.analysisSummaries[0];
await arm.downloadAnalysisResults(analysisSummary, publisher);
// Should not have made the request since the analysis result is already on disk
expect(mockOctokit.request).to.not.have.been.called;
// result should have been published twice
// first time, it is in progress
expect(publisher.getCall(0).args[0][0]).to.include({
nwo: 'github/vscode-codeql',
status: 'InProgress',
// results: ... avoid checking the results object since it is complex
});
// second time, it has the path to the sarif file.
expect(publisher.getCall(1).args[0][0]).to.include({
nwo: 'github/vscode-codeql',
status: 'Completed',
// results: ... avoid checking the results object since it is complex
});
expect(publisher).to.have.been.calledTwice;
// result should be stored in the manager
expect(arm.getAnalysesResults(rawQueryHistory[0].queryId)[0]).to.include({
nwo: 'github/vscode-codeql',
status: 'Completed',
// results: ... avoid checking the results object since it is complex
});
publisher.resetHistory();
// now, let's try to download it again. This time, since it's already in memory,
// it should not even be re-published
await arm.downloadAnalysisResults(analysisSummary, publisher);
expect(publisher).to.not.have.been.called;
});
it('should download two artifacts at once', async () => {
const publisher = sandbox.spy();
const analysisSummaries = [...remoteQueryResult0.analysisSummaries];
await arm.downloadAnalysesResults(analysisSummaries, undefined, publisher);
const trimmed = publisher.getCalls().map(call => call.args[0]).map(args => {
args.forEach((analysisResult: any) => delete analysisResult.results);
return args;
});
// As before, but now both summaries should have been published
expect(trimmed[0]).to.deep.eq([{
nwo: 'github/vscode-codeql',
status: 'InProgress',
}]);
expect(trimmed[1]).to.deep.eq([{
nwo: 'github/vscode-codeql',
status: 'InProgress',
}, {
nwo: 'other/hucairz',
status: 'InProgress',
}]);
// there is a third call. It is non-deterministic if
// github/vscode-codeql is completed first or other/hucairz is.
// There is not much point in trying to test it if the other calls are correct.
expect(trimmed[3]).to.deep.eq([{
nwo: 'github/vscode-codeql',
status: 'Completed',
}, {
nwo: 'other/hucairz',
status: 'Completed',
}]);
expect(publisher).to.have.callCount(4);
});
it('should avoid publishing when the request is cancelled', async () => {
const publisher = sandbox.spy();
const analysisSummaries = [...remoteQueryResult0.analysisSummaries];
try {
await arm.downloadAnalysesResults(analysisSummaries, {
isCancellationRequested: true
} as CancellationToken, publisher);
expect.fail('Should have thrown');
} catch (e) {
expect(e.message).to.contain('cancelled');
}
expect(publisher).not.to.have.been.called;
});
it('should get the analysis results', async () => {
const publisher = sandbox.spy();
const analysisSummaries0 = [...remoteQueryResult0.analysisSummaries];
const analysisSummaries1 = [...remoteQueryResult1.analysisSummaries];
await arm.downloadAnalysesResults(analysisSummaries0, undefined, publisher);
await arm.downloadAnalysesResults(analysisSummaries1, undefined, publisher);
const result0 = arm.getAnalysesResults(rawQueryHistory[0].queryId);
const result1 = arm.getAnalysesResults(rawQueryHistory[1].queryId);
// Shoule be equal, but not equivalent
expect(result0).to.deep.eq((arm as any).analysesResults.get(rawQueryHistory[0].queryId));
expect(result0).not.to.eq((arm as any).analysesResults.get(rawQueryHistory[0].queryId));
expect(result1).to.deep.eq((arm as any).analysesResults.get(rawQueryHistory[1].queryId));
expect(result1).not.to.eq((arm as any).analysesResults.get(rawQueryHistory[1].queryId));
});
// This test is failing on windows in CI.
it.skip('should read sarif', async () => {
const publisher = sandbox.spy();
const analysisSummaries0 = [remoteQueryResult0.analysisSummaries[0]];
await arm.downloadAnalysesResults(analysisSummaries0, undefined, publisher);
const sarif = fs.readJSONSync(path.join(STORAGE_DIR, 'queries', rawQueryHistory[0].queryId, '171543249', 'results.sarif'));
const queryResults = sarif.runs
.flatMap((run: any) => run.results)
.map((result: any) => ({ message: result.message.text }));
expect(publisher.getCall(1).args[0][0].results).to.deep.eq(queryResults);
});
});
// Since this test changes the state of the query history manager, we need to copy the original
// to a temporary folder where we can manipulate it for tests
function copyHistoryState() {
fs.ensureDirSync(STORAGE_DIR);
fs.copySync(path.join(__dirname, 'data/remote-queries/'), path.join(tmpDir.name, 'remote-queries'));
// also, replace the files with "PLACEHOLDER" so that they have the correct directory
for (const p of walk(STORAGE_DIR)) {
replacePlaceholder(path.join(p));
}
}
function deleteHistoryState() {
fs.removeSync(STORAGE_DIR);
}
function replacePlaceholder(filePath: string) {
if (filePath.endsWith('.json')) {
const newContents = fs.readFileSync(filePath, 'utf8').replaceAll('PLACEHOLDER', STORAGE_DIR.replaceAll('\\', '/'));
fs.writeFileSync(filePath, newContents, 'utf8');
}
}
});

View File

@@ -0,0 +1,21 @@
import * as path from 'path';
import * as fs from 'fs-extra';
/**
* Recursively walk a directory and return the full path to all files found.
* Note that this function uses synchronous fs calls, so it should only be used in tests.
*
* @param dir the directory to walk
*/
export function* walk(dir: string): IterableIterator<string> {
const files = fs.readdirSync(dir);
for (const file of files) {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
yield* walk(filePath);
} else {
yield filePath;
}
}
}

View File

@@ -6,7 +6,7 @@
"module": "commonjs",
"target": "es2017",
"outDir": "out",
"lib": ["ES2020"],
"lib": ["ES2021"],
"moduleResolution": "node",
"sourceMap": true,
"rootDir": "src",