Merge branch 'main' into robertbrignull/remove_withInheritedProgress
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
## [UNRELEASED]
|
## [UNRELEASED]
|
||||||
|
|
||||||
|
- Databases created from [CodeQL test cases](https://docs.github.com/en/code-security/codeql-cli/using-the-advanced-functionality-of-the-codeql-cli/testing-custom-queries) are now copied into a shared VS Code storage location. This avoids a bug where re-running test cases would fail if the test's database is already imported into the workspace. [#3433](https://github.com/github/vscode-codeql/pull/3433)
|
||||||
|
|
||||||
## 1.12.3 - 29 February 2024
|
## 1.12.3 - 29 February 2024
|
||||||
|
|
||||||
- Update variant analysis view to show when cancelation is in progress. [#3405](https://github.com/github/vscode-codeql/pull/3405)
|
- Update variant analysis view to show when cancelation is in progress. [#3405](https://github.com/github/vscode-codeql/pull/3405)
|
||||||
|
|||||||
@@ -738,6 +738,10 @@
|
|||||||
"command": "codeQL.setCurrentDatabase",
|
"command": "codeQL.setCurrentDatabase",
|
||||||
"title": "CodeQL: Set Current Database"
|
"title": "CodeQL: Set Current Database"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "codeQL.importTestDatabase",
|
||||||
|
"title": "CodeQL: (Re-)Import Test Database"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "codeQL.getCurrentDatabase",
|
"command": "codeQL.getCurrentDatabase",
|
||||||
"title": "CodeQL: Get Current Database"
|
"title": "CodeQL: Get Current Database"
|
||||||
@@ -1322,7 +1326,12 @@
|
|||||||
{
|
{
|
||||||
"command": "codeQL.setCurrentDatabase",
|
"command": "codeQL.setCurrentDatabase",
|
||||||
"group": "9_qlCommands",
|
"group": "9_qlCommands",
|
||||||
"when": "resourceScheme == codeql-zip-archive || explorerResourceIsFolder || resourceExtname == .zip"
|
"when": "resourceExtname != .testproj && (resourceScheme == codeql-zip-archive || explorerResourceIsFolder || resourceExtname == .zipz)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "codeQL.importTestDatabase",
|
||||||
|
"group": "9_qlCommands",
|
||||||
|
"when": "explorerResourceIsFolder && resourceExtname == .testproj"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "codeQL.viewAstContextExplorer",
|
"command": "codeQL.viewAstContextExplorer",
|
||||||
@@ -1476,6 +1485,10 @@
|
|||||||
"command": "codeQL.setCurrentDatabase",
|
"command": "codeQL.setCurrentDatabase",
|
||||||
"when": "false"
|
"when": "false"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "codeQL.importTestDatabase",
|
||||||
|
"when": "false"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "codeQL.getCurrentDatabase",
|
"command": "codeQL.getCurrentDatabase",
|
||||||
"when": "false"
|
"when": "false"
|
||||||
@@ -2018,7 +2031,6 @@
|
|||||||
"@types/tar-stream": "^3.1.3",
|
"@types/tar-stream": "^3.1.3",
|
||||||
"@types/through2": "^2.0.36",
|
"@types/through2": "^2.0.36",
|
||||||
"@types/tmp": "^0.2.6",
|
"@types/tmp": "^0.2.6",
|
||||||
"@types/unzipper": "^0.10.1",
|
|
||||||
"@types/vscode": "^1.82.0",
|
"@types/vscode": "^1.82.0",
|
||||||
"@types/yauzl": "^2.10.3",
|
"@types/yauzl": "^2.10.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ export type LocalDatabasesCommands = {
|
|||||||
|
|
||||||
// Explorer context menu
|
// Explorer context menu
|
||||||
"codeQL.setCurrentDatabase": (uri: Uri) => Promise<void>;
|
"codeQL.setCurrentDatabase": (uri: Uri) => Promise<void>;
|
||||||
|
"codeQL.importTestDatabase": (uri: Uri) => Promise<void>;
|
||||||
|
|
||||||
// Database panel view title commands
|
// Database panel view title commands
|
||||||
"codeQLDatabases.chooseDatabaseFolder": () => Promise<void>;
|
"codeQLDatabases.chooseDatabaseFolder": () => Promise<void>;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import type { Method } from "../model-editor/method";
|
|||||||
import type { ModeledMethod } from "../model-editor/modeled-method";
|
import type { ModeledMethod } from "../model-editor/modeled-method";
|
||||||
import type {
|
import type {
|
||||||
MethodModelingPanelViewState,
|
MethodModelingPanelViewState,
|
||||||
|
ModelAlertsViewState,
|
||||||
ModelEditorViewState,
|
ModelEditorViewState,
|
||||||
} from "../model-editor/shared/view-state";
|
} from "../model-editor/shared/view-state";
|
||||||
import type { Mode } from "../model-editor/shared/mode";
|
import type { Mode } from "../model-editor/shared/mode";
|
||||||
@@ -726,10 +727,11 @@ export type ToMethodModelingMessage =
|
|||||||
| SetInProgressMessage
|
| SetInProgressMessage
|
||||||
| SetProcessedByAutoModelMessage;
|
| SetProcessedByAutoModelMessage;
|
||||||
|
|
||||||
interface SetModelAlertsMessage {
|
interface SetModelAlertsViewStateMessage {
|
||||||
t: "setModelAlerts";
|
t: "setModelAlertsViewState";
|
||||||
|
viewState: ModelAlertsViewState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ToModelAlertsMessage = SetModelAlertsMessage;
|
export type ToModelAlertsMessage = SetModelAlertsViewStateMessage;
|
||||||
|
|
||||||
export type FromModelAlertsMessage = CommonFromViewMessages;
|
export type FromModelAlertsMessage = CommonFromViewMessages;
|
||||||
|
|||||||
4
extensions/ql-vscode/src/common/model-pack-details.ts
Normal file
4
extensions/ql-vscode/src/common/model-pack-details.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface ModelPackDetails {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
createWriteStream,
|
createWriteStream,
|
||||||
remove,
|
remove,
|
||||||
readdir,
|
readdir,
|
||||||
|
copy,
|
||||||
} from "fs-extra";
|
} from "fs-extra";
|
||||||
import { basename, join } from "path";
|
import { basename, join } from "path";
|
||||||
import type { Octokit } from "@octokit/rest";
|
import type { Octokit } from "@octokit/rest";
|
||||||
@@ -64,7 +65,7 @@ export async function promptImportInternetDatabase(
|
|||||||
|
|
||||||
validateUrl(databaseUrl);
|
validateUrl(databaseUrl);
|
||||||
|
|
||||||
const item = await databaseArchiveFetcher(
|
const item = await fetchDatabaseToWorkspaceStorage(
|
||||||
databaseUrl,
|
databaseUrl,
|
||||||
{},
|
{},
|
||||||
databaseManager,
|
databaseManager,
|
||||||
@@ -258,7 +259,7 @@ export async function downloadGitHubDatabaseFromUrl(
|
|||||||
* We only need the actual token string.
|
* We only need the actual token string.
|
||||||
*/
|
*/
|
||||||
const octokitToken = ((await octokit.auth()) as { token: string })?.token;
|
const octokitToken = ((await octokit.auth()) as { token: string })?.token;
|
||||||
return await databaseArchiveFetcher(
|
return await fetchDatabaseToWorkspaceStorage(
|
||||||
databaseUrl,
|
databaseUrl,
|
||||||
{
|
{
|
||||||
Accept: "application/zip",
|
Accept: "application/zip",
|
||||||
@@ -282,14 +283,15 @@ export async function downloadGitHubDatabaseFromUrl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imports a database from a local archive.
|
* Imports a database from a local archive or a test database that is in a folder
|
||||||
|
* ending with `.testproj`.
|
||||||
*
|
*
|
||||||
* @param databaseUrl the file url of the archive to import
|
* @param databaseUrl the file url of the archive or directory to import
|
||||||
* @param databaseManager the DatabaseManager
|
* @param databaseManager the DatabaseManager
|
||||||
* @param storagePath where to store the unzipped database.
|
* @param storagePath where to store the unzipped database.
|
||||||
* @param cli the CodeQL CLI server
|
* @param cli the CodeQL CLI server
|
||||||
*/
|
*/
|
||||||
export async function importArchiveDatabase(
|
export async function importLocalDatabase(
|
||||||
commandManager: AppCommandManager,
|
commandManager: AppCommandManager,
|
||||||
databaseUrl: string,
|
databaseUrl: string,
|
||||||
databaseManager: DatabaseManager,
|
databaseManager: DatabaseManager,
|
||||||
@@ -298,16 +300,17 @@ export async function importArchiveDatabase(
|
|||||||
cli: CodeQLCliServer,
|
cli: CodeQLCliServer,
|
||||||
): Promise<DatabaseItem | undefined> {
|
): Promise<DatabaseItem | undefined> {
|
||||||
try {
|
try {
|
||||||
const item = await databaseArchiveFetcher(
|
const origin: DatabaseOrigin = {
|
||||||
|
type: databaseUrl.endsWith(".testproj") ? "testproj" : "archive",
|
||||||
|
path: Uri.parse(databaseUrl).fsPath,
|
||||||
|
};
|
||||||
|
const item = await fetchDatabaseToWorkspaceStorage(
|
||||||
databaseUrl,
|
databaseUrl,
|
||||||
{},
|
{},
|
||||||
databaseManager,
|
databaseManager,
|
||||||
storagePath,
|
storagePath,
|
||||||
undefined,
|
undefined,
|
||||||
{
|
origin,
|
||||||
type: "archive",
|
|
||||||
path: databaseUrl,
|
|
||||||
},
|
|
||||||
progress,
|
progress,
|
||||||
cli,
|
cli,
|
||||||
);
|
);
|
||||||
@@ -315,7 +318,9 @@ export async function importArchiveDatabase(
|
|||||||
await commandManager.execute("codeQLDatabases.focus");
|
await commandManager.execute("codeQLDatabases.focus");
|
||||||
void showAndLogInformationMessage(
|
void showAndLogInformationMessage(
|
||||||
extLogger,
|
extLogger,
|
||||||
"Database unzipped and imported successfully.",
|
origin.type === "testproj"
|
||||||
|
? "Test database imported successfully."
|
||||||
|
: "Database unzipped and imported successfully.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
@@ -332,10 +337,10 @@ export async function importArchiveDatabase(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches an archive database. The database might be on the internet
|
* Fetches a database into workspace storage. The database might be on the internet
|
||||||
* or in the local filesystem.
|
* or in the local filesystem.
|
||||||
*
|
*
|
||||||
* @param databaseUrl URL from which to grab the database
|
* @param databaseUrl URL from which to grab the database. This could be a local archive file, a local directory, or a remote URL.
|
||||||
* @param requestHeaders Headers to send with the request
|
* @param requestHeaders Headers to send with the request
|
||||||
* @param databaseManager the DatabaseManager
|
* @param databaseManager the DatabaseManager
|
||||||
* @param storagePath where to store the unzipped database.
|
* @param storagePath where to store the unzipped database.
|
||||||
@@ -346,7 +351,7 @@ export async function importArchiveDatabase(
|
|||||||
* @param makeSelected make the new database selected in the databases panel (default: true)
|
* @param makeSelected make the new database selected in the databases panel (default: true)
|
||||||
* @param addSourceArchiveFolder whether to add a workspace folder containing the source archive to the workspace
|
* @param addSourceArchiveFolder whether to add a workspace folder containing the source archive to the workspace
|
||||||
*/
|
*/
|
||||||
async function databaseArchiveFetcher(
|
async function fetchDatabaseToWorkspaceStorage(
|
||||||
databaseUrl: string,
|
databaseUrl: string,
|
||||||
requestHeaders: { [key: string]: string },
|
requestHeaders: { [key: string]: string },
|
||||||
databaseManager: DatabaseManager,
|
databaseManager: DatabaseManager,
|
||||||
@@ -374,7 +379,11 @@ async function databaseArchiveFetcher(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (isFile(databaseUrl)) {
|
if (isFile(databaseUrl)) {
|
||||||
await readAndUnzip(databaseUrl, unzipPath, cli, progress);
|
if (origin.type === "testproj") {
|
||||||
|
await copyDatabase(databaseUrl, unzipPath, progress);
|
||||||
|
} else {
|
||||||
|
await readAndUnzip(databaseUrl, unzipPath, cli, progress);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await fetchAndUnzip(databaseUrl, requestHeaders, unzipPath, cli, progress);
|
await fetchAndUnzip(databaseUrl, requestHeaders, unzipPath, cli, progress);
|
||||||
}
|
}
|
||||||
@@ -438,6 +447,8 @@ async function getStorageFolder(
|
|||||||
lastName = basename(url.path).substring(0, 250);
|
lastName = basename(url.path).substring(0, 250);
|
||||||
if (lastName.endsWith(".zip")) {
|
if (lastName.endsWith(".zip")) {
|
||||||
lastName = lastName.substring(0, lastName.length - 4);
|
lastName = lastName.substring(0, lastName.length - 4);
|
||||||
|
} else if (lastName.endsWith(".testproj")) {
|
||||||
|
lastName = lastName.substring(0, lastName.length - 9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,6 +495,26 @@ function validateUrl(databaseUrl: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies a database folder from the file system into the workspace storage.
|
||||||
|
* @param scrDirURL the original location of the database as a URL string
|
||||||
|
* @param destDir the location to copy the database to. This should be a folder in the workspace storage.
|
||||||
|
* @param progress callback to send progress messages to
|
||||||
|
*/
|
||||||
|
async function copyDatabase(
|
||||||
|
srcDirURL: string,
|
||||||
|
destDir: string,
|
||||||
|
progress?: ProgressCallback,
|
||||||
|
) {
|
||||||
|
progress?.({
|
||||||
|
maxStep: 10,
|
||||||
|
step: 9,
|
||||||
|
message: `Copying database ${basename(destDir)} into the workspace`,
|
||||||
|
});
|
||||||
|
await ensureDir(destDir);
|
||||||
|
await copy(Uri.parse(srcDirURL).fsPath, destDir);
|
||||||
|
}
|
||||||
|
|
||||||
async function readAndUnzip(
|
async function readAndUnzip(
|
||||||
zipUrl: string,
|
zipUrl: string,
|
||||||
unzipPath: string,
|
unzipPath: string,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import {
|
|||||||
showAndLogErrorMessage,
|
showAndLogErrorMessage,
|
||||||
} from "../common/logging";
|
} from "../common/logging";
|
||||||
import {
|
import {
|
||||||
importArchiveDatabase,
|
importLocalDatabase,
|
||||||
promptImportGithubDatabase,
|
promptImportGithubDatabase,
|
||||||
promptImportInternetDatabase,
|
promptImportInternetDatabase,
|
||||||
} from "./database-fetcher";
|
} from "./database-fetcher";
|
||||||
@@ -140,7 +140,8 @@ class DatabaseTreeDataProvider
|
|||||||
item.iconPath = new ThemeIcon("error", new ThemeColor("errorForeground"));
|
item.iconPath = new ThemeIcon("error", new ThemeColor("errorForeground"));
|
||||||
}
|
}
|
||||||
item.tooltip = element.databaseUri.fsPath;
|
item.tooltip = element.databaseUri.fsPath;
|
||||||
item.description = element.language;
|
item.description =
|
||||||
|
element.language + (element.origin?.type === "testproj" ? " (test)" : "");
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,6 +277,7 @@ export class DatabaseUI extends DisposableObject {
|
|||||||
this.handleChooseDatabaseInternet.bind(this),
|
this.handleChooseDatabaseInternet.bind(this),
|
||||||
"codeQL.chooseDatabaseGithub": this.handleChooseDatabaseGithub.bind(this),
|
"codeQL.chooseDatabaseGithub": this.handleChooseDatabaseGithub.bind(this),
|
||||||
"codeQL.setCurrentDatabase": this.handleSetCurrentDatabase.bind(this),
|
"codeQL.setCurrentDatabase": this.handleSetCurrentDatabase.bind(this),
|
||||||
|
"codeQL.importTestDatabase": this.handleImportTestDatabase.bind(this),
|
||||||
"codeQL.setDefaultTourDatabase":
|
"codeQL.setDefaultTourDatabase":
|
||||||
this.handleSetDefaultTourDatabase.bind(this),
|
this.handleSetDefaultTourDatabase.bind(this),
|
||||||
"codeQL.upgradeCurrentDatabase":
|
"codeQL.upgradeCurrentDatabase":
|
||||||
@@ -705,7 +707,7 @@ export class DatabaseUI extends DisposableObject {
|
|||||||
try {
|
try {
|
||||||
// Assume user has selected an archive if the file has a .zip extension
|
// Assume user has selected an archive if the file has a .zip extension
|
||||||
if (uri.path.endsWith(".zip")) {
|
if (uri.path.endsWith(".zip")) {
|
||||||
await importArchiveDatabase(
|
await importLocalDatabase(
|
||||||
this.app.commands,
|
this.app.commands,
|
||||||
uri.toString(true),
|
uri.toString(true),
|
||||||
this.databaseManager,
|
this.databaseManager,
|
||||||
@@ -733,6 +735,60 @@ export class DatabaseUI extends DisposableObject {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async handleImportTestDatabase(uri: Uri): Promise<void> {
|
||||||
|
return withProgress(
|
||||||
|
async (progress) => {
|
||||||
|
try {
|
||||||
|
if (!uri.path.endsWith(".testproj")) {
|
||||||
|
throw new Error(
|
||||||
|
"Please select a valid test database to import. Test databases end with `.testproj`.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the database is already in the workspace. If
|
||||||
|
// so, delete it first before importing the new one.
|
||||||
|
const existingItem = this.databaseManager.findTestDatabase(uri);
|
||||||
|
const baseName = basename(uri.fsPath);
|
||||||
|
if (existingItem !== undefined) {
|
||||||
|
progress({
|
||||||
|
maxStep: 9,
|
||||||
|
step: 1,
|
||||||
|
message: `Removing existing test database ${baseName}`,
|
||||||
|
});
|
||||||
|
await this.databaseManager.removeDatabaseItem(existingItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
await importLocalDatabase(
|
||||||
|
this.app.commands,
|
||||||
|
uri.toString(true),
|
||||||
|
this.databaseManager,
|
||||||
|
this.storagePath,
|
||||||
|
progress,
|
||||||
|
this.queryServer.cliServer,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingItem !== undefined) {
|
||||||
|
progress({
|
||||||
|
maxStep: 9,
|
||||||
|
step: 9,
|
||||||
|
message: `Successfully re-imported ${baseName}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// rethrow and let this be handled by default error handling.
|
||||||
|
throw new Error(
|
||||||
|
`Could not set database to ${basename(
|
||||||
|
uri.fsPath,
|
||||||
|
)}. Reason: ${getErrorMessage(e)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "(Re-)importing test database from directory",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private async handleRemoveDatabase(
|
private async handleRemoveDatabase(
|
||||||
databaseItems: DatabaseItem[],
|
databaseItems: DatabaseItem[],
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@@ -948,7 +1004,7 @@ export class DatabaseUI extends DisposableObject {
|
|||||||
} else {
|
} else {
|
||||||
// we are selecting a database archive. Must unzip into a workspace-controlled area
|
// we are selecting a database archive. Must unzip into a workspace-controlled area
|
||||||
// before importing.
|
// before importing.
|
||||||
return await importArchiveDatabase(
|
return await importLocalDatabase(
|
||||||
this.app.commands,
|
this.app.commands,
|
||||||
uri.toString(true),
|
uri.toString(true),
|
||||||
this.databaseManager,
|
this.databaseManager,
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ import {
|
|||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import type { FullDatabaseOptions } from "./database-options";
|
import type { FullDatabaseOptions } from "./database-options";
|
||||||
import { DatabaseItemImpl } from "./database-item-impl";
|
import { DatabaseItemImpl } from "./database-item-impl";
|
||||||
import { showNeverAskAgainDialog } from "../../common/vscode/dialog";
|
import {
|
||||||
|
showBinaryChoiceDialog,
|
||||||
|
showNeverAskAgainDialog,
|
||||||
|
} from "../../common/vscode/dialog";
|
||||||
import {
|
import {
|
||||||
getFirstWorkspaceFolder,
|
getFirstWorkspaceFolder,
|
||||||
isFolderAlreadyInWorkspace,
|
isFolderAlreadyInWorkspace,
|
||||||
@@ -32,7 +35,7 @@ import { QlPackGenerator } from "../../local-queries/qlpack-generator";
|
|||||||
import { asError, getErrorMessage } from "../../common/helpers-pure";
|
import { asError, getErrorMessage } from "../../common/helpers-pure";
|
||||||
import type { DatabaseItem, PersistedDatabaseItem } from "./database-item";
|
import type { DatabaseItem, PersistedDatabaseItem } from "./database-item";
|
||||||
import { redactableError } from "../../common/errors";
|
import { redactableError } from "../../common/errors";
|
||||||
import { remove } from "fs-extra";
|
import { copy, remove, stat } from "fs-extra";
|
||||||
import { containsPath } from "../../common/files";
|
import { containsPath } from "../../common/files";
|
||||||
import type { DatabaseChangedEvent } from "./database-events";
|
import type { DatabaseChangedEvent } from "./database-events";
|
||||||
import { DatabaseEventKind } from "./database-events";
|
import { DatabaseEventKind } from "./database-events";
|
||||||
@@ -120,6 +123,7 @@ export class DatabaseManager extends DisposableObject {
|
|||||||
super();
|
super();
|
||||||
|
|
||||||
qs.onStart(this.reregisterDatabases.bind(this));
|
qs.onStart(this.reregisterDatabases.bind(this));
|
||||||
|
qs.onQueryRunStarting(this.maybeReimportTestDatabase.bind(this));
|
||||||
|
|
||||||
this.push(
|
this.push(
|
||||||
this.languageContext.onLanguageContextChanged(async () => {
|
this.languageContext.onLanguageContextChanged(async () => {
|
||||||
@@ -165,6 +169,99 @@ export class DatabaseManager extends DisposableObject {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a test database that was originally imported from `uri`.
|
||||||
|
* A test database is creeated by the `codeql test run` command
|
||||||
|
* and ends with `.testproj`.
|
||||||
|
* @param uri The original location of the database
|
||||||
|
* @returns The first database item found that matches the uri
|
||||||
|
*/
|
||||||
|
public findTestDatabase(uri: vscode.Uri): DatabaseItem | undefined {
|
||||||
|
const originPath = uri.fsPath;
|
||||||
|
for (const item of this._databaseItems) {
|
||||||
|
if (item.origin?.type === "testproj" && item.origin.path === originPath) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async maybeReimportTestDatabase(
|
||||||
|
databaseUri: vscode.Uri,
|
||||||
|
forceImport = false,
|
||||||
|
): Promise<void> {
|
||||||
|
const res = await this.isTestDatabaseOutdated(databaseUri);
|
||||||
|
if (!res) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const doit =
|
||||||
|
forceImport ||
|
||||||
|
(await showBinaryChoiceDialog(
|
||||||
|
"This test database is outdated. Do you want to reimport it?",
|
||||||
|
));
|
||||||
|
|
||||||
|
if (doit) {
|
||||||
|
await this.reimportTestDatabase(databaseUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the origin of the imported database is newer.
|
||||||
|
* The imported database must be a test database.
|
||||||
|
* @param databaseUri the URI of the imported database to check
|
||||||
|
* @returns true if both databases exist and the origin database is newer.
|
||||||
|
*/
|
||||||
|
private async isTestDatabaseOutdated(
|
||||||
|
databaseUri: vscode.Uri,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const dbItem = this.findDatabaseItem(databaseUri);
|
||||||
|
if (dbItem === undefined || dbItem.origin?.type !== "testproj") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare timestmps of the codeql-database.yml files of the original and the
|
||||||
|
// imported databases.
|
||||||
|
const originDbYml = join(dbItem.origin.path, "codeql-database.yml");
|
||||||
|
const importedDbYml = join(
|
||||||
|
dbItem.databaseUri.fsPath,
|
||||||
|
"codeql-database.yml",
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const originStat = await stat(originDbYml);
|
||||||
|
const importedStat = await stat(importedDbYml);
|
||||||
|
return originStat.mtimeMs > importedStat.mtimeMs;
|
||||||
|
} catch (e) {
|
||||||
|
// If either of the files does not exist, we assume the origin is newer.
|
||||||
|
// This shouldn't happen unless the user manually deleted one of the files.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reimport the specified imported database from its origin.
|
||||||
|
* The imported databsae must be a testproj database.
|
||||||
|
*
|
||||||
|
* @param databaseUri the URI of the imported database to reimport
|
||||||
|
*/
|
||||||
|
private async reimportTestDatabase(databaseUri: vscode.Uri): Promise<void> {
|
||||||
|
const dbItem = this.findDatabaseItem(databaseUri);
|
||||||
|
if (dbItem === undefined || dbItem.origin?.type !== "testproj") {
|
||||||
|
throw new Error(`Database ${databaseUri} is not a testproj.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.removeDatabaseItem(dbItem);
|
||||||
|
await copy(dbItem.origin.path, databaseUri.fsPath);
|
||||||
|
const newDbItem = new DatabaseItemImpl(databaseUri, dbItem.contents, {
|
||||||
|
dateAdded: Date.now(),
|
||||||
|
language: dbItem.language,
|
||||||
|
origin: dbItem.origin,
|
||||||
|
extensionManagedLocation: dbItem.extensionManagedLocation,
|
||||||
|
});
|
||||||
|
await this.addDatabaseItem(newDbItem);
|
||||||
|
await this.setCurrentDatabaseItem(newDbItem);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a {@link DatabaseItem} to the list of open databases, if that database is not already on
|
* Adds a {@link DatabaseItem} to the list of open databases, if that database is not already on
|
||||||
* the list.
|
* the list.
|
||||||
|
|||||||
@@ -24,9 +24,15 @@ interface DatabaseOriginDebugger {
|
|||||||
type: "debugger";
|
type: "debugger";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DatabaseOriginTestProj {
|
||||||
|
type: "testproj";
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type DatabaseOrigin =
|
export type DatabaseOrigin =
|
||||||
| DatabaseOriginFolder
|
| DatabaseOriginFolder
|
||||||
| DatabaseOriginArchive
|
| DatabaseOriginArchive
|
||||||
| DatabaseOriginGitHub
|
| DatabaseOriginGitHub
|
||||||
| DatabaseOriginInternet
|
| DatabaseOriginInternet
|
||||||
| DatabaseOriginDebugger;
|
| DatabaseOriginDebugger
|
||||||
|
| DatabaseOriginTestProj;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { showAndLogExceptionWithTelemetry } from "../../common/logging";
|
|||||||
import type { ModelingEvents } from "../modeling-events";
|
import type { ModelingEvents } from "../modeling-events";
|
||||||
import type { ModelingStore } from "../modeling-store";
|
import type { ModelingStore } from "../modeling-store";
|
||||||
import type { DatabaseItem } from "../../databases/local-databases";
|
import type { DatabaseItem } from "../../databases/local-databases";
|
||||||
|
import type { ExtensionPack } from "../shared/extension-pack";
|
||||||
|
|
||||||
export class ModelAlertsView extends AbstractWebview<
|
export class ModelAlertsView extends AbstractWebview<
|
||||||
ToModelAlertsMessage,
|
ToModelAlertsMessage,
|
||||||
@@ -26,6 +27,7 @@ export class ModelAlertsView extends AbstractWebview<
|
|||||||
private readonly modelingEvents: ModelingEvents,
|
private readonly modelingEvents: ModelingEvents,
|
||||||
private readonly modelingStore: ModelingStore,
|
private readonly modelingStore: ModelingStore,
|
||||||
private readonly dbItem: DatabaseItem,
|
private readonly dbItem: DatabaseItem,
|
||||||
|
private readonly extensionPack: ExtensionPack,
|
||||||
) {
|
) {
|
||||||
super(app);
|
super(app);
|
||||||
|
|
||||||
@@ -37,6 +39,7 @@ export class ModelAlertsView extends AbstractWebview<
|
|||||||
panel.reveal(undefined, true);
|
panel.reveal(undefined, true);
|
||||||
|
|
||||||
await this.waitForPanelLoaded();
|
await this.waitForPanelLoaded();
|
||||||
|
await this.setViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getPanelConfig(): Promise<WebviewPanelConfig> {
|
protected async getPanelConfig(): Promise<WebviewPanelConfig> {
|
||||||
@@ -75,6 +78,15 @@ export class ModelAlertsView extends AbstractWebview<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async setViewState(): Promise<void> {
|
||||||
|
await this.postMessage({
|
||||||
|
t: "setModelAlertsViewState",
|
||||||
|
viewState: {
|
||||||
|
title: this.extensionPack.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async focusView(): Promise<void> {
|
public async focusView(): Promise<void> {
|
||||||
this.panel?.reveal();
|
this.panel?.reveal();
|
||||||
}
|
}
|
||||||
@@ -87,5 +99,13 @@ export class ModelAlertsView extends AbstractWebview<
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.push(
|
||||||
|
this.modelingEvents.onDbClosed(async (event) => {
|
||||||
|
if (event === this.dbItem.databaseUri.toString()) {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
this.variantAnalysisManager,
|
this.variantAnalysisManager,
|
||||||
databaseItem,
|
databaseItem,
|
||||||
language,
|
language,
|
||||||
|
this.extensionPack,
|
||||||
this.updateModelEvaluationRun.bind(this),
|
this.updateModelEvaluationRun.bind(this),
|
||||||
);
|
);
|
||||||
this.push(this.modelEvaluator);
|
this.push(this.modelEvaluator);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { CancellationTokenSource } from "vscode";
|
|||||||
import type { QlPackDetails } from "../variant-analysis/ql-pack-details";
|
import type { QlPackDetails } from "../variant-analysis/ql-pack-details";
|
||||||
import type { App } from "../common/app";
|
import type { App } from "../common/app";
|
||||||
import { ModelAlertsView } from "./model-alerts/model-alerts-view";
|
import { ModelAlertsView } from "./model-alerts/model-alerts-view";
|
||||||
|
import type { ExtensionPack } from "./shared/extension-pack";
|
||||||
|
|
||||||
export class ModelEvaluator extends DisposableObject {
|
export class ModelEvaluator extends DisposableObject {
|
||||||
// Cancellation token source to allow cancelling of the current run
|
// Cancellation token source to allow cancelling of the current run
|
||||||
@@ -34,6 +35,7 @@ export class ModelEvaluator extends DisposableObject {
|
|||||||
private readonly variantAnalysisManager: VariantAnalysisManager,
|
private readonly variantAnalysisManager: VariantAnalysisManager,
|
||||||
private readonly dbItem: DatabaseItem,
|
private readonly dbItem: DatabaseItem,
|
||||||
private readonly language: QueryLanguage,
|
private readonly language: QueryLanguage,
|
||||||
|
private readonly extensionPack: ExtensionPack,
|
||||||
private readonly updateView: (
|
private readonly updateView: (
|
||||||
run: ModelEvaluationRunState,
|
run: ModelEvaluationRunState,
|
||||||
) => Promise<void>,
|
) => Promise<void>,
|
||||||
@@ -120,6 +122,7 @@ export class ModelEvaluator extends DisposableObject {
|
|||||||
this.modelingEvents,
|
this.modelingEvents,
|
||||||
this.modelingStore,
|
this.modelingStore,
|
||||||
this.dbItem,
|
this.dbItem,
|
||||||
|
this.extensionPack,
|
||||||
);
|
);
|
||||||
await view.showView();
|
await view.showView();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,3 +19,7 @@ export interface MethodModelingPanelViewState {
|
|||||||
language: QueryLanguage | undefined;
|
language: QueryLanguage | undefined;
|
||||||
modelConfig: ModelConfig;
|
modelConfig: ModelConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ModelAlertsViewState {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { window } from "vscode";
|
import { window, Uri } from "vscode";
|
||||||
import type { CancellationToken, MessageItem } from "vscode";
|
import type { CancellationToken, MessageItem } from "vscode";
|
||||||
import type { CodeQLCliServer } from "../codeql-cli/cli";
|
import type { CodeQLCliServer } from "../codeql-cli/cli";
|
||||||
import type { ProgressCallback } from "../common/vscode/progress";
|
import type { ProgressCallback } from "../common/vscode/progress";
|
||||||
@@ -63,9 +63,22 @@ export interface CoreQueryRun {
|
|||||||
export type CoreCompletedQuery = CoreQueryResults &
|
export type CoreCompletedQuery = CoreQueryResults &
|
||||||
Omit<CoreQueryRun, "evaluate">;
|
Omit<CoreQueryRun, "evaluate">;
|
||||||
|
|
||||||
|
type OnQueryRunStartingListener = (dbPath: Uri) => Promise<void>;
|
||||||
export class QueryRunner {
|
export class QueryRunner {
|
||||||
constructor(public readonly qs: QueryServerClient) {}
|
constructor(public readonly qs: QueryServerClient) {}
|
||||||
|
|
||||||
|
// Event handlers that get notified whenever a query is about to start running.
|
||||||
|
// Can't use vscode EventEmitters since they are not asynchronous.
|
||||||
|
private readonly onQueryRunStartingListeners: OnQueryRunStartingListener[] =
|
||||||
|
[];
|
||||||
|
public onQueryRunStarting(listener: OnQueryRunStartingListener) {
|
||||||
|
this.onQueryRunStartingListeners.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fireQueryRunStarting(dbPath: Uri) {
|
||||||
|
await Promise.all(this.onQueryRunStartingListeners.map((l) => l(dbPath)));
|
||||||
|
}
|
||||||
|
|
||||||
get cliServer(): CodeQLCliServer {
|
get cliServer(): CodeQLCliServer {
|
||||||
return this.qs.cliServer;
|
return this.qs.cliServer;
|
||||||
}
|
}
|
||||||
@@ -138,6 +151,8 @@ export class QueryRunner {
|
|||||||
templates: Record<string, string> | undefined,
|
templates: Record<string, string> | undefined,
|
||||||
logger: BaseLogger,
|
logger: BaseLogger,
|
||||||
): Promise<CoreQueryResults> {
|
): Promise<CoreQueryResults> {
|
||||||
|
await this.fireQueryRunStarting(Uri.file(dbPath));
|
||||||
|
|
||||||
return await compileAndRunQueryAgainstDatabaseCore(
|
return await compileAndRunQueryAgainstDatabaseCore(
|
||||||
this.qs,
|
this.qs,
|
||||||
dbPath,
|
dbPath,
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import type { Meta, StoryFn } from "@storybook/react";
|
||||||
|
|
||||||
|
import { ModelAlerts as ModelAlertsComponent } from "../../view/model-alerts/ModelAlerts";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "CodeQL Model Alerts/CodeQL Model Alerts",
|
||||||
|
component: ModelAlertsComponent,
|
||||||
|
} as Meta<typeof ModelAlertsComponent>;
|
||||||
|
|
||||||
|
const Template: StoryFn<typeof ModelAlertsComponent> = (args) => (
|
||||||
|
<ModelAlertsComponent {...args} />
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ModelAlerts = Template.bind({});
|
||||||
|
ModelAlerts.args = {
|
||||||
|
initialViewState: { title: "codeql/sql2o-models" },
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import type { Meta, StoryFn } from "@storybook/react";
|
||||||
|
|
||||||
|
import { ModelPacks as ModelPacksComponent } from "../../view/model-alerts/ModelPacks";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Model Alerts/Model Packs",
|
||||||
|
component: ModelPacksComponent,
|
||||||
|
argTypes: {
|
||||||
|
openModelPackClick: {
|
||||||
|
action: "open-model-pack-clicked",
|
||||||
|
table: {
|
||||||
|
disable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Meta<typeof ModelPacksComponent>;
|
||||||
|
|
||||||
|
const Template: StoryFn<typeof ModelPacksComponent> = (args) => (
|
||||||
|
<ModelPacksComponent {...args} />
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ModelPacks = Template.bind({});
|
||||||
|
ModelPacks.args = {
|
||||||
|
modelPacks: [
|
||||||
|
{
|
||||||
|
name: "Model pack 1",
|
||||||
|
path: "/path/to/model-pack-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Model pack 2",
|
||||||
|
path: "/path/to/model-pack-2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -1,10 +1,27 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { ModelAlertsHeader } from "./ModelAlertsHeader";
|
||||||
|
import type { ModelAlertsViewState } from "../../model-editor/shared/view-state";
|
||||||
|
import type { ToModelAlertsMessage } from "../../common/interface-types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
initialViewState?: ModelAlertsViewState;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ModelAlerts({ initialViewState }: Props): React.JSX.Element {
|
||||||
|
const [viewState, setViewState] = useState<ModelAlertsViewState | undefined>(
|
||||||
|
initialViewState,
|
||||||
|
);
|
||||||
|
|
||||||
export function ModelAlerts(): React.JSX.Element {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listener = (evt: MessageEvent) => {
|
const listener = (evt: MessageEvent) => {
|
||||||
if (evt.origin === window.origin) {
|
if (evt.origin === window.origin) {
|
||||||
// TODO: handle messages
|
const msg: ToModelAlertsMessage = evt.data;
|
||||||
|
switch (msg.t) {
|
||||||
|
case "setModelAlertsViewState": {
|
||||||
|
setViewState(msg.viewState);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// sanitize origin
|
// sanitize origin
|
||||||
const origin = evt.origin.replace(/\n|\r/g, "");
|
const origin = evt.origin.replace(/\n|\r/g, "");
|
||||||
@@ -18,5 +35,9 @@ export function ModelAlerts(): React.JSX.Element {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return <>hello world</>;
|
if (viewState === undefined) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ModelAlertsHeader viewState={viewState}></ModelAlertsHeader>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import type { ModelAlertsViewState } from "../../model-editor/shared/view-state";
|
||||||
|
import { ViewTitle } from "../common";
|
||||||
|
|
||||||
|
type Props = { viewState: ModelAlertsViewState };
|
||||||
|
|
||||||
|
export const ModelAlertsHeader = ({ viewState }: Props) => {
|
||||||
|
return <ViewTitle>Model evaluation results for {viewState.title}</ViewTitle>;
|
||||||
|
};
|
||||||
43
extensions/ql-vscode/src/view/model-alerts/ModelPacks.tsx
Normal file
43
extensions/ql-vscode/src/view/model-alerts/ModelPacks.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { styled } from "styled-components";
|
||||||
|
import { LinkIconButton } from "../common/LinkIconButton";
|
||||||
|
import type { ModelPackDetails } from "../../common/model-pack-details";
|
||||||
|
|
||||||
|
const Title = styled.h3`
|
||||||
|
font-size: medium;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const List = styled.ul`
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ModelPacks = ({
|
||||||
|
modelPacks,
|
||||||
|
openModelPackClick,
|
||||||
|
}: {
|
||||||
|
modelPacks: ModelPackDetails[];
|
||||||
|
openModelPackClick: (path: string) => void;
|
||||||
|
}): React.JSX.Element => {
|
||||||
|
if (modelPacks.length <= 0) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title>Model packs</Title>
|
||||||
|
<List>
|
||||||
|
{modelPacks.map((modelPack) => (
|
||||||
|
<li key={modelPack.path}>
|
||||||
|
<LinkIconButton onClick={() => openModelPackClick(modelPack.path)}>
|
||||||
|
<span slot="start" className="codicon codicon-file-code"></span>
|
||||||
|
{modelPack.name}
|
||||||
|
</LinkIconButton>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -11,7 +11,7 @@ import type { ModeledMethod } from "../../model-editor/modeled-method";
|
|||||||
import { assertNever } from "../../common/helpers-pure";
|
import { assertNever } from "../../common/helpers-pure";
|
||||||
import { vscode } from "../vscode-api";
|
import { vscode } from "../vscode-api";
|
||||||
import { calculateModeledPercentage } from "../../model-editor/shared/modeled-percentage";
|
import { calculateModeledPercentage } from "../../model-editor/shared/modeled-percentage";
|
||||||
import { LinkIconButton } from "../variant-analysis/LinkIconButton";
|
import { LinkIconButton } from "../common/LinkIconButton";
|
||||||
import type { ModelEditorViewState } from "../../model-editor/shared/view-state";
|
import type { ModelEditorViewState } from "../../model-editor/shared/view-state";
|
||||||
import { ModeledMethodsList } from "./ModeledMethodsList";
|
import { ModeledMethodsList } from "./ModeledMethodsList";
|
||||||
import { percentFormatter } from "./formatters";
|
import { percentFormatter } from "./formatters";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { ModelEditorViewState } from "../../model-editor/shared/view-state"
|
|||||||
import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state";
|
import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state";
|
||||||
import { modelEvaluationRunIsRunning } from "../../model-editor/shared/model-evaluation-run-state";
|
import { modelEvaluationRunIsRunning } from "../../model-editor/shared/model-evaluation-run-state";
|
||||||
import { ModelEditorProgressRing } from "./ModelEditorProgressRing";
|
import { ModelEditorProgressRing } from "./ModelEditorProgressRing";
|
||||||
import { LinkIconButton } from "../variant-analysis/LinkIconButton";
|
import { LinkIconButton } from "../common/LinkIconButton";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
viewState: ModelEditorViewState;
|
viewState: ModelEditorViewState;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { styled } from "styled-components";
|
import { styled } from "styled-components";
|
||||||
import { ViewTitle } from "../common";
|
import { ViewTitle } from "../common";
|
||||||
import { LinkIconButton } from "./LinkIconButton";
|
import { LinkIconButton } from "../common/LinkIconButton";
|
||||||
|
|
||||||
export type QueryDetailsProps = {
|
export type QueryDetailsProps = {
|
||||||
queryName: string;
|
queryName: string;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type { ModelEvaluationRun } from "../../../../src/model-editor/model-eval
|
|||||||
import { ModelEvaluator } from "../../../../src/model-editor/model-evaluator";
|
import { ModelEvaluator } from "../../../../src/model-editor/model-evaluator";
|
||||||
import type { ModelingEvents } from "../../../../src/model-editor/modeling-events";
|
import type { ModelingEvents } from "../../../../src/model-editor/modeling-events";
|
||||||
import type { ModelingStore } from "../../../../src/model-editor/modeling-store";
|
import type { ModelingStore } from "../../../../src/model-editor/modeling-store";
|
||||||
|
import type { ExtensionPack } from "../../../../src/model-editor/shared/extension-pack";
|
||||||
import type { VariantAnalysisManager } from "../../../../src/variant-analysis/variant-analysis-manager";
|
import type { VariantAnalysisManager } from "../../../../src/variant-analysis/variant-analysis-manager";
|
||||||
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
||||||
import { createMockModelingEvents } from "../../../__mocks__/model-editor/modelingEventsMock";
|
import { createMockModelingEvents } from "../../../__mocks__/model-editor/modelingEventsMock";
|
||||||
@@ -23,6 +24,7 @@ describe("Model Evaluator", () => {
|
|||||||
let variantAnalysisManager: VariantAnalysisManager;
|
let variantAnalysisManager: VariantAnalysisManager;
|
||||||
let dbItem: DatabaseItem;
|
let dbItem: DatabaseItem;
|
||||||
let language: QueryLanguage;
|
let language: QueryLanguage;
|
||||||
|
let extensionPack: ExtensionPack;
|
||||||
let updateView: jest.Mock;
|
let updateView: jest.Mock;
|
||||||
let getModelEvaluationRunMock = jest.fn();
|
let getModelEvaluationRunMock = jest.fn();
|
||||||
|
|
||||||
@@ -40,6 +42,7 @@ describe("Model Evaluator", () => {
|
|||||||
});
|
});
|
||||||
dbItem = mockedObject<DatabaseItem>({});
|
dbItem = mockedObject<DatabaseItem>({});
|
||||||
language = QueryLanguage.Java;
|
language = QueryLanguage.Java;
|
||||||
|
extensionPack = mockedObject<ExtensionPack>({});
|
||||||
updateView = jest.fn();
|
updateView = jest.fn();
|
||||||
|
|
||||||
modelEvaluator = new ModelEvaluator(
|
modelEvaluator = new ModelEvaluator(
|
||||||
@@ -50,6 +53,7 @@ describe("Model Evaluator", () => {
|
|||||||
variantAnalysisManager,
|
variantAnalysisManager,
|
||||||
dbItem,
|
dbItem,
|
||||||
language,
|
language,
|
||||||
|
extensionPack,
|
||||||
updateView,
|
updateView,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Uri, window } from "vscode";
|
|||||||
import type { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
|
import type { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
|
||||||
import type { DatabaseManager } from "../../../../src/databases/local-databases";
|
import type { DatabaseManager } from "../../../../src/databases/local-databases";
|
||||||
import {
|
import {
|
||||||
importArchiveDatabase,
|
importLocalDatabase,
|
||||||
promptImportInternetDatabase,
|
promptImportInternetDatabase,
|
||||||
} from "../../../../src/databases/database-fetcher";
|
} from "../../../../src/databases/database-fetcher";
|
||||||
import {
|
import {
|
||||||
@@ -13,9 +13,11 @@ import {
|
|||||||
DB_URL,
|
DB_URL,
|
||||||
getActivatedExtension,
|
getActivatedExtension,
|
||||||
storagePath,
|
storagePath,
|
||||||
|
testprojLoc,
|
||||||
} from "../../global.helper";
|
} from "../../global.helper";
|
||||||
import { createMockCommandManager } from "../../../__mocks__/commandsMock";
|
import { createMockCommandManager } from "../../../__mocks__/commandsMock";
|
||||||
import { remove } from "fs-extra";
|
import { utimesSync } from "fs";
|
||||||
|
import { remove, existsSync } from "fs-extra";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run various integration tests for databases
|
* Run various integration tests for databases
|
||||||
@@ -46,10 +48,10 @@ describe("database-fetcher", () => {
|
|||||||
await remove(storagePath);
|
await remove(storagePath);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("importArchiveDatabase", () => {
|
describe("importLocalDatabase", () => {
|
||||||
it("should add a database from a folder", async () => {
|
it("should add a database from an archive", async () => {
|
||||||
const uri = Uri.file(dbLoc);
|
const uri = Uri.file(dbLoc);
|
||||||
let dbItem = await importArchiveDatabase(
|
let dbItem = await importLocalDatabase(
|
||||||
createMockCommandManager(),
|
createMockCommandManager(),
|
||||||
uri.toString(true),
|
uri.toString(true),
|
||||||
databaseManager,
|
databaseManager,
|
||||||
@@ -64,6 +66,42 @@ describe("database-fetcher", () => {
|
|||||||
expect(dbItem.name).toBe("db");
|
expect(dbItem.name).toBe("db");
|
||||||
expect(dbItem.databaseUri.fsPath).toBe(join(storagePath, "db", "db"));
|
expect(dbItem.databaseUri.fsPath).toBe(join(storagePath, "db", "db"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should import a testproj database", async () => {
|
||||||
|
let dbItem = await importLocalDatabase(
|
||||||
|
createMockCommandManager(),
|
||||||
|
Uri.file(testprojLoc).toString(true),
|
||||||
|
databaseManager,
|
||||||
|
storagePath,
|
||||||
|
progressCallback,
|
||||||
|
cli,
|
||||||
|
);
|
||||||
|
expect(dbItem).toBe(databaseManager.currentDatabaseItem);
|
||||||
|
expect(dbItem).toBe(databaseManager.databaseItems[0]);
|
||||||
|
expect(dbItem).toBeDefined();
|
||||||
|
dbItem = dbItem!;
|
||||||
|
expect(dbItem.name).toBe("db");
|
||||||
|
expect(dbItem.databaseUri.fsPath).toBe(join(storagePath, "db"));
|
||||||
|
|
||||||
|
// Now that we have fetched it. Check for re-importing
|
||||||
|
// Delete a file in the imported database and we can check if the file is recreated
|
||||||
|
const srczip = join(dbItem.databaseUri.fsPath, "src.zip");
|
||||||
|
await remove(srczip);
|
||||||
|
|
||||||
|
// Attempt 1: re-import database should be a no-op since timestamp of imported database is newer
|
||||||
|
await databaseManager.maybeReimportTestDatabase(dbItem.databaseUri);
|
||||||
|
expect(existsSync(srczip)).toBeFalsy();
|
||||||
|
|
||||||
|
// Attempt 3: re-import database should re-import the database after updating modified time
|
||||||
|
utimesSync(
|
||||||
|
join(testprojLoc, "codeql-database.yml"),
|
||||||
|
new Date(),
|
||||||
|
new Date(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await databaseManager.maybeReimportTestDatabase(dbItem.databaseUri, true);
|
||||||
|
expect(existsSync(srczip)).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("promptImportInternetDatabase", () => {
|
describe("promptImportInternetDatabase", () => {
|
||||||
|
|||||||
@@ -6,13 +6,18 @@ import {
|
|||||||
beforeEachAction,
|
beforeEachAction,
|
||||||
} from "../jest.activated-extension.setup";
|
} from "../jest.activated-extension.setup";
|
||||||
import { createWriteStream, existsSync, mkdirpSync } from "fs-extra";
|
import { createWriteStream, existsSync, mkdirpSync } from "fs-extra";
|
||||||
import { dirname } from "path";
|
import { dirname, join } from "path";
|
||||||
import { DB_URL, dbLoc } from "../global.helper";
|
import { DB_URL, dbLoc, testprojLoc } from "../global.helper";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
|
import { renameSync } from "fs";
|
||||||
|
import { unzipToDirectoryConcurrently } from "../../../src/common/unzip-concurrently";
|
||||||
|
import { platform } from "os";
|
||||||
|
import { sleep } from "../../../src/common/time";
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
// ensure the test database is downloaded
|
// ensure the test database is downloaded
|
||||||
mkdirpSync(dirname(dbLoc));
|
const dbParentDir = dirname(dbLoc);
|
||||||
|
mkdirpSync(dbParentDir);
|
||||||
if (!existsSync(dbLoc)) {
|
if (!existsSync(dbLoc)) {
|
||||||
console.log(`Downloading test database to ${dbLoc}`);
|
console.log(`Downloading test database to ${dbLoc}`);
|
||||||
|
|
||||||
@@ -30,6 +35,19 @@ beforeAll(async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unzip the database from dbLoc to testprojLoc
|
||||||
|
if (!existsSync(testprojLoc)) {
|
||||||
|
console.log(`Unzipping test database to ${testprojLoc}`);
|
||||||
|
await unzipToDirectoryConcurrently(dbLoc, dbParentDir);
|
||||||
|
// On Windows, wait a few seconds to make sure all background processes
|
||||||
|
// release their lock on the files before renaming the directory.
|
||||||
|
if (platform() === "win32") {
|
||||||
|
await sleep(3000);
|
||||||
|
}
|
||||||
|
renameSync(join(dbParentDir, "db"), testprojLoc);
|
||||||
|
console.log("Unzip completed.");
|
||||||
|
}
|
||||||
|
|
||||||
await beforeAllAction();
|
await beforeAllAction();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type {
|
|||||||
} from "../../src/databases/local-databases";
|
} from "../../src/databases/local-databases";
|
||||||
import type { CodeQLCliServer } from "../../src/codeql-cli/cli";
|
import type { CodeQLCliServer } from "../../src/codeql-cli/cli";
|
||||||
import type { CodeQLExtensionInterface } from "../../src/extension";
|
import type { CodeQLExtensionInterface } from "../../src/extension";
|
||||||
import { importArchiveDatabase } from "../../src/databases/database-fetcher";
|
import { importLocalDatabase } from "../../src/databases/database-fetcher";
|
||||||
import { createMockCommandManager } from "../__mocks__/commandsMock";
|
import { createMockCommandManager } from "../__mocks__/commandsMock";
|
||||||
|
|
||||||
// This file contains helpers shared between tests that work with an activated extension.
|
// This file contains helpers shared between tests that work with an activated extension.
|
||||||
@@ -21,6 +21,12 @@ export const dbLoc = join(
|
|||||||
realpathSync(join(__dirname, "../../../")),
|
realpathSync(join(__dirname, "../../../")),
|
||||||
"build/tests/db.zip",
|
"build/tests/db.zip",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const testprojLoc = join(
|
||||||
|
realpathSync(join(__dirname, "../../../")),
|
||||||
|
"build/tests/db.testproj",
|
||||||
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-mutable-exports
|
// eslint-disable-next-line import/no-mutable-exports
|
||||||
export let storagePath: string;
|
export let storagePath: string;
|
||||||
|
|
||||||
@@ -34,7 +40,7 @@ export async function ensureTestDatabase(
|
|||||||
// Add a database, but make sure the database manager is empty first
|
// Add a database, but make sure the database manager is empty first
|
||||||
await cleanDatabases(databaseManager);
|
await cleanDatabases(databaseManager);
|
||||||
const uri = Uri.file(dbLoc);
|
const uri = Uri.file(dbLoc);
|
||||||
const maybeDbItem = await importArchiveDatabase(
|
const maybeDbItem = await importLocalDatabase(
|
||||||
createMockCommandManager(),
|
createMockCommandManager(),
|
||||||
uri.toString(true),
|
uri.toString(true),
|
||||||
databaseManager,
|
databaseManager,
|
||||||
|
|||||||
@@ -101,6 +101,9 @@ describe("local databases", () => {
|
|||||||
onStart: () => {
|
onStart: () => {
|
||||||
/**/
|
/**/
|
||||||
},
|
},
|
||||||
|
onQueryRunStarting: () => {
|
||||||
|
/**/
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
mockedObject<CodeQLCliServer>({
|
mockedObject<CodeQLCliServer>({
|
||||||
resolveDatabase: resolveDatabaseSpy,
|
resolveDatabase: resolveDatabaseSpy,
|
||||||
|
|||||||
Reference in New Issue
Block a user