Merge pull request #284 from aeisenberg/aeisenberg/log-files

feat: Save log files per query
This commit is contained in:
Andrew Eisenberg 2020-03-19 08:29:10 -07:00 коммит произвёл GitHub
Родитель a9eb0a40fd 66e9272525
Коммит 54ad3649b1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
24 изменённых файлов: 558 добавлений и 127 удалений

2
.github/workflows/main.yml поставляемый
Просмотреть файл

@ -80,7 +80,7 @@ jobs:
if: matrix.os == 'windows-latest'
run: |
cd extensions/ql-vscode
$env:CODEQL_PATH=$(Join-Path $env:GITHUB_WORKSPACE -ChildPath 'codeql-home/codeql/codeql.cmd')
$env:CODEQL_PATH=$(Join-Path $env:GITHUB_WORKSPACE -ChildPath 'codeql-home/codeql/codeql.exe')
npm run test
- name: Run integration tests (Linux)

3
.vscode/launch.json поставляемый
Просмотреть файл

@ -8,7 +8,8 @@
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql"
"--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql",
"${workspaceRoot}/../vscode-codeql-starter/vscode-codeql-starter.code-workspace"
],
"stopOnEntry": false,
"sourceMaps": true,

Просмотреть файл

@ -22,9 +22,12 @@ dependencies:
'@types/node': 12.12.30
'@types/node-fetch': 2.5.5
'@types/npm-packlist': 1.1.1
'@types/proxyquire': 1.3.28
'@types/react': 16.9.23
'@types/react-dom': 16.9.5
'@types/sarif': 2.1.2
'@types/sinon': 7.5.2
'@types/sinon-chai': 3.2.3
'@types/through2': 2.0.34
'@types/tmp': 0.1.0
'@types/unzipper': 0.10.2
@ -57,10 +60,12 @@ dependencies:
npm-packlist: 1.4.8
npm-run-all: 4.1.5
plugin-error: 1.0.1
proxyquire: 2.1.3
react: 16.13.0
react-dom: 16.13.0_react@16.13.0
reflect-metadata: 0.1.13
sinon: 9.0.1
sinon-chai: 3.5.0_chai@4.2.0+sinon@9.0.1
style-loader: 0.23.1
through2: 3.0.1
tmp: 0.1.0
@ -453,6 +458,10 @@ packages:
dev: false
resolution:
integrity: sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
/@types/proxyquire/1.3.28:
dev: false
resolution:
integrity: sha512-SQaNzWQ2YZSr7FqAyPPiA3FYpux2Lqh3HWMZQk47x3xbMCqgC/w0dY3dw9rGqlweDDkrySQBcaScXWeR+Yb11Q==
/@types/react-dom/16.9.5:
dependencies:
'@types/react': 16.9.23
@ -470,6 +479,17 @@ packages:
dev: false
resolution:
integrity: sha512-TELZl5h48KaB6SFZqTuaMEw1hrGuusbBcH+yfMaaHdS2pwDr3RTH4CVN0LyY1kqSiDm9PPvAMx8FJ0LUZreOCQ==
/@types/sinon-chai/3.2.3:
dependencies:
'@types/chai': 4.2.11
'@types/sinon': 7.5.2
dev: false
resolution:
integrity: sha512-TOUFS6vqS0PVL1I8NGVSNcFaNJtFoyZPXZ5zur+qlhDfOmQECZZM4H4kKgca6O8L+QceX/ymODZASfUfn+y4yQ==
/@types/sinon/7.5.2:
dev: false
resolution:
integrity: sha512-T+m89VdXj/eidZyejvmoP9jivXgBDdkOSBVQjU9kF349NEx10QdPNGxHeZUaj1IlJ32/ewdyXJjnJxyxJroYwg==
/@types/source-list-map/0.1.2:
dev: false
resolution:
@ -2595,6 +2615,15 @@ packages:
optional: true
resolution:
integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
/fill-keys/1.0.2:
dependencies:
is-object: 1.0.1
merge-descriptors: 1.0.1
dev: false
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=
/fill-range/4.0.0:
dependencies:
extend-shallow: 2.0.1
@ -3557,6 +3586,10 @@ packages:
node: '>=0.10.0'
resolution:
integrity: sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==
/is-object/1.0.1:
dev: false
resolution:
integrity: sha1-iVJojF7C/9awPsyF52ngKQMINHA=
/is-plain-obj/2.1.0:
dev: false
engines:
@ -4089,6 +4122,10 @@ packages:
node: '>= 0.10.0'
resolution:
integrity: sha1-htcJCzDORV1j+64S3aUaR93K+bI=
/merge-descriptors/1.0.1:
dev: false
resolution:
integrity: sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
/micromatch/3.1.10:
dependencies:
arr-diff: 4.0.0
@ -4264,6 +4301,10 @@ packages:
hasBin: true
resolution:
integrity: sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==
/module-not-found-error/1.0.1:
dev: false
resolution:
integrity: sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=
/move-concurrently/1.0.1:
dependencies:
aproba: 1.2.0
@ -5118,6 +5159,14 @@ packages:
dev: false
resolution:
integrity: sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
/proxyquire/2.1.3:
dependencies:
fill-keys: 1.0.2
module-not-found-error: 1.0.1
resolve: 1.15.1
dev: false
resolution:
integrity: sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==
/prr/1.0.1:
dev: false
resolution:
@ -5674,6 +5723,16 @@ packages:
dev: false
resolution:
integrity: sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
/sinon-chai/3.5.0_chai@4.2.0+sinon@9.0.1:
dependencies:
chai: 4.2.0
sinon: 9.0.1
dev: false
peerDependencies:
chai: ^4.0.0
sinon: '>=4.0.0 <10.0.0'
resolution:
integrity: sha512-IifbusYiQBpUxxFJkR3wTU68xzBN0+bxCScEaKMjBvAQERg6FnTTc1F17rseLb1tjmkJ23730AXpFI0c47FgAg==
/sinon/9.0.1:
dependencies:
'@sinonjs/commons': 1.7.1
@ -7178,9 +7237,12 @@ packages:
'@types/mocha': 5.2.7
'@types/node': 12.12.30
'@types/node-fetch': 2.5.5
'@types/proxyquire': 1.3.28
'@types/react': 16.9.23
'@types/react-dom': 16.9.5
'@types/sarif': 2.1.2
'@types/sinon': 7.5.2
'@types/sinon-chai': 3.2.3
'@types/tmp': 0.1.0
'@types/unzipper': 0.10.2
'@types/vscode': 1.43.0
@ -7206,9 +7268,11 @@ packages:
mocha-sinon: 2.1.0
node-fetch: 2.6.0
npm-run-all: 4.1.5
proxyquire: 2.1.3
react: 16.13.0
react-dom: 16.13.0_react@16.13.0
sinon: 9.0.1
sinon-chai: 3.5.0_chai@4.2.0+sinon@9.0.1
style-loader: 0.23.1
through2: 3.0.1
tmp: 0.1.0
@ -7230,9 +7294,10 @@ packages:
dev: false
name: '@rush-temp/vscode-codeql'
resolution:
integrity: sha512-XEQBLdsSWxofNXxh6Uwah2wvOUgMi/yGuU2afjauGOegrsH1G2DdKZ4kbbhfxKsEE7xxlQb4G8mu7SqjPt1Dag==
integrity: sha512-PvC3L2Tp+VYm+hMzTgXfdBJPLJopSQpVsT8Ym7kdIxZj/cyTWzO3A+n7HnrH5q/B4DJSRfiDwjp73GwjGhbteQ==
tarball: 'file:projects/vscode-codeql.tgz'
version: 0.0.0
registry: ''
specifiers:
'@microsoft/node-core-library': ~3.13.0
'@microsoft/rush-lib': ~5.20.0
@ -7257,9 +7322,12 @@ specifiers:
'@types/node': ^12.0.8
'@types/node-fetch': ~2.5.2
'@types/npm-packlist': ~1.1.1
'@types/proxyquire': ~1.3.28
'@types/react': ^16.8.17
'@types/react-dom': ^16.8.4
'@types/sarif': ~2.1.2
'@types/sinon': ~7.5.2
'@types/sinon-chai': ~3.2.3
'@types/through2': ~2.0.34
'@types/tmp': ^0.1.0
'@types/unzipper': ~0.10.1
@ -7292,10 +7360,12 @@ specifiers:
npm-packlist: ~1.4.4
npm-run-all: ^4.1.5
plugin-error: ^1.0.1
proxyquire: ~2.1.3
react: ^16.8.6
react-dom: ^16.8.6
reflect-metadata: ~0.1.13
sinon: ~9.0.0
sinon-chai: ~3.5.0
style-loader: ~0.23.1
through2: ^3.0.1
tmp: ^0.1.0

