Merge pull request #2042 from github/elena/add-more-test-coverage

Add test coverage for `openDatabase`
This commit is contained in:
Elena Tanasoiu
2023-02-07 14:03:09 +00:00
committed by GitHub
2 changed files with 151 additions and 53 deletions

View File

@@ -154,67 +154,69 @@ export async function findSourceArchive(
return undefined; return undefined;
} }
async function resolveDatabase(
databasePath: string,
): Promise<DatabaseContents> {
const name = basename(databasePath);
// Look for dataset and source archive.
const datasetUri = await findDataset(databasePath);
const sourceArchiveUri = await findSourceArchive(databasePath);
return {
kind: DatabaseKind.Database,
name,
datasetUri,
sourceArchiveUri,
};
}
/** Gets the relative paths of all `.dbscheme` files in the given directory. */ /** Gets the relative paths of all `.dbscheme` files in the given directory. */
async function getDbSchemeFiles(dbDirectory: string): Promise<string[]> { async function getDbSchemeFiles(dbDirectory: string): Promise<string[]> {
return await glob("*.dbscheme", { cwd: dbDirectory }); return await glob("*.dbscheme", { cwd: dbDirectory });
} }
async function resolveDatabaseContents( export class DatabaseResolver {
uri: vscode.Uri, public static async resolveDatabaseContents(
): Promise<DatabaseContents> { uri: vscode.Uri,
if (uri.scheme !== "file") { ): Promise<DatabaseContents> {
throw new Error( if (uri.scheme !== "file") {
`Database URI scheme '${uri.scheme}' not supported; only 'file' URIs are supported.`, throw new Error(
); `Database URI scheme '${uri.scheme}' not supported; only 'file' URIs are supported.`,
} );
const databasePath = uri.fsPath; }
if (!(await pathExists(databasePath))) { const databasePath = uri.fsPath;
throw new InvalidDatabaseError( if (!(await pathExists(databasePath))) {
`Database '${databasePath}' does not exist.`, throw new InvalidDatabaseError(
); `Database '${databasePath}' does not exist.`,
);
}
const contents = await this.resolveDatabase(databasePath);
if (contents === undefined) {
throw new InvalidDatabaseError(
`'${databasePath}' is not a valid database.`,
);
}
// Look for a single dbscheme file within the database.
// This should be found in the dataset directory, regardless of the form of database.
const dbPath = contents.datasetUri.fsPath;
const dbSchemeFiles = await getDbSchemeFiles(dbPath);
if (dbSchemeFiles.length === 0) {
throw new InvalidDatabaseError(
`Database '${databasePath}' does not contain a CodeQL dbscheme under '${dbPath}'.`,
);
} else if (dbSchemeFiles.length > 1) {
throw new InvalidDatabaseError(
`Database '${databasePath}' contains multiple CodeQL dbschemes under '${dbPath}'.`,
);
} else {
contents.dbSchemeUri = vscode.Uri.file(resolve(dbPath, dbSchemeFiles[0]));
}
return contents;
} }
const contents = await resolveDatabase(databasePath); public static async resolveDatabase(
databasePath: string,
): Promise<DatabaseContents> {
const name = basename(databasePath);
if (contents === undefined) { // Look for dataset and source archive.
throw new InvalidDatabaseError( const datasetUri = await findDataset(databasePath);
`'${databasePath}' is not a valid database.`, const sourceArchiveUri = await findSourceArchive(databasePath);
);
}
// Look for a single dbscheme file within the database. return {
// This should be found in the dataset directory, regardless of the form of database. kind: DatabaseKind.Database,
const dbPath = contents.datasetUri.fsPath; name,
const dbSchemeFiles = await getDbSchemeFiles(dbPath); datasetUri,
if (dbSchemeFiles.length === 0) { sourceArchiveUri,
throw new InvalidDatabaseError( };
`Database '${databasePath}' does not contain a CodeQL dbscheme under '${dbPath}'.`,
);
} else if (dbSchemeFiles.length > 1) {
throw new InvalidDatabaseError(
`Database '${databasePath}' contains multiple CodeQL dbschemes under '${dbPath}'.`,
);
} else {
contents.dbSchemeUri = vscode.Uri.file(resolve(dbPath, dbSchemeFiles[0]));
} }
return contents;
} }
/** An item in the list of available databases */ /** An item in the list of available databases */
@@ -370,7 +372,9 @@ export class DatabaseItemImpl implements DatabaseItem {
public async refresh(): Promise<void> { public async refresh(): Promise<void> {
try { try {
try { try {
this._contents = await resolveDatabaseContents(this.databaseUri); this._contents = await DatabaseResolver.resolveDatabaseContents(
this.databaseUri,
);
this._error = undefined; this._error = undefined;
} catch (e) { } catch (e) {
this._contents = undefined; this._contents = undefined;
@@ -602,7 +606,7 @@ export class DatabaseManager extends DisposableObject {
uri: vscode.Uri, uri: vscode.Uri,
displayName?: string, displayName?: string,
): Promise<DatabaseItem> { ): Promise<DatabaseItem> {
const contents = await resolveDatabaseContents(uri); const contents = await DatabaseResolver.resolveDatabaseContents(uri);
// Ignore the source archive for QLTest databases by default. // Ignore the source archive for QLTest databases by default.
const isQLTestDatabase = extname(uri.fsPath) === ".testproj"; const isQLTestDatabase = extname(uri.fsPath) === ".testproj";
const fullOptions: FullDatabaseOptions = { const fullOptions: FullDatabaseOptions = {

View File

@@ -10,6 +10,7 @@ import {
DatabaseContents, DatabaseContents,
FullDatabaseOptions, FullDatabaseOptions,
findSourceArchive, findSourceArchive,
DatabaseResolver,
} from "../../../src/databases"; } from "../../../src/databases";
import { Logger } from "../../../src/common"; import { Logger } from "../../../src/common";
import { ProgressCallback } from "../../../src/commandRunner"; import { ProgressCallback } from "../../../src/commandRunner";
@@ -21,6 +22,7 @@ import {
import { testDisposeHandler } from "../test-dispose-handler"; import { testDisposeHandler } from "../test-dispose-handler";
import { QueryRunner } from "../../../src/queryRunner"; import { QueryRunner } from "../../../src/queryRunner";
import * as helpers from "../../../src/helpers"; import * as helpers from "../../../src/helpers";
import { Setting } from "../../../src/config";
describe("databases", () => { describe("databases", () => {
const MOCK_DB_OPTIONS: FullDatabaseOptions = { const MOCK_DB_OPTIONS: FullDatabaseOptions = {
@@ -623,6 +625,98 @@ describe("databases", () => {
}); });
}); });
describe("openDatabase", () => {
let createSkeletonPacksSpy: jest.SpyInstance;
let resolveDatabaseContentsSpy: jest.SpyInstance;
let addDatabaseSourceArchiveFolderSpy: jest.SpyInstance;
let mockDbItem: DatabaseItemImpl;
beforeEach(() => {
createSkeletonPacksSpy = jest
.spyOn(databaseManager, "createSkeletonPacks")
.mockImplementation(async () => {
/* no-op */
});
resolveDatabaseContentsSpy = jest
.spyOn(DatabaseResolver, "resolveDatabaseContents")
.mockResolvedValue({} as DatabaseContents);
addDatabaseSourceArchiveFolderSpy = jest.spyOn(
databaseManager,
"addDatabaseSourceArchiveFolder",
);
jest.mock("fs", () => ({
promises: {
pathExists: jest.fn().mockResolvedValue(true),
},
}));
mockDbItem = createMockDB();
});
it("should resolve the database contents", async () => {
await databaseManager.openDatabase(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem.databaseUri,
);
expect(resolveDatabaseContentsSpy).toBeCalledTimes(1);
});
it("should add database source archive folder", async () => {
await databaseManager.openDatabase(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem.databaseUri,
);
expect(addDatabaseSourceArchiveFolderSpy).toBeCalledTimes(1);
});
describe("when codeQL.codespacesTemplate is set to true", () => {
it("should create a skeleton QL pack", async () => {
jest.spyOn(Setting.prototype, "getValue").mockReturnValue(true);
await databaseManager.openDatabase(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem.databaseUri,
);
expect(createSkeletonPacksSpy).toBeCalledTimes(1);
});
});
describe("when codeQL.codespacesTemplate is set to false", () => {
it("should not create a skeleton QL pack", async () => {
jest.spyOn(Setting.prototype, "getValue").mockReturnValue(false);
await databaseManager.openDatabase(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem.databaseUri,
);
expect(createSkeletonPacksSpy).toBeCalledTimes(0);
});
});
describe("when codeQL.codespacesTemplate is not set", () => {
it("should not create a skeleton QL pack", async () => {
jest.spyOn(Setting.prototype, "getValue").mockReturnValue(undefined);
await databaseManager.openDatabase(
{} as ProgressCallback,
{} as CancellationToken,
mockDbItem.databaseUri,
);
expect(createSkeletonPacksSpy).toBeCalledTimes(0);
});
});
});
function createMockDB( function createMockDB(
mockDbOptions = MOCK_DB_OPTIONS, mockDbOptions = MOCK_DB_OPTIONS,
// source archive location must be a real(-ish) location since // source archive location must be a real(-ish) location since