Compare commits

...

17 Commits

Author SHA1 Message Date
Andrew Eisenberg
b7b5a6ec30 v1.3.7
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Release / Release (push) Has been cancelled
2020-11-24 14:02:25 -08:00
Andrew Eisenberg
da9576fee0 Add workflow_dispatch to release workflow 2020-11-23 17:56:36 -08:00
Andrew Eisenberg
579df25be4 Use the ast edge label when building the ast node label
The C PrintAST library now includes the edge name in the AST Viewer
tree.
2020-11-23 15:27:24 -08:00
Andrew Eisenberg
1886c0c9ec Add a setting to control page size
Also, set a max and min value on the input control of the page. This
prevents going to a negative page, or a page after the last one.
2020-11-21 09:45:52 -08:00
Andrew Eisenberg
f48176bebf Re-sort databases list after db rename 2020-11-20 15:12:47 -08:00
Andrew Eisenberg
83f64fbdcd Avoid dependabot error 2020-11-19 14:02:21 -08:00
Andrew Eisenberg
a7bf5e60f3 Add debug flag for query server
And separate flag for IDE server. Setting these flags to `true` will
start the respective Java processes in debug mode so that they can
be attached to a debugger.
2020-11-18 15:55:24 -08:00
Andrew Eisenberg
e0cd041d98 Clean databases folder on startup (#675)
Cleans orphan databases on startup. This commit also bumps the fs-extra
dependency to get readdir with dirent objects.

Adds the `asyncFilter` to filter arrays asynchronously.
2020-11-16 16:32:05 +00:00
Andrew Eisenberg
4f76e9da60 Use the value not the label for the print ast node
Fixes #659
2020-11-13 09:45:30 -08:00
Andrew Eisenberg
966cc5af92 Add more structured output for tests
The diff and the errors were always available, but they were not being
sent to the output.

Additionally, make sure to send output to both the test explorer log and
the codeql test log.
2020-11-09 14:31:02 -08:00
Andrew Eisenberg
f4998d90e7 Creates an empty .expected file when running test output compare
If the expected file does not already exists. This helps with test
creation and allows users to create tests more quickly.
2020-11-09 14:27:20 -08:00
Andrew Eisenberg
245496c854 Remove QLPackDiscovery
We no longer rely on qlpacks for our ql test structure. For this reason,
we no longer need to do qlpack discovery.
2020-11-09 11:53:42 -08:00
Andrew Eisenberg
d553f6c069 Restructure the tree in the Test Explorer View
With this change we display the tree based on the file system not based
on ql-packs. We also merge test folders whose only child is another
test folder.

Resolves #595
2020-11-09 11:53:42 -08:00
Andrew Eisenberg
afd0694111 Update changelog 2020-11-09 11:53:42 -08:00
Andrew Eisenberg
32db9cdec6 Open editor containing query location in non-preview mode 2020-11-05 10:32:58 -08:00
github-actions[bot]
ad3cd7e7ac Bump version to v1.3.7 (#672)
Co-authored-by: aeisenberg <aeisenberg@users.noreply.github.com>
2020-11-04 14:09:37 -08:00
Andrew Eisenberg
e719c68321 Update the contributing docs
Just adds some more details.
2020-11-04 12:47:38 -08:00
33 changed files with 737 additions and 355 deletions

View File

@@ -6,20 +6,16 @@
name: Release
on:
push:
# Path filters are not evaluated for pushes to tags.
# (source: https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#onpushpull_requestpaths)
# So this workflow is triggered in the following events:
# - Release event: a SemVer tag, e.g. v1.0.0 or v1.0.0-alpha, is pushed
tags:
- 'v[0-9]+.[0-9]+.[0-9]+*'
# OR
# - Test event: this file is modified on a branch in the main repo containing `/actions/` in the name.
branches:
- '**/actions/**'
pull_request:
paths:
- '**/workflows/release.yml'
workflow_dispatch:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+*'
jobs:
build:
name: Release

5
.vscode/launch.json vendored
View File

@@ -16,8 +16,9 @@
"${workspaceRoot}/extensions/ql-vscode/out/**/*.js",
],
"env": {
// uncomment to allow debugging the language server Java process from a remote java debugger
// "DEBUG_LANGUAGE_SERVER": "true"
// change to 'true' debug the IDE or Query servers
"IDE_SERVER_JAVA_DEBUG": "false",
"QUERY_SERVER_JAVA_DEBUG": "false",
}
},
{

View File

@@ -91,15 +91,28 @@ Alternatively, you can run the tests inside of vscode. There are several vscode
## Releasing (write access required)
1. Double-check the `CHANGELOG.md` contains all desired change comments
and has the version to be released with date at the top.
1. Double-check that the extension `package.json` has the version you intend to release.
If you are doing a patch release (as opposed to minor or major version) this should already
be correct.
1. Trigger a release build on Actions by adding a new tag on branch `main` of the format `vxx.xx.xx`
1. Double-check the `CHANGELOG.md` contains all desired change comments and has the version to be released with date at the top.
* Go through all recent PRs and make sure they are properly accounted for.
* Make sure all changelog entries have links back to their PR(s) if appropriate.
1. Double-check that the extension `package.json` has the version you intend to release. If you are doing a patch release (as opposed to minor or major version) this should already be correct.
1. Create a PR for this release:
* This PR will contain any missing bits from steps 1 and 2. Most of the time, this will just be updating `CHANGELOG.md` with today's date.
* Create a new branch for the release named after the new version. For example: `v1.3.6`
* Create a new commit with a message the same as the branch name.
* Create a PR for this branch.
* Wait for the PR to be merged into `main`
1. Trigger a release build on Actions by adding a new tag on branch `main` named after the release, as above. Note that when you push to upstream, you will need to fully qualify the ref. A command like this will work:
```bash
git push upstream refs/tags/v1.3.6
```
* **IMPORTANT** Make sure you are on the `main` branch and your local checkout is fully updated when you add the tag.
* If you accidentally add the tag to the wrong ref, you can just force push it to the right one later.
1. Monitor the status of the release build in the `Release` workflow in the Actions tab.
1. Download the VSIX from the draft GitHub release at the top of [the releases page](https://github.com/github/vscode-codeql/releases) that is created when the release build finishes.
1. Optionally unzip the `.vsix` and inspect its `package.json` to make sure the version is what you expect,
1. Unzip the `.vsix` and inspect its `package.json` to make sure the version is what you expect,
or look at the source if there's any doubt the right code is being shipped.
1. Log into the [Visual Studio Marketplace](https://marketplace.visualstudio.com/manage/publishers/github).
1. Click the `...` menu in the CodeQL row and click **Update**.

View File

@@ -1,5 +1,17 @@
# CodeQL for Visual Studio Code: Changelog
## 1.3.7 - 24 November 2020
- Editors opened by navigating from the results view are no longer opened in _preview mode_. Now they are opened as a persistent editor. [#630](https://github.com/github/vscode-codeql/pull/630)
- When comparing the results of a failed QL test run and the `.expected` file does not exist, an empty `.expected` file is created and compared against the `.actual` file. [#669](https://github.com/github/vscode-codeql/pull/669)
- Alter structure of the _Test Explorer_ tree. It now follows the structure of the filesystem instead of the QL Packs. [#624](https://github.com/github/vscode-codeql/pull/624)
- Alter structure of the _Test Explorer_ tree. It now follows the structure of the filesystem instead of the QL Packs. [#624](https://github.com/github/vscode-codeql/pull/624)
- Add more structured output for tests. [#626](https://github.com/github/vscode-codeql/pull/626)
- Whenever the extension restarts, orphaned databases will be cleaned up. These are databases whose files are located inside of the extension's storage area, but are not imported into the workspace.
- After renaming a database, the database list is re-sorted. [#685](https://github.com/github/vscode-codeql/pull/685)
- Add a `codeQl.resultsDisplay.pageSize` setting to configure the number of results displayed in a single results view page. Increase the default page size from 100 to 200. [#686](https://github.com/github/vscode-codeql/pull/686)
- Update the AST Viewer to include edge labels (if available) in addition to the target node labels. So far, only C/C++ databases take advantage of this change. [#688](https://github.com/github/vscode-codeql/pull/688)
## 1.3.6 - 4 November 2020
- Fix URI encoding for databases that were created with special characters in their paths. [#648](https://github.com/github/vscode-codeql/pull/648)
@@ -10,6 +22,7 @@
## 1.3.5 - 27 October 2020
- Fix a bug where archived source folders for databases were not showing any contents.
- Fix URI encoding for databases that were created with special characters in their paths.
## 1.3.4 - 22 October 2020
@@ -26,6 +39,8 @@
- Add a `View DIL` command on query history items. This opens a text editor containing the Datalog Intermediary Language representation of the compiled query.
- Remove feature flag for the AST Viewer. For more information on how to use the AST Viewer, [see the documentation](https://help.semmle.com/codeql/codeql-for-vscode/procedures/exploring-the-structure-of-your-source-code.html).
- The `codeQL.runningTests.numberOfThreads` setting is now used correctly when running tests.
- Alter structure of the _Test Explorer_ tree. It now follows the structure of the filesystem instead of the qlpacks.
- Ensure output of CodeQL test runs includes compilation error messages and test failure messages.
## 1.3.3 - 16 September 2020

View File

@@ -1,6 +1,6 @@
{
"name": "vscode-codeql",
"version": "1.3.6",
"version": "1.3.7",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -217,9 +217,9 @@
"dev": true
},
"@types/fs-extra": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz",
"integrity": "sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w==",
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.3.tgz",
"integrity": "sha512-NKdGoXLTFTRED3ENcfCsH8+ekV4gbsysanx2OPbstXVV6fZMgUCqTxubs6I9r7pbOJbFgVq1rpFtLURjKCZWUw==",
"dev": true,
"requires": {
"@types/node": "*"
@@ -310,9 +310,9 @@
"dev": true
},
"@types/node": {
"version": "12.12.50",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.50.tgz",
"integrity": "sha512-5ImO01Fb8YsEOYpV+aeyGYztcYcjGsBvN4D7G5r1ef2cuQOpymjWNQi5V0rKHE6PC2ru3HkoUr/Br2/8GUA84w==",
"version": "12.19.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.4.tgz",
"integrity": "sha512-o3oj1bETk8kBwzz1WlO6JWL/AfAA3Vm6J1B3C9CsdxHYp7XgPiH7OEXPUbZTndHlRaIElrANkQfe6ZmfJb3H2w==",
"dev": true
},
"@types/node-fetch": {
@@ -1251,6 +1251,11 @@
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
"dev": true
},
"at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="
},
"atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
@@ -3748,13 +3753,14 @@
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
},
"fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
"jsonfile": "^6.0.1",
"universalify": "^1.0.0"
}
},
"fs-mkdirp-stream": {
@@ -5026,11 +5032,19 @@
"dev": true
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"requires": {
"graceful-fs": "^4.1.6"
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"dependencies": {
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
}
}
},
"jsx-ast-utils": {
@@ -9001,9 +9015,9 @@
}
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug=="
},
"unset-value": {
"version": "1.0.0",
@@ -10016,6 +10030,16 @@
"requires": {
"ansi-regex": "^2.0.0"
}
},
"yargs-parser": {
"version": "5.0.0-security.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz",
"integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==",
"dev": true,
"requires": {
"camelcase": "^3.0.0",
"object.assign": "^4.1.0"
}
}
}
},

View File

@@ -4,7 +4,7 @@
"description": "CodeQL for Visual Studio Code",
"author": "GitHub",
"private": true,
"version": "1.3.6",
"version": "1.3.7",
"publisher": "GitHub",
"license": "MIT",
"icon": "media/VS-marketplace-CodeQL-icon.png",
@@ -157,6 +157,11 @@
"default": 20,
"description": "Max number of simultaneous queries to run using the 'CodeQL: Run Queries' command."
},
"codeQL.resultsDisplay.pageSize": {
"type": "integer",
"default": 200,
"description": "Number of queries displayed per page of the results view."
},
"codeQL.queryHistory.format": {
"type": "string",
"default": "[%t] %q on %d - %s",
@@ -197,6 +202,10 @@
"dark": "media/dark/folder-opened-plus.svg"
}
},
{
"command": "codeQLDatabases.removeOrphanedDatabases",
"title": "Delete unused databases"
},
{
"command": "codeQLDatabases.chooseDatabaseArchive",
"title": "Choose Database from Archive",
@@ -573,6 +582,10 @@
"command": "codeQLDatabases.chooseDatabaseArchive",
"when": "false"
},
{
"command": "codeQLDatabases.removeOrphanedDatabases",
"when": "false"
},
{
"command": "codeQLDatabases.chooseDatabaseInternet",
"when": "false"
@@ -704,7 +717,7 @@
"dependencies": {
"child-process-promise": "^2.2.1",
"classnames": "~2.2.6",
"fs-extra": "^8.1.0",
"fs-extra": "^9.0.1",
"glob-promise": "^3.4.0",
"js-yaml": "^3.14.0",
"minimist": "~1.2.5",
@@ -727,7 +740,7 @@
"@types/chai-as-promised": "~7.1.2",
"@types/child-process-promise": "^2.2.1",
"@types/classnames": "~2.2.9",
"@types/fs-extra": "^8.0.0",
"@types/fs-extra": "^9.0.3",
"@types/glob": "^7.1.1",
"@types/google-protobuf": "^3.2.7",
"@types/gulp": "^4.0.6",
@@ -735,7 +748,7 @@
"@types/js-yaml": "^3.12.5",
"@types/jszip": "~3.1.6",
"@types/mocha": "~8.0.3",
"@types/node": "^12.0.8",
"@types/node": "^12.14.1",
"@types/node-fetch": "~2.5.2",
"@types/proxyquire": "~1.3.28",
"@types/react": "^16.8.17",

View File

@@ -17,6 +17,7 @@ import { DistributionProvider, FindDistributionResultKind } from './distribution
import { assertNever } from './pure/helpers-pure';
import { QueryMetadata, SortDirection } from './pure/interface-types';
import { Logger, ProgressReporter } from './logging';
import { CompilationMessage } from './pure/messages';
/**
* The version of the SARIF format that we are using.
@@ -90,10 +91,11 @@ export interface TestRunOptions {
export interface TestCompleted {
test: string;
pass: boolean;
messages: string[];
messages: CompilationMessage[];
compilationMs: number;
evaluationMs: number;
expected: string;
diff: string[] | undefined;
}
/**
@@ -470,7 +472,11 @@ export class CodeQLCliServer implements Disposable {
const subcommandArgs = [
testPath
];
return await this.runJsonCodeQlCliCommand<ResolvedTests>(['resolve', 'tests'], subcommandArgs, 'Resolving tests');
return await this.runJsonCodeQlCliCommand<ResolvedTests>(
['resolve', 'tests', '--strict-test-discovery'],
subcommandArgs,
'Resolving tests'
);
}
/**
@@ -901,3 +907,16 @@ async function logStream(stream: Readable, logger: Logger): Promise<void> {
logger.log(line);
}
}
export function shouldDebugIdeServer() {
return 'IDE_SERVER_JAVA_DEBUG' in process.env
&& process.env.IDE_SERVER_JAVA_DEBUG !== '0'
&& process.env.IDE_SERVER_JAVA_DEBUG?.toLocaleLowerCase() !== 'false';
}
export function shouldDebugQueryServer() {
return 'QUERY_SERVER_JAVA_DEBUG' in process.env
&& process.env.QUERY_SERVER_JAVA_DEBUG !== '0'
&& process.env.QUERY_SERVER_JAVA_DEBUG?.toLocaleLowerCase() !== 'false';
}

View File

@@ -68,10 +68,12 @@ const TIMEOUT_SETTING = new Setting('timeout', RUNNING_QUERIES_SETTING);
const MEMORY_SETTING = new Setting('memory', RUNNING_QUERIES_SETTING);
const DEBUG_SETTING = new Setting('debug', RUNNING_QUERIES_SETTING);
const RUNNING_TESTS_SETTING = new Setting('runningTests', ROOT_SETTING);
const RESULTS_DISPLAY_SETTING = new Setting('resultsDisplay', ROOT_SETTING);
export const NUMBER_OF_TEST_THREADS_SETTING = new Setting('numberOfThreads', RUNNING_TESTS_SETTING);
export const MAX_QUERIES = new Setting('maxQueries', RUNNING_QUERIES_SETTING);
export const AUTOSAVE_SETTING = new Setting('autoSave', RUNNING_QUERIES_SETTING);
export const PAGE_SIZE = new Setting('pageSize', RESULTS_DISPLAY_SETTING);
/** When these settings change, the running query server should be restarted. */
const QUERY_SERVER_RESTARTING_SETTINGS = [NUMBER_OF_THREADS_SETTING, MEMORY_SETTING, DEBUG_SETTING];

View File

@@ -45,17 +45,18 @@ export default class AstBuilder {
const parentToChildren = new Map<BqrsId, BqrsId[]>();
const childToParent = new Map<BqrsId, BqrsId>();
const astOrder = new Map<BqrsId, number>();
const edgeLabels = new Map<BqrsId, string>();
const roots = [];
// Build up the parent-child relationships
edgeTuples.tuples.forEach(tuple => {
const [source, target, tupleType, orderValue] = tuple as [EntityValue, EntityValue, string, string];
const [source, target, tupleType, value] = tuple as [EntityValue, EntityValue, string, string];
const sourceId = source.id!;
const targetId = target.id!;
switch (tupleType) {
case 'semmle.order':
astOrder.set(targetId, Number(orderValue));
astOrder.set(targetId, Number(value));
break;
case 'semmle.label': {
@@ -65,6 +66,11 @@ export default class AstBuilder {
parentToChildren.set(sourceId, children = []);
}
children.push(targetId);
// ignore values that indicate a numeric order.
if (!Number.isFinite(Number(value))) {
edgeLabels.set(targetId, value);
}
break;
}
@@ -75,18 +81,22 @@ export default class AstBuilder {
// populate parents and children
nodeTuples.tuples.forEach(tuple => {
const [entity, tupleType, orderValue] = tuple as [EntityValue, string, string];
const [entity, tupleType, value] = tuple as [EntityValue, string, string];
const id = entity.id!;
switch (tupleType) {
case 'semmle.order':
astOrder.set(id, Number(orderValue));
astOrder.set(id, Number(value));
break;
case 'semmle.label': {
// If an edge label exists, include it and separate from the node label using ':'
const nodeLabel = value ?? entity.label;
const edgeLabel = edgeLabels.get(id);
const label = [edgeLabel, nodeLabel].filter(e => e).join(': ');
const item = {
id,
label: entity.label,
label,
location: entity.url,
fileLocation: fileRangeFromURI(entity.url, this.db),
children: [] as ChildAstItem[],

View File

@@ -8,7 +8,7 @@ import {
TreeItem,
Uri,
window,
env
env,
} from 'vscode';
import * as fs from 'fs-extra';
@@ -18,6 +18,8 @@ import {
DatabaseItem,
DatabaseManager,
getUpgradesDirectories,
isLikelyDatabaseRoot,
isLikelyDbLanguageFolder,
} from './databases';
import {
commandRunner,
@@ -35,7 +37,8 @@ import {
promptImportInternetDatabase,
promptImportLgtmDatabase,
} from './databaseFetcher';
import { CancellationToken } from 'vscode-jsonrpc';
import { CancellationToken } from 'vscode';
import { asyncFilter } from './pure/helpers-pure';
type ThemableIconPath = { light: string; dark: string } | string;
@@ -229,7 +232,9 @@ export class DatabaseUI extends DisposableObject {
canSelectMany: true,
})
);
}
init() {
logger.log('Registering database panel commands.');
this.push(
commandRunnerWithProgress(
@@ -340,6 +345,12 @@ export class DatabaseUI extends DisposableObject {
this.handleOpenFolder
)
);
this.push(
commandRunner(
'codeQLDatabases.removeOrphanedDatabases',
this.handleRemoveOrphanedDatabases
)
);
}
private handleMakeCurrentDatabase = async (
@@ -360,6 +371,53 @@ export class DatabaseUI extends DisposableObject {
}
};
handleRemoveOrphanedDatabases = async (): Promise<void> => {
logger.log('Removing orphaned databases from workspace storage.');
let dbDirs =
// read directory
(await fs.readdir(this.storagePath, { withFileTypes: true }))
// remove non-directories
.filter(dirent => dirent.isDirectory())
// get the full path
.map(dirent => path.join(this.storagePath, dirent.name))
// remove databases still in workspace
.filter(dbDir => {
const dbUri = Uri.file(dbDir);
return this.databaseManager.databaseItems.every(item => item.databaseUri.fsPath !== dbUri.fsPath);
});
// remove non-databases
dbDirs = await asyncFilter(dbDirs, isLikelyDatabaseRoot);
if (!dbDirs.length) {
logger.log('No orphaned databases found.');
return;
}
// delete
const failures = [] as string[];
await Promise.all(
dbDirs.map(async dbDir => {
try {
logger.log(`Deleting orphaned database '${dbDir}'.`);
await fs.rmdir(dbDir, { recursive: true } as any); // typings doesn't recognize the options argument
} catch (e) {
failures.push(`${path.basename(dbDir)}`);
}
})
);
if (failures.length) {
const dirname = path.dirname(failures[0]);
showAndLogErrorMessage(
`Failed to delete unused databases:\n ${
failures.join('\n ')
}\n. To delete unused databases, please remove them manually from the storage folder ${dirname}.`
);
}
};
handleChooseDatabaseArchive = async (
progress: ProgressCallback,
token: CancellationToken
@@ -653,7 +711,7 @@ export class DatabaseUI extends DisposableObject {
dbPath = path.dirname(dbPath);
}
if (isLikelyDbFolder(dbPath)) {
if (isLikelyDbLanguageFolder(dbPath)) {
dbPath = path.dirname(dbPath);
}
return Uri.file(dbPath);
@@ -668,9 +726,3 @@ export class DatabaseUI extends DisposableObject {
}
}
}
// TODO: Get the list of supported languages from a list that will be auto-updated.
const dbRegeEx = /^db-(javascript|go|cpp|java|python|csharp)$/;
function isLikelyDbFolder(dbPath: string) {
return path.basename(dbPath).match(dbRegeEx);
}

View File

@@ -397,10 +397,7 @@ export class DatabaseItemImpl implements DatabaseItem {
* Holds if the database item refers to an exported snapshot
*/
public async hasMetadataFile(): Promise<boolean> {
return (await Promise.all([
fs.pathExists(path.join(this.databaseUri.fsPath, '.dbinfo')),
fs.pathExists(path.join(this.databaseUri.fsPath, 'codeql-database.yml'))
])).some(x => x);
return await isLikelyDatabaseRoot(this.databaseUri.fsPath);
}
/**
@@ -670,7 +667,8 @@ export class DatabaseManager extends DisposableObject {
item.name = newName;
this.updatePersistedDatabaseList();
this._onDidChangeDatabaseItem.fire({
item,
// pass undefined so that the entire tree is rebuilt in order to re-sort
item: undefined,
kind: DatabaseEventKind.Rename
});
}
@@ -730,3 +728,23 @@ export function getUpgradesDirectories(scripts: string[]): vscode.Uri[] {
const uniqueParentDirs = new Set(parentDirs);
return Array.from(uniqueParentDirs).map(filePath => vscode.Uri.file(filePath));
}
// TODO: Get the list of supported languages from a list that will be auto-updated.
export async function isLikelyDatabaseRoot(fsPath: string) {
const [a, b, c] = (await Promise.all([
// databases can have either .dbinfo or codeql-database.yml.
fs.pathExists(path.join(fsPath, '.dbinfo')),
fs.pathExists(path.join(fsPath, 'codeql-database.yml')),
// they *must* have a db-language folder
(await fs.readdir(fsPath)).some(isLikelyDbLanguageFolder)
]));
return (a || b) && c;
}
export function isLikelyDbLanguageFolder(dbPath: string) {
return !!path.basename(dbPath).startsWith('db-');
}

View File

@@ -349,6 +349,7 @@ async function activateWithInstalledDistribution(
getContextStoragePath(ctx),
ctx.extensionPath
);
databaseUI.init();
ctx.subscriptions.push(databaseUI);
logger.log('Initializing query history manager.');
@@ -643,6 +644,8 @@ async function activateWithInstalledDistribution(
title: 'Calculate AST'
}));
commands.executeCommand('codeQLDatabases.removeOrphanedDatabases');
logger.log('Successfully finished extension initialization.');
}

View File

@@ -12,7 +12,7 @@ import { ideServerLogger } from './logging';
export async function spawnIdeServer(config: QueryServerConfig): Promise<StreamInfo> {
return window.withProgress({ title: 'CodeQL language server', location: ProgressLocation.Window }, async (progressReporter, _) => {
const args = ['--check-errors', 'ON_CHANGE'];
if (shouldDebug()) {
if (cli.shouldDebugIdeServer()) {
args.push('-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9009,server=y,suspend=n,quiet=y');
}
const child = cli.spawnServer(
@@ -28,9 +28,3 @@ export async function spawnIdeServer(config: QueryServerConfig): Promise<StreamI
return { writer: child.stdin!, reader: child.stdout! };
});
}
function shouldDebug() {
return 'DEBUG_LANGUAGE_SERVER' in process.env
&& process.env.DEBUG_LANGUAGE_SERVER !== '0'
&& process.env.DEBUG_LANGUAGE_SERVER?.toLocaleLowerCase() !== 'false';
}

View File

@@ -167,7 +167,13 @@ export async function showLocation(location?: Location) {
const editor =
editorsWithDoc.length > 0
? editorsWithDoc[0]
: await Window.showTextDocument(doc, ViewColumn.One);
: await Window.showTextDocument(
doc, {
// avoid preview mode so editor is sticky and will be added to navigation and search histories.
preview: false,
viewColumn: ViewColumn.One,
});
const range = location.range;
// When highlighting the range, vscode's occurrence-match and bracket-match highlighting will
// trigger based on where we place the cursor/selection, and will compete for the user's attention.

View File

@@ -26,8 +26,6 @@ import {
SortedResultsMap,
InterpretedResultsSortState,
SortDirection,
RAW_RESULTS_PAGE_SIZE,
INTERPRETED_RESULTS_PAGE_SIZE,
ALERTS_TABLE_NAME,
RawResultsSortState,
} from './pure/interface-types';
@@ -48,6 +46,7 @@ import {
} from './interface-utils';
import { getDefaultResultSetName, ParsedResultSets } from './pure/interface-types';
import { RawResultSet, transformBqrsResultSet, ResultSetSchema } from './pure/bqrs-cli-types';
import { PAGE_SIZE } from './config';
/**
* interface.ts
@@ -89,11 +88,11 @@ function sortInterpretedResults(
}
function numPagesOfResultSet(resultSet: RawResultSet): number {
return Math.ceil(resultSet.schema.rows / RAW_RESULTS_PAGE_SIZE);
return Math.ceil(resultSet.schema.rows / PAGE_SIZE.getValue<number>());
}
function numInterpretedPages(interpretation: Interpretation | undefined): number {
return Math.ceil((interpretation?.sarif.runs[0].results?.length || 0) / INTERPRETED_RESULTS_PAGE_SIZE);
return Math.ceil((interpretation?.sarif.runs[0].results?.length || 0) / PAGE_SIZE.getValue<number>());
}
export class InterfaceManager extends DisposableObject {
@@ -378,7 +377,7 @@ export class InterfaceManager extends DisposableObject {
// Use sorted results path if it exists. This may happen if we are
// reloading the results view after it has been sorted in the past.
const resultsPath = results.getResultsPath(selectedTable);
const pageSize = PAGE_SIZE.getValue<number>();
const chunk = await this.cliServer.bqrsDecode(
resultsPath,
schema.name,
@@ -388,12 +387,13 @@ export class InterfaceManager extends DisposableObject {
// if there are interpreted results, but speculatively
// send anyway.
offset: schema.pagination?.offsets[0],
pageSize: RAW_RESULTS_PAGE_SIZE
pageSize
}
);
const resultSet = transformBqrsResultSet(schema, chunk);
const parsedResultSets: ParsedResultSets = {
pageNumber: 0,
pageSize,
numPages: numPagesOfResultSet(resultSet),
numInterpretedPages: numInterpretedPages(this._interpretation),
resultSet: { ...resultSet, t: 'RawResultSet' },
@@ -442,6 +442,7 @@ export class InterfaceManager extends DisposableObject {
metadata: this._displayedQuery.query.metadata,
pageNumber,
resultSetNames,
pageSize: PAGE_SIZE.getValue(),
numPages: numInterpretedPages(this._interpretation),
});
}
@@ -450,7 +451,7 @@ export class InterfaceManager extends DisposableObject {
const resultsPath = results.getResultsPath(selectedTable);
const schemas = await this.cliServer.bqrsInfo(
resultsPath,
RAW_RESULTS_PAGE_SIZE
PAGE_SIZE.getValue()
);
return schemas['result-sets'];
}
@@ -483,18 +484,20 @@ export class InterfaceManager extends DisposableObject {
if (schema === undefined)
throw new Error(`Query result set '${selectedTable}' not found.`);
const pageSize = PAGE_SIZE.getValue<number>();
const chunk = await this.cliServer.bqrsDecode(
results.getResultsPath(selectedTable, sorted),
schema.name,
{
offset: schema.pagination?.offsets[pageNumber],
pageSize: RAW_RESULTS_PAGE_SIZE
pageSize
}
);
const resultSet = transformBqrsResultSet(schema, chunk);
const parsedResultSets: ParsedResultSets = {
pageNumber,
pageSize,
resultSet: { t: 'RawResultSet', ...resultSet },
numPages: numPagesOfResultSet(resultSet),
numInterpretedPages: numInterpretedPages(this._interpretation),
@@ -559,8 +562,8 @@ export class InterfaceManager extends DisposableObject {
function getPageOfRun(run: Sarif.Run): Sarif.Run {
return {
...run, results: run.results?.slice(
INTERPRETED_RESULTS_PAGE_SIZE * pageNumber,
INTERPRETED_RESULTS_PAGE_SIZE * (pageNumber + 1)
PAGE_SIZE.getValue<number>() * pageNumber,
PAGE_SIZE.getValue<number>() * (pageNumber + 1)
)
};
}

View File

@@ -21,3 +21,11 @@ class ExhaustivityCheckingError extends Error {
export function assertNever(value: never): never {
throw new ExhaustivityCheckingError(value);
}
/**
* Use to perform array filters where the predicate is asynchronous.
*/
export const asyncFilter = async function <T>(arr: T[], predicate: (arg0: T) => Promise<boolean>) {
const results = await Promise.all(arr.map(predicate));
return arr.filter((_, index) => results[index]);
};

View File

@@ -23,16 +23,6 @@ export type ResultSet = RawTableResultSet | PathTableResultSet;
*/
export const RAW_RESULTS_LIMIT = 10000;
/**
* Show this many rows in a raw result table at a time.
*/
export const RAW_RESULTS_PAGE_SIZE = 100;
/**
* Show this many rows in an interpreted results table at a time.
*/
export const INTERPRETED_RESULTS_PAGE_SIZE = 100;
export interface DatabaseInfo {
name: string;
databaseUri: string;
@@ -124,6 +114,7 @@ export interface ShowInterpretedPageMsg {
metadata?: QueryMetadata;
pageNumber: number;
numPages: number;
pageSize: number;
resultSetNames: string[];
}
@@ -352,6 +343,7 @@ export function getDefaultResultSetName(
export interface ParsedResultSets {
pageNumber: number;
pageSize: number;
numPages: number;
numInterpretedPages: number;
selectedTable?: string; // when undefined, means 'show default table'

View File

@@ -1,58 +0,0 @@
import { EventEmitter, Event, Uri, WorkspaceFolder, RelativePattern } from 'vscode';
import { MultiFileSystemWatcher } from './vscode-utils/multi-file-system-watcher';
import { CodeQLCliServer, QlpacksInfo } from './cli';
import { Discovery } from './discovery';
export interface QLPack {
name: string;
uri: Uri;
}
/**
* Service to discover all available QL packs in a workspace folder.
*/
export class QLPackDiscovery extends Discovery<QlpacksInfo> {
private readonly _onDidChangeQLPacks = this.push(new EventEmitter<void>());
private readonly watcher = this.push(new MultiFileSystemWatcher());
private _qlPacks: readonly QLPack[] = [];
constructor(
private readonly workspaceFolder: WorkspaceFolder,
private readonly cliServer: CodeQLCliServer
) {
super('QL Pack Discovery');
// Watch for any changes to `qlpack.yml` files in this workspace folder.
// TODO: The CLI server should tell us what paths to watch for.
this.watcher.addWatch(new RelativePattern(this.workspaceFolder, '**/qlpack.yml'));
this.watcher.addWatch(new RelativePattern(this.workspaceFolder, '**/.codeqlmanifest.json'));
this.push(this.watcher.onDidChange(this.handleQLPackFileChanged, this));
}
public get onDidChangeQLPacks(): Event<void> { return this._onDidChangeQLPacks.event; }
public get qlPacks(): readonly QLPack[] { return this._qlPacks; }
private handleQLPackFileChanged(_uri: Uri): void {
this.refresh();
}
protected discover(): Promise<QlpacksInfo> {
// Only look for QL packs in this workspace folder.
return this.cliServer.resolveQlpacks([this.workspaceFolder.uri.fsPath], []);
}
protected update(results: QlpacksInfo): void {
const qlPacks: QLPack[] = [];
for (const id in results) {
qlPacks.push(...results[id].map(fsPath => {
return {
name: id,
uri: Uri.file(fsPath)
};
}));
}
this._qlPacks = qlPacks;
this._onDidChangeQLPacks.fire();
}
}

View File

@@ -1,7 +1,6 @@
import * as path from 'path';
import { QLPackDiscovery, QLPack } from './qlpack-discovery';
import { Discovery } from './discovery';
import { EventEmitter, Event, Uri, RelativePattern, WorkspaceFolder, env, workspace } from 'vscode';
import { EventEmitter, Event, Uri, RelativePattern, WorkspaceFolder, env } from 'vscode';
import { MultiFileSystemWatcher } from './vscode-utils/multi-file-system-watcher';
import { CodeQLCliServer } from './cli';
@@ -29,9 +28,8 @@ export abstract class QLTestNode {
* A directory containing one or more QL tests or other test directories.
*/
export class QLTestDirectory extends QLTestNode {
private _children: QLTestNode[] = [];
constructor(_path: string, _name: string) {
constructor(_path: string, _name: string, private _children: QLTestNode[] = []) {
super(_path, _name);
}
@@ -55,10 +53,23 @@ export class QLTestDirectory extends QLTestNode {
}
public finish(): void {
// remove empty directories
this._children.filter(child =>
child instanceof QLTestFile || child.children.length > 0
);
this._children.sort((a, b) => a.name.localeCompare(b.name, env.language));
for (const child of this._children) {
this._children.forEach((child, i) => {
child.finish();
}
if (child.children?.length === 1 && child.children[0] instanceof QLTestDirectory) {
// collapse children
const replacement = new QLTestDirectory(
child.children[0].path,
child.name + ' / ' + child.children[0].name,
Array.from(child.children[0].children)
);
this._children[i] = replacement;
}
});
}
private createChildDirectory(name: string): QLTestDirectory {
@@ -96,14 +107,15 @@ export class QLTestFile extends QLTestNode {
*/
interface QLTestDiscoveryResults {
/**
* The root test directory for each QL pack that contains tests.
* A directory that contains one or more QL Tests, or other QLTestDirectories.
*/
testDirectories: QLTestDirectory[];
testDirectory: QLTestDirectory | undefined;
/**
* The list of file system paths to watch. If any of these paths changes, the discovery results
* may be out of date.
* The file system path to a directory to watch. If any ql or qlref file changes in
* this directory, then this signifies a change in tests.
*/
watchPaths: string[];
watchPath: string;
}
/**
@@ -112,31 +124,30 @@ interface QLTestDiscoveryResults {
export class QLTestDiscovery extends Discovery<QLTestDiscoveryResults> {
private readonly _onDidChangeTests = this.push(new EventEmitter<void>());
private readonly watcher: MultiFileSystemWatcher = this.push(new MultiFileSystemWatcher());
private _testDirectories: QLTestDirectory[] = [];
private _testDirectory: QLTestDirectory | undefined;
constructor(
private readonly qlPackDiscovery: QLPackDiscovery,
private readonly workspaceFolder: WorkspaceFolder,
private readonly cliServer: CodeQLCliServer
) {
super('QL Test Discovery');
this.push(this.qlPackDiscovery.onDidChangeQLPacks(this.handleDidChangeQLPacks, this));
this.push(this.watcher.onDidChange(this.handleDidChange, this));
}
/**
* Event to be fired when the set of discovered tests may have changed.
*/
public get onDidChangeTests(): Event<void> { return this._onDidChangeTests.event; }
public get onDidChangeTests(): Event<void> {
return this._onDidChangeTests.event;
}
/**
* The root test directory for each QL pack that contains tests.
* The root directory. There is at least one test in this directory, or
* in a subdirectory of this.
*/
public get testDirectories(): QLTestDirectory[] { return this._testDirectories; }
private handleDidChangeQLPacks(): void {
this.refresh();
public get testDirectory(): QLTestDirectory | undefined {
return this._testDirectory;
}
private handleDidChange(uri: Uri): void {
@@ -144,72 +155,45 @@ export class QLTestDiscovery extends Discovery<QLTestDiscoveryResults> {
this.refresh();
}
}
protected async discover(): Promise<QLTestDiscoveryResults> {
const testDirectories: QLTestDirectory[] = [];
const watchPaths: string[] = [];
const qlPacks = this.qlPackDiscovery.qlPacks;
for (const qlPack of qlPacks) {
//HACK: Assume that only QL packs whose name ends with '-tests' contain tests.
if (this.isRelevantQlPack(qlPack)) {
watchPaths.push(qlPack.uri.fsPath);
const testPackage = await this.discoverTests(qlPack.uri.fsPath, qlPack.name);
if (testPackage !== undefined) {
testDirectories.push(testPackage);
}
}
}
return { testDirectories, watchPaths };
const testDirectory = await this.discoverTests();
return {
testDirectory,
watchPath: this.workspaceFolder.uri.fsPath
};
}
protected update(results: QLTestDiscoveryResults): void {
this._testDirectories = results.testDirectories;
this._testDirectory = results.testDirectory;
// Watch for changes to any `.ql` or `.qlref` file in any of the QL packs that contain tests.
this.watcher.clear();
results.watchPaths.forEach(watchPath => {
this.watcher.addWatch(new RelativePattern(watchPath, '**/*.{ql,qlref}'));
});
this.watcher.addWatch(new RelativePattern(results.watchPath, '**/*.{ql,qlref}'));
this._onDidChangeTests.fire();
}
/**
* Only include qlpacks suffixed with '-tests' that are contained
* within the provided workspace folder.
*/
private isRelevantQlPack(qlPack: QLPack): boolean {
return qlPack.name.endsWith('-tests')
&& workspace.getWorkspaceFolder(qlPack.uri)?.index === this.workspaceFolder.index;
}
/**
* Discover all QL tests in the specified directory and its subdirectories.
* @param fullPath The full path of the test directory.
* @param name The display name to use for the returned `TestDirectory` object.
* @returns A `QLTestDirectory` object describing the contents of the directory, or `undefined` if
* no tests were found.
*/
private async discoverTests(fullPath: string, name: string): Promise<QLTestDirectory | undefined> {
private async discoverTests(): Promise<QLTestDirectory> {
const fullPath = this.workspaceFolder.uri.fsPath;
const name = this.workspaceFolder.name;
const resolvedTests = (await this.cliServer.resolveTests(fullPath))
.filter((testPath) => !QLTestDiscovery.ignoreTestPath(testPath));
if (resolvedTests.length === 0) {
return undefined;
const rootDirectory = new QLTestDirectory(fullPath, name);
for (const testPath of resolvedTests) {
const relativePath = path.normalize(path.relative(fullPath, testPath));
const dirName = path.dirname(relativePath);
const parentDirectory = rootDirectory.createDirectory(dirName);
parentDirectory.addChild(new QLTestFile(testPath, path.basename(testPath)));
}
else {
const rootDirectory = new QLTestDirectory(fullPath, name);
for (const testPath of resolvedTests) {
const relativePath = path.normalize(path.relative(fullPath, testPath));
const dirName = path.dirname(relativePath);
const parentDirectory = rootDirectory.createDirectory(dirName);
parentDirectory.addChild(new QLTestFile(testPath, path.basename(testPath)));
}
rootDirectory.finish();
rootDirectory.finish();
return rootDirectory;
}
return rootDirectory;
}
/**

View File

@@ -545,9 +545,7 @@ export class QueryHistoryManager extends DisposableObject {
private async tryOpenExternalFile(fileLocation: string) {
const uri = vscode.Uri.file(fileLocation);
try {
await vscode.window.showTextDocument(uri, {
preview: false
});
await vscode.window.showTextDocument(uri, { preview: false });
} catch (e) {
if (
e.message.includes(

View File

@@ -107,6 +107,11 @@ export class QueryServerClient extends DisposableObject {
if (this.config.debug) {
args.push('--debug', '--tuple-counting');
}
if (cli.shouldDebugQueryServer()) {
args.push('-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9010,server=y,suspend=n,quiet=y');
}
const child = cli.spawnServer(
this.config.codeQlPath,
'CodeQL query server',

View File

@@ -16,7 +16,6 @@ import { TestAdapterRegistrar } from 'vscode-test-adapter-util';
import { QLTestFile, QLTestNode, QLTestDirectory, QLTestDiscovery } from './qltest-discovery';
import { Event, EventEmitter, CancellationTokenSource, CancellationToken } from 'vscode';
import { DisposableObject } from './vscode-utils/disposable-object';
import { QLPackDiscovery } from './qlpack-discovery';
import { CodeQLCliServer } from './cli';
import { getOnDiskWorkspaceFolders } from './helpers';
import { testLogger } from './logging';
@@ -82,7 +81,6 @@ function changeExtension(p: string, ext: string): string {
* Test adapter for QL tests.
*/
export class QLTestAdapter extends DisposableObject implements TestAdapter {
private readonly qlPackDiscovery: QLPackDiscovery;
private readonly qlTestDiscovery: QLTestDiscovery;
private readonly _tests = this.push(
new EventEmitter<TestLoadStartedEvent | TestLoadFinishedEvent>());
@@ -97,9 +95,7 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
) {
super();
this.qlPackDiscovery = this.push(new QLPackDiscovery(workspaceFolder, cliServer));
this.qlTestDiscovery = this.push(new QLTestDiscovery(this.qlPackDiscovery, workspaceFolder, cliServer));
this.qlPackDiscovery.refresh();
this.qlTestDiscovery = this.push(new QLTestDiscovery(workspaceFolder, cliServer));
this.qlTestDiscovery.refresh();
this.push(this.qlTestDiscovery.onDidChangeTests(this.discoverTests, this));
@@ -160,20 +156,20 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
private discoverTests(): void {
this._tests.fire({ type: 'started' } as TestLoadStartedEvent);
const testDirectories = this.qlTestDiscovery.testDirectories;
const children = testDirectories.map(
testDirectory => QLTestAdapter.createTestSuiteInfo(testDirectory, testDirectory.name)
);
const testSuite: TestSuiteInfo = {
type: 'suite',
label: 'CodeQL',
id: '.',
children
};
const testDirectory = this.qlTestDiscovery.testDirectory;
let testSuite: TestSuiteInfo | undefined;
if (testDirectory?.children.length) {
const children = QLTestAdapter.createTestOrSuiteInfos(testDirectory.children);
testSuite = {
type: 'suite',
label: 'CodeQL',
id: testDirectory.path,
children
};
}
this._tests.fire({
type: 'finished',
suite: children.length > 0 ? testSuite : undefined
suite: testSuite
} as TestLoadFinishedEvent);
}
@@ -221,10 +217,26 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
cancellationToken: cancellationToken,
logger: testLogger
})) {
const state = event.pass
? 'passed'
: event.messages?.length
? 'errored'
: 'failed';
let message: string | undefined;
if (event.diff?.length) {
message = ['', `${state}: ${event.test}`, ...event.diff, ''].join('\n');
testLogger.log(message);
}
(event.diff || []).join('\n');
this._testStates.fire({
type: 'test',
state: event.pass ? 'passed' : 'failed',
test: event.test
state,
test: event.test,
message,
decorations: event.messages?.map(msg => ({
line: msg.position.line,
message: msg.message
}))
});
}
}

View File

@@ -1,10 +1,20 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import { Uri, TextDocumentShowOptions, commands, window } from 'vscode';
import {
TestHub,
TestController,
TestAdapter,
TestRunStartedEvent,
TestRunFinishedEvent,
TestEvent,
TestSuiteEvent
} from 'vscode-test-adapter-api';
import { showAndLogWarningMessage } from './helpers';
import { TestTreeNode } from './test-tree-node';
import { DisposableObject } from './vscode-utils/disposable-object';
import { UIService } from './vscode-utils/ui-service';
import { TestHub, TestController, TestAdapter, TestRunStartedEvent, TestRunFinishedEvent, TestEvent, TestSuiteEvent } from 'vscode-test-adapter-api';
import { QLTestAdapter, getExpectedFile, getActualFile } from './test-adapter';
import { logger } from './logging';
@@ -78,12 +88,17 @@ export class TestUIService extends UIService implements TestController {
preserveFocus: true,
preview: true
};
if (!await fs.pathExists(expectedPath)) {
showAndLogWarningMessage(`'${path.basename(expectedPath)}' does not exist. Creating an empty file.`);
await fs.createFile(expectedPath);
}
if (await fs.pathExists(actualPath)) {
const actualUri = Uri.file(actualPath);
await commands.executeCommand<void>('vscode.diff', expectedUri, actualUri,
`Expected vs. Actual for ${path.basename(testId)}`, options);
}
else {
} else {
await window.showTextDocument(expectedUri, options);
}
}

View File

@@ -6,7 +6,6 @@ import {
QueryMetadata,
ResultsPaths,
InterpretedResultsSortState,
RAW_RESULTS_PAGE_SIZE,
ResultSet,
ALERTS_TABLE_NAME,
SELECT_TABLE_NAME,
@@ -100,7 +99,7 @@ export class ResultTables
return resultSets;
}
private getResultSetNames(resultSets: ResultSet[]): string[] {
private getResultSetNames(): string[] {
return this.props.parsedResultSets.resultSetNames.concat([ALERTS_TABLE_NAME]);
}
@@ -169,7 +168,7 @@ export class ResultTables
getOffset(): number {
const { parsedResultSets } = this.props;
return parsedResultSets.pageNumber * RAW_RESULTS_PAGE_SIZE;
return parsedResultSets.pageNumber * parsedResultSets.pageSize;
}
renderPageButtons(): JSX.Element {
@@ -220,6 +219,8 @@ export class ResultTables
type="number"
size={3}
value={this.state.selectedPage}
min="1"
max={numPages}
onChange={onChange}
onBlur={e => choosePage(e.target.value)}
onKeyDown={e => {
@@ -239,7 +240,7 @@ export class ResultTables
render(): React.ReactNode {
const { selectedTable } = this.state;
const resultSets = this.getResultSets();
const resultSetNames = this.getResultSetNames(resultSets);
const resultSetNames = this.getResultSetNames();
const resultSet = resultSets.find(resultSet => resultSet.schema.name == selectedTable);
const nonemptyRawResults = resultSets.some(resultSet => resultSet.t == 'RawResultSet' && resultSet.rows.length > 0);

View File

@@ -105,6 +105,7 @@ class App extends React.Component<{}, ResultsViewState> {
resultsPath: '', // FIXME: Not used for interpreted, refactor so this is not needed
parsedResultSets: {
numPages: msg.numPages,
pageSize: msg.pageSize,
numInterpretedPages: msg.numPages,
resultSetNames: msg.resultSetNames,
pageNumber: msg.pageNumber,

View File

@@ -1,40 +1,60 @@
import 'vscode-test';
import 'mocha';
import * as path from 'path';
import { Uri, workspace } from 'vscode';
import { Uri, WorkspaceFolder } from 'vscode';
import { expect } from 'chai';
import { QLTestDiscovery } from '../../qltest-discovery';
describe('qltest-discovery', () => {
describe('isRelevantQlPack', () => {
it('should check if a qlpack is relevant', () => {
const qlTestDiscover: any = new QLTestDiscovery(
{ onDidChangeQLPacks: () => ({}) } as any,
{ index: 0 } as any,
{} as any
describe('discoverTests', () => {
it('should run discovery', async () => {
const baseUri = Uri.parse('file:/a/b');
const baseDir = baseUri.fsPath;
const cDir = Uri.parse('file:/a/b/c').fsPath;
const dFile = Uri.parse('file:/a/b/c/d.ql').fsPath;
const eFile = Uri.parse('file:/a/b/c/e.ql').fsPath;
const hDir = Uri.parse('file:/a/b/c/f/g/h').fsPath;
const iFile = Uri.parse('file:/a/b/c/f/g/h/i.ql').fsPath;
const qlTestDiscover = new QLTestDiscovery(
{
uri: baseUri,
name: 'My tests'
} as unknown as WorkspaceFolder,
{
resolveTests() {
return [
Uri.parse('file:/a/b/c/d.ql').fsPath,
Uri.parse('file:/a/b/c/e.ql').fsPath,
Uri.parse('file:/a/b/c/f/g/h/i.ql').fsPath
];
}
} as any
);
const uri = workspace.workspaceFolders![0].uri;
expect(qlTestDiscover.isRelevantQlPack({
name: '-hucairz',
uri
})).to.be.false;
const result = await (qlTestDiscover as any).discover();
expect(result.watchPath).to.eq(baseDir);
expect(result.testDirectory.path).to.eq(baseDir);
expect(result.testDirectory.name).to.eq('My tests');
expect(qlTestDiscover.isRelevantQlPack({
name: '-tests',
uri: Uri.file('/a/b/')
})).to.be.false;
let children = result.testDirectory.children;
expect(children[0].path).to.eq(cDir);
expect(children[0].name).to.eq('c');
expect(children.length).to.eq(1);
expect(qlTestDiscover.isRelevantQlPack({
name: '-tests',
uri
})).to.be.true;
children = children[0].children;
expect(children[0].path).to.eq(dFile);
expect(children[0].name).to.eq('d.ql');
expect(children[1].path).to.eq(eFile);
expect(children[1].name).to.eq('e.ql');
expect(qlTestDiscover.isRelevantQlPack({
name: '-tests',
uri: Uri.file(path.join(uri.fsPath, 'other'))
})).to.be.true;
// A merged foler
expect(children[2].path).to.eq(hDir);
expect(children[2].name).to.eq('f / g / h');
expect(children.length).to.eq(3);
children = children[2].children;
expect(children[0].path).to.eq(iFile);
expect(children[0].name).to.eq('i.ql');
});
});
});

View File

@@ -65,6 +65,66 @@ describe('AstBuilder', () => {
)).to.deep.eq(expectedRoots);
});
it('should build an AST child without edge label', async () => {
// just test one of the children to make sure that the structure is right
// this label should only come from the node, not the edge
const astBuilder = createAstBuilder();
const roots = await astBuilder.getRoots();
expect(roots[0].children[0].parent).to.eq(roots[0]);
// break the recursion
(roots[0].children[0] as any).parent = undefined;
(roots[0].children[0] as any).children = undefined;
const child = {
children: undefined,
fileLocation: undefined,
id: 26359,
label: 'params',
location: {
endColumn: 22,
endLine: 19,
startColumn: 5,
startLine: 19,
uri: 'file:/opt/src/arch/sandbox/lib/interrupts.c'
},
order: 0,
parent: undefined
};
expect(roots[0].children[0]).to.deep.eq(child);
});
it('should build an AST child with edge label', async () => {
// just test one of the children to make sure that the structure is right
// this label should only come from both the node and the edge
const astBuilder = createAstBuilder();
const roots = await astBuilder.getRoots();
expect(roots[0].children[1].parent).to.eq(roots[0]);
// break the recursion
(roots[0].children[1] as any).parent = undefined;
(roots[0].children[1] as any).children = undefined;
const child = {
children: undefined,
fileLocation: undefined,
id: 26367,
label: 'body: [Block] { ... }',
location: {
endColumn: 1,
endLine: 22,
startColumn: 1,
startLine: 20,
uri: 'file:/opt/src/arch/sandbox/lib/interrupts.c'
},
order: 2,
parent: undefined
};
expect(roots[0].children[1]).to.deep.eq(child);
});
it('should fail when graphProperties are not correct', async () => {
overrides.graphProperties = {
tuples: [

View File

@@ -18,7 +18,7 @@
[
{
"id": 26359,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -28,12 +28,12 @@
}
},
"semmle.label",
""
"params"
],
[
{
"id": 26360,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -48,7 +48,7 @@
[
{
"id": 26361,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -63,7 +63,7 @@
[
{
"id": 0,
"label": "[TopLevelFunction] int disable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] int disable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -78,7 +78,7 @@
[
{
"id": 0,
"label": "[TopLevelFunction] int disable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] int disable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -93,7 +93,7 @@
[
{
"id": 26363,
"label": "[TopLevelFunction] void enable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] void enable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -108,7 +108,7 @@
[
{
"id": 26363,
"label": "[TopLevelFunction] void enable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] void enable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -123,7 +123,7 @@
[
{
"id": 26364,
"label": "[TopLevelFunction] int interrupt_init()",
"label": "SHOULD NOT USE [TopLevelFunction] int interrupt_init()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -138,7 +138,7 @@
[
{
"id": 26364,
"label": "[TopLevelFunction] int interrupt_init()",
"label": "SHOULD NOT USE [TopLevelFunction] int interrupt_init()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -153,7 +153,7 @@
[
{
"id": 26365,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -168,7 +168,7 @@
[
{
"id": 26365,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -183,7 +183,7 @@
[
{
"id": 26365,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -198,7 +198,7 @@
[
{
"id": 26365,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -213,7 +213,7 @@
[
{
"id": 26366,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -228,7 +228,7 @@
[
{
"id": 26367,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 20,
@@ -243,7 +243,7 @@
[
{
"id": 26368,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 17,
@@ -258,7 +258,7 @@
[
{
"id": 26369,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 16,
@@ -273,7 +273,7 @@
[
{
"id": 26370,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -288,7 +288,7 @@
[
{
"id": 26370,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -303,7 +303,7 @@
[
{
"id": 26370,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -318,7 +318,7 @@
[
{
"id": 26370,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -333,7 +333,7 @@
[
{
"id": 26371,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -348,7 +348,7 @@
[
{
"id": 26372,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 11,
@@ -385,7 +385,7 @@
[
{
"id": 0,
"label": "[TopLevelFunction] int disable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] int disable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -396,7 +396,7 @@
},
{
"id": 26359,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -406,12 +406,12 @@
}
},
"semmle.label",
"params"
"1234"
],
[
{
"id": 0,
"label": "[TopLevelFunction] int disable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] int disable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -422,7 +422,7 @@
},
{
"id": 26359,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -437,7 +437,7 @@
[
{
"id": 0,
"label": "[TopLevelFunction] int disable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] int disable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -448,7 +448,7 @@
},
{
"id": 26367,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 20,
@@ -463,7 +463,7 @@
[
{
"id": 0,
"label": "[TopLevelFunction] int disable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] int disable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -474,7 +474,7 @@
},
{
"id": 26367,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 20,
@@ -489,7 +489,7 @@
[
{
"id": 26363,
"label": "[TopLevelFunction] void enable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] void enable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -500,7 +500,7 @@
},
{
"id": 26360,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -515,7 +515,7 @@
[
{
"id": 26363,
"label": "[TopLevelFunction] void enable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] void enable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -526,7 +526,7 @@
},
{
"id": 26360,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -541,7 +541,7 @@
[
{
"id": 26363,
"label": "[TopLevelFunction] void enable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] void enable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -552,7 +552,7 @@
},
{
"id": 26369,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 16,
@@ -567,7 +567,7 @@
[
{
"id": 26363,
"label": "[TopLevelFunction] void enable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] void enable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -578,7 +578,7 @@
},
{
"id": 26369,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 16,
@@ -593,7 +593,7 @@
[
{
"id": 26364,
"label": "[TopLevelFunction] int interrupt_init()",
"label": "SHOULD NOT USE [TopLevelFunction] int interrupt_init()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -604,7 +604,7 @@
},
{
"id": 26361,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -619,7 +619,7 @@
[
{
"id": 26364,
"label": "[TopLevelFunction] int interrupt_init()",
"label": "SHOULD NOT USE [TopLevelFunction] int interrupt_init()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -630,7 +630,7 @@
},
{
"id": 26361,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -645,7 +645,7 @@
[
{
"id": 26364,
"label": "[TopLevelFunction] int interrupt_init()",
"label": "SHOULD NOT USE [TopLevelFunction] int interrupt_init()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -656,7 +656,7 @@
},
{
"id": 26372,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 11,
@@ -671,7 +671,7 @@
[
{
"id": 26364,
"label": "[TopLevelFunction] int interrupt_init()",
"label": "SHOULD NOT USE [TopLevelFunction] int interrupt_init()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -682,7 +682,7 @@
},
{
"id": 26372,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 11,
@@ -697,7 +697,7 @@
[
{
"id": 26366,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -708,7 +708,7 @@
},
{
"id": 26365,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -723,7 +723,7 @@
[
{
"id": 26366,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -734,7 +734,7 @@
},
{
"id": 26365,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -749,7 +749,7 @@
[
{
"id": 26367,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 20,
@@ -760,7 +760,7 @@
},
{
"id": 26366,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -775,7 +775,7 @@
[
{
"id": 26367,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 20,
@@ -786,7 +786,7 @@
},
{
"id": 26366,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -801,7 +801,7 @@
[
{
"id": 26369,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 16,
@@ -812,7 +812,7 @@
},
{
"id": 26368,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 17,
@@ -827,7 +827,7 @@
[
{
"id": 26369,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 16,
@@ -838,7 +838,7 @@
},
{
"id": 26368,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 17,
@@ -853,7 +853,7 @@
[
{
"id": 26371,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -864,7 +864,7 @@
},
{
"id": 26370,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -879,7 +879,7 @@
[
{
"id": 26371,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -890,7 +890,7 @@
},
{
"id": 26370,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -905,7 +905,7 @@
[
{
"id": 26372,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 11,
@@ -916,7 +916,7 @@
},
{
"id": 26371,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -931,7 +931,7 @@
[
{
"id": 26372,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 11,
@@ -942,7 +942,7 @@
},
{
"id": 26371,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,

View File

@@ -49,11 +49,59 @@ describe('databases-ui', () => {
const parentDir = path.join(dir, 'db-hucairz');
const dbDir = path.join(parentDir, 'db-javascript');
const file = path.join(dbDir, 'nested');
await fs.mkdirs(dbDir);
await fs.createFile(file);
fs.mkdirsSync(dbDir);
fs.createFileSync(file);
const uri = await fixDbUri(Uri.file(file));
expect(uri.toString()).to.eq(Uri.file(parentDir).toString());
});
});
it('should delete orphaned databases', async () => {
const storageDir = tmp.dirSync().name;
const db1 = createDatabase(storageDir, 'db1-imported', 'cpp');
const db2 = createDatabase(storageDir, 'db2-notimported', 'cpp');
const db3 = createDatabase(storageDir, 'db3-invalidlanguage', 'hucairz');
// these two should be deleted
const db4 = createDatabase(storageDir, 'db2-notimported-with-db-info', 'cpp', '.dbinfo');
const db5 = createDatabase(storageDir, 'db2-notimported-with-codeql-database.yml', 'cpp', 'codeql-database.yml');
const databaseUI = new DatabaseUI(
{} as any,
{
databaseItems: [
{ databaseUri: Uri.file(db1) }
],
onDidChangeDatabaseItem: () => { /**/ },
onDidChangeCurrentDatabaseItem: () => { /**/ },
} as any,
{} as any,
storageDir,
storageDir
);
await databaseUI.handleRemoveOrphanedDatabases();
expect(fs.pathExistsSync(db1)).to.be.true;
expect(fs.pathExistsSync(db2)).to.be.true;
expect(fs.pathExistsSync(db3)).to.be.true;
expect(fs.pathExistsSync(db4)).to.be.false;
expect(fs.pathExistsSync(db5)).to.be.false;
databaseUI.dispose();
});
function createDatabase(storageDir: string, dbName: string, language: string, extraFile?: string) {
const parentDir = path.join(storageDir, dbName);
const dbDir = path.join(parentDir, `db-${language}`);
fs.mkdirsSync(dbDir);
if (extraFile) {
fs.createFileSync(path.join(parentDir, extraFile));
}
return parentDir;
}
});

View File

@@ -9,7 +9,8 @@ import {
DatabaseItem,
DatabaseManager,
DatabaseItemImpl,
DatabaseContents
DatabaseContents,
isLikelyDbLanguageFolder
} from '../../databases';
import { QueryServerConfig } from '../../config';
import { Logger } from '../../logging';
@@ -24,8 +25,9 @@ describe('databases', () => {
databaseManager = new DatabaseManager(
{
workspaceState: {
update: updateSpy
}
update: updateSpy,
get: sinon.stub()
},
} as unknown as ExtensionContext,
{} as QueryServerConfig,
{} as Logger,
@@ -81,7 +83,7 @@ describe('databases', () => {
expect(mockDbItem.name).to.eq('new name');
expect(updateSpy).to.have.been.calledWith('databaseList', ['new name']);
expect(spy).to.have.been.calledWith({
item: mockDbItem,
item: undefined,
kind: DatabaseEventKind.Rename
});
});
@@ -178,4 +180,9 @@ describe('databases', () => {
);
}
});
it('should find likely db language folders', () => {
expect(isLikelyDbLanguageFolder('db-javascript')).to.be.true;
expect(isLikelyDbLanguageFolder('dbnot-a-db')).to.be.false;
});
});

View File

@@ -0,0 +1,106 @@
import 'vscode-test';
import 'mocha';
import * as sinon from 'sinon';
import { Uri, WorkspaceFolder } from 'vscode';
import { expect } from 'chai';
import { QLTestAdapter } from '../../test-adapter';
import { CodeQLCliServer } from '../../cli';
describe('test-adapter', () => {
let adapter: QLTestAdapter;
let runTestsSpy: sinon.SinonStub;
let resolveTestsSpy: sinon.SinonStub;
let resolveQlpacksSpy: sinon.SinonStub;
let sandox: sinon.SinonSandbox;
beforeEach(() => {
sandox = sinon.createSandbox();
mockRunTests();
resolveQlpacksSpy = sandox.stub().resolves({});
resolveTestsSpy = sandox.stub().resolves([]);
adapter = new QLTestAdapter({
name: 'ABC',
uri: Uri.parse('file:/ab/c')
} as WorkspaceFolder, {
runTests: runTestsSpy,
resolveQlpacks: resolveQlpacksSpy,
resolveTests: resolveTestsSpy
} as unknown as CodeQLCliServer);
});
afterEach(() => {
sandox.restore();
});
it('should run some tests', async () => {
const listenerSpy = sandox.spy();
adapter.testStates(listenerSpy);
const testsPath = Uri.parse('file:/ab/c').fsPath;
const dPath = Uri.parse('file:/ab/c/d.ql').fsPath;
const gPath = Uri.parse('file:/ab/c/e/f/g.ql').fsPath;
const hPath = Uri.parse('file:/ab/c/e/f/h.ql').fsPath;
await adapter.run([testsPath]);
expect(listenerSpy.getCall(0).args).to.deep.eq([
{ type: 'started', tests: [testsPath] }
]);
expect(listenerSpy.getCall(1).args).to.deep.eq([{
type: 'test',
state: 'passed',
test: dPath,
message: undefined,
decorations: []
}]);
expect(listenerSpy.getCall(2).args).to.deep.eq([{
type: 'test',
state: 'errored',
test: gPath,
message: `\nerrored: ${gPath}\npqr\nxyz\n`,
decorations: [
{ line: 1, message: 'abc' }
]
}]);
expect(listenerSpy.getCall(3).args).to.deep.eq([{
type: 'test',
state: 'failed',
test: hPath,
message: `\nfailed: ${hPath}\njkh\ntuv\n`,
decorations: []
}]);
expect(listenerSpy.getCall(4).args).to.deep.eq([{ type: 'finished' }]);
expect(listenerSpy).to.have.callCount(5);
});
function mockRunTests() {
// runTests is an async generator function. This is not directly supported in sinon
// However, we can pretend the same thing by just returning an async array.
runTestsSpy = sandox.stub();
runTestsSpy.returns(
(async function*() {
yield Promise.resolve({
test: Uri.parse('file:/ab/c/d.ql').fsPath,
pass: true,
messages: []
});
yield Promise.resolve({
test: Uri.parse('file:/ab/c/e/f/g.ql').fsPath,
pass: false,
diff: ['pqr', 'xyz'],
// a compile error
messages: [
{ position: { line: 1 }, message: 'abc' }
]
});
yield Promise.resolve({
test: Uri.parse('file:/ab/c/e/f/h.ql').fsPath,
pass: false,
diff: ['jkh', 'tuv'],
messages: []
});
})()
);
}
});

View File

@@ -0,0 +1,22 @@
import { fail } from 'assert';
import { expect } from 'chai';
import { asyncFilter } from '../../src/pure/helpers-pure';
describe('helpers-pure', () => {
it('should filter asynchronously', async () => {
expect(await asyncFilter([1, 2, 3], x => Promise.resolve(x > 2))).to.deep.eq([3]);
});
it('should throw on error when filtering', async () => {
const rejects = (x: number) => x === 3
? Promise.reject(new Error('opps'))
: Promise.resolve(true);
try {
await asyncFilter([1, 2, 3], rejects);
fail('Should have thrown');
} catch (e) {
expect(e.message).to.eq('opps');
}
});
});

View File

@@ -6,9 +6,7 @@
"module": "commonjs",
"target": "es2017",
"outDir": "out",
"lib": [
"es6"
],
"lib": ["ES2020"],
"moduleResolution": "node",
"sourceMap": true,
"rootDir": "src",
@@ -21,12 +19,6 @@
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"test",
"**/view"
]
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "test", "**/view"]
}