Merge pull request #1166 from github/aeisenberg/remote-queries-unit-tests

Add unit tests for query history and remote queries
This commit is contained in:
Andrew Eisenberg
2022-02-25 10:07:56 -08:00
committed by GitHub
38 changed files with 1987 additions and 61 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

@@ -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

@@ -244,7 +244,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,23 @@
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
*
* @return An iterator of the full path to all files recursively found in the directory.
*/
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

@@ -0,0 +1,16 @@
import { Disposable } from 'vscode';
import { DisposableObject } from '../pure/disposable-object';
/**
* 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

@@ -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/avocado-corp/hucairz/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/avocado-corp/hucairz/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/avocado-corp/hucairz/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

@@ -0,0 +1,344 @@
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 { Credentials } from '../../authentication';
import { AnalysesResultsManager } from '../../remote-queries/analyses-results-manager';
import { RemoteQueryResult } from '../../remote-queries/shared/remote-query-result';
import { DisposableBucket } from '../disposable-bucket';
import { testDisposeHandler } from '../test-dispose-handler';
import { walk } from '../directory-walker';
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(() => {
// Since these tests change the state of the query history manager, we need to copy the original
// to a temporary folder where we can manipulate it for tests
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 add 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 re-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 result0Again = arm.getAnalysesResults(rawQueryHistory[0].queryId);
// Shoule be equal, but not equivalent
expect(result0).to.deep.eq(result0Again);
expect(result0).not.to.eq(result0Again);
const result1 = arm.getAnalysesResults(rawQueryHistory[1].queryId);
const result1Again = arm.getAnalysesResults(rawQueryHistory[1].queryId);
expect(result1).to.deep.eq(result1Again);
expect(result1).not.to.eq(result1Again);
});
// 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);
});
});
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

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