Skip to content

Commit 4cdbdd0

Browse files
authored
Merge pull request #3433 from github/aeisenberg/re-import-test-db
Import testproj databases into workspace storage
2 parents a559a0a + 2d85018 commit 4cdbdd0

File tree

12 files changed

+320
-35
lines changed

12 files changed

+320
-35
lines changed

extensions/ql-vscode/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## [UNRELEASED]
44

5+
- 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)
6+
57
## 1.12.3 - 29 February 2024
68

79
- Update variant analysis view to show when cancelation is in progress. [#3405](https://github.com/github/vscode-codeql/pull/3405)

extensions/ql-vscode/package.json

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,10 @@
738738
"command": "codeQL.setCurrentDatabase",
739739
"title": "CodeQL: Set Current Database"
740740
},
741+
{
742+
"command": "codeQL.importTestDatabase",
743+
"title": "CodeQL: (Re-)Import Test Database"
744+
},
741745
{
742746
"command": "codeQL.getCurrentDatabase",
743747
"title": "CodeQL: Get Current Database"
@@ -1322,7 +1326,12 @@
13221326
{
13231327
"command": "codeQL.setCurrentDatabase",
13241328
"group": "9_qlCommands",
1325-
"when": "resourceScheme == codeql-zip-archive || explorerResourceIsFolder || resourceExtname == .zip"
1329+
"when": "resourceExtname != .testproj && (resourceScheme == codeql-zip-archive || explorerResourceIsFolder || resourceExtname == .zipz)"
1330+
},
1331+
{
1332+
"command": "codeQL.importTestDatabase",
1333+
"group": "9_qlCommands",
1334+
"when": "explorerResourceIsFolder && resourceExtname == .testproj"
13261335
},
13271336
{
13281337
"command": "codeQL.viewAstContextExplorer",
@@ -1476,6 +1485,10 @@
14761485
"command": "codeQL.setCurrentDatabase",
14771486
"when": "false"
14781487
},
1488+
{
1489+
"command": "codeQL.importTestDatabase",
1490+
"when": "false"
1491+
},
14791492
{
14801493
"command": "codeQL.getCurrentDatabase",
14811494
"when": "false"
@@ -2018,7 +2031,6 @@
20182031
"@types/tar-stream": "^3.1.3",
20192032
"@types/through2": "^2.0.36",
20202033
"@types/tmp": "^0.2.6",
2021-
"@types/unzipper": "^0.10.1",
20222034
"@types/vscode": "^1.82.0",
20232035
"@types/yauzl": "^2.10.3",
20242036
"@typescript-eslint/eslint-plugin": "^6.19.0",

extensions/ql-vscode/src/common/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ export type LocalDatabasesCommands = {
220220

221221
// Explorer context menu
222222
"codeQL.setCurrentDatabase": (uri: Uri) => Promise<void>;
223+
"codeQL.importTestDatabase": (uri: Uri) => Promise<void>;
223224

224225
// Database panel view title commands
225226
"codeQLDatabases.chooseDatabaseFolder": () => Promise<void>;

extensions/ql-vscode/src/databases/database-fetcher.ts

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
createWriteStream,
1212
remove,
1313
readdir,
14+
copy,
1415
} from "fs-extra";
1516
import { basename, join } from "path";
1617
import type { Octokit } from "@octokit/rest";
@@ -64,7 +65,7 @@ export async function promptImportInternetDatabase(
6465

6566
validateUrl(databaseUrl);
6667

67-
const item = await databaseArchiveFetcher(
68+
const item = await fetchDatabaseToWorkspaceStorage(
6869
databaseUrl,
6970
{},
7071
databaseManager,
@@ -258,7 +259,7 @@ export async function downloadGitHubDatabaseFromUrl(
258259
* We only need the actual token string.
259260
*/
260261
const octokitToken = ((await octokit.auth()) as { token: string })?.token;
261-
return await databaseArchiveFetcher(
262+
return await fetchDatabaseToWorkspaceStorage(
262263
databaseUrl,
263264
{
264265
Accept: "application/zip",
@@ -282,14 +283,15 @@ export async function downloadGitHubDatabaseFromUrl(
282283
}
283284

284285
/**
285-
* Imports a database from a local archive.
286+
* Imports a database from a local archive or a test database that is in a folder
287+
* ending with `.testproj`.
286288
*
287-
* @param databaseUrl the file url of the archive to import
289+
* @param databaseUrl the file url of the archive or directory to import
288290
* @param databaseManager the DatabaseManager
289291
* @param storagePath where to store the unzipped database.
290292
* @param cli the CodeQL CLI server
291293
*/
292-
export async function importArchiveDatabase(
294+
export async function importLocalDatabase(
293295
commandManager: AppCommandManager,
294296
databaseUrl: string,
295297
databaseManager: DatabaseManager,
@@ -298,24 +300,27 @@ export async function importArchiveDatabase(
298300
cli: CodeQLCliServer,
299301
): Promise<DatabaseItem | undefined> {
300302
try {
301-
const item = await databaseArchiveFetcher(
303+
const origin: DatabaseOrigin = {
304+
type: databaseUrl.endsWith(".testproj") ? "testproj" : "archive",
305+
path: Uri.parse(databaseUrl).fsPath,
306+
};
307+
const item = await fetchDatabaseToWorkspaceStorage(
302308
databaseUrl,
303309
{},
304310
databaseManager,
305311
storagePath,
306312
undefined,
307-
{
308-
type: "archive",
309-
path: databaseUrl,
310-
},
313+
origin,
311314
progress,
312315
cli,
313316
);
314317
if (item) {
315318
await commandManager.execute("codeQLDatabases.focus");
316319
void showAndLogInformationMessage(
317320
extLogger,
318-
"Database unzipped and imported successfully.",
321+
origin.type === "testproj"
322+
? "Test database imported successfully."
323+
: "Database unzipped and imported successfully.",
319324
);
320325
}
321326
return item;
@@ -332,10 +337,10 @@ export async function importArchiveDatabase(
332337
}
333338

334339
/**
335-
* Fetches an archive database. The database might be on the internet
340+
* Fetches a database into workspace storage. The database might be on the internet
336341
* or in the local filesystem.
337342
*
338-
* @param databaseUrl URL from which to grab the database
343+
* @param databaseUrl URL from which to grab the database. This could be a local archive file, a local directory, or a remote URL.
339344
* @param requestHeaders Headers to send with the request
340345
* @param databaseManager the DatabaseManager
341346
* @param storagePath where to store the unzipped database.
@@ -346,7 +351,7 @@ export async function importArchiveDatabase(
346351
* @param makeSelected make the new database selected in the databases panel (default: true)
347352
* @param addSourceArchiveFolder whether to add a workspace folder containing the source archive to the workspace
348353
*/
349-
async function databaseArchiveFetcher(
354+
async function fetchDatabaseToWorkspaceStorage(
350355
databaseUrl: string,
351356
requestHeaders: { [key: string]: string },
352357
databaseManager: DatabaseManager,
@@ -374,7 +379,11 @@ async function databaseArchiveFetcher(
374379
);
375380

376381
if (isFile(databaseUrl)) {
377-
await readAndUnzip(databaseUrl, unzipPath, cli, progress);
382+
if (origin.type === "testproj") {
383+
await copyDatabase(databaseUrl, unzipPath, progress);
384+
} else {
385+
await readAndUnzip(databaseUrl, unzipPath, cli, progress);
386+
}
378387
} else {
379388
await fetchAndUnzip(databaseUrl, requestHeaders, unzipPath, cli, progress);
380389
}
@@ -438,6 +447,8 @@ async function getStorageFolder(
438447
lastName = basename(url.path).substring(0, 250);
439448
if (lastName.endsWith(".zip")) {
440449
lastName = lastName.substring(0, lastName.length - 4);
450+
} else if (lastName.endsWith(".testproj")) {
451+
lastName = lastName.substring(0, lastName.length - 9);
441452
}
442453
}
443454

@@ -484,6 +495,26 @@ function validateUrl(databaseUrl: string) {
484495
}
485496
}
486497

498+
/**
499+
* Copies a database folder from the file system into the workspace storage.
500+
* @param scrDirURL the original location of the database as a URL string
501+
* @param destDir the location to copy the database to. This should be a folder in the workspace storage.
502+
* @param progress callback to send progress messages to
503+
*/
504+
async function copyDatabase(
505+
srcDirURL: string,
506+
destDir: string,
507+
progress?: ProgressCallback,
508+
) {
509+
progress?.({
510+
maxStep: 10,
511+
step: 9,
512+
message: `Copying database ${basename(destDir)} into the workspace`,
513+
});
514+
await ensureDir(destDir);
515+
await copy(Uri.parse(srcDirURL).fsPath, destDir);
516+
}
517+
487518
async function readAndUnzip(
488519
zipUrl: string,
489520
unzipPath: string,

extensions/ql-vscode/src/databases/local-databases-ui.ts

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import {
4343
showAndLogErrorMessage,
4444
} from "../common/logging";
4545
import {
46-
importArchiveDatabase,
46+
importLocalDatabase,
4747
promptImportGithubDatabase,
4848
promptImportInternetDatabase,
4949
} from "./database-fetcher";
@@ -145,7 +145,8 @@ class DatabaseTreeDataProvider
145145
item.iconPath = new ThemeIcon("error", new ThemeColor("errorForeground"));
146146
}
147147
item.tooltip = element.databaseUri.fsPath;
148-
item.description = element.language;
148+
item.description =
149+
element.language + (element.origin?.type === "testproj" ? " (test)" : "");
149150
return item;
150151
}
151152

@@ -281,6 +282,7 @@ export class DatabaseUI extends DisposableObject {
281282
this.handleChooseDatabaseInternet.bind(this),
282283
"codeQL.chooseDatabaseGithub": this.handleChooseDatabaseGithub.bind(this),
283284
"codeQL.setCurrentDatabase": this.handleSetCurrentDatabase.bind(this),
285+
"codeQL.importTestDatabase": this.handleImportTestDatabase.bind(this),
284286
"codeQL.setDefaultTourDatabase":
285287
this.handleSetDefaultTourDatabase.bind(this),
286288
"codeQL.upgradeCurrentDatabase":
@@ -712,7 +714,7 @@ export class DatabaseUI extends DisposableObject {
712714
try {
713715
// Assume user has selected an archive if the file has a .zip extension
714716
if (uri.path.endsWith(".zip")) {
715-
await importArchiveDatabase(
717+
await importLocalDatabase(
716718
this.app.commands,
717719
uri.toString(true),
718720
this.databaseManager,
@@ -740,6 +742,60 @@ export class DatabaseUI extends DisposableObject {
740742
);
741743
}
742744

745+
private async handleImportTestDatabase(uri: Uri): Promise<void> {
746+
return withProgress(
747+
async (progress) => {
748+
try {
749+
if (!uri.path.endsWith(".testproj")) {
750+
throw new Error(
751+
"Please select a valid test database to import. Test databases end with `.testproj`.",
752+
);
753+
}
754+
755+
// Check if the database is already in the workspace. If
756+
// so, delete it first before importing the new one.
757+
const existingItem = this.databaseManager.findTestDatabase(uri);
758+
const baseName = basename(uri.fsPath);
759+
if (existingItem !== undefined) {
760+
progress({
761+
maxStep: 9,
762+
step: 1,
763+
message: `Removing existing test database ${baseName}`,
764+
});
765+
await this.databaseManager.removeDatabaseItem(existingItem);
766+
}
767+
768+
await importLocalDatabase(
769+
this.app.commands,
770+
uri.toString(true),
771+
this.databaseManager,
772+
this.storagePath,
773+
progress,
774+
this.queryServer.cliServer,
775+
);
776+
777+
if (existingItem !== undefined) {
778+
progress({
779+
maxStep: 9,
780+
step: 9,
781+
message: `Successfully re-imported ${baseName}`,
782+
});
783+
}
784+
} catch (e) {
785+
// rethrow and let this be handled by default error handling.
786+
throw new Error(
787+
`Could not set database to ${basename(
788+
uri.fsPath,
789+
)}. Reason: ${getErrorMessage(e)}`,
790+
);
791+
}
792+
},
793+
{
794+
title: "(Re-)importing test database from directory",
795+
},
796+
);
797+
}
798+
743799
private async handleRemoveDatabase(
744800
databaseItems: DatabaseItem[],
745801
): Promise<void> {
@@ -959,7 +1015,7 @@ export class DatabaseUI extends DisposableObject {
9591015
} else {
9601016
// we are selecting a database archive. Must unzip into a workspace-controlled area
9611017
// before importing.
962-
return await importArchiveDatabase(
1018+
return await importLocalDatabase(
9631019
this.app.commands,
9641020
uri.toString(true),
9651021
this.databaseManager,

0 commit comments

Comments
 (0)