Просмотреть файл

@ -6,6 +6,7 @@ module.exports = {
ecmaFeatures: {
modules: true,
},
project: ['tsconfig.json', './src/**/tsconfig.json'],
},
plugins: ['@typescript-eslint'],
env: {

Просмотреть файл

@ -99,7 +99,7 @@
"scope": "machine",
"type": "string",
"default": "",
"description": "Path to the CodeQL executable that should be used by the CodeQL extension. The executable is named `codeql` on Linux/Mac and `codeql.cmd` on Windows. This overrides all other CodeQL CLI settings."
"description": "Path to the CodeQL executable that should be used by the CodeQL extension. The executable is named `codeql` on Linux/Mac and `codeql.exe` on Windows. This overrides all other CodeQL CLI settings."
},
"codeQL.runningQueries.numberOfThreads": {
"type": "integer",
@ -208,6 +208,10 @@
"command": "codeQLQueryHistory.itemClicked",
"title": "Query History Item"
},
{
"command": "codeQLQueryHistory.showQueryLog",
"title": "Show Query Log"
},
{
"command": "codeQLQueryResults.nextPathStep",
"title": "CodeQL: Show Next Step on Path"
@ -272,6 +276,11 @@
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory"
},
{
"command": "codeQLQueryHistory.showQueryLog",
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory"
},
{
"command": "codeQLTests.showOutputDifferences",
"group": "qltest@1",
@ -328,6 +337,10 @@
"command": "codeQLQueryHistory.itemClicked",
"when": "false"
},
{
"command": "codeQLQueryHistory.showQueryLog",
"when": "false"
},
{
"command": "codeQLQueryHistory.setLabel",
"when": "false"
@ -447,6 +460,11 @@
"@typescript-eslint/eslint-plugin": "~2.23.0",
"@typescript-eslint/parser": "~2.23.0",
"chai-as-promised": "~7.1.1",
"@types/chai-as-promised": "~7.1.2"
"@types/chai-as-promised": "~7.1.2",
"@types/sinon": "~7.5.2",
"sinon-chai": "~3.5.0",
"@types/sinon-chai": "~3.2.3",
"proxyquire": "~2.1.3",
"@types/proxyquire": "~1.3.28"
}
}

Просмотреть файл

@ -51,7 +51,10 @@ export type Entry = File | Directory;
*/
export type DirectoryHierarchyMap = Map<string, Map<string, vscode.FileType>>;
export type ZipFileReference = { sourceArchiveZipPath: string, pathWithinSourceArchive: string };
export type ZipFileReference = {
sourceArchiveZipPath: string;
pathWithinSourceArchive: string;
};
/** Encodes a reference to a source file within a zipped source archive into a single URI. */
export function encodeSourceArchiveUri(ref: ZipFileReference): vscode.Uri {
@ -169,7 +172,7 @@ export class ArchiveFileSystemProvider implements vscode.FileSystemProvider {
async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {
const ref = decodeSourceArchiveUri(uri);
const archive = await this.getArchive(ref.sourceArchiveZipPath);
let contents = archive.dirMap.get(ref.pathWithinSourceArchive);
const contents = archive.dirMap.get(ref.pathWithinSourceArchive);
const result = contents === undefined ? [] : Array.from(contents.entries());
if (result === undefined) {
throw vscode.FileSystemError.FileNotFound(uri);
@ -239,7 +242,7 @@ export class ArchiveFileSystemProvider implements vscode.FileSystemProvider {
}
private async _lookupAsFile(uri: vscode.Uri): Promise<File> {
let entry = await this._lookup(uri);
const entry = await this._lookup(uri);
if (entry instanceof File) {
return entry;
}

Просмотреть файл

@ -243,11 +243,12 @@ export class CodeQLCliServer implements Disposable {
// Kill the process if it isn't already dead.
this.killProcessIfRunning();
// Report the error (if there is a stderr then use that otherwise just report the error cod or nodejs error)
if (stderrBuffers.length == 0) {
throw new Error(`${description} failed: ${err}`)
} else {
throw new Error(`${description} failed: ${Buffer.concat(stderrBuffers).toString("utf8")}`);
}
const newError =
stderrBuffers.length == 0
? new Error(`${description} failed: ${err}`)
: new Error(`${description} failed: ${Buffer.concat(stderrBuffers).toString("utf8")}`);
newError.stack += (err.stack || '');
throw newError;
} finally {
this.logger.log(Buffer.concat(stderrBuffers).toString("utf8"));
// Remove the listeners we set up.
@ -604,7 +605,7 @@ export class CodeQLCliServer implements Disposable {
resolveQlpacks(additionalPacks: string[], searchPath?: string[]): Promise<QlpacksInfo> {
const args = ['--additional-packs', additionalPacks.join(path.delimiter)];
if (searchPath !== undefined) {
args.push('--search-path', searchPath.join(path.delimiter));
args.push('--search-path', path.join(...searchPath));
}
return this.runJsonCodeQlCliCommand<QlpacksInfo>(

Просмотреть файл

@ -64,11 +64,11 @@ const DEBUG_SETTING = new Setting('debug', RUNNING_QUERIES_SETTING);
const QUERY_SERVER_RESTARTING_SETTINGS = [NUMBER_OF_THREADS_SETTING, MEMORY_SETTING, DEBUG_SETTING];
export interface QueryServerConfig {
codeQlPath: string,
debug: boolean,
numThreads: number,
queryMemoryMb?: number,
timeoutSecs: number,
codeQlPath: string;
debug: boolean;
numThreads: number;
queryMemoryMb?: number;
timeoutSecs: number;
onDidChangeQueryServerConfiguration?: Event<void>;
}
@ -76,7 +76,7 @@ export interface QueryServerConfig {
const QUERY_HISTORY_SETTINGS = [QUERY_HISTORY_FORMAT_SETTING];
export interface QueryHistoryConfig {
format: string,
format: string;
onDidChangeQueryHistoryConfiguration: Event<void>;
}
@ -111,7 +111,7 @@ abstract class ConfigListener extends DisposableObject {
export class DistributionConfigListener extends ConfigListener implements DistributionConfig {
public get customCodeQlPath(): string | undefined {
return CUSTOM_CODEQL_PATH_SETTING.getValue() ? CUSTOM_CODEQL_PATH_SETTING.getValue() : undefined;
return CUSTOM_CODEQL_PATH_SETTING.getValue() || undefined;
}
public get includePrerelease(): boolean {
@ -119,7 +119,7 @@ export class DistributionConfigListener extends ConfigListener implements Distri
}
public get personalAccessToken(): string | undefined {
return PERSONAL_ACCESS_TOKEN_SETTING.getValue() ? PERSONAL_ACCESS_TOKEN_SETTING.getValue() : undefined;
return PERSONAL_ACCESS_TOKEN_SETTING.getValue() || undefined;
}
public get onDidChangeDistributionConfiguration(): Event<void> {

Просмотреть файл

@ -19,7 +19,7 @@ import { getCodeQlCliVersion, tryParseVersionString, Version } from "./cli-versi
/**
* Default value for the owner name of the extension-managed distribution on GitHub.
*
*
* We set the default here rather than as a default config value so that this default is invoked
* upon blanking the setting.
*/
@ -27,7 +27,7 @@ const DEFAULT_DISTRIBUTION_OWNER_NAME = "github";
/**
* Default value for the repository name of the extension-managed distribution on GitHub.
*
*
* We set the default here rather than as a default config value so that this default is invoked
* upon blanking the setting.
*/
@ -35,7 +35,7 @@ const DEFAULT_DISTRIBUTION_REPOSITORY_NAME = "codeql-cli-binaries";
/**
* Version constraint for the CLI.
*
*
* This applies to both extension-managed and CLI distributions.
*/
export const DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT: VersionConstraint = {
@ -46,8 +46,8 @@ export const DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT: VersionConstraint = {
}
export interface DistributionProvider {
getCodeQlPathWithoutVersionCheck(): Promise<string | undefined>,
onDidChangeDistribution?: Event<void>
getCodeQlPathWithoutVersionCheck(): Promise<string | undefined>;
onDidChangeDistribution?: Event<void>;
}
export class DistributionManager implements DistributionProvider {
@ -99,7 +99,7 @@ export class DistributionManager implements DistributionProvider {
*/
public async getCodeQlPathWithoutVersionCheck(): Promise<string | undefined> {
// Check config setting, then extension specific distribution, then PATH.
if (this._config.customCodeQlPath !== undefined) {
if (this._config.customCodeQlPath) {
if (!await fs.pathExists(this._config.customCodeQlPath)) {
showAndLogErrorMessage(`The CodeQL executable path is specified as "${this._config.customCodeQlPath}" ` +
"by a configuration setting, but a CodeQL executable could not be found at that path. Please check " +
@ -130,7 +130,7 @@ export class DistributionManager implements DistributionProvider {
/**
* Check for updates to the extension-managed distribution. If one has not already been installed,
* this will return an update available result with the latest available release.
*
*
* Returns a failed promise if an unexpected error occurs during installation.
*/
public async checkForUpdatesToExtensionManagedDistribution(
@ -152,7 +152,7 @@ export class DistributionManager implements DistributionProvider {
/**
* Installs a release of the extension-managed distribution.
*
*
* Returns a failed promise if an unexpected error occurs during installation.
*/
public installExtensionManagedDistributionRelease(release: Release,
@ -200,7 +200,7 @@ class ExtensionSpecificDistributionManager {
/**
* Check for updates to the extension-managed distribution. If one has not already been installed,
* this will return an update available result with the latest available release.
*
*
* Returns a failed promise if an unexpected error occurs during installation.
*/
public async checkForUpdatesToDistribution(): Promise<DistributionUpdateCheckResult> {
@ -216,7 +216,7 @@ class ExtensionSpecificDistributionManager {
/**
* Installs a release of the extension-managed distribution.
*
*
* Returns a failed promise if an unexpected error occurs during installation.
*/
public async installDistributionRelease(release: Release,
@ -247,8 +247,8 @@ class ExtensionSpecificDistributionManager {
if (progressCallback && contentLength !== null) {
const totalNumBytes = parseInt(contentLength, 10);
const bytesToDisplayMB = (numBytes: number) => `${(numBytes / (1024 * 1024)).toFixed(1)} MB`;
const updateProgress = () => {
const bytesToDisplayMB = (numBytes: number): string => `${(numBytes / (1024 * 1024)).toFixed(1)} MB`;
const updateProgress = (): void => {
progressCallback({
step: numBytesDownloaded,
maxStep: totalNumBytes,
@ -282,7 +282,7 @@ class ExtensionSpecificDistributionManager {
/**
* Remove the extension-managed distribution.
*
*
* This should not be called for a distribution that is currently in use, as remove may fail.
*/
private async removeDistribution(): Promise<void> {
@ -357,7 +357,7 @@ export class ReleasesApiConsumer {
this._repoName = repoName;
}
public async getLatestRelease(versionConstraint: VersionConstraint, includePrerelease: boolean = false): Promise<Release> {
public async getLatestRelease(versionConstraint: VersionConstraint, includePrerelease = false): Promise<Release> {
const apiPath = `/repos/${this._ownerName}/${this._repoName}/releases`;
const allReleases: GithubRelease[] = await (await this.makeApiCall(apiPath)).json();
const compatibleReleases = allReleases.filter(release => {
@ -428,7 +428,7 @@ export class ReleasesApiConsumer {
private async makeRawRequest(
requestUrl: string,
headers: { [key: string]: string },
redirectCount: number = 0): Promise<fetch.Response> {
redirectCount = 0): Promise<fetch.Response> {
const response = await fetch.default(requestUrl, {
headers,
redirect: "manual"
@ -480,7 +480,7 @@ export async function extractZipArchive(archivePath: string, outPath: string): P
/**
* Comparison of semantic versions.
*
*
* Returns a positive number if a is greater than b.
* Returns 0 if a equals b.
* Returns a negative number if a is less than b.
@ -502,7 +502,7 @@ export function versionCompare(a: Version, b: Version): number {
}
function codeQlLauncherName(): string {
return (os.platform() === "win32") ? "codeql.cmd" : "codeql";
return (os.platform() === "win32") ? "codeql.exe" : "codeql";
}
function isRedirectStatusCode(statusCode: number): boolean {
@ -520,13 +520,16 @@ export enum FindDistributionResultKind {
NoDistribution
}
export type FindDistributionResult = CompatibleDistributionResult | UnknownCompatibilityDistributionResult |
IncompatibleDistributionResult | NoDistributionResult;
export type FindDistributionResult =
| CompatibleDistributionResult
| UnknownCompatibilityDistributionResult
| IncompatibleDistributionResult
| NoDistributionResult;
interface CompatibleDistributionResult {
codeQlPath: string;
kind: FindDistributionResultKind.CompatibleDistribution;
version: Version
version: Version;
}
interface UnknownCompatibilityDistributionResult {
@ -551,11 +554,14 @@ export enum DistributionUpdateCheckResultKind {
UpdateAvailable
}
type DistributionUpdateCheckResult = AlreadyCheckedRecentlyResult | AlreadyUpToDateResult | InvalidLocationResult |
UpdateAvailableResult;
type DistributionUpdateCheckResult =
| AlreadyCheckedRecentlyResult
| AlreadyUpToDateResult
| InvalidLocationResult
| UpdateAvailableResult;
export interface AlreadyCheckedRecentlyResult {
kind: DistributionUpdateCheckResultKind.AlreadyCheckedRecentlyResult
kind: DistributionUpdateCheckResultKind.AlreadyCheckedRecentlyResult;
}
export interface AlreadyUpToDateResult {

Просмотреть файл

@ -76,10 +76,10 @@ function registerErrorStubs(excludedCommands: string[], stubGenerator: (command:
}
export async function activate(ctx: ExtensionContext): Promise<void> {
// Initialise logging, and ensure all loggers are disposed upon exit.
ctx.subscriptions.push(logger);
logger.log('Starting CodeQL extension');
initializeLogging(ctx);
const distributionConfigListener = new DistributionConfigListener();
ctx.subscriptions.push(distributionConfigListener);
const distributionManager = new DistributionManager(ctx, distributionConfigListener, DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT);
@ -238,10 +238,6 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu
const qlConfigurationListener = await QueryServerConfigListener.createQueryServerConfigListener(distributionManager);
ctx.subscriptions.push(qlConfigurationListener);
ctx.subscriptions.push(queryServerLogger);
ctx.subscriptions.push(ideServerLogger);
const cliServer = new CodeQLCliServer(distributionManager, logger);
ctx.subscriptions.push(cliServer);
@ -327,4 +323,13 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu
ctx.subscriptions.push(client.start());
}
function initializeLogging(ctx: ExtensionContext): void {
logger.init(ctx);
queryServerLogger.init(ctx);
ideServerLogger.init(ctx);
ctx.subscriptions.push(logger);
ctx.subscriptions.push(queryServerLogger);
ctx.subscriptions.push(ideServerLogger);
}
const checkForUpdatesCommand = 'codeQL.checkForUpdatesToCLI';

Просмотреть файл

@ -17,8 +17,8 @@ export async function spawnIdeServer(config: QueryServerConfig): Promise<StreamI
['execute', 'language-server'],
['--check-errors', 'ON_CHANGE'],
ideServerLogger,
data => ideServerLogger.logWithoutTrailingNewline(data.toString()),
data => ideServerLogger.logWithoutTrailingNewline(data.toString()),
data => ideServerLogger.log(data.toString(), { trailingNewline: false }),
data => ideServerLogger.log(data.toString(), { trailingNewline: false }),
progressReporter
);
return { writer: child.stdin!, reader: child.stdout! };

Просмотреть файл

@ -1,17 +1,36 @@
import { window as Window, OutputChannel, Progress } from 'vscode';
import { window as Window, OutputChannel, Progress, ExtensionContext, Disposable } from 'vscode';
import { DisposableObject } from 'semmle-vscode-utils';
import * as fs from 'fs-extra';
import * as path from 'path';
interface LogOptions {
/** If false, don't output a trailing newline for the log entry. Default true. */
trailingNewline?: boolean;
/** If specified, add this log entry to the log file at the specified location. */
additionalLogLocation?: string;
}
export interface Logger {
/** Writes the given log message, followed by a newline. */
log(message: string): void;
/** Writes the given log message, not followed by a newline. */
logWithoutTrailingNewline(message: string): void;
/** Writes the given log message, optionally followed by a newline. */
log(message: string, options?: LogOptions): Promise<void>;
/**
* Reveal this channel in the UI.
*
* @param preserveFocus When `true` the channel will not take focus.
*/
show(preserveFocus?: boolean): void;
/**
* Remove the log at the specified location
* @param location log to remove
*/
removeAdditionalLogLocation(location: string | undefined): void;
/**
* The base location location where all side log files are stored.
*/
getBaseLocation(): string | undefined;
}
export type ProgressReporter = Progress<{ message: string }>;
@ -19,24 +38,98 @@ export type ProgressReporter = Progress<{ message: string }>;
/** A logger that writes messages to an output channel in the Output tab. */
export class OutputChannelLogger extends DisposableObject implements Logger {
public readonly outputChannel: OutputChannel;
private readonly additionalLocations = new Map<string, AdditionalLogLocation>();
private additionalLogLocationPath: string | undefined;
constructor(title: string) {
constructor(private title: string) {
super();
this.outputChannel = Window.createOutputChannel(title);
this.push(this.outputChannel);
}
log(message: string) {
this.outputChannel.appendLine(message);
init(ctx: ExtensionContext): void {
this.additionalLogLocationPath = path.join(ctx.storagePath || ctx.globalStoragePath, this.title);
// clear out any old state from previous runs
fs.remove(this.additionalLogLocationPath);
}
logWithoutTrailingNewline(message: string) {
this.outputChannel.append(message);
/**
* This function is asynchronous and will only resolve once the message is written
* to the side log (if required). It is not necessary to await the results of this
* function if you don't need to guarantee that the log writing is complete before
* continuing.
*/
async log(message: string, options = { } as LogOptions): Promise<void> {
if (options.trailingNewline === undefined) {
options.trailingNewline = true;
}
if (options.trailingNewline) {
this.outputChannel.appendLine(message);
} else {
this.outputChannel.append(message);
}
if (this.additionalLogLocationPath && options.additionalLogLocation) {
const logPath = path.join(this.additionalLogLocationPath, options.additionalLogLocation);
let additional = this.additionalLocations.get(logPath);
if (!additional) {
const msg = `| Log being saved to ${logPath} |`;
const separator = new Array(msg.length).fill('-').join('');
this.outputChannel.appendLine(separator);
this.outputChannel.appendLine(msg);
this.outputChannel.appendLine(separator);
additional = new AdditionalLogLocation(logPath);
this.additionalLocations.set(logPath, additional);
this.track(additional);
}
await additional.log(message, options);
}
}
show(preserveFocus?: boolean) {
show(preserveFocus?: boolean): void {
this.outputChannel.show(preserveFocus);
}
removeAdditionalLogLocation(location: string | undefined): void {
if (this.additionalLogLocationPath && location) {
const logPath = location.startsWith(this.additionalLogLocationPath)
? location
: path.join(this.additionalLogLocationPath, location);
const additional = this.additionalLocations.get(logPath);
if (additional) {
this.disposeAndStopTracking(additional);
this.additionalLocations.delete(logPath);
}
}
}
getBaseLocation() {
return this.additionalLogLocationPath;
}
}
class AdditionalLogLocation extends Disposable {
constructor(private location: string) {
super(() => { /**/ });
}
async log(message: string, options = { } as LogOptions): Promise<void> {
if (options.trailingNewline === undefined) {
options.trailingNewline = true;
}
await fs.ensureFile(this.location);
await fs.appendFile(this.location, message + (options.trailingNewline ? '\n' : ''), {
encoding: 'utf8'
});
}
async dispose(): Promise<void> {
await fs.remove(this.location);
}
}
/** The global logger for the extension. */
@ -46,7 +139,9 @@ export const logger = new OutputChannelLogger('CodeQL Extension Log');
export const queryServerLogger = new OutputChannelLogger('CodeQL Query Server');
/** The logger for messages from the language server. */
export const ideServerLogger = new OutputChannelLogger('CodeQL Language Server');
export const ideServerLogger = new OutputChannelLogger(
'CodeQL Language Server'
);
/** The logger for messages from tests. */
export const testLogger = new OutputChannelLogger('CodeQL Tests');

Просмотреть файл

@ -778,6 +778,11 @@ export interface EvaluationResult {
* An error message if an error happened
*/
message?: string;
/**
* Full path to file with all log messages emitted while this query was active, if one exists
*/
logFileLocation?: string;
}
export type QueryResultType = number;

Просмотреть файл

@ -4,6 +4,7 @@ import { ExtensionContext, window as Window } from 'vscode';
import { CompletedQuery } from './query-results';
import { QueryHistoryConfig } from './config';
import { QueryWithResults } from './run-queries';
import * as helpers from './helpers';
/**
* query-history.ts
@ -15,14 +16,14 @@ import { QueryWithResults } from './run-queries';
*/
export type QueryHistoryItemOptions = {
label?: string, // user-settable label
queryText?: string, // stored query for quick query
label?: string; // user-settable label
queryText?: string; // stored query for quick query
}
/**
* Path to icon to display next to a failed query history item.
*/
const FAILED_QUERY_HISTORY_ITEM_ICON: string = 'media/red-x.svg';
const FAILED_QUERY_HISTORY_ITEM_ICON = 'media/red-x.svg';
/**
* Tree data provider for the query history view.
@ -122,7 +123,7 @@ export class QueryHistoryManager {
ctx: ExtensionContext;
treeView: vscode.TreeView<CompletedQuery>;
selectedCallback: ((item: CompletedQuery) => void) | undefined;
lastItemClick: { time: Date, item: CompletedQuery } | undefined;
lastItemClick: { time: Date; item: CompletedQuery } | undefined;
async invokeCallbackOn(queryHistoryItem: CompletedQuery) {
if (this.selectedCallback !== undefined) {
@ -144,6 +145,7 @@ export class QueryHistoryManager {
async handleRemoveHistoryItem(queryHistoryItem: CompletedQuery) {
this.treeDataProvider.remove(queryHistoryItem);
queryHistoryItem.dispose();
const current = this.treeDataProvider.getCurrent();
if (current !== undefined) {
this.treeView.reveal(current);
@ -187,6 +189,20 @@ export class QueryHistoryManager {
}
}
async handleShowQueryLog(queryHistoryItem: CompletedQuery) {
if (queryHistoryItem.logFileLocation) {
try {
await vscode.window.showTextDocument(vscode.Uri.parse(queryHistoryItem.logFileLocation), {
viewColumn: vscode.ViewColumn.Beside
});
} catch (e) {
helpers.showAndLogErrorMessage(`Could not open log file ${queryHistoryItem.logFileLocation}`);
}
} else {
helpers.showAndLogWarningMessage('No log file available');
}
}
constructor(
ctx: ExtensionContext,
private queryHistoryConfigListener: QueryHistoryConfig,
@ -208,6 +224,7 @@ export class QueryHistoryManager {
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.openQuery', this.handleOpenQuery));
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.removeHistoryItem', this.handleRemoveHistoryItem.bind(this)));
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.setLabel', this.handleSetLabel.bind(this)));
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.showQueryLog', this.handleShowQueryLog.bind(this)));
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.itemClicked', async (item) => {
return this.handleItemClicked(item);
}));

Просмотреть файл

@ -14,7 +14,9 @@ export class CompletedQuery implements QueryWithResults {
readonly query: QueryInfo;
readonly result: messages.EvaluationResult;
readonly database: DatabaseInfo;
readonly logFileLocation?: string
options: QueryHistoryItemOptions;
dispose: () => void;
/**
* Map from result set name to SortedResultSetInfo.
@ -31,15 +33,18 @@ export class CompletedQuery implements QueryWithResults {
interpretedResultsSortState: InterpretedResultsSortState | undefined;
constructor(
evalaution: QueryWithResults,
evaluation: QueryWithResults,
public config: QueryHistoryConfig,
) {
this.query = evalaution.query;
this.result = evalaution.result;
this.database = evalaution.database;
this.query = evaluation.query;
this.result = evaluation.result;
this.database = evaluation.database;
this.logFileLocation = evaluation.logFileLocation;
this.options = evaluation.options;
this.dispose = evaluation.dispose;
this.time = new Date().toLocaleString();
this.sortedResultsInfo = new Map();
this.options = evalaution.options;
}
get databaseName(): string {

Просмотреть файл

@ -1,4 +1,5 @@
import * as cp from 'child_process';
import * as path from 'path';
// Import from the specific module within `semmle-vscode-utils`, rather than via `index.ts`, because
// we avoid taking an accidental runtime dependency on `vscode` this way.
import { DisposableObject } from 'semmle-vscode-utils/out/disposable-object';
@ -8,9 +9,10 @@ import * as cli from './cli';
import { QueryServerConfig } from './config';
import { Logger, ProgressReporter } from './logging';
import { completeQuery, EvaluationResult, progress, ProgressMessage, WithProgressId } from './messages';
import * as messages from './messages';
type ServerOpts = {
logger: Logger
logger: Logger;
}
/** A running query server process and its associated message connection. */
@ -25,7 +27,7 @@ class ServerProcess implements Disposable {
this.logger = logger;
}
dispose() {
dispose(): void {
this.logger.log('Stopping query server...');
this.connection.dispose();
this.child.stdin!.end();
@ -53,6 +55,7 @@ export class QueryServerClient extends DisposableObject {
nextCallback: number;
nextProgress: number;
withProgressReporting: WithProgressReporting;
public activeQueryName: string | undefined;
constructor(readonly config: QueryServerConfig, readonly cliServer: cli.CodeQLCliServer, readonly opts: ServerOpts, withProgressReporting: WithProgressReporting) {
super();
@ -70,10 +73,12 @@ export class QueryServerClient extends DisposableObject {
this.evaluationResultCallbacks = {};
}
get logger() { return this.opts.logger; }
get logger(): Logger {
return this.opts.logger;
}
/** Stops the query server by disposing of the current server process. */
private stopQueryServer() {
private stopQueryServer(): void {
if (this.serverProcess !== undefined) {
this.disposeAndStopTracking(this.serverProcess);
} else {
@ -82,23 +87,23 @@ export class QueryServerClient extends DisposableObject {
}
/** Restarts the query server by disposing of the current server process and then starting a new one. */
async restartQueryServer() {
async restartQueryServer(): Promise<void> {
this.stopQueryServer();
await this.startQueryServer();
}
async showLog() {
showLog(): void {
this.logger.show();
}
/** Starts a new query server process, sending progress messages to the status bar. */
async startQueryServer() {
async startQueryServer(): Promise<void> {
// Use an arrow function to preserve the value of `this`.
return this.withProgressReporting((progress, _) => this.startQueryServerImpl(progress));
}
/** Starts a new query server process, sending progress messages to the given reporter. */
private async startQueryServerImpl(progressReporter: ProgressReporter) {
private async startQueryServerImpl(progressReporter: ProgressReporter): Promise<void> {
const ramArgs = await this.cliServer.resolveRam(this.config.queryMemoryMb, progressReporter);
const args = ['--threads', this.config.numThreads.toString()].concat(ramArgs);
if (this.config.debug) {
@ -110,7 +115,10 @@ export class QueryServerClient extends DisposableObject {
['execute', 'query-server'],
args,
this.logger,
data => this.logger.logWithoutTrailingNewline(data.toString()),
data => this.logger.log(data.toString(), {
trailingNewline: false,
additionalLogLocation: this.activeQueryName
}),
undefined, // no listener for stdout
progressReporter
);
@ -121,12 +129,16 @@ export class QueryServerClient extends DisposableObject {
this.logger.log(`No callback associated with run id ${res.runId}, continuing without executing any callback`);
}
else {
const baseLocation = this.logger.getBaseLocation();
if (baseLocation && this.activeQueryName) {
res.logFileLocation = path.join(baseLocation, this.activeQueryName);
}
this.evaluationResultCallbacks[res.runId](res);
}
return {};
})
connection.onNotification(progress, res => {
let callback = this.progressCallbacks[res.id];
const callback = this.progressCallbacks[res.id];
if (callback) {
callback(res);
}
@ -148,7 +160,7 @@ export class QueryServerClient extends DisposableObject {
return id;
}
unRegisterCallback(id: number) {
unRegisterCallback(id: number): void {
delete this.evaluationResultCallbacks[id];
}
@ -157,8 +169,10 @@ export class QueryServerClient extends DisposableObject {
}
async sendRequest<P, R, E, RO>(type: RequestType<WithProgressId<P>, R, E, RO>, parameter: P, token?: CancellationToken, progress?: (res: ProgressMessage) => void): Promise<R> {
let id = this.nextProgress++;
const id = this.nextProgress++;
this.progressCallbacks[id] = progress;
this.updateActiveQuery(type.method, parameter);
try {
if (this.serverProcess === undefined) {
throw new Error('No query server process found.');
@ -168,4 +182,19 @@ export class QueryServerClient extends DisposableObject {
delete this.progressCallbacks[id];
}
}
/**
* Updates the active query every time there is a new request to compile.
* The active query is used to specify the side log.
*
* This isn't ideal because in situations where there are queries running
* in parallel, each query's log messages are interleaved. Fixing this
* properly will require a change in the query server.
*/
private updateActiveQuery(method: string, parameter: any): void {
if (method === messages.compileQuery.method) {
const queryPath = parameter?.queryToCheck?.queryPath || 'unknown';
this.activeQueryName = `query-${path.basename(queryPath)}-${this.nextProgress}.log`;
}
}
}

Просмотреть файл

@ -20,7 +20,7 @@ export function isQuickQueryPath(queryPath: string): boolean {
async function getQlPackFor(cliServer: CodeQLCliServer, dbschemePath: string): Promise<string> {
const qlpacks = await cliServer.resolveQlpacks(helpers.getOnDiskWorkspaceFolders());
const packs: { packDir: string | undefined, packName: string }[] =
const packs: { packDir: string | undefined; packName: string }[] =
Object.entries(qlpacks).map(([packName, dirs]) => {
if (dirs.length < 1) {
logger.log(`In getQlPackFor ${dbschemePath}, qlpack ${packName} has no directories`);
@ -60,7 +60,7 @@ function getBaseText(dbschemeBase: string) {
return 'select ""';
}
async function getQuickQueriesDir(ctx: ExtensionContext): Promise<string> {
function getQuickQueriesDir(ctx: ExtensionContext): string {
const storagePath = ctx.storagePath;
if (storagePath === undefined) {
throw new Error('Workspace storage path is undefined');

Просмотреть файл

@ -136,10 +136,13 @@ export class QueryInfo {
},
queryToCheck: this.program,
resultPath: this.compiledQueryPath,
target: !!this.quickEvalPosition ? { quickEval: { quickEvalPos: this.quickEvalPosition } } : { query: {} }
target: this.quickEvalPosition ? {
quickEval: { quickEvalPos: this.quickEvalPosition }
} : {
query: {}
}
};
compiled = await helpers.withProgress({
location: vscode.ProgressLocation.Notification,
title: "Compiling Query",
@ -170,10 +173,13 @@ export interface QueryWithResults {
readonly result: messages.EvaluationResult;
readonly database: DatabaseInfo;
readonly options: QueryHistoryItemOptions;
readonly logFileLocation?: string;
readonly dispose: () => void;
}
export async function clearCacheInDatabase(qs: qsClient.QueryServerClient, dbItem: DatabaseItem):
Promise<messages.ClearCacheResult> {
export async function clearCacheInDatabase(
qs: qsClient.QueryServerClient, dbItem: DatabaseItem
): Promise<messages.ClearCacheResult> {
if (dbItem.contents === undefined) {
throw new Error('Can\'t clear the cache in an invalid database.');
}
@ -253,7 +259,7 @@ async function checkDbschemeCompatibility(
if (query.dbItem.contents !== undefined && query.dbItem.contents.dbSchemeUri !== undefined) {
const { scripts, finalDbscheme } = await cliServer.resolveUpgrades(query.dbItem.contents.dbSchemeUri.fsPath, searchPath);
async function hash(filename: string): Promise<string> {
const hash = async function (filename: string): Promise<string> {
return crypto.createHash('sha256').update(await fs.readFile(filename)).digest('hex');
}
@ -289,7 +295,7 @@ async function checkDbschemeCompatibility(
}
/** Prompts the user to save `document` if it has unsaved changes. */
async function promptUserToSaveChanges(document: vscode.TextDocument) {
async function promptUserToSaveChanges(document: vscode.TextDocument): Promise<void> {
if (document.isDirty) {
// TODO: add 'always save' button which records preference in configuration
if (await helpers.showBinaryChoiceDialog('Query file has unsaved changes. Save now?')) {
@ -299,8 +305,8 @@ async function promptUserToSaveChanges(document: vscode.TextDocument) {
}
type SelectedQuery = {
queryPath: string,
quickEvalPosition?: messages.Position
queryPath: string;
quickEvalPosition?: messages.Position;
};
/**
@ -457,7 +463,11 @@ export async function compileAndRunQueryAgainstDatabase(
name: db.name,
databaseUri: db.databaseUri.toString(true)
},
options: historyItemOptions
options: historyItemOptions,
logFileLocation: result.logFileLocation,
dispose: () => {
qs.logger.removeAdditionalLogLocation(result.logFileLocation);
}
};
} else {
// Error dialogs are limited in size and scrollability,
@ -466,7 +476,7 @@ export async function compileAndRunQueryAgainstDatabase(
// However we don't show quick eval errors there so we need to display them anyway.
qs.logger.log(`Failed to compile query ${query.program.queryPath} against database scheme ${query.program.dbschemePath}:`);
let formattedMessages: string[] = [];
const formattedMessages: string[] = [];
for (const error of errors) {
const message = error.message || "[no error message available]";
@ -493,7 +503,7 @@ function createSyntheticResult(
historyItemOptions: QueryHistoryItemOptions,
message: string,
resultType: number
) {
): QueryWithResults {
return {
query,
@ -509,5 +519,6 @@ function createSyntheticResult(
databaseUri: db.databaseUri.toString(true)
},
options: historyItemOptions,
dispose: () => { /**/ },
};
}

Просмотреть файл

@ -19,8 +19,9 @@ const MAX_UPGRADE_MESSAGE_LINES = 10;
* Reports errors to both the user and the console.
* @returns the `UpgradeParams` needed to start the upgrade, if the upgrade is possible and was confirmed by the user, or `undefined` otherwise.
*/
async function checkAndConfirmDatabaseUpgrade(qs: qsClient.QueryServerClient, db: DatabaseItem, targetDbScheme: vscode.Uri, upgradesDirectories: vscode.Uri[]):
Promise<messages.UpgradeParams | undefined> {
async function checkAndConfirmDatabaseUpgrade(
qs: qsClient.QueryServerClient, db: DatabaseItem, targetDbScheme: vscode.Uri, upgradesDirectories: vscode.Uri[]
): Promise<messages.UpgradeParams | undefined> {
if (db.contents === undefined || db.contents.dbSchemeUri === undefined) {
helpers.showAndLogErrorMessage("Database is invalid, and cannot be upgraded.");
return;
@ -80,7 +81,7 @@ async function checkAndConfirmDatabaseUpgrade(qs: qsClient.QueryServerClient, db
const showLogItem: vscode.MessageItem = { title: 'No, Show Changes', isCloseAffordance: true };
const yesItem = { title: 'Yes', isCloseAffordance: false };
const noItem = { title: 'No', isCloseAffordance: true }
let dialogOptions: vscode.MessageItem[] = [yesItem, noItem];
const dialogOptions: vscode.MessageItem[] = [yesItem, noItem];
let messageLines = descriptionMessage.split('\n');
if (messageLines.length > MAX_UPGRADE_MESSAGE_LINES) {
@ -110,8 +111,9 @@ async function checkAndConfirmDatabaseUpgrade(qs: qsClient.QueryServerClient, db
* First performs a dry-run and prompts the user to confirm the upgrade.
* Reports errors during compilation and evaluation of upgrades to the user.
*/
export async function upgradeDatabase(qs: qsClient.QueryServerClient, db: DatabaseItem, targetDbScheme: vscode.Uri, upgradesDirectories: vscode.Uri[]):
Promise<messages.RunUpgradeResult | undefined> {
export async function upgradeDatabase(
qs: qsClient.QueryServerClient, db: DatabaseItem, targetDbScheme: vscode.Uri, upgradesDirectories: vscode.Uri[]
): Promise<messages.RunUpgradeResult | undefined> {
const upgradeParams = await checkAndConfirmDatabaseUpgrade(qs, db, targetDbScheme, upgradesDirectories);
if (upgradeParams === undefined) {
@ -150,8 +152,9 @@ export async function upgradeDatabase(qs: qsClient.QueryServerClient, db: Databa
}
}
async function checkDatabaseUpgrade(qs: qsClient.QueryServerClient, upgradeParams: messages.UpgradeParams):
Promise<messages.CheckUpgradeResult> {
async function checkDatabaseUpgrade(
qs: qsClient.QueryServerClient, upgradeParams: messages.UpgradeParams
): Promise<messages.CheckUpgradeResult> {
return helpers.withProgress({
location: vscode.ProgressLocation.Notification,
title: "Checking for database upgrades",
@ -159,8 +162,9 @@ async function checkDatabaseUpgrade(qs: qsClient.QueryServerClient, upgradeParam
}, (progress, token) => qs.sendRequest(messages.checkUpgrade, upgradeParams, token, progress));
}
async function compileDatabaseUpgrade(qs: qsClient.QueryServerClient, upgradeParams: messages.UpgradeParams):
Promise<messages.CompileUpgradeResult> {
async function compileDatabaseUpgrade(
qs: qsClient.QueryServerClient, upgradeParams: messages.UpgradeParams
): Promise<messages.CompileUpgradeResult> {
const params: messages.CompileUpgradeParams = {
upgrade: upgradeParams,
upgradeTempDir: upgradesTmpDir.name
@ -173,8 +177,9 @@ async function compileDatabaseUpgrade(qs: qsClient.QueryServerClient, upgradePar
}, (progress, token) => qs.sendRequest(messages.compileUpgrade, params, token, progress));
}
async function runDatabaseUpgrade(qs: qsClient.QueryServerClient, db: DatabaseItem, upgrades: messages.CompiledUpgrades):
Promise<messages.RunUpgradeResult> {
async function runDatabaseUpgrade(
qs: qsClient.QueryServerClient, db: DatabaseItem, upgrades: messages.CompiledUpgrades
): Promise<messages.RunUpgradeResult> {
if (db.contents === undefined || db.contents.datasetUri === undefined) {
throw new Error('Can\'t upgrade an invalid database.');

Просмотреть файл

@ -4,5 +4,5 @@ module.exports = {
},
env: {
browser: true
},
}
}

Просмотреть файл

@ -1,5 +1,8 @@
module.exports = {
env: {
mocha: true
}
},
parserOptions: {
project: 'tsconfig.json',
},
}

Просмотреть файл

@ -0,0 +1,154 @@
import * as chai from 'chai';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as tmp from 'tmp';
import 'mocha';
import * as sinonChai from 'sinon-chai';
import * as sinon from 'sinon';
import * as pq from 'proxyquire';
const proxyquire = pq.noPreserveCache().noCallThru();
chai.should();
chai.use(sinonChai);
const expect = chai.expect;
describe('OutputChannelLogger tests', () => {
let OutputChannelLogger;
const tempFolders: Record<string, tmp.DirResult> = {};
let logger: any;
let mockOutputChannel: Record<string, sinon.SinonStub>;
beforeEach(async () => {
OutputChannelLogger = createModule().OutputChannelLogger;
tempFolders.globalStoragePath = tmp.dirSync({ prefix: 'logging-tests-global' });
tempFolders.storagePath = tmp.dirSync({ prefix: 'logging-tests-workspace' });
logger = new OutputChannelLogger('test-logger');
});
afterEach(() => {
tempFolders.globalStoragePath.removeCallback();
tempFolders.storagePath.removeCallback();
});
it('should log to the output channel', async () => {
await logger.log('xxx');
expect(mockOutputChannel.appendLine).to.have.been.calledWith('xxx');
expect(mockOutputChannel.append).not.to.have.been.calledWith('xxx');
await logger.log('yyy', { trailingNewline: false });
expect(mockOutputChannel.appendLine).not.to.have.been.calledWith('yyy');
expect(mockOutputChannel.append).to.have.been.calledWith('yyy');
// additionalLogLocation ignored since not initialized
await logger.log('zzz', { additionalLogLocation: 'hucairz' });
// should not have created any side logs
expect(fs.readdirSync(tempFolders.globalStoragePath.name).length).to.equal(0);
expect(fs.readdirSync(tempFolders.storagePath.name).length).to.equal(0);
});
it('should create a side log in the workspace area', async () => {
await sideLogTest('storagePath', 'globalStoragePath');
});
it('should create a side log in the global area', async () => {
await sideLogTest('globalStoragePath', 'storagePath');
});
async function sideLogTest(expectedArea: string, otherArea: string): Promise<void> {
logger.init({
[expectedArea]: tempFolders[expectedArea].name,
[otherArea]: undefined
});
await logger.log('xxx', { additionalLogLocation: 'first' });
await logger.log('yyy', { additionalLogLocation: 'second' });
await logger.log('zzz', { additionalLogLocation: 'first', trailingNewline: false });
await logger.log('aaa');
// expect 2 side logs
const testLoggerFolder = path.join(tempFolders[expectedArea].name, 'test-logger');
expect(fs.readdirSync(testLoggerFolder).length).to.equal(2);
expect(fs.readdirSync(tempFolders[otherArea].name).length).to.equal(0);
// contents
expect(fs.readFileSync(path.join(testLoggerFolder, 'first'), 'utf8')).to.equal('xxx\nzzz');
expect(fs.readFileSync(path.join(testLoggerFolder, 'second'), 'utf8')).to.equal('yyy\n');
}
it('should delete side logs on dispose', async () => {
logger.init({
storagePath: tempFolders.storagePath.name
});
await logger.log('xxx', { additionalLogLocation: 'first' });
await logger.log('yyy', { additionalLogLocation: 'second' });
const testLoggerFolder = path.join(tempFolders.storagePath.name, 'test-logger');
expect(fs.readdirSync(testLoggerFolder).length).to.equal(2);
await logger.dispose();
// need to wait for disposable-object to dispose
await waitABit();
expect(fs.readdirSync(testLoggerFolder).length).to.equal(0);
expect(mockOutputChannel.dispose).to.have.been.calledWith();
});
it('should remove an additional log location', async () => {
logger.init({
storagePath: tempFolders.storagePath.name
});
await logger.log('xxx', { additionalLogLocation: 'first' });
await logger.log('yyy', { additionalLogLocation: 'second' });
const testLoggerFolder = path.join(tempFolders.storagePath.name, 'test-logger');
expect(fs.readdirSync(testLoggerFolder).length).to.equal(2);
await logger.removeAdditionalLogLocation('first');
// need to wait for disposable-object to dispose
await waitABit();
expect(fs.readdirSync(testLoggerFolder).length).to.equal(1);
expect(fs.readFileSync(path.join(testLoggerFolder, 'second'), 'utf8')).to.equal('yyy\n');
});
it('should delete an existing folder on init', async () => {
fs.createFileSync(path.join(tempFolders.storagePath.name, 'test-logger', 'xxx'));
logger.init({
storagePath: tempFolders.storagePath.name
});
// should be empty dir
const testLoggerFolder = path.join(tempFolders.storagePath.name, 'test-logger');
expect(fs.readdirSync(testLoggerFolder).length).to.equal(1);
});
it('should show the output channel', () => {
logger.show(true);
expect(mockOutputChannel.show).to.have.been.calledWith(true);
});
function createModule(): any {
mockOutputChannel = {
append: sinon.stub(),
appendLine: sinon.stub(),
show: sinon.stub(),
dispose: sinon.stub(),
};
return proxyquire('../../src/logging', {
vscode: {
window: {
createOutputChannel: () => mockOutputChannel
},
Disposable: function() {
/**/
},
'@noCallThru': true,
'@global': true
}
});
}
function waitABit(ms = 50): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
});

Просмотреть файл

@ -31,31 +31,31 @@ class Checkpoint<T> {
private promise: Promise<T>;
constructor() {
this.res = () => { };
this.rej = () => { };
this.res = () => { /**/ };
this.rej = () => { /**/ };
this.promise = new Promise((res, rej) => { this.res = res; this.rej = rej; })
}
async done() {
async done(): Promise<T> {
return this.promise;
}
async resolve() {
(this.res)();
async resolve(): Promise<void> {
await (this.res)();
}
async reject(e: Error) {
(this.rej)(e);
async reject(e: Error): Promise<void> {
await (this.rej)(e);
}
}
type ResultSets = {
[name: string]: bqrs.ColumnValue[][]
[name: string]: bqrs.ColumnValue[][];
}
type QueryTestCase = {
queryPath: string,
expectedResultSets: ResultSets
queryPath: string;
expectedResultSets: ResultSets;
}
// Test cases: queries to run and their expected results.
@ -105,9 +105,10 @@ describe('using the query server', function() {
report: (v: { message: string }) => console.log(`progress reporter says ${v.message}`)
};
const logger: Logger = {
log: (s: string) => console.log('logger says', s),
logWithoutTrailingNewline: (s: string) => console.log('logger says', s),
show: () => { },
log: async (s: string) => console.log('logger says', s),
show: () => { /**/ },
removeAdditionalLogLocation: async () => { /**/ },
getBaseLocation: () => ''
};
cliServer = new cli.CodeQLCliServer({
async getCodeQlPathWithoutVersionCheck(): Promise<string | undefined> {
@ -161,7 +162,7 @@ describe('using the query server', function() {
resultPath: COMPILED_QUERY_PATH,
target: { query: {} }
};
const result = await qs.sendRequest(messages.compileQuery, params, token, () => { });
const result = await qs.sendRequest(messages.compileQuery, params, token, () => { /**/ });
expect(result.messages!.length).to.equal(0);
compilationSucceeded.resolve();
}
@ -194,7 +195,7 @@ describe('using the query server', function() {
stopOnError: false,
useSequenceHint: false
};
await qs.sendRequest(messages.runQueries, params, token, () => { });
await qs.sendRequest(messages.runQueries, params, token, () => { /**/ });
}
catch (e) {
evaluationSucceeded.reject(e);

1
tsconfig.json Normal file
Просмотреть файл

@ -0,0 +1 @@
{}