Migrate no-workspace integration tests to Jest

This migrates all no-workspace VSCode integration tests to Jest by
running `npx jest-codemods`, followed by manual fixes (mostly for Sinon
and Proxyquire).
This commit is contained in:
Koen Vlaswinkel
2022-11-22 15:51:56 +01:00
parent cc869daf05
commit a0ab34bf55
36 changed files with 1683 additions and 1517 deletions

View File

@@ -0,0 +1,18 @@
const path = require("path");
const tmp = require("tmp-promise");
const tmpDir = tmp.dirSync({ unsafeCleanup: true });
/** @type {import('jest-runner-vscode').RunnerOptions} */
module.exports = {
version: "stable",
launchArgs: [
"--disable-extensions",
"--disable-gpu",
"--new-window",
"--user-data-dir=" + path.join(tmpDir.name, "user-data"),
],
workspaceDir: path.resolve(__dirname, "test/data"),
openInFolder: true,
extensionDevelopmentPath: path.resolve(__dirname),
};

View File

@@ -4,5 +4,9 @@
*/
module.exports = {
projects: ["<rootDir>/src/view", "<rootDir>/test"],
projects: [
"<rootDir>/src/view",
"<rootDir>/test",
"<rootDir>/out/vscode-tests/no-workspace",
],
};

View File

@@ -1272,8 +1272,8 @@
"test": "npm-run-all -p test:*",
"test:unit": "jest --projects test",
"test:view": "jest --projects src/view",
"integration": "node ./out/vscode-tests/run-integration-tests.js no-workspace,minimal-workspace",
"integration:no-workspace": "node ./out/vscode-tests/run-integration-tests.js no-workspace",
"integration": "npm-run-all -p integration:*",
"integration:no-workspace": "jest --projects out/vscode-tests/no-workspace",
"integration:minimal-workspace": "node ./out/vscode-tests/run-integration-tests.js minimal-workspace",
"cli-integration": "node ./out/vscode-tests/run-integration-tests.js cli-integration",
"update-vscode": "node ./node_modules/vscode/bin/install",

View File

@@ -66,7 +66,6 @@ export function createMockLocalQueryInfo({
}
export function createMockQueryWithResults({
sandbox = undefined,
didRunSuccessfully = true,
hasInterpretedResults = true,
hasMetadata = undefined,
@@ -76,16 +75,8 @@ export function createMockQueryWithResults({
hasInterpretedResults?: boolean;
hasMetadata?: boolean;
}): QueryWithResults {
const dispose = sandbox
? sandbox.spy()
: () => {
/**/
};
const deleteQuery = sandbox
? sandbox.stub()
: () => {
/**/
};
const dispose = jest.fn();
const deleteQuery = jest.fn();
const metadata = hasMetadata
? ({ name: "query-name" } as QueryMetadata)
: undefined;

View File

@@ -0,0 +1,205 @@
import type { Config } from "jest";
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/configuration
*/
const config: Config = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/6m/1394pht172qgd7dmw1fwjk100000gn/T/jest_dx",
// Automatically clear mock calls, instances, contexts and results before every test
// clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
// collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
// coverageDirectory: undefined,
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// Indicates which provider should be used to instrument code for coverage
coverageProvider: "v8",
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// The default configuration for fake timers
// fakeTimers: {
// "enableGlobally": false
// },
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// Insert Jest's globals (expect, test, describe, beforeEach etc.) into the global environment. If you set this to false, you should import from @jest/globals.
// injectGlobals: false,
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: 1,
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
moduleFileExtensions: ["js", "mjs", "cjs", "jsx", "ts", "tsx", "json"],
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: 'ts-jest',
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state before every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state and implementation before every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
runner: "vscode",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
setupFilesAfterEnv: ["<rootDir>/../jest.setup.js"],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
// testEnvironment: 'jsdom',
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
testMatch: ["**/*.test.[jt]s"],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// A map from regular expressions to paths to transformers
// transform: {
// '^.+\\.tsx?$': [
// 'ts-jest',
// {
// tsconfig: 'src/view/tsconfig.spec.json',
// },
// ],
// 'node_modules': [
// 'babel-jest',
// {
// presets: [
// '@babel/preset-env'
// ],
// plugins: [
// '@babel/plugin-transform-modules-commonjs',
// ]
// }
// ]
// },
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// 'transformIgnorePatterns': [
// // These use ES modules, so need to be transformed
// 'node_modules/(?!(?:@vscode/webview-ui-toolkit|@microsoft/.+|exenv-es6)/.*)'
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};
export default config;

View File

@@ -0,0 +1,9 @@
import { env } from "vscode";
(env as any).openExternal = () => {
/**/
};
afterAll(() => {
jest.restoreAllMocks();
});

View File

@@ -1,13 +1,15 @@
import * as assert from "assert";
// This file needs to be located in a separate directory. This will ensure that this
// test is run at the start-up of a new VSCode instance.
import * as vscode from "vscode";
// Note that this may open the most recent VSCode workspace.
describe("launching with no specified workspace", () => {
const ext = vscode.extensions.getExtension("GitHub.vscode-codeql");
it("should install the extension", () => {
assert(ext);
expect(ext).not.toBeUndefined();
});
it("should not activate the extension at first", () => {
assert(ext!.isActive === false);
expect(ext!.isActive).toBeFalsy();
});
});

View File

@@ -1,4 +1,3 @@
import { expect } from "chai";
import * as path from "path";
import {
@@ -22,7 +21,7 @@ describe("archive-filesystem-provider", () => {
pathWithinSourceArchive: "/aFileName.txt",
});
const data = await archiveProvider.readFile(uri);
expect(data.length).to.equal(0);
expect(data.length).toBe(0);
});
it("read non-empty file correctly", async () => {
@@ -35,7 +34,7 @@ describe("archive-filesystem-provider", () => {
pathWithinSourceArchive: "folder1/textFile.txt",
});
const data = await archiveProvider.readFile(uri);
expect(Buffer.from(data).toString("utf8")).to.be.equal("I am a text\n");
expect(Buffer.from(data).toString("utf8")).toBe("I am a text\n");
});
it("read a directory", async () => {
@@ -48,7 +47,7 @@ describe("archive-filesystem-provider", () => {
pathWithinSourceArchive: "folder1",
});
const files = await archiveProvider.readDirectory(uri);
expect(files).to.be.deep.equal([
expect(files).toEqual([
["folder2", FileType.Directory],
["textFile.txt", FileType.File],
["textFile2.txt", FileType.File],
@@ -68,7 +67,7 @@ describe("archive-filesystem-provider", () => {
await archiveProvider.readDirectory(uri);
throw new Error("Failed");
} catch (e) {
expect(e).to.be.instanceOf(FileSystemError);
expect(e).toBeInstanceOf(FileSystemError);
}
});
@@ -85,7 +84,7 @@ describe("archive-filesystem-provider", () => {
await archiveProvider.readFile(uri);
throw new Error("Failed");
} catch (e) {
expect(e).to.be.instanceOf(FileSystemError);
expect(e).toBeInstanceOf(FileSystemError);
}
});
@@ -102,7 +101,7 @@ describe("archive-filesystem-provider", () => {
await archiveProvider.readDirectory(uri);
throw new Error("Failed");
} catch (e) {
expect(e).to.be.instanceOf(FileSystemError);
expect(e).toBeInstanceOf(FileSystemError);
}
});
@@ -119,7 +118,7 @@ describe("archive-filesystem-provider", () => {
await archiveProvider.readFile(uri);
throw new Error("Failed");
} catch (e) {
expect(e).to.be.instanceOf(FileSystemError);
expect(e).toBeInstanceOf(FileSystemError);
}
});
@@ -133,11 +132,11 @@ describe("archive-filesystem-provider", () => {
pathWithinSourceArchive: "folder1/folder2",
});
const files = await archiveProvider.readDirectory(uri);
expect(files).to.be.deep.equal([["textFile3.txt", FileType.File]]);
expect(files).toEqual([["textFile3.txt", FileType.File]]);
});
});
describe("source archive uri encoding", function () {
describe("source archive uri encoding", () => {
const testCases: { name: string; input: ZipFileReference }[] = [
{
name: "mixed case and unicode",
@@ -169,11 +168,11 @@ describe("source archive uri encoding", function () {
},
];
for (const testCase of testCases) {
it(`should work round trip with ${testCase.name}`, function () {
it(`should work round trip with ${testCase.name}`, () => {
const output = decodeSourceArchiveUri(
encodeSourceArchiveUri(testCase.input),
);
expect(output).to.eql(testCase.input);
expect(output).toEqual(testCase.input);
});
}
@@ -182,7 +181,7 @@ describe("source archive uri encoding", function () {
pathWithinSourceArchive: "",
sourceArchiveZipPath: "a/b/c",
});
expect(decodeSourceArchiveUri(uri)).to.deep.eq({
expect(decodeSourceArchiveUri(uri)).toEqual({
pathWithinSourceArchive: "/",
sourceArchiveZipPath: "a/b/c",
});
@@ -191,10 +190,10 @@ describe("source archive uri encoding", function () {
it("should encode a uri at the root of the archive", () => {
const path = "/a/b/c/src.zip";
const uri = encodeArchiveBasePath(path);
expect(uri.path).to.eq(path);
expect(decodeSourceArchiveUri(uri).pathWithinSourceArchive).to.eq("/");
expect(decodeSourceArchiveUri(uri).sourceArchiveZipPath).to.eq(path);
expect(uri.authority).to.eq("0-14");
expect(uri.path).toBe(path);
expect(decodeSourceArchiveUri(uri).pathWithinSourceArchive).toBe("/");
expect(decodeSourceArchiveUri(uri).sourceArchiveZipPath).toBe(path);
expect(uri.authority).toBe("0-14");
});
it("should handle malformed uri with no authority", () => {
@@ -202,8 +201,8 @@ describe("source archive uri encoding", function () {
const uri = Uri.parse("file:/a/b/c/src.zip").with({
scheme: zipArchiveScheme,
});
expect(uri.authority).to.eq("");
expect(decodeSourceArchiveUri(uri)).to.deep.eq({
expect(uri.authority).toBe("");
expect(decodeSourceArchiveUri(uri)).toEqual({
sourceArchiveZipPath: "/a/b/c/src.zip",
pathWithinSourceArchive: "/",
});

View File

@@ -1,6 +1,4 @@
import * as fs from "fs-extra";
import { expect } from "chai";
import * as sinon from "sinon";
import * as yaml from "js-yaml";
import { AstViewer, AstItem } from "../../astViewer";
@@ -11,20 +9,21 @@ import { testDisposeHandler } from "../test-dispose-handler";
describe("AstViewer", () => {
let astRoots: AstItem[];
let viewer: AstViewer | undefined;
let sandbox: sinon.SinonSandbox;
beforeEach(async () => {
sandbox = sinon.createSandbox();
// the ast is stored in yaml because there are back pointers
// making a json representation impossible.
// The complication here is that yaml files are not copied into the 'out' directory by tsc.
astRoots = await buildAst();
sandbox.stub(commands, "registerCommand");
sandbox.stub(commands, "executeCommand");
jest.spyOn(commands, "registerCommand").mockImplementation(() => ({
dispose: jest.fn(),
}));
jest
.spyOn(commands, "executeCommand")
.mockImplementation(() => Promise.resolve());
});
afterEach(() => {
sandbox.restore();
if (viewer) {
viewer.dispose(testDisposeHandler);
viewer = undefined;
@@ -36,9 +35,9 @@ describe("AstViewer", () => {
viewer = new AstViewer();
viewer.updateRoots(astRoots, item, Uri.file("def/abc"));
expect((viewer as any).treeDataProvider.roots).to.eq(astRoots);
expect((viewer as any).treeDataProvider.db).to.eq(item);
expect((viewer as any).treeView.message).to.eq("AST for abc");
expect((viewer as any).treeDataProvider.roots).toBe(astRoots);
expect((viewer as any).treeDataProvider.db).toBe(item);
expect((viewer as any).treeView.message).toBe("AST for abc");
});
it("should update the tree selection based on a change in the editor selection", () => {
@@ -49,7 +48,7 @@ describe("AstViewer", () => {
it("should select an AssignExpr", () => {
// this one is interesting because it spans a couple of other nodes
const expr = findNodeById(300, astRoots);
expect(expr.label).to.eq("[AssignExpr] ... = ...");
expect(expr.label).toBe("[AssignExpr] ... = ...");
doSelectionTest(expr, expr.fileLocation?.range);
});
@@ -75,8 +74,10 @@ describe("AstViewer", () => {
const item = {} as DatabaseItem;
viewer = new AstViewer();
viewer.updateRoots(astRoots, item, defaultUri);
const spy = sandbox.spy();
(viewer as any).treeView.reveal = spy;
const revealMock = jest.fn();
(viewer as any).treeView.reveal = revealMock;
Object.defineProperty((viewer as any).treeView, "visible", {
value: true,
});
@@ -84,9 +85,9 @@ describe("AstViewer", () => {
const mockEvent = createMockEvent(selectionRange, fileUri);
(viewer as any).updateTreeSelection(mockEvent);
if (expectedSelection) {
expect(spy).to.have.been.calledWith(expectedSelection);
expect(revealMock).toBeCalledWith(expectedSelection);
} else {
expect(spy).not.to.have.been.called;
expect(revealMock).not.toBeCalled();
}
}

View File

@@ -1,6 +1,4 @@
import * as fs from "fs-extra";
import { expect } from "chai";
import * as sinon from "sinon";
import AstBuilder from "../../../contextual/astBuilder";
import { CodeQLCliServer } from "../../../cli";
@@ -35,9 +33,9 @@ describe("AstBuilder", () => {
beforeEach(() => {
mockCli = {
bqrsDecode: sinon
.stub()
.callsFake(
bqrsDecode: jest
.fn()
.mockImplementation(
(_: string, resultSet: "nodes" | "edges" | "graphProperties") => {
return mockDecode(resultSet);
},
@@ -55,23 +53,15 @@ describe("AstBuilder", () => {
const roots = await astBuilder.getRoots();
const options = { entities: ["id", "url", "string"] };
expect(mockCli.bqrsDecode).to.have.been.calledWith(
"/a/b/c",
"nodes",
options,
);
expect(mockCli.bqrsDecode).to.have.been.calledWith(
"/a/b/c",
"edges",
options,
);
expect(mockCli.bqrsDecode).to.have.been.calledWith(
expect(mockCli.bqrsDecode).toBeCalledWith("/a/b/c", "nodes", options);
expect(mockCli.bqrsDecode).toBeCalledWith("/a/b/c", "edges", options);
expect(mockCli.bqrsDecode).toBeCalledWith(
"/a/b/c",
"graphProperties",
options,
);
expect(roots.map((r) => ({ ...r, children: undefined }))).to.deep.eq(
expect(roots.map((r) => ({ ...r, children: undefined }))).toEqual(
expectedRoots,
);
});
@@ -82,7 +72,7 @@ describe("AstBuilder", () => {
const astBuilder = createAstBuilder();
const roots = await astBuilder.getRoots();
expect(roots[0].children[0].parent).to.eq(roots[0]);
expect(roots[0].children[0].parent).toBe(roots[0]);
// break the recursion
(roots[0].children[0] as any).parent = undefined;
(roots[0].children[0] as any).children = undefined;
@@ -103,7 +93,7 @@ describe("AstBuilder", () => {
parent: undefined,
};
expect(roots[0].children[0]).to.deep.eq(child);
expect(roots[0].children[0]).toEqual(child);
});
it("should build an AST child with edge label", async () => {
@@ -112,7 +102,7 @@ describe("AstBuilder", () => {
const astBuilder = createAstBuilder();
const roots = await astBuilder.getRoots();
expect(roots[0].children[1].parent).to.eq(roots[0]);
expect(roots[0].children[1].parent).toBe(roots[0]);
// break the recursion
(roots[0].children[1] as any).parent = undefined;
(roots[0].children[1] as any).children = undefined;
@@ -133,7 +123,7 @@ describe("AstBuilder", () => {
parent: undefined,
};
expect(roots[0].children[1]).to.deep.eq(child);
expect(roots[0].children[1]).toEqual(child);
});
it("should fail when graphProperties are not correct", async () => {
@@ -142,7 +132,7 @@ describe("AstBuilder", () => {
};
const astBuilder = createAstBuilder();
await expect(astBuilder.getRoots()).to.be.rejectedWith("AST is invalid");
await expect(astBuilder.getRoots()).rejects.toThrow("AST is invalid");
});
function createAstBuilder() {

View File

@@ -1,4 +1,3 @@
import { expect } from "chai";
import { Uri, Range } from "vscode";
import fileRangeFromURI from "../../../contextual/fileRangeFromURI";
@@ -10,8 +9,9 @@ import {
describe("fileRangeFromURI", () => {
it("should return undefined when value is not a file URI", () => {
expect(fileRangeFromURI("hucairz", createMockDatabaseItem())).to.be
.undefined;
expect(
fileRangeFromURI("hucairz", createMockDatabaseItem()),
).toBeUndefined();
});
it("should fail to find a location when not a file URI and a full 5 part location", () => {
@@ -26,7 +26,7 @@ describe("fileRangeFromURI", () => {
} as LineColumnLocation,
createMockDatabaseItem(),
),
).to.be.undefined;
).toBeUndefined();
});
it("should fail to find a location when there is a silly protocol", () => {
@@ -41,7 +41,7 @@ describe("fileRangeFromURI", () => {
} as LineColumnLocation,
createMockDatabaseItem(),
),
).to.be.undefined;
).toBeUndefined();
});
it("should return undefined when value is an empty uri", () => {
@@ -56,7 +56,7 @@ describe("fileRangeFromURI", () => {
} as LineColumnLocation,
createMockDatabaseItem(),
),
).to.be.undefined;
).toBeUndefined();
});
it("should return a range for a WholeFileLocation", () => {
@@ -67,7 +67,7 @@ describe("fileRangeFromURI", () => {
} as WholeFileLocation,
createMockDatabaseItem(),
),
).to.deep.eq({
).toEqual({
uri: Uri.parse("file:///hucairz", true),
range: new Range(0, 0, 0, 0),
});
@@ -85,7 +85,7 @@ describe("fileRangeFromURI", () => {
} as LineColumnLocation,
createMockDatabaseItem(),
),
).to.deep.eq({
).toEqual({
uri: Uri.parse("file:///hucairz", true),
range: new Range(0, 1, 2, 4),
});

View File

@@ -1,42 +1,63 @@
import * as yaml from "js-yaml";
import * as sinon from "sinon";
import { expect } from "chai";
import * as pq from "proxyquire";
import * as fs from "fs-extra";
import { KeyType } from "../../../contextual/keyType";
import { getErrorMessage } from "../../../pure/helpers-pure";
const proxyquire = pq.noPreserveCache().noCallThru();
import * as helpers from "../../../helpers";
import {
qlpackOfDatabase,
resolveQueries,
} from "../../../contextual/queryResolver";
import { CodeQLCliServer } from "../../../cli";
import { DatabaseItem } from "../../../databases";
describe("queryResolver", () => {
let module: Record<string, Function>;
let writeFileSpy: sinon.SinonSpy;
let getQlPackForDbschemeSpy: sinon.SinonStub;
let getPrimaryDbschemeSpy: sinon.SinonStub;
let mockCli: Record<
string,
sinon.SinonStub | Record<string, sinon.SinonStub>
>;
const writeFileSpy = jest.spyOn(fs, "writeFile");
const getQlPackForDbschemeSpy = jest.spyOn(helpers, "getQlPackForDbscheme");
const getPrimaryDbschemeSpy = jest.spyOn(helpers, "getPrimaryDbscheme");
jest.spyOn(helpers, "getOnDiskWorkspaceFolders").mockReturnValue([]);
jest.spyOn(helpers, "showAndLogErrorMessage").mockResolvedValue(undefined);
const mockCli = {
resolveQueriesInSuite: jest.fn(),
cliConstraints: {
supportsAllowLibraryPacksInResolveQueries: jest.fn(),
},
};
beforeEach(() => {
mockCli = {
resolveQueriesInSuite: sinon.stub(),
cliConstraints: {
supportsAllowLibraryPacksInResolveQueries: sinon.stub().returns(true),
},
};
module = createModule();
writeFileSpy.mockReset().mockImplementation(() => Promise.resolve());
getQlPackForDbschemeSpy.mockReset().mockResolvedValue({
dbschemePack: "dbschemePack",
dbschemePackIsLibraryPack: false,
});
getPrimaryDbschemeSpy.mockReset().mockResolvedValue("primaryDbscheme");
mockCli.resolveQueriesInSuite.mockReset();
mockCli.cliConstraints.supportsAllowLibraryPacksInResolveQueries
.mockReset()
.mockReturnValue(true);
});
describe("resolveQueries", () => {
it("should resolve a query", async () => {
mockCli.resolveQueriesInSuite.returns(["a", "b"]);
const result = await module.resolveQueries(
mockCli,
{ dbschemePack: "my-qlpack" },
mockCli.resolveQueriesInSuite.mockReturnValue(["a", "b"]);
const result = await resolveQueries(
mockCli as unknown as CodeQLCliServer,
{ dbschemePack: "my-qlpack", dbschemePackIsLibraryPack: false },
KeyType.DefinitionQuery,
);
expect(result).to.deep.equal(["a", "b"]);
expect(writeFileSpy.getCall(0).args[0]).to.match(/.qls$/);
expect(yaml.load(writeFileSpy.getCall(0).args[1])).to.deep.equal([
expect(result).toEqual(["a", "b"]);
expect(writeFileSpy).toHaveBeenNthCalledWith(
1,
expect.stringMatching(/.qls$/),
expect.anything(),
expect.anything(),
);
expect(yaml.load(writeFileSpy.mock.calls[0][1])).toEqual([
{
from: "my-qlpack",
queries: ".",
@@ -50,12 +71,12 @@ describe("queryResolver", () => {
it("should resolve a query from the queries pack if this is an old CLI", async () => {
// pretend this is an older CLI
(
mockCli.cliConstraints as any
).supportsAllowLibraryPacksInResolveQueries.returns(false);
mockCli.resolveQueriesInSuite.returns(["a", "b"]);
const result = await module.resolveQueries(
mockCli,
mockCli.cliConstraints.supportsAllowLibraryPacksInResolveQueries.mockReturnValue(
false,
);
mockCli.resolveQueriesInSuite.mockReturnValue(["a", "b"]);
const result = await resolveQueries(
mockCli as unknown as CodeQLCliServer,
{
dbschemePackIsLibraryPack: true,
dbschemePack: "my-qlpack",
@@ -63,9 +84,14 @@ describe("queryResolver", () => {
},
KeyType.DefinitionQuery,
);
expect(result).to.deep.equal(["a", "b"]);
expect(writeFileSpy.getCall(0).args[0]).to.match(/.qls$/);
expect(yaml.load(writeFileSpy.getCall(0).args[1])).to.deep.equal([
expect(result).toEqual(["a", "b"]);
expect(writeFileSpy).toHaveBeenNthCalledWith(
1,
expect.stringMatching(/.qls$/),
expect.anything(),
expect.anything(),
);
expect(yaml.load(writeFileSpy.mock.calls[0][1])).toEqual([
{
from: "my-qlpack2",
queries: ".",
@@ -78,18 +104,18 @@ describe("queryResolver", () => {
});
it("should throw an error when there are no queries found", async () => {
mockCli.resolveQueriesInSuite.returns([]);
mockCli.resolveQueriesInSuite.mockReturnValue([]);
try {
await module.resolveQueries(
mockCli,
{ dbschemePack: "my-qlpack" },
await resolveQueries(
mockCli as unknown as CodeQLCliServer,
{ dbschemePack: "my-qlpack", dbschemePackIsLibraryPack: false },
KeyType.DefinitionQuery,
);
// should reject
expect(true).to.be.false;
expect(true).toBe(false);
} catch (e) {
expect(getErrorMessage(e)).to.eq(
expect(getErrorMessage(e)).toBe(
"Couldn't find any queries tagged ide-contextual-queries/local-definitions in any of the following packs: my-qlpack.",
);
}
@@ -98,37 +124,26 @@ describe("queryResolver", () => {
describe("qlpackOfDatabase", () => {
it("should get the qlpack of a database", async () => {
getQlPackForDbschemeSpy.resolves("my-qlpack");
getQlPackForDbschemeSpy.mockResolvedValue({
dbschemePack: "my-qlpack",
dbschemePackIsLibraryPack: false,
});
const db = {
contents: {
datasetUri: {
fsPath: "/path/to/database",
},
},
};
const result = await module.qlpackOfDatabase(mockCli, db);
expect(result).to.eq("my-qlpack");
expect(getPrimaryDbschemeSpy).to.have.been.calledWith(
"/path/to/database",
} as unknown as DatabaseItem;
const result = await qlpackOfDatabase(
mockCli as unknown as CodeQLCliServer,
db,
);
expect(result).toEqual({
dbschemePack: "my-qlpack",
dbschemePackIsLibraryPack: false,
});
expect(getPrimaryDbschemeSpy).toBeCalledWith("/path/to/database");
});
});
function createModule() {
writeFileSpy = sinon.spy();
getQlPackForDbschemeSpy = sinon.stub();
getPrimaryDbschemeSpy = sinon.stub();
return proxyquire("../../../contextual/queryResolver", {
"fs-extra": {
writeFile: writeFileSpy,
},
"../helpers": {
getQlPackForDbscheme: getQlPackForDbschemeSpy,
getPrimaryDbscheme: getPrimaryDbschemeSpy,
getOnDiskWorkspaceFolders: () => ({}),
showAndLogErrorMessage: () => ({}),
},
});
}
});

View File

@@ -1,9 +1,7 @@
import * as sinon from "sinon";
import * as path from "path";
import * as fs from "fs-extra";
import * as tmp from "tmp";
import { expect } from "chai";
import { window } from "vscode";
import { QuickPickItem, window } from "vscode";
import {
convertGithubNwoToDatabaseUrl,
@@ -12,32 +10,24 @@ import {
findDirWithFile,
looksLikeGithubRepo,
} from "../../databaseFetcher";
import { ProgressCallback } from "../../commandRunner";
import * as Octokit from "@octokit/rest";
describe("databaseFetcher", function () {
// These tests make API calls and may need extra time to complete.
this.timeout(10000);
// These tests make API calls and may need extra time to complete.
jest.setTimeout(10000);
describe("databaseFetcher", () => {
describe("convertGithubNwoToDatabaseUrl", () => {
let sandbox: sinon.SinonSandbox;
let quickPickSpy: sinon.SinonStub;
let progressSpy: ProgressCallback;
let mockRequest: sinon.SinonStub;
let octokit: Octokit.Octokit;
const quickPickSpy = jest.spyOn(window, "showQuickPick");
const progressSpy = jest.fn();
const mockRequest = jest.fn();
const octokit: Octokit.Octokit = {
request: mockRequest,
} as unknown as Octokit.Octokit;
beforeEach(() => {
sandbox = sinon.createSandbox();
quickPickSpy = sandbox.stub(window, "showQuickPick");
progressSpy = sandbox.spy();
mockRequest = sandbox.stub();
octokit = {
request: mockRequest,
} as unknown as Octokit.Octokit;
});
afterEach(() => {
sandbox.restore();
quickPickSpy.mockReset().mockResolvedValue(undefined);
progressSpy.mockReset();
mockRequest.mockReset();
});
it("should convert a GitHub nwo to a database url", async () => {
@@ -82,31 +72,31 @@ describe("databaseFetcher", function () {
},
],
};
mockRequest.resolves(mockApiResponse);
quickPickSpy.resolves("javascript");
mockRequest.mockResolvedValue(mockApiResponse);
quickPickSpy.mockResolvedValue("javascript" as unknown as QuickPickItem);
const githubRepo = "github/codeql";
const result = await convertGithubNwoToDatabaseUrl(
githubRepo,
octokit,
progressSpy,
);
expect(result).not.to.be.undefined;
expect(result).toBeDefined();
if (result === undefined) {
return;
}
const { databaseUrl, name, owner } = result;
expect(databaseUrl).to.equal(
expect(databaseUrl).toBe(
"https://api.github.com/repos/github/codeql/code-scanning/codeql/databases/javascript",
);
expect(name).to.equal("codeql");
expect(owner).to.equal("github");
expect(quickPickSpy.firstCall.args[0]).to.deep.equal([
"csharp",
"javascript",
"ql",
]);
expect(name).toBe("codeql");
expect(owner).toBe("github");
expect(quickPickSpy).toHaveBeenNthCalledWith(
1,
["csharp", "javascript", "ql"],
expect.anything(),
);
});
// Repository doesn't exist, or the user has no access to the repository.
@@ -117,12 +107,12 @@ describe("databaseFetcher", function () {
},
status: 404,
};
mockRequest.resolves(mockApiResponse);
mockRequest.mockResolvedValue(mockApiResponse);
const githubRepo = "foo/bar-not-real";
await expect(
convertGithubNwoToDatabaseUrl(githubRepo, octokit, progressSpy),
).to.be.rejectedWith(/Unable to get database/);
expect(progressSpy).to.have.callCount(0);
).rejects.toThrow(/Unable to get database/);
expect(progressSpy).toBeCalledTimes(0);
});
// User has access to the repository, but there are no databases for any language.
@@ -131,121 +121,124 @@ describe("databaseFetcher", function () {
data: [],
};
mockRequest.resolves(mockApiResponse);
mockRequest.mockResolvedValue(mockApiResponse);
const githubRepo = "foo/bar-with-no-dbs";
await expect(
convertGithubNwoToDatabaseUrl(githubRepo, octokit, progressSpy),
).to.be.rejectedWith(/Unable to get database/);
expect(progressSpy).to.have.been.calledOnce;
).rejects.toThrow(/Unable to get database/);
expect(progressSpy).toBeCalledTimes(1);
});
});
describe("convertLgtmUrlToDatabaseUrl", () => {
let sandbox: sinon.SinonSandbox;
let quickPickSpy: sinon.SinonStub;
let progressSpy: ProgressCallback;
const quickPickSpy = jest.spyOn(window, "showQuickPick");
const progressSpy = jest.fn();
beforeEach(() => {
sandbox = sinon.createSandbox();
quickPickSpy = sandbox.stub(window, "showQuickPick");
progressSpy = sandbox.spy();
});
afterEach(() => {
sandbox.restore();
quickPickSpy.mockReset().mockResolvedValue(undefined);
progressSpy.mockReset();
});
it("should convert a project url to a database url", async () => {
quickPickSpy.resolves("javascript");
quickPickSpy.mockResolvedValue("javascript" as unknown as QuickPickItem);
const lgtmUrl = "https://lgtm.com/projects/g/github/codeql";
const dbUrl = await convertLgtmUrlToDatabaseUrl(lgtmUrl, progressSpy);
expect(dbUrl).to.equal(
expect(dbUrl).toBe(
"https://lgtm.com/api/v1.0/snapshots/1506465042581/javascript",
);
expect(quickPickSpy.firstCall.args[0]).to.contain("javascript");
expect(quickPickSpy.firstCall.args[0]).to.contain("python");
expect(quickPickSpy).toHaveBeenNthCalledWith(
1,
expect.arrayContaining(["javascript", "python"]),
expect.anything(),
);
});
it("should convert a project url to a database url with extra path segments", async () => {
quickPickSpy.resolves("python");
quickPickSpy.mockResolvedValue("python" as unknown as QuickPickItem);
const lgtmUrl =
"https://lgtm.com/projects/g/github/codeql/subpage/subpage2?query=xxx";
const dbUrl = await convertLgtmUrlToDatabaseUrl(lgtmUrl, progressSpy);
expect(dbUrl).to.equal(
expect(dbUrl).toBe(
"https://lgtm.com/api/v1.0/snapshots/1506465042581/python",
);
expect(progressSpy).to.have.been.calledOnce;
expect(progressSpy).toBeCalledTimes(1);
});
it("should convert a raw slug to a database url with extra path segments", async () => {
quickPickSpy.resolves("python");
quickPickSpy.mockResolvedValue("python" as unknown as QuickPickItem);
const lgtmUrl = "g/github/codeql";
const dbUrl = await convertLgtmUrlToDatabaseUrl(lgtmUrl, progressSpy);
expect(dbUrl).to.equal(
expect(dbUrl).toBe(
"https://lgtm.com/api/v1.0/snapshots/1506465042581/python",
);
expect(progressSpy).to.have.been.calledOnce;
expect(progressSpy).toBeCalledTimes(1);
});
it("should fail on a nonexistent project", async () => {
quickPickSpy.resolves("javascript");
quickPickSpy.mockResolvedValue("javascript" as unknown as QuickPickItem);
const lgtmUrl = "https://lgtm.com/projects/g/github/hucairz";
await expect(
convertLgtmUrlToDatabaseUrl(lgtmUrl, progressSpy),
).to.rejectedWith(/Invalid LGTM URL/);
expect(progressSpy).to.have.callCount(0);
).rejects.toThrow(/Invalid LGTM URL/);
expect(progressSpy).toBeCalledTimes(0);
});
});
describe("looksLikeGithubRepo", () => {
it("should handle invalid urls", () => {
expect(looksLikeGithubRepo("")).to.be.false;
expect(looksLikeGithubRepo("http://github.com/foo/bar")).to.be.false;
expect(looksLikeGithubRepo("https://ww.github.com/foo/bar")).to.be.false;
expect(looksLikeGithubRepo("https://ww.github.com/foo")).to.be.false;
expect(looksLikeGithubRepo("foo")).to.be.false;
expect(looksLikeGithubRepo("")).toBe(false);
expect(looksLikeGithubRepo("http://github.com/foo/bar")).toBe(false);
expect(looksLikeGithubRepo("https://ww.github.com/foo/bar")).toBe(false);
expect(looksLikeGithubRepo("https://ww.github.com/foo")).toBe(false);
expect(looksLikeGithubRepo("foo")).toBe(false);
});
it("should handle valid urls", () => {
expect(looksLikeGithubRepo("https://github.com/foo/bar")).to.be.true;
expect(looksLikeGithubRepo("https://www.github.com/foo/bar")).to.be.true;
expect(looksLikeGithubRepo("https://github.com/foo/bar/sub/pages")).to.be
.true;
expect(looksLikeGithubRepo("foo/bar")).to.be.true;
expect(looksLikeGithubRepo("https://github.com/foo/bar")).toBe(true);
expect(looksLikeGithubRepo("https://www.github.com/foo/bar")).toBe(true);
expect(looksLikeGithubRepo("https://github.com/foo/bar/sub/pages")).toBe(
true,
);
expect(looksLikeGithubRepo("foo/bar")).toBe(true);
});
});
describe("looksLikeLgtmUrl", () => {
it("should handle invalid urls", () => {
expect(looksLikeLgtmUrl("")).to.be.false;
expect(looksLikeLgtmUrl("http://lgtm.com/projects/g/github/codeql")).to.be
.false;
expect(looksLikeLgtmUrl("https://ww.lgtm.com/projects/g/github/codeql"))
.to.be.false;
expect(looksLikeLgtmUrl("https://ww.lgtm.com/projects/g/github")).to.be
.false;
expect(looksLikeLgtmUrl("g/github")).to.be.false;
expect(looksLikeLgtmUrl("ggg/github/myproj")).to.be.false;
expect(looksLikeLgtmUrl("")).toBe(false);
expect(looksLikeLgtmUrl("http://lgtm.com/projects/g/github/codeql")).toBe(
false,
);
expect(
looksLikeLgtmUrl("https://ww.lgtm.com/projects/g/github/codeql"),
).toBe(false);
expect(looksLikeLgtmUrl("https://ww.lgtm.com/projects/g/github")).toBe(
false,
);
expect(looksLikeLgtmUrl("g/github")).toBe(false);
expect(looksLikeLgtmUrl("ggg/github/myproj")).toBe(false);
});
it("should handle valid urls", () => {
expect(looksLikeLgtmUrl("https://lgtm.com/projects/g/github/codeql")).to
.be.true;
expect(looksLikeLgtmUrl("https://www.lgtm.com/projects/g/github/codeql"))
.to.be.true;
expect(
looksLikeLgtmUrl("https://lgtm.com/projects/g/github/codeql"),
).toBe(true);
expect(
looksLikeLgtmUrl("https://www.lgtm.com/projects/g/github/codeql"),
).toBe(true);
expect(
looksLikeLgtmUrl("https://lgtm.com/projects/g/github/codeql/sub/pages"),
).to.be.true;
).toBe(true);
expect(
looksLikeLgtmUrl(
"https://lgtm.com/projects/g/github/codeql/sub/pages?query=string",
),
).to.be.true;
expect(looksLikeLgtmUrl("g/github/myproj")).to.be.true;
expect(looksLikeLgtmUrl("git/github/myproj")).to.be.true;
).toBe(true);
expect(looksLikeLgtmUrl("g/github/myproj")).toBe(true);
expect(looksLikeLgtmUrl("git/github/myproj")).toBe(true);
});
});
@@ -274,21 +267,21 @@ describe("databaseFetcher", function () {
});
it("should find files", async () => {
expect(await findDirWithFile(dir.name, "k")).to.equal(
expect(await findDirWithFile(dir.name, "k")).toBe(
path.join(dir.name, "dir2", "dir3"),
);
expect(await findDirWithFile(dir.name, "h")).to.equal(
expect(await findDirWithFile(dir.name, "h")).toBe(
path.join(dir.name, "dir2"),
);
expect(await findDirWithFile(dir.name, "z", "a")).to.equal(dir.name);
expect(await findDirWithFile(dir.name, "z", "a")).toBe(dir.name);
// there's some slight indeterminism when more than one name exists
// but in general, this will find files in the current directory before
// finding files in sub-dirs
expect(await findDirWithFile(dir.name, "k", "a")).to.equal(dir.name);
expect(await findDirWithFile(dir.name, "k", "a")).toBe(dir.name);
});
it("should not find files", async () => {
expect(await findDirWithFile(dir.name, "x", "y", "z")).to.be.undefined;
expect(await findDirWithFile(dir.name, "x", "y", "z")).toBeUndefined();
});
function createFile(...segments: string[]) {

View File

@@ -1,7 +1,6 @@
import * as tmp from "tmp";
import * as path from "path";
import * as fs from "fs-extra";
import { expect } from "chai";
import { Uri } from "vscode";
import { DatabaseUI } from "../../databases-ui";
@@ -14,13 +13,13 @@ describe("databases-ui", () => {
it("should choose current directory direcory normally", async () => {
const dir = tmp.dirSync().name;
const uri = await fixDbUri(Uri.file(dir));
expect(uri.toString()).to.eq(Uri.file(dir).toString());
expect(uri.toString()).toBe(Uri.file(dir).toString());
});
it("should choose parent direcory when file is selected", async () => {
const file = tmp.fileSync().name;
const uri = await fixDbUri(Uri.file(file));
expect(uri.toString()).to.eq(Uri.file(path.dirname(file)).toString());
expect(uri.toString()).toBe(Uri.file(path.dirname(file)).toString());
});
it("should choose parent direcory when db-* is selected", async () => {
@@ -29,7 +28,7 @@ describe("databases-ui", () => {
await fs.mkdirs(dbDir);
const uri = await fixDbUri(Uri.file(dbDir));
expect(uri.toString()).to.eq(Uri.file(dir).toString());
expect(uri.toString()).toBe(Uri.file(dir).toString());
});
it("should choose parent's parent direcory when file selected is in db-*", async () => {
@@ -40,7 +39,7 @@ describe("databases-ui", () => {
await fs.createFile(file);
const uri = await fixDbUri(Uri.file(file));
expect(uri.toString()).to.eq(Uri.file(dir).toString());
expect(uri.toString()).toBe(Uri.file(dir).toString());
});
it("should handle a parent whose name is db-*", async () => {
@@ -53,7 +52,7 @@ describe("databases-ui", () => {
fs.createFileSync(file);
const uri = await fixDbUri(Uri.file(file));
expect(uri.toString()).to.eq(Uri.file(parentDir).toString());
expect(uri.toString()).toBe(Uri.file(parentDir).toString());
});
});
@@ -95,12 +94,12 @@ describe("databases-ui", () => {
await databaseUI.handleRemoveOrphanedDatabases();
expect(fs.pathExistsSync(db1)).to.be.true;
expect(fs.pathExistsSync(db2)).to.be.true;
expect(fs.pathExistsSync(db3)).to.be.true;
expect(fs.pathExistsSync(db1)).toBe(true);
expect(fs.pathExistsSync(db2)).toBe(true);
expect(fs.pathExistsSync(db3)).toBe(true);
expect(fs.pathExistsSync(db4)).to.be.false;
expect(fs.pathExistsSync(db5)).to.be.false;
expect(fs.pathExistsSync(db4)).toBe(false);
expect(fs.pathExistsSync(db5)).toBe(false);
databaseUI.dispose(testDisposeHandler);
});

View File

@@ -1,18 +1,19 @@
import { expect } from "chai";
import * as path from "path";
import * as fetch from "node-fetch";
import * as semver from "semver";
import * as sinon from "sinon";
import * as pq from "proxyquire";
import * as helpers from "../../helpers";
import { logger } from "../../logging";
import * as fs from "fs-extra";
import * as os from "os";
import {
GithubRelease,
GithubReleaseAsset,
ReleasesApiConsumer,
getExecutableFromDirectory,
DistributionManager,
} from "../../distribution";
const proxyquire = pq.noPreserveCache();
describe("Releases API consumer", () => {
const owner = "someowner";
const repo = "somerepo";
@@ -87,7 +88,7 @@ describe("Releases API consumer", () => {
const latestRelease = await consumer.getLatestRelease(
unconstrainedVersionRange,
);
expect(latestRelease.id).to.equal(2);
expect(latestRelease.id).toBe(2);
});
it("version of picked release is within the version range", async () => {
@@ -96,7 +97,7 @@ describe("Releases API consumer", () => {
const latestRelease = await consumer.getLatestRelease(
new semver.Range("2.*.*"),
);
expect(latestRelease.id).to.equal(1);
expect(latestRelease.id).toBe(1);
});
it("fails if none of the releases are within the version range", async () => {
@@ -104,7 +105,7 @@ describe("Releases API consumer", () => {
await expect(
consumer.getLatestRelease(new semver.Range("5.*.*")),
).to.be.rejectedWith(Error);
).rejects.toThrowError();
});
it("picked release passes additional compatibility test if an additional compatibility test is specified", async () => {
@@ -116,7 +117,7 @@ describe("Releases API consumer", () => {
(release) =>
release.assets.some((asset) => asset.name === "exampleAsset.txt"),
);
expect(latestRelease.id).to.equal(3);
expect(latestRelease.id).toBe(3);
});
it("fails if none of the releases pass the additional compatibility test", async () => {
@@ -128,7 +129,7 @@ describe("Releases API consumer", () => {
(asset) => asset.name === "otherExampleAsset.txt",
),
),
).to.be.rejectedWith(Error);
).rejects.toThrowError();
});
it("picked release is the most recent prerelease when includePrereleases is set", async () => {
@@ -138,7 +139,7 @@ describe("Releases API consumer", () => {
unconstrainedVersionRange,
true,
);
expect(latestRelease.id).to.equal(5);
expect(latestRelease.id).toBe(5);
});
});
@@ -183,11 +184,11 @@ describe("Releases API consumer", () => {
const assets = (await consumer.getLatestRelease(unconstrainedVersionRange))
.assets;
expect(assets.length).to.equal(expectedAssets.length);
expect(assets.length).toBe(expectedAssets.length);
expectedAssets.map((expectedAsset, index) => {
expect(assets[index].id).to.equal(expectedAsset.id);
expect(assets[index].name).to.equal(expectedAsset.name);
expect(assets[index].size).to.equal(expectedAsset.size);
expect(assets[index].id).toBe(expectedAsset.id);
expect(assets[index].name).toBe(expectedAsset.name);
expect(assets[index].size).toBe(expectedAsset.size);
});
});
});
@@ -196,82 +197,80 @@ describe("Launcher path", () => {
const pathToCmd = `abc${path.sep}codeql.cmd`;
const pathToExe = `abc${path.sep}codeql.exe`;
let sandbox: sinon.SinonSandbox;
let warnSpy: sinon.SinonSpy;
let errorSpy: sinon.SinonSpy;
let logSpy: sinon.SinonSpy;
let fsSpy: sinon.SinonSpy;
let platformSpy: sinon.SinonSpy;
let getExecutableFromDirectory: Function;
const warnSpy = jest.spyOn(helpers, "showAndLogWarningMessage");
const errorSpy = jest.spyOn(helpers, "showAndLogErrorMessage");
const logSpy = jest.spyOn(logger, "log");
const pathExistsSpy = jest.spyOn(fs, "pathExists");
const platformSpy = jest.spyOn(os, "platform");
let launcherThatExists = "";
beforeEach(() => {
sandbox = sinon.createSandbox();
getExecutableFromDirectory = createModule().getExecutableFromDirectory;
});
afterEach(() => {
sandbox.restore();
warnSpy.mockClear().mockResolvedValue(undefined);
errorSpy.mockClear().mockResolvedValue(undefined);
logSpy.mockClear().mockResolvedValue(undefined);
pathExistsSpy.mockClear().mockImplementation(async (path: string) => {
return path.endsWith(launcherThatExists);
});
platformSpy.mockClear().mockReturnValue("win32");
});
it("should not warn with proper launcher name", async () => {
launcherThatExists = "codeql.exe";
const result = await getExecutableFromDirectory("abc");
expect(fsSpy).to.have.been.calledWith(pathToExe);
expect(pathExistsSpy).toBeCalledWith(pathToExe);
// correct launcher has been found, so alternate one not looked for
expect(fsSpy).not.to.have.been.calledWith(pathToCmd);
expect(pathExistsSpy).not.toBeCalledWith(pathToCmd);
// no warning message
expect(warnSpy).not.to.have.been.calledWith(sinon.match.string);
expect(warnSpy).not.toHaveBeenCalled();
// No log message
expect(logSpy).not.to.have.been.calledWith(sinon.match.string);
expect(result).to.equal(pathToExe);
expect(logSpy).not.toHaveBeenCalled();
expect(result).toBe(pathToExe);
});
it("should warn when using a hard-coded deprecated launcher name", async () => {
launcherThatExists = "codeql.cmd";
const result = await getExecutableFromDirectory("abc");
expect(fsSpy).to.have.been.calledWith(pathToExe);
expect(fsSpy).to.have.been.calledWith(pathToCmd);
expect(pathExistsSpy).toBeCalledWith(pathToExe);
expect(pathExistsSpy).toBeCalledWith(pathToCmd);
// Should have opened a warning message
expect(warnSpy).to.have.been.calledWith(sinon.match.string);
expect(warnSpy).toHaveBeenCalled();
// No log message
expect(logSpy).not.to.have.been.calledWith(sinon.match.string);
expect(result).to.equal(pathToCmd);
expect(logSpy).not.toHaveBeenCalled();
expect(result).toBe(pathToCmd);
});
it("should avoid warn when no launcher is found", async () => {
launcherThatExists = "xxx";
const result = await getExecutableFromDirectory("abc", false);
expect(fsSpy).to.have.been.calledWith(pathToExe);
expect(fsSpy).to.have.been.calledWith(pathToCmd);
expect(pathExistsSpy).toBeCalledWith(pathToExe);
expect(pathExistsSpy).toBeCalledWith(pathToCmd);
// no warning message
expect(warnSpy).not.to.have.been.calledWith(sinon.match.string);
expect(warnSpy).not.toHaveBeenCalled();
// log message sent out
expect(logSpy).not.to.have.been.calledWith(sinon.match.string);
expect(result).to.equal(undefined);
expect(logSpy).not.toHaveBeenCalled();
expect(result).toBeUndefined();
});
it("should warn when no launcher is found", async () => {
launcherThatExists = "xxx";
const result = await getExecutableFromDirectory("abc", true);
expect(fsSpy).to.have.been.calledWith(pathToExe);
expect(fsSpy).to.have.been.calledWith(pathToCmd);
expect(pathExistsSpy).toBeCalledWith(pathToExe);
expect(pathExistsSpy).toBeCalledWith(pathToCmd);
// no warning message
expect(warnSpy).not.to.have.been.calledWith(sinon.match.string);
expect(warnSpy).not.toHaveBeenCalled();
// log message sent out
expect(logSpy).to.have.been.calledWith(sinon.match.string);
expect(result).to.equal(undefined);
expect(logSpy).toHaveBeenCalled();
expect(result).toBeUndefined();
});
it("should not warn when deprecated launcher is used, but no new launcher is available", async function () {
const manager = new (createModule().DistributionManager)(
const manager = new DistributionManager(
{ customCodeQlPath: pathToCmd } as any,
{} as any,
undefined as any,
@@ -279,15 +278,15 @@ describe("Launcher path", () => {
launcherThatExists = "codeql.cmd";
const result = await manager.getCodeQlPathWithoutVersionCheck();
expect(result).to.equal(pathToCmd);
expect(result).toBe(pathToCmd);
// no warning or error message
expect(warnSpy).to.have.callCount(0);
expect(errorSpy).to.have.callCount(0);
expect(warnSpy).toBeCalledTimes(0);
expect(errorSpy).toBeCalledTimes(0);
});
it("should warn when deprecated launcher is used, and new launcher is available", async () => {
const manager = new (createModule().DistributionManager)(
const manager = new DistributionManager(
{ customCodeQlPath: pathToCmd } as any,
{} as any,
undefined as any,
@@ -295,15 +294,15 @@ describe("Launcher path", () => {
launcherThatExists = ""; // pretend both launchers exist
const result = await manager.getCodeQlPathWithoutVersionCheck();
expect(result).to.equal(pathToCmd);
expect(result).toBe(pathToCmd);
// has warning message
expect(warnSpy).to.have.callCount(1);
expect(errorSpy).to.have.callCount(0);
expect(warnSpy).toBeCalledTimes(1);
expect(errorSpy).toBeCalledTimes(0);
});
it("should warn when launcher path is incorrect", async () => {
const manager = new (createModule().DistributionManager)(
const manager = new DistributionManager(
{ customCodeQlPath: pathToCmd } as any,
{} as any,
undefined as any,
@@ -311,39 +310,10 @@ describe("Launcher path", () => {
launcherThatExists = "xxx"; // pretend neither launcher exists
const result = await manager.getCodeQlPathWithoutVersionCheck();
expect(result).to.equal(undefined);
expect(result).toBeUndefined();
// no error message
expect(warnSpy).to.have.callCount(0);
expect(errorSpy).to.have.callCount(1);
expect(warnSpy).toBeCalledTimes(0);
expect(errorSpy).toBeCalledTimes(1);
});
function createModule() {
warnSpy = sandbox.spy();
errorSpy = sandbox.spy();
logSpy = sandbox.spy();
// pretend that only the .cmd file exists
fsSpy = sandbox
.stub()
.callsFake((arg) => (arg.endsWith(launcherThatExists) ? true : false));
platformSpy = sandbox.stub().returns("win32");
return proxyquire("../../distribution", {
"./helpers": {
showAndLogWarningMessage: warnSpy,
showAndLogErrorMessage: errorSpy,
},
"./logging": {
logger: {
log: logSpy,
},
},
"fs-extra": {
pathExists: fsSpy,
},
os: {
platform: platformSpy,
},
});
}
});

View File

@@ -1,5 +1,3 @@
import { expect } from "chai";
import "mocha";
import * as path from "path";
import {
@@ -19,7 +17,7 @@ describe("createDownloadPath", () => {
const actualPath = createDownloadPath("storage", downloadLink);
expect(actualPath).to.equal(expectedPath);
expect(actualPath).toBe(expectedPath);
});
it("should return the correct path with extension", () => {
@@ -34,6 +32,6 @@ describe("createDownloadPath", () => {
const actualPath = createDownloadPath("storage", downloadLink, "zip");
expect(actualPath).to.equal(expectedPath);
expect(actualPath).toBe(expectedPath);
});
});

View File

@@ -1,4 +1,3 @@
import { expect } from "chai";
import EvalLogTreeBuilder from "../../eval-log-tree-builder";
import { EvalLogData } from "../../pure/log-summary-parser";
@@ -60,7 +59,7 @@ describe("EvalLogTreeBuilder", () => {
const roots = await builder.getRoots();
// Force children, parent to be undefined for ease of testing.
expect(roots.map((r) => ({ ...r, children: undefined }))).to.deep.eq(
expect(roots.map((r) => ({ ...r, children: undefined }))).toEqual(
expectedRoots,
);
@@ -70,7 +69,7 @@ describe("EvalLogTreeBuilder", () => {
children: undefined,
parent: undefined,
})),
).to.deep.eq(expectedPredicate);
).toEqual(expectedPredicate);
expect(
roots[0].children[0].children.map((ra) => ({
@@ -78,7 +77,7 @@ describe("EvalLogTreeBuilder", () => {
children: undefined,
parent: undefined,
})),
).to.deep.eq(expectedRA);
).toEqual(expectedRA);
// Pipeline steps' children should be empty so do not force undefined children here.
expect(
@@ -86,7 +85,7 @@ describe("EvalLogTreeBuilder", () => {
...step,
parent: undefined,
})),
).to.deep.eq(expectedPipelineSteps);
).toEqual(expectedPipelineSteps);
});
it("should build the tree with descriptive message when no data exists", async () => {
@@ -107,12 +106,12 @@ describe("EvalLogTreeBuilder", () => {
const builder = new EvalLogTreeBuilder("test-query-cached.ql", []);
const roots = await builder.getRoots();
expect(roots.map((r) => ({ ...r, children: undefined }))).to.deep.eq(
expect(roots.map((r) => ({ ...r, children: undefined }))).toEqual(
expectedRoots,
);
expect(
roots[0].children.map((noPreds) => ({ ...noPreds, parent: undefined })),
).to.deep.eq(expectedNoPredicates);
).toEqual(expectedNoPredicates);
});
});

View File

@@ -1,5 +1,3 @@
import { expect } from "chai";
import sinon = require("sinon");
import { commands } from "vscode";
import {
ChildEvalLogTreeItem,
@@ -11,18 +9,18 @@ import { testDisposeHandler } from "../test-dispose-handler";
describe("EvalLogViewer", () => {
let roots: EvalLogTreeItem[];
let viewer: EvalLogViewer;
let sandbox: sinon.SinonSandbox;
beforeEach(async () => {
sandbox = sinon.createSandbox();
viewer = new EvalLogViewer();
sandbox.stub(commands, "registerCommand");
sandbox.stub(commands, "executeCommand");
jest.spyOn(commands, "registerCommand").mockImplementation(() => ({
dispose: jest.fn(),
}));
jest
.spyOn(commands, "executeCommand")
.mockImplementation(() => Promise.resolve());
});
afterEach(() => {
sandbox.restore();
if (viewer) {
viewer.dispose(testDisposeHandler);
}
@@ -71,12 +69,12 @@ describe("EvalLogViewer", () => {
viewer.updateRoots(roots);
expect((viewer as any).treeDataProvider.roots).to.eq(roots);
expect((viewer as any).treeView.message).to.eq("Viewer for query run:");
expect((viewer as any).treeDataProvider.roots).toBe(roots);
expect((viewer as any).treeView.message).toBe("Viewer for query run:");
});
it("should clear the viewer's roots", () => {
viewer.dispose(testDisposeHandler);
expect((viewer as any).treeDataProvider.roots.length).to.eq(0);
expect((viewer as any).treeDataProvider.roots.length).toBe(0);
});
});

View File

@@ -1,4 +1,3 @@
import { expect } from "chai";
import {
EnvironmentVariableCollection,
EnvironmentVariableMutator,
@@ -15,7 +14,6 @@ import * as yaml from "js-yaml";
import * as tmp from "tmp";
import * as path from "path";
import * as fs from "fs-extra";
import * as sinon from "sinon";
import { DirResult } from "tmp";
import {
@@ -29,20 +27,8 @@ import {
walkDirectory,
} from "../../helpers";
import { reportStreamProgress } from "../../commandRunner";
import Sinon = require("sinon");
import { fail } from "assert";
describe("helpers", () => {
let sandbox: sinon.SinonSandbox;
beforeEach(() => {
sandbox = sinon.createSandbox();
});
afterEach(() => {
sandbox.restore();
});
describe("Invocation rate limiter", () => {
// 1 January 2020
let currentUnixTime = 1577836800;
@@ -76,7 +62,7 @@ describe("helpers", () => {
},
);
await invocationRateLimiter.invokeFunctionIfIntervalElapsed(100);
expect(numTimesFuncCalled).to.equal(1);
expect(numTimesFuncCalled).toBe(1);
});
it("doesn't invoke function again if no time has passed", async () => {
@@ -89,7 +75,7 @@ describe("helpers", () => {
);
await invocationRateLimiter.invokeFunctionIfIntervalElapsed(100);
await invocationRateLimiter.invokeFunctionIfIntervalElapsed(100);
expect(numTimesFuncCalled).to.equal(1);
expect(numTimesFuncCalled).toBe(1);
});
it("doesn't invoke function again if requested time since last invocation hasn't passed", async () => {
@@ -103,7 +89,7 @@ describe("helpers", () => {
await invocationRateLimiter.invokeFunctionIfIntervalElapsed(100);
currentUnixTime += 1;
await invocationRateLimiter.invokeFunctionIfIntervalElapsed(2);
expect(numTimesFuncCalled).to.equal(1);
expect(numTimesFuncCalled).toBe(1);
});
it("invokes function again immediately if requested time since last invocation is 0 seconds", async () => {
@@ -116,7 +102,7 @@ describe("helpers", () => {
);
await invocationRateLimiter.invokeFunctionIfIntervalElapsed(0);
await invocationRateLimiter.invokeFunctionIfIntervalElapsed(0);
expect(numTimesFuncCalled).to.equal(2);
expect(numTimesFuncCalled).toBe(2);
});
it("invokes function again after requested time since last invocation has elapsed", async () => {
@@ -130,7 +116,7 @@ describe("helpers", () => {
await invocationRateLimiter.invokeFunctionIfIntervalElapsed(1);
currentUnixTime += 1;
await invocationRateLimiter.invokeFunctionIfIntervalElapsed(1);
expect(numTimesFuncCalled).to.equal(2);
expect(numTimesFuncCalled).toBe(2);
});
it("invokes functions with different rate limiters", async () => {
@@ -150,8 +136,8 @@ describe("helpers", () => {
);
await invocationRateLimiterA.invokeFunctionIfIntervalElapsed(100);
await invocationRateLimiterB.invokeFunctionIfIntervalElapsed(100);
expect(numTimesFuncACalled).to.equal(1);
expect(numTimesFuncBCalled).to.equal(1);
expect(numTimesFuncACalled).toBe(1);
expect(numTimesFuncBCalled).toBe(1);
});
});
@@ -174,19 +160,19 @@ describe("helpers", () => {
});
it("should get initial query contents when language is known", () => {
expect(getInitialQueryContents("cpp", "hucairz")).to.eq(
expect(getInitialQueryContents("cpp", "hucairz")).toBe(
'import cpp\n\nselect ""',
);
});
it("should get initial query contents when dbscheme is known", () => {
expect(getInitialQueryContents("", "semmlecode.cpp.dbscheme")).to.eq(
expect(getInitialQueryContents("", "semmlecode.cpp.dbscheme")).toBe(
'import cpp\n\nselect ""',
);
});
it("should get initial query contents when nothing is known", () => {
expect(getInitialQueryContents("", "hucairz")).to.eq('select ""');
expect(getInitialQueryContents("", "hucairz")).toBe('select ""');
});
});
@@ -206,7 +192,7 @@ describe("helpers", () => {
fs.mkdirSync(path.join(dbFolder, "db-python"));
fs.writeFileSync(path.join(dbFolder, "codeql-database.yml"), "", "utf8");
expect(await isLikelyDatabaseRoot(dbFolder)).to.be.true;
expect(await isLikelyDatabaseRoot(dbFolder)).toBe(true);
});
it("should likely be a database root: .dbinfo", async () => {
@@ -215,7 +201,7 @@ describe("helpers", () => {
fs.mkdirSync(path.join(dbFolder, "db-python"));
fs.writeFileSync(path.join(dbFolder, ".dbinfo"), "", "utf8");
expect(await isLikelyDatabaseRoot(dbFolder)).to.be.true;
expect(await isLikelyDatabaseRoot(dbFolder)).toBe(true);
});
it("should likely NOT be a database root: empty dir", async () => {
@@ -223,7 +209,7 @@ describe("helpers", () => {
fs.mkdirSync(dbFolder);
fs.mkdirSync(path.join(dbFolder, "db-python"));
expect(await isLikelyDatabaseRoot(dbFolder)).to.be.false;
expect(await isLikelyDatabaseRoot(dbFolder)).toBe(false);
});
it("should likely NOT be a database root: no db language folder", async () => {
@@ -231,7 +217,7 @@ describe("helpers", () => {
fs.mkdirSync(dbFolder);
fs.writeFileSync(path.join(dbFolder, ".dbinfo"), "", "utf8");
expect(await isLikelyDatabaseRoot(dbFolder)).to.be.false;
expect(await isLikelyDatabaseRoot(dbFolder)).toBe(false);
});
it("should find likely db language folder", async () => {
@@ -241,10 +227,10 @@ describe("helpers", () => {
fs.writeFileSync(path.join(dbFolder, "codeql-database.yml"), "", "utf8");
// not a db folder since there is a db-python folder inside this one
expect(await isLikelyDbLanguageFolder(dbFolder)).to.be.false;
expect(await isLikelyDbLanguageFolder(dbFolder)).toBe(false);
const nestedDbPythonFolder = path.join(dbFolder, "db-python");
expect(await isLikelyDbLanguageFolder(nestedDbPythonFolder)).to.be.true;
expect(await isLikelyDbLanguageFolder(nestedDbPythonFolder)).toBe(true);
});
});
@@ -352,32 +338,33 @@ describe("helpers", () => {
}
it("should report stream progress", () => {
const spy = sandbox.spy();
const progressSpy = jest.fn();
const mockReadable = {
on: sandbox.spy(),
on: jest.fn(),
};
const max = 1024 * 1024 * 4;
const firstStep = 1024 * 1024 + 1024 * 600;
const secondStep = 1024 * 1024 * 2;
(reportStreamProgress as any)(mockReadable, "My prefix", max, spy);
(reportStreamProgress as any)(mockReadable, "My prefix", max, progressSpy);
// now pretend that we have received some messages
mockReadable.on.getCall(0).args[1]({ length: firstStep });
mockReadable.on.getCall(0).args[1]({ length: secondStep });
const listener = mockReadable.on.mock.calls[0][1] as (data: any) => void;
listener({ length: firstStep });
listener({ length: secondStep });
expect(spy).to.have.callCount(3);
expect(spy).to.have.been.calledWith({
expect(progressSpy).toBeCalledTimes(3);
expect(progressSpy).toBeCalledWith({
step: 0,
maxStep: max,
message: "My prefix [0.0 MB of 4.0 MB]",
});
expect(spy).to.have.been.calledWith({
expect(progressSpy).toBeCalledWith({
step: firstStep,
maxStep: max,
message: "My prefix [1.6 MB of 4.0 MB]",
});
expect(spy).to.have.been.calledWith({
expect(progressSpy).toBeCalledWith({
step: firstStep + secondStep,
maxStep: max,
message: "My prefix [3.6 MB of 4.0 MB]",
@@ -385,17 +372,22 @@ describe("helpers", () => {
});
it("should report stream progress when total bytes unknown", () => {
const spy = sandbox.spy();
const progressSpy = jest.fn();
const mockReadable = {
on: sandbox.spy(),
on: jest.fn(),
};
(reportStreamProgress as any)(mockReadable, "My prefix", undefined, spy);
(reportStreamProgress as any)(
mockReadable,
"My prefix",
undefined,
progressSpy,
);
// There are no listeners registered to this readable
expect(mockReadable.on).not.to.have.been.called;
expect(mockReadable.on).not.toBeCalled();
expect(spy).to.have.callCount(1);
expect(spy).to.have.been.calledWith({
expect(progressSpy).toBeCalledTimes(1);
expect(progressSpy).toBeCalledWith({
step: 1,
maxStep: 2,
message: "My prefix (Size unknown)",
@@ -403,106 +395,80 @@ describe("helpers", () => {
});
describe("open dialog", () => {
let showInformationMessageSpy: Sinon.SinonStub;
const showInformationMessageSpy = jest.spyOn(
window,
"showInformationMessage",
);
beforeEach(() => {
showInformationMessageSpy = sandbox.stub(
window,
"showInformationMessage",
);
showInformationMessageSpy.mockClear().mockResolvedValue(undefined);
});
it("should show a binary choice dialog and return `yes`", (done) => {
const resolveArg =
(index: number) =>
(...args: any[]) =>
Promise.resolve(args[index]);
it("should show a binary choice dialog and return `yes`", async () => {
// pretend user chooses 'yes'
showInformationMessageSpy.onCall(0).resolvesArg(2);
const res = showBinaryChoiceDialog("xxx");
res
.then((val) => {
expect(val).to.eq(true);
done();
})
.catch((e) => fail(e));
showInformationMessageSpy.mockImplementationOnce(resolveArg(2));
const val = await showBinaryChoiceDialog("xxx");
expect(val).toBe(true);
});
it("should show a binary choice dialog and return `no`", (done) => {
it("should show a binary choice dialog and return `no`", async () => {
// pretend user chooses 'no'
showInformationMessageSpy.onCall(0).resolvesArg(3);
const res = showBinaryChoiceDialog("xxx");
res
.then((val) => {
expect(val).to.eq(false);
done();
})
.catch((e) => fail(e));
showInformationMessageSpy.mockImplementationOnce(resolveArg(3));
const val = await showBinaryChoiceDialog("xxx");
expect(val).toBe(false);
});
it("should show an info dialog and confirm the action", (done) => {
it("should show an info dialog and confirm the action", async () => {
// pretend user chooses to run action
showInformationMessageSpy.onCall(0).resolvesArg(1);
const res = showInformationMessageWithAction("xxx", "yyy");
res
.then((val) => {
expect(val).to.eq(true);
done();
})
.catch((e) => fail(e));
showInformationMessageSpy.mockImplementationOnce(resolveArg(1));
const val = await showInformationMessageWithAction("xxx", "yyy");
expect(val).toBe(true);
});
it("should show an action dialog and avoid choosing the action", (done) => {
it("should show an action dialog and avoid choosing the action", async () => {
// pretend user does not choose to run action
showInformationMessageSpy.onCall(0).resolves(undefined);
const res = showInformationMessageWithAction("xxx", "yyy");
res
.then((val) => {
expect(val).to.eq(false);
done();
})
.catch((e) => fail(e));
showInformationMessageSpy.mockResolvedValueOnce(undefined);
const val = await showInformationMessageWithAction("xxx", "yyy");
expect(val).toBe(false);
});
it("should show a binary choice dialog with a url and return `yes`", (done) => {
it("should show a binary choice dialog with a url and return `yes`", async () => {
// pretend user clicks on the url twice and then clicks 'yes'
showInformationMessageSpy.onCall(0).resolvesArg(2);
showInformationMessageSpy.onCall(1).resolvesArg(2);
showInformationMessageSpy.onCall(2).resolvesArg(3);
const res = showBinaryChoiceWithUrlDialog("xxx", "invalid:url");
res
.then((val) => {
expect(val).to.eq(true);
done();
})
.catch((e) => fail(e));
showInformationMessageSpy
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(3));
const val = await showBinaryChoiceWithUrlDialog("xxx", "invalid:url");
expect(val).toBe(true);
});
it("should show a binary choice dialog with a url and return `no`", (done) => {
it("should show a binary choice dialog with a url and return `no`", async () => {
// pretend user clicks on the url twice and then clicks 'no'
showInformationMessageSpy.onCall(0).resolvesArg(2);
showInformationMessageSpy.onCall(1).resolvesArg(2);
showInformationMessageSpy.onCall(2).resolvesArg(4);
const res = showBinaryChoiceWithUrlDialog("xxx", "invalid:url");
res
.then((val) => {
expect(val).to.eq(false);
done();
})
.catch((e) => fail(e));
showInformationMessageSpy
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(4));
const val = await showBinaryChoiceWithUrlDialog("xxx", "invalid:url");
expect(val).toBe(false);
});
it("should show a binary choice dialog and exit after clcking `more info` 5 times", (done) => {
it("should show a binary choice dialog and exit after clcking `more info` 5 times", async () => {
// pretend user clicks on the url twice and then clicks 'no'
showInformationMessageSpy.onCall(0).resolvesArg(2);
showInformationMessageSpy.onCall(1).resolvesArg(2);
showInformationMessageSpy.onCall(2).resolvesArg(2);
showInformationMessageSpy.onCall(3).resolvesArg(2);
showInformationMessageSpy.onCall(4).resolvesArg(2);
const res = showBinaryChoiceWithUrlDialog("xxx", "invalid:url");
res
.then((val) => {
// No choie was made
expect(val).to.eq(undefined);
expect(showInformationMessageSpy.getCalls().length).to.eq(5);
done();
})
.catch((e) => fail(e));
showInformationMessageSpy
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(2));
const val = await showBinaryChoiceWithUrlDialog("xxx", "invalid:url");
// No choice was made
expect(val).toBeUndefined();
expect(showInformationMessageSpy).toHaveBeenCalledTimes(5);
});
});
});
@@ -567,6 +533,6 @@ describe("walkDirectory", () => {
}
// Only real files should be returned.
expect(files.sort()).to.deep.eq([file1, file2, file3, file4, file5, file6]);
expect(files.sort()).toEqual([file1, file2, file3, file4, file5, file6]);
});
});

View File

@@ -1,5 +1,4 @@
import { env } from "vscode";
import { expect } from "chai";
import { QueryHistoryConfig } from "../../config";
import { HistoryItemLabelProvider } from "../../history-item-label-provider";
import { createMockLocalQueryInfo } from "../factories/local-queries/local-query-history-item";
@@ -30,15 +29,15 @@ describe("HistoryItemLabelProvider", () => {
hasMetadata: true,
});
expect(labelProvider.getLabel(fqi)).to.eq("user-specified-name");
expect(labelProvider.getLabel(fqi)).toBe("user-specified-name");
fqi.userSpecifiedLabel = "%t %q %d %s %f %r %%";
expect(labelProvider.getLabel(fqi)).to.eq(
expect(labelProvider.getLabel(fqi)).toBe(
`${dateStr} query-name db-name finished in 0 seconds query-file.ql (456 results) %`,
);
fqi.userSpecifiedLabel = "%t %q %d %s %f %r %%::%t %q %d %s %f %r %%";
expect(labelProvider.getLabel(fqi)).to.eq(
expect(labelProvider.getLabel(fqi)).toBe(
`${dateStr} query-name db-name finished in 0 seconds query-file.ql (456 results) %::${dateStr} query-name db-name finished in 0 seconds query-file.ql (456 results) %`,
);
});
@@ -50,15 +49,15 @@ describe("HistoryItemLabelProvider", () => {
hasMetadata: true,
});
expect(labelProvider.getLabel(fqi)).to.eq("xxx query-name xxx");
expect(labelProvider.getLabel(fqi)).toBe("xxx query-name xxx");
config.format = "%t %q %d %s %f %r %%";
expect(labelProvider.getLabel(fqi)).to.eq(
expect(labelProvider.getLabel(fqi)).toBe(
`${dateStr} query-name db-name finished in 0 seconds query-file.ql (456 results) %`,
);
config.format = "%t %q %d %s %f %r %%::%t %q %d %s %f %r %%";
expect(labelProvider.getLabel(fqi)).to.eq(
expect(labelProvider.getLabel(fqi)).toBe(
`${dateStr} query-name db-name finished in 0 seconds query-file.ql (456 results) %::${dateStr} query-name db-name finished in 0 seconds query-file.ql (456 results) %`,
);
});
@@ -72,18 +71,18 @@ describe("HistoryItemLabelProvider", () => {
});
// fall back on user specified if one exists.
expect(labelProvider.getShortLabel(fqi)).to.eq("user-specified-name");
expect(labelProvider.getShortLabel(fqi)).toBe("user-specified-name");
// use query name if no user-specified label exists
fqi.userSpecifiedLabel = undefined;
expect(labelProvider.getShortLabel(fqi)).to.eq("query-name");
expect(labelProvider.getShortLabel(fqi)).toBe("query-name");
// use file name if no user-specified label exists and the query is not yet completed (meaning it has no results)
const fqi2 = createMockLocalQueryInfo({
startTime: date,
hasMetadata: true,
});
expect(labelProvider.getShortLabel(fqi2)).to.eq("query-file.ql");
expect(labelProvider.getShortLabel(fqi2)).toBe("query-file.ql");
});
});
@@ -91,15 +90,15 @@ describe("HistoryItemLabelProvider", () => {
it("should interpolate query when user specified", () => {
const fqi = createMockRemoteQueryHistoryItem({ userSpecifiedLabel });
expect(labelProvider.getLabel(fqi)).to.eq(userSpecifiedLabel);
expect(labelProvider.getLabel(fqi)).toBe(userSpecifiedLabel);
fqi.userSpecifiedLabel = "%t %q %d %s %%";
expect(labelProvider.getLabel(fqi)).to.eq(
expect(labelProvider.getLabel(fqi)).toBe(
`${dateStr} query-name (javascript) github/vscode-codeql-integration-tests in progress %`,
);
fqi.userSpecifiedLabel = "%t %q %d %s %%::%t %q %d %s %%";
expect(labelProvider.getLabel(fqi)).to.eq(
expect(labelProvider.getLabel(fqi)).toBe(
`${dateStr} query-name (javascript) github/vscode-codeql-integration-tests in progress %::${dateStr} query-name (javascript) github/vscode-codeql-integration-tests in progress %`,
);
});
@@ -111,17 +110,17 @@ describe("HistoryItemLabelProvider", () => {
resultCount: 16,
});
expect(labelProvider.getLabel(fqi)).to.eq(
expect(labelProvider.getLabel(fqi)).toBe(
"xxx query-name (javascript) xxx",
);
config.format = "%t %q %d %s %f %r %%";
expect(labelProvider.getLabel(fqi)).to.eq(
expect(labelProvider.getLabel(fqi)).toBe(
`${dateStr} query-name (javascript) github/vscode-codeql-integration-tests completed query-file.ql (16 results) %`,
);
config.format = "%t %q %d %s %f %r %%::%t %q %d %s %f %r %%";
expect(labelProvider.getLabel(fqi)).to.eq(
expect(labelProvider.getLabel(fqi)).toBe(
`${dateStr} query-name (javascript) github/vscode-codeql-integration-tests completed query-file.ql (16 results) %::${dateStr} query-name (javascript) github/vscode-codeql-integration-tests completed query-file.ql (16 results) %`,
);
});
@@ -135,7 +134,7 @@ describe("HistoryItemLabelProvider", () => {
});
config.format = "%t %q %d %s %f %r %%";
expect(labelProvider.getLabel(fqi)).to.eq(
expect(labelProvider.getLabel(fqi)).toBe(
`${dateStr} query-name (javascript) 2 repositories completed query-file.ql (16 results) %`,
);
});
@@ -148,12 +147,12 @@ describe("HistoryItemLabelProvider", () => {
});
// fall back on user specified if one exists.
expect(labelProvider.getShortLabel(fqi)).to.eq("user-specified-name");
expect(labelProvider.getShortLabel(fqi)).toBe("user-specified-name");
// use query name if no user-specified label exists
const fqi2 = createMockRemoteQueryHistoryItem({});
expect(labelProvider.getShortLabel(fqi2)).to.eq("query-name");
expect(labelProvider.getShortLabel(fqi2)).toBe("query-name");
});
describe("when results are present", () => {
@@ -165,7 +164,7 @@ describe("HistoryItemLabelProvider", () => {
repositoryCount: 2,
});
config.format = "%t %q %d %s %f %r %%";
expect(labelProvider.getLabel(fqi)).to.eq(
expect(labelProvider.getLabel(fqi)).toBe(
`${dateStr} query-name (javascript) 2 repositories completed query-file.ql (16 results) %`,
);
});
@@ -180,7 +179,7 @@ describe("HistoryItemLabelProvider", () => {
repositoryCount: 2,
});
config.format = "%t %q %d %s %f %r %%";
expect(labelProvider.getLabel(fqi)).to.eq(
expect(labelProvider.getLabel(fqi)).toBe(
`${dateStr} query-name (javascript) 2 repositories completed query-file.ql %`,
);
});
@@ -195,7 +194,7 @@ describe("HistoryItemLabelProvider", () => {
repositoryCount: 2,
});
config.format = "%t %q %d %s %f %r %%";
expect(labelProvider.getLabel(fqi)).to.eq(
expect(labelProvider.getLabel(fqi)).toBe(
`${dateStr} query-name (javascript) 2 repositories completed query-file.ql %`,
);
});
@@ -210,7 +209,7 @@ describe("HistoryItemLabelProvider", () => {
repositoryCount: 2,
});
config.format = " %t %q %d %s %f %r %%";
expect(labelProvider.getLabel(fqi)).to.eq(
expect(labelProvider.getLabel(fqi)).toBe(
` ${dateStr} query-name (javascript) 2 repositories completed query-file.ql %`,
);
});
@@ -225,7 +224,7 @@ describe("HistoryItemLabelProvider", () => {
repositoryCount: 2,
});
config.format = "%t %q %d %s %f %r %% ";
expect(labelProvider.getLabel(fqi)).to.eq(
expect(labelProvider.getLabel(fqi)).toBe(
`${dateStr} query-name (javascript) 2 repositories completed query-file.ql % `,
);
});

View File

@@ -1,19 +1,5 @@
import "source-map-support/register";
import * as sinonChai from "sinon-chai";
import * as chai from "chai";
import * as chaiAsPromised from "chai-as-promised";
import "chai/register-should";
import { ExtensionContext } from "vscode";
import { runTestsInDirectory } from "../index-template";
chai.use(chaiAsPromised);
chai.use(sinonChai);
export function run(): Promise<void> {
return runTestsInDirectory(__dirname);
}
export function createMockExtensionContext(): ExtensionContext {
return {
globalState: {

View File

@@ -1,15 +1,14 @@
import { expect } from "chai";
import * as vscode from "vscode";
import * as path from "path";
import * as sinon from "sinon";
import * as tmp from "tmp";
import { window, ViewColumn, Uri } from "vscode";
import { window, ViewColumn, Uri, WebviewPanel } from "vscode";
import { fileUriToWebviewUri, tryResolveLocation } from "../../interface-utils";
import { getDefaultResultSetName } from "../../pure/interface-types";
import { DatabaseItem } from "../../databases";
import { FileResult } from "tmp";
describe("interface-utils", () => {
describe("webview uri conversion", function () {
describe("webview uri conversion", () => {
const fileSuffix = ".bqrs";
function setupWebview(filePrefix: string) {
@@ -29,11 +28,6 @@ describe("interface-utils", () => {
},
);
after(function () {
panel.dispose();
tmpFile.removeCallback();
});
// CSP allowing nothing, to prevent warnings.
const html =
'<html><head><meta http-equiv="Content-Security-Policy" content="default-src \'none\';"></head></html>';
@@ -41,14 +35,28 @@ describe("interface-utils", () => {
return {
fileUriOnDisk,
panel,
tmpFile,
};
}
it("does not double-encode # in URIs", function () {
const { fileUriOnDisk, panel } = setupWebview("#");
let webview: {
fileUriOnDisk: Uri;
panel: WebviewPanel;
tmpFile: FileResult;
};
afterEach(() => {
webview?.panel.dispose();
webview?.tmpFile?.removeCallback();
});
it("does not double-encode # in URIs", () => {
webview = setupWebview("#");
const { fileUriOnDisk, panel } = webview;
const webviewUri = fileUriToWebviewUri(panel, fileUriOnDisk);
const parsedUri = Uri.parse(webviewUri);
expect(path.basename(parsedUri.path, fileSuffix)).to.equal(
expect(path.basename(parsedUri.path, fileSuffix)).toBe(
path.basename(fileUriOnDisk.path, fileSuffix),
);
});
@@ -56,25 +64,23 @@ describe("interface-utils", () => {
describe("getDefaultResultSetName", () => {
it("should get the default name", () => {
expect(getDefaultResultSetName(["a", "b", "#select", "alerts"])).to.equal(
expect(getDefaultResultSetName(["a", "b", "#select", "alerts"])).toBe(
"alerts",
);
expect(getDefaultResultSetName(["a", "b", "#select"])).to.equal(
"#select",
);
expect(getDefaultResultSetName(["a", "b"])).to.equal("a");
expect(getDefaultResultSetName([])).to.be.undefined;
expect(getDefaultResultSetName(["a", "b", "#select"])).toBe("#select");
expect(getDefaultResultSetName(["a", "b"])).toBe("a");
expect(getDefaultResultSetName([])).toBeUndefined();
});
});
describe("resolveWholeFileLocation", () => {
it("should resolve a whole file location", () => {
const mockDatabaseItem: DatabaseItem = {
resolveSourceFile: sinon.stub().returns(vscode.Uri.file("abc")),
resolveSourceFile: jest.fn().mockReturnValue(vscode.Uri.file("abc")),
} as unknown as DatabaseItem;
expect(
tryResolveLocation("file://hucairz:0:0:0:0", mockDatabaseItem),
).to.deep.equal(
).toEqual(
new vscode.Location(
vscode.Uri.file("abc"),
new vscode.Range(0, 0, 0, 0),
@@ -84,11 +90,11 @@ describe("interface-utils", () => {
it("should resolve a five-part location edge case", () => {
const mockDatabaseItem: DatabaseItem = {
resolveSourceFile: sinon.stub().returns(vscode.Uri.file("abc")),
resolveSourceFile: jest.fn().mockReturnValue(vscode.Uri.file("abc")),
} as unknown as DatabaseItem;
expect(
tryResolveLocation("file://hucairz:1:1:1:1", mockDatabaseItem),
).to.deep.equal(
).toEqual(
new vscode.Location(
vscode.Uri.file("abc"),
new vscode.Range(0, 0, 0, 1),
@@ -98,7 +104,7 @@ describe("interface-utils", () => {
it("should resolve a five-part location", () => {
const mockDatabaseItem: DatabaseItem = {
resolveSourceFile: sinon.stub().returns(vscode.Uri.parse("abc")),
resolveSourceFile: jest.fn().mockReturnValue(vscode.Uri.parse("abc")),
} as unknown as DatabaseItem;
expect(
@@ -112,7 +118,7 @@ describe("interface-utils", () => {
},
mockDatabaseItem,
),
).to.deep.equal(
).toEqual(
new vscode.Location(
vscode.Uri.parse("abc"),
new vscode.Range(
@@ -121,14 +127,15 @@ describe("interface-utils", () => {
),
),
);
expect(mockDatabaseItem.resolveSourceFile).to.have.been.calledOnceWith(
expect(mockDatabaseItem.resolveSourceFile).toHaveBeenCalledTimes(1);
expect(mockDatabaseItem.resolveSourceFile).toHaveBeenCalledWith(
"hucairz",
);
});
it("should resolve a five-part location with an empty path", () => {
const mockDatabaseItem: DatabaseItem = {
resolveSourceFile: sinon.stub().returns(vscode.Uri.parse("abc")),
resolveSourceFile: jest.fn().mockReturnValue(vscode.Uri.parse("abc")),
} as unknown as DatabaseItem;
expect(
@@ -142,35 +149,36 @@ describe("interface-utils", () => {
},
mockDatabaseItem,
),
).to.be.undefined;
).toBeUndefined();
});
it("should resolve a string location for whole file", () => {
const mockDatabaseItem: DatabaseItem = {
resolveSourceFile: sinon.stub().returns(vscode.Uri.parse("abc")),
resolveSourceFile: jest.fn().mockReturnValue(vscode.Uri.parse("abc")),
} as unknown as DatabaseItem;
expect(
tryResolveLocation("file://hucairz:0:0:0:0", mockDatabaseItem),
).to.deep.equal(
).toEqual(
new vscode.Location(
vscode.Uri.parse("abc"),
new vscode.Range(0, 0, 0, 0),
),
);
expect(mockDatabaseItem.resolveSourceFile).to.have.been.calledOnceWith(
expect(mockDatabaseItem.resolveSourceFile).toHaveBeenCalledTimes(1);
expect(mockDatabaseItem.resolveSourceFile).toHaveBeenCalledWith(
"hucairz",
);
});
it("should resolve a string location for five-part location", () => {
const mockDatabaseItem: DatabaseItem = {
resolveSourceFile: sinon.stub().returns(vscode.Uri.parse("abc")),
resolveSourceFile: jest.fn().mockReturnValue(vscode.Uri.parse("abc")),
} as unknown as DatabaseItem;
expect(
tryResolveLocation("file://hucairz:5:4:3:2", mockDatabaseItem),
).to.deep.equal(
).toEqual(
new vscode.Location(
vscode.Uri.parse("abc"),
new vscode.Range(
@@ -179,18 +187,20 @@ describe("interface-utils", () => {
),
),
);
expect(mockDatabaseItem.resolveSourceFile).to.have.been.calledOnceWith(
expect(mockDatabaseItem.resolveSourceFile).toHaveBeenCalledTimes(1);
expect(mockDatabaseItem.resolveSourceFile).toHaveBeenCalledWith(
"hucairz",
);
});
it("should resolve a string location for invalid string", () => {
const mockDatabaseItem: DatabaseItem = {
resolveSourceFile: sinon.stub().returns(vscode.Uri.parse("abc")),
resolveSourceFile: jest.fn().mockReturnValue(vscode.Uri.parse("abc")),
} as unknown as DatabaseItem;
expect(tryResolveLocation("file://hucairz:x:y:z:a", mockDatabaseItem)).to
.be.undefined;
expect(
tryResolveLocation("file://hucairz:x:y:z:a", mockDatabaseItem),
).toBeUndefined();
});
});
});

View File

@@ -0,0 +1,9 @@
import type { Config } from "jest";
import baseConfig from "../jest.config.base";
const config: Config = {
...baseConfig,
};
export default config;

View File

@@ -1,4 +1,3 @@
import { expect } from "chai";
import { QueryStatus } from "../../query-status";
import {
buildRepoLabel,
@@ -40,19 +39,19 @@ describe("Query history info", () => {
it("should get the name for local history items", () => {
const queryName = getRawQueryName(localQueryHistoryItem);
expect(queryName).to.equal(localQueryHistoryItem.getQueryName());
expect(queryName).toBe(localQueryHistoryItem.getQueryName());
});
it("should get the name for remote query history items", () => {
const queryName = getRawQueryName(remoteQueryHistoryItem);
expect(queryName).to.equal(remoteQueryHistoryItem.remoteQuery.queryName);
expect(queryName).toBe(remoteQueryHistoryItem.remoteQuery.queryName);
});
it("should get the name for variant analysis history items", () => {
const queryName = getRawQueryName(variantAnalysisHistoryItem);
expect(queryName).to.equal(
expect(queryName).toBe(
variantAnalysisHistoryItem.variantAnalysis.query.name,
);
});
@@ -62,19 +61,19 @@ describe("Query history info", () => {
it("should get the ID for local history items", () => {
const historyItemId = getQueryId(localQueryHistoryItem);
expect(historyItemId).to.equal(localQueryHistoryItem.initialInfo.id);
expect(historyItemId).toBe(localQueryHistoryItem.initialInfo.id);
});
it("should get the ID for remote query history items", () => {
const historyItemId = getQueryId(remoteQueryHistoryItem);
expect(historyItemId).to.equal(remoteQueryHistoryItem.queryId);
expect(historyItemId).toBe(remoteQueryHistoryItem.queryId);
});
it("should get the ID for variant analysis history items", () => {
const historyItemId = getQueryId(variantAnalysisHistoryItem);
expect(historyItemId).to.equal(
expect(historyItemId).toBe(
variantAnalysisHistoryItem.variantAnalysis.id.toString(),
);
});
@@ -84,19 +83,19 @@ describe("Query history info", () => {
it("should get the query text for local history items", () => {
const queryText = getQueryText(localQueryHistoryItem);
expect(queryText).to.equal(localQueryHistoryItem.initialInfo.queryText);
expect(queryText).toBe(localQueryHistoryItem.initialInfo.queryText);
});
it("should get the query text for remote query history items", () => {
const queryText = getQueryText(remoteQueryHistoryItem);
expect(queryText).to.equal(remoteQueryHistoryItem.remoteQuery.queryText);
expect(queryText).toBe(remoteQueryHistoryItem.remoteQuery.queryText);
});
it("should get the query text for variant analysis history items", () => {
const queryText = getQueryText(variantAnalysisHistoryItem);
expect(queryText).to.equal(
expect(queryText).toBe(
variantAnalysisHistoryItem.variantAnalysis.query.text,
);
});
@@ -108,7 +107,7 @@ describe("Query history info", () => {
const repoLabel = buildRepoLabel(remoteQueryHistoryItem);
const expectedRepoLabel = `${remoteQueryHistoryItem.remoteQuery.controllerRepository.owner}/${remoteQueryHistoryItem.remoteQuery.controllerRepository.name}`;
expect(repoLabel).to.equal(expectedRepoLabel);
expect(repoLabel).toBe(expectedRepoLabel);
});
it("should return number of repositories when `repositoryCount` is non-zero", () => {
const remoteQueryHistoryItem2 = createMockRemoteQueryHistoryItem({
@@ -117,7 +116,7 @@ describe("Query history info", () => {
const repoLabel2 = buildRepoLabel(remoteQueryHistoryItem2);
const expectedRepoLabel2 = "3 repositories";
expect(repoLabel2).to.equal(expectedRepoLabel2);
expect(repoLabel2).toBe(expectedRepoLabel2);
});
});
describe("repo label for variant analysis history items", () => {
@@ -133,7 +132,7 @@ describe("Query history info", () => {
};
const repoLabel0 = buildRepoLabel(variantAnalysisHistoryItem0);
expect(repoLabel0).to.equal("0/0 repositories");
expect(repoLabel0).toBe("0/0 repositories");
});
it("should return label when `totalScannedRepositoryCount` is 1", () => {
const variantAnalysisHistoryItem1: VariantAnalysisHistoryItem = {
@@ -149,12 +148,12 @@ describe("Query history info", () => {
};
const repoLabel1 = buildRepoLabel(variantAnalysisHistoryItem1);
expect(repoLabel1).to.equal("0/1 repository");
expect(repoLabel1).toBe("0/1 repository");
});
it("should return label when `totalScannedRepositoryCount` is greater than 1", () => {
const repoLabel = buildRepoLabel(variantAnalysisHistoryItem);
expect(repoLabel).to.equal("2/4 repositories");
expect(repoLabel).toBe("2/4 repositories");
});
});
});
@@ -167,7 +166,7 @@ describe("Query history info", () => {
const remoteQuery = remoteQueryHistoryItem.remoteQuery;
const fullName = `${remoteQuery.controllerRepository.owner}/${remoteQuery.controllerRepository.name}`;
expect(actionsWorkflowRunUrl).to.equal(
expect(actionsWorkflowRunUrl).toBe(
`https://github.com/${fullName}/actions/runs/${remoteQuery.actionsWorkflowRunId}`,
);
});
@@ -179,7 +178,7 @@ describe("Query history info", () => {
const variantAnalysis = variantAnalysisHistoryItem.variantAnalysis;
const fullName = variantAnalysis.controllerRepo.fullName;
expect(actionsWorkflowRunUrl).to.equal(
expect(actionsWorkflowRunUrl).toBe(
`https://github.com/${fullName}/actions/runs/${variantAnalysis.actionsWorkflowRunId}`,
);
});

View File

@@ -1,8 +1,6 @@
import { expect } from "chai";
import * as path from "path";
import * as fs from "fs-extra";
import * as os from "os";
import * as sinon from "sinon";
import {
LocalQueryInfo,
InitialQueryInfo,
@@ -26,36 +24,27 @@ import {
QueryInProgress,
} from "../../legacy-query-server/run-queries";
import { EvaluationResult, QueryResultType } from "../../pure/legacy-messages";
import Sinon = require("sinon");
import { sleep } from "../../pure/time";
describe("query-results", () => {
let disposeSpy: sinon.SinonSpy;
let sandbox: sinon.SinonSandbox;
let queryPath: string;
let cnt = 0;
beforeEach(() => {
sandbox = sinon.createSandbox();
disposeSpy = sandbox.spy();
queryPath = path.join(Uri.file(tmpDir.name).fsPath, `query-${cnt++}`);
});
afterEach(() => {
sandbox.restore();
});
describe("FullQueryInfo", () => {
it("should get the query name", () => {
const fqi = createMockFullQueryInfo();
// from the query path
expect(fqi.getQueryName()).to.eq("hucairz");
expect(fqi.getQueryName()).toBe("hucairz");
fqi.completeThisQuery(createMockQueryWithResults(queryPath));
// from the metadata
expect(fqi.getQueryName()).to.eq("vwx");
expect(fqi.getQueryName()).toBe("vwx");
// from quick eval position
(fqi.initialInfo as any).quickEvalPosition = {
@@ -63,16 +52,16 @@ describe("query-results", () => {
endLine: 2,
fileName: "/home/users/yz",
};
expect(fqi.getQueryName()).to.eq("Quick evaluation of yz:1-2");
expect(fqi.getQueryName()).toBe("Quick evaluation of yz:1-2");
(fqi.initialInfo as any).quickEvalPosition.endLine = 1;
expect(fqi.getQueryName()).to.eq("Quick evaluation of yz:1");
expect(fqi.getQueryName()).toBe("Quick evaluation of yz:1");
});
it("should get the query file name", () => {
const fqi = createMockFullQueryInfo();
// from the query path
expect(fqi.getQueryFileName()).to.eq("hucairz");
expect(fqi.getQueryFileName()).toBe("hucairz");
// from quick eval position
(fqi.initialInfo as any).quickEvalPosition = {
@@ -80,9 +69,9 @@ describe("query-results", () => {
endLine: 2,
fileName: "/home/users/yz",
};
expect(fqi.getQueryFileName()).to.eq("yz:1-2");
expect(fqi.getQueryFileName()).toBe("yz:1-2");
(fqi.initialInfo as any).quickEvalPosition.endLine = 1;
expect(fqi.getQueryFileName()).to.eq("yz:1");
expect(fqi.getQueryFileName()).toBe("yz:1");
});
it("should get the getResultsPath", () => {
@@ -92,7 +81,7 @@ describe("query-results", () => {
const expectedResultsPath = path.join(queryPath, "results.bqrs");
// from results path
expect(completedQuery.getResultsPath("zxa", false)).to.eq(
expect(completedQuery.getResultsPath("zxa", false)).toBe(
expectedResultsPath,
);
@@ -101,12 +90,12 @@ describe("query-results", () => {
} as SortedResultSetInfo;
// still from results path
expect(completedQuery.getResultsPath("zxa", false)).to.eq(
expect(completedQuery.getResultsPath("zxa", false)).toBe(
expectedResultsPath,
);
// from sortedResultsInfo
expect(completedQuery.getResultsPath("zxa")).to.eq("bxa");
expect(completedQuery.getResultsPath("zxa")).toBe("bxa");
});
it("should format the statusString", () => {
@@ -118,27 +107,23 @@ describe("query-results", () => {
};
evalResult.message = "Tremendously";
expect(formatLegacyMessage(evalResult)).to.eq("failed: Tremendously");
expect(formatLegacyMessage(evalResult)).toBe("failed: Tremendously");
evalResult.resultType = QueryResultType.OTHER_ERROR;
expect(formatLegacyMessage(evalResult)).to.eq("failed: Tremendously");
expect(formatLegacyMessage(evalResult)).toBe("failed: Tremendously");
evalResult.resultType = QueryResultType.CANCELLATION;
evalResult.evaluationTime = 2345;
expect(formatLegacyMessage(evalResult)).to.eq(
"cancelled after 2 seconds",
);
expect(formatLegacyMessage(evalResult)).toBe("cancelled after 2 seconds");
evalResult.resultType = QueryResultType.OOM;
expect(formatLegacyMessage(evalResult)).to.eq("out of memory");
expect(formatLegacyMessage(evalResult)).toBe("out of memory");
evalResult.resultType = QueryResultType.SUCCESS;
expect(formatLegacyMessage(evalResult)).to.eq("finished in 2 seconds");
expect(formatLegacyMessage(evalResult)).toBe("finished in 2 seconds");
evalResult.resultType = QueryResultType.TIMEOUT;
expect(formatLegacyMessage(evalResult)).to.eq(
"timed out after 2 seconds",
);
expect(formatLegacyMessage(evalResult)).toBe("timed out after 2 seconds");
});
it("should updateSortState", async () => {
// setup
@@ -148,7 +133,7 @@ describe("query-results", () => {
);
const completedQuery = fqi.completedQuery!;
const spy = sandbox.spy();
const spy = jest.fn();
const mockServer = {
sortBqrs: spy,
} as unknown as CodeQLCliServer;
@@ -170,7 +155,7 @@ describe("query-results", () => {
queryPath,
"sortedResults-a-result-set-name.bqrs",
);
expect(spy).to.have.been.calledWith(
expect(spy).toBeCalledWith(
expectedResultsPath,
expectedSortedResultsPath,
"a-result-set-name",
@@ -178,22 +163,20 @@ describe("query-results", () => {
[sortState.sortDirection],
);
expect(
completedQuery.sortedResultsInfo["a-result-set-name"],
).to.deep.equal({
expect(completedQuery.sortedResultsInfo["a-result-set-name"]).toEqual({
resultsPath: expectedSortedResultsPath,
sortState,
});
// delete the sort state
await completedQuery.updateSortState(mockServer, "a-result-set-name");
expect(Object.values(completedQuery.sortedResultsInfo).length).to.eq(0);
expect(Object.values(completedQuery.sortedResultsInfo).length).toBe(0);
});
});
describe("interpretResultsSarif", () => {
let mockServer: CodeQLCliServer;
let spy: Sinon.SinonExpectation;
const spy = jest.fn();
const metadata = {
kind: "my-kind",
id: "my-id" as string | undefined,
@@ -204,8 +187,7 @@ describe("query-results", () => {
const sourceInfo = {};
beforeEach(() => {
spy = sandbox.mock();
spy.returns({ a: "1234" });
spy.mockReset().mockReturnValue({ a: "1234" });
mockServer = {
interpretBqrsSarif: spy,
@@ -213,13 +195,12 @@ describe("query-results", () => {
});
afterEach(async () => {
sandbox.restore();
safeDel(interpretedResultsPath);
});
it("should interpretResultsSarif", async function () {
it("should interpretResultsSarif", async () => {
// up to 2 minutes per test
this.timeout(2 * 60 * 1000);
jest.setTimeout(2 * 60 * 1000);
const results = await interpretResultsSarif(
mockServer,
@@ -231,8 +212,8 @@ describe("query-results", () => {
sourceInfo as SourceInfo,
);
expect(results).to.deep.eq({ a: "1234", t: "SarifInterpretationData" });
expect(spy).to.have.been.calledWith(
expect(results).toEqual({ a: "1234", t: "SarifInterpretationData" });
expect(spy).toBeCalledWith(
metadata,
resultsPath,
interpretedResultsPath,
@@ -240,9 +221,9 @@ describe("query-results", () => {
);
});
it("should interpretBqrsSarif without ID", async function () {
it("should interpretBqrsSarif without ID", async () => {
// up to 2 minutes per test
this.timeout(2 * 60 * 1000);
jest.setTimeout(2 * 60 * 1000);
delete metadata.id;
const results = await interpretResultsSarif(
@@ -254,8 +235,8 @@ describe("query-results", () => {
},
sourceInfo as SourceInfo,
);
expect(results).to.deep.eq({ a: "1234", t: "SarifInterpretationData" });
expect(spy).to.have.been.calledWith(
expect(results).toEqual({ a: "1234", t: "SarifInterpretationData" });
expect(spy).toBeCalledWith(
{ kind: "my-kind", id: "dummy-id", scored: undefined },
resultsPath,
interpretedResultsPath,
@@ -263,9 +244,9 @@ describe("query-results", () => {
);
});
it("should use sarifParser on a valid small SARIF file", async function () {
it("should use sarifParser on a valid small SARIF file", async () => {
// up to 2 minutes per test
this.timeout(2 * 60 * 1000);
jest.setTimeout(2 * 60 * 1000);
fs.writeFileSync(
interpretedResultsPath,
@@ -284,15 +265,15 @@ describe("query-results", () => {
sourceInfo as SourceInfo,
);
// We do not re-interpret if we are reading from a SARIF file.
expect(spy).to.not.have.been.called;
expect(spy).not.toBeCalled();
expect(results).to.have.property("t", "SarifInterpretationData");
expect(results).to.have.nested.property("runs[0].results");
expect(results).toHaveProperty("t", "SarifInterpretationData");
expect(results).toHaveProperty("runs[0].results");
});
it("should throw an error on an invalid small SARIF file", async function () {
it("should throw an error on an invalid small SARIF file", async () => {
// up to 2 minutes per test
this.timeout(2 * 60 * 1000);
jest.setTimeout(2 * 60 * 1000);
fs.writeFileSync(
interpretedResultsPath,
@@ -312,17 +293,17 @@ describe("query-results", () => {
},
sourceInfo as SourceInfo,
),
).to.be.rejectedWith(
).rejects.toThrow(
"Parsing output of interpretation failed: Invalid SARIF file: expecting at least one run with result.",
);
// We do not attempt to re-interpret if we are reading from a SARIF file.
expect(spy).to.not.have.been.called;
expect(spy).not.toBeCalled();
});
it("should use sarifParser on a valid large SARIF file", async function () {
it("should use sarifParser on a valid large SARIF file", async () => {
// up to 2 minutes per test
this.timeout(2 * 60 * 1000);
jest.setTimeout(2 * 60 * 1000);
const validSarifStream = fs.createWriteStream(interpretedResultsPath, {
flags: "w",
@@ -373,15 +354,15 @@ describe("query-results", () => {
sourceInfo as SourceInfo,
);
// We do not re-interpret if we are reading from a SARIF file.
expect(spy).to.not.have.been.called;
expect(spy).not.toBeCalled();
expect(results).to.have.property("t", "SarifInterpretationData");
expect(results).to.have.nested.property("runs[0].results");
expect(results).toHaveProperty("t", "SarifInterpretationData");
expect(results).toHaveProperty("runs[0].results");
});
it("should throw an error on an invalid large SARIF file", async function () {
it("should throw an error on an invalid large SARIF file", async () => {
// up to 2 minutes per test
this.timeout(2 * 60 * 1000);
jest.setTimeout(2 * 60 * 1000);
// There is a problem on Windows where the file at the prior path isn't able
// to be deleted or written to, so we rename the path for this last test.
@@ -431,12 +412,12 @@ describe("query-results", () => {
},
sourceInfo as SourceInfo,
),
).to.be.rejectedWith(
).rejects.toThrow(
"Parsing output of interpretation failed: Invalid SARIF file: expecting at least one run with result.",
);
// We do not attempt to re-interpret if we are reading from a SARIF file.
expect(spy).to.not.have.been.called;
expect(spy).not.toBeCalled();
});
});
@@ -532,9 +513,9 @@ describe("query-results", () => {
// make the diffs somewhat sane by comparing each element directly
for (let i = 0; i < allHistoryActual.length; i++) {
expect(allHistoryActual[i]).to.deep.eq(expectedHistory[i]);
expect(allHistoryActual[i]).toEqual(expectedHistory[i]);
}
expect(allHistoryActual.length).to.deep.eq(expectedHistory.length);
expect(allHistoryActual.length).toEqual(expectedHistory.length);
});
it("should handle an invalid query history version", async () => {
@@ -550,7 +531,7 @@ describe("query-results", () => {
const allHistoryActual = await slurpQueryHistory(badPath);
// version number is invalid. Should return an empty array.
expect(allHistoryActual).to.deep.eq([]);
expect(allHistoryActual).toEqual([]);
});
});
@@ -589,7 +570,7 @@ describe("query-results", () => {
query: query.queryEvalInfo,
successful: didRunSuccessfully,
message: "foo",
dispose: disposeSpy,
dispose: jest.fn(),
result: {
evaluationTime: 1,
queryId: 0,

View File

@@ -1,50 +1,24 @@
import { expect } from "chai";
import * as path from "path";
import * as fs from "fs-extra";
import * as sinon from "sinon";
import * as pq from "proxyquire";
import { ExtensionContext } from "vscode";
import { createMockExtensionContext } from "../index";
import { Credentials } from "../../../authentication";
import { MarkdownFile } from "../../../remote-queries/remote-queries-markdown-generation";
import * as markdownGenerator from "../../../remote-queries/remote-queries-markdown-generation";
import * as ghApiClient from "../../../remote-queries/gh-api/gh-api-client";
import { exportRemoteQueryAnalysisResults } from "../../../remote-queries/export-results";
const proxyquire = pq.noPreserveCache();
describe("export results", () => {
describe("exportRemoteQueryAnalysisResults", () => {
const mockCredentials = {} as unknown as Credentials;
describe("export results", async function () {
describe("exportRemoteQueryAnalysisResults", async function () {
let sandbox: sinon.SinonSandbox;
let mockCredentials: Credentials;
let mockResponse: sinon.SinonStub<any, Promise<{ status: number }>>;
let mockCreateGist: sinon.SinonStub;
let ctx: ExtensionContext;
beforeEach(() => {
sandbox = sinon.createSandbox();
mockCredentials = {
getOctokit: () =>
Promise.resolve({
request: mockResponse,
}),
} as unknown as Credentials;
sandbox.stub(Credentials, "initialize").resolves(mockCredentials);
const resultFiles = [] as MarkdownFile[];
proxyquire("../../../remote-queries/remote-queries-markdown-generation", {
generateMarkdown: sinon.stub().returns(resultFiles),
});
});
afterEach(() => {
sandbox.restore();
});
jest.spyOn(markdownGenerator, "generateMarkdown").mockReturnValue([]);
jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials);
it("should call the GitHub Actions API with the correct gist title", async function () {
mockCreateGist = sinon.stub(ghApiClient, "createGist");
const mockCreateGist = jest
.spyOn(ghApiClient, "createGist")
.mockResolvedValue(undefined);
ctx = createMockExtensionContext();
const ctx = createMockExtensionContext();
const query = JSON.parse(
await fs.readFile(
path.join(
@@ -72,9 +46,11 @@ describe("export results", async function () {
"gist",
);
expect(mockCreateGist.calledOnce).to.be.true;
expect(mockCreateGist.firstCall.args[1]).to.equal(
expect(mockCreateGist).toHaveBeenCalledTimes(1);
expect(mockCreateGist).toHaveBeenCalledWith(
expect.anything(),
"Shell command built from environment values (javascript) 3 results (10 repositories)",
expect.anything(),
);
});
});

View File

@@ -1,6 +1,4 @@
import { fail } from "assert";
import { expect } from "chai";
import * as sinon from "sinon";
import { Credentials } from "../../../../authentication";
import {
cancelRemoteQuery,
@@ -11,46 +9,43 @@ import { RemoteQuery } from "../../../../remote-queries/remote-query";
import { createMockVariantAnalysis } from "../../../factories/remote-queries/shared/variant-analysis";
import { VariantAnalysis } from "../../../../remote-queries/shared/variant-analysis";
jest.setTimeout(10000);
describe("gh-actions-api-client mock responses", () => {
let sandbox: sinon.SinonSandbox;
let mockCredentials: Credentials;
let mockResponse: sinon.SinonStub<any, Promise<{ status: number }>>;
const mockRequest = jest.fn();
const mockCredentials = {
getOctokit: () =>
Promise.resolve({
request: mockRequest,
}),
} as unknown as Credentials;
beforeEach(() => {
sandbox = sinon.createSandbox();
mockCredentials = {
getOctokit: () =>
Promise.resolve({
request: mockResponse,
}),
} as unknown as Credentials;
});
afterEach(() => {
sandbox.restore();
mockRequest.mockReset();
});
describe("cancelRemoteQuery", () => {
it("should cancel a remote query", async () => {
mockResponse = sinon.stub().resolves({ status: 202 });
mockRequest.mockReturnValue({ status: 202 });
await cancelRemoteQuery(mockCredentials, createMockRemoteQuery());
expect(mockResponse.calledOnce).to.be.true;
expect(mockResponse.firstCall.args[0]).to.equal(
expect(mockRequest).toHaveBeenCalledTimes(1);
expect(mockRequest).toHaveBeenCalledWith(
"POST /repos/github/codeql/actions/runs/123/cancel",
);
});
it("should fail to cancel a remote query", async () => {
mockResponse = sinon
.stub()
.resolves({ status: 409, data: { message: "Uh oh!" } });
mockRequest.mockResolvedValue({
status: 409,
data: { message: "Uh oh!" },
});
await expect(
cancelRemoteQuery(mockCredentials, createMockRemoteQuery()),
).to.be.rejectedWith(/Error cancelling variant analysis: 409 Uh oh!/);
expect(mockResponse.calledOnce).to.be.true;
expect(mockResponse.firstCall.args[0]).to.equal(
).rejects.toThrow(/Error cancelling variant analysis: 409 Uh oh!/);
expect(mockRequest).toHaveBeenCalledTimes(1);
expect(mockRequest).toHaveBeenCalledWith(
"POST /repos/github/codeql/actions/runs/123/cancel",
);
});
@@ -68,39 +63,38 @@ describe("gh-actions-api-client mock responses", () => {
describe("cancelVariantAnalysis", () => {
let variantAnalysis: VariantAnalysis;
before(() => {
beforeAll(() => {
variantAnalysis = createMockVariantAnalysis({});
});
it("should cancel a variant analysis", async () => {
mockResponse = sinon.stub().resolves({ status: 202 });
mockRequest.mockResolvedValue({ status: 202 });
await cancelVariantAnalysis(mockCredentials, variantAnalysis);
expect(mockResponse.calledOnce).to.be.true;
expect(mockResponse.firstCall.args[0]).to.equal(
expect(mockRequest).toHaveBeenCalledTimes(1);
expect(mockRequest).toHaveBeenCalledWith(
`POST /repos/${variantAnalysis.controllerRepo.fullName}/actions/runs/${variantAnalysis.actionsWorkflowRunId}/cancel`,
);
});
it("should fail to cancel a variant analysis", async () => {
mockResponse = sinon
.stub()
.resolves({ status: 409, data: { message: "Uh oh!" } });
mockRequest.mockResolvedValue({
status: 409,
data: { message: "Uh oh!" },
});
await expect(
cancelVariantAnalysis(mockCredentials, variantAnalysis),
).to.be.rejectedWith(/Error cancelling variant analysis: 409 Uh oh!/);
expect(mockResponse.calledOnce).to.be.true;
expect(mockResponse.firstCall.args[0]).to.equal(
).rejects.toThrow(/Error cancelling variant analysis: 409 Uh oh!/);
expect(mockRequest).toHaveBeenCalledTimes(1);
expect(mockRequest).toHaveBeenCalledWith(
`POST /repos/${variantAnalysis.controllerRepo.fullName}/actions/runs/${variantAnalysis.actionsWorkflowRunId}/cancel`,
);
});
});
});
describe("gh-actions-api-client real responses", function () {
this.timeout(10000);
describe("gh-actions-api-client real responses", () => {
it("should get the stargazers for repos", async () => {
if (skip()) {
return;
@@ -123,7 +117,7 @@ describe("gh-actions-api-client real responses", function () {
);
const stargazersKeys = Object.keys(stargazers).sort();
expect(stargazersKeys).to.deep.eq([
expect(stargazersKeys).toEqual([
"angular/angular",
"github/codeql",
"github/vscode-codeql",

View File

@@ -1,4 +1,3 @@
import { expect } from "chai";
import * as os from "os";
import { parseResponse } from "../../../remote-queries/remote-queries-api";
import { Repository } from "../../../remote-queries/shared/repository";
@@ -16,10 +15,10 @@ describe("parseResponse", () => {
repositories_queried: ["a/b", "c/d"],
});
expect(result.popupMessage).to.equal(
expect(result.popupMessage).toBe(
"Successfully scheduled runs on 2 repositories. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
);
expect(result.logMessage).to.equal(
expect(result.logMessage).toBe(
[
"Successfully scheduled runs on 2 repositories. See https://github.com/org/name/actions/runs/123.",
"",
@@ -38,14 +37,14 @@ describe("parseResponse", () => {
},
});
expect(result.popupMessage).to.equal(
expect(result.popupMessage).toBe(
[
"Successfully scheduled runs on 2 repositories. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
"",
"Some repositories could not be scheduled. See extension log for details.",
].join(os.EOL),
);
expect(result.logMessage).to.equal(
expect(result.logMessage).toBe(
[
"Successfully scheduled runs on 2 repositories. See https://github.com/org/name/actions/runs/123.",
"",
@@ -69,14 +68,14 @@ describe("parseResponse", () => {
},
});
expect(result.popupMessage).to.equal(
expect(result.popupMessage).toBe(
[
"Successfully scheduled runs on 2 repositories. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
"",
"Some repositories could not be scheduled. See extension log for details.",
].join(os.EOL),
);
expect(result.logMessage).to.equal(
expect(result.logMessage).toBe(
[
"Successfully scheduled runs on 2 repositories. See https://github.com/org/name/actions/runs/123.",
"",
@@ -101,14 +100,14 @@ describe("parseResponse", () => {
},
});
expect(result.popupMessage).to.equal(
expect(result.popupMessage).toBe(
[
"Successfully scheduled runs on 2 repositories. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
"",
"Some repositories could not be scheduled. See extension log for details.",
].join(os.EOL),
);
expect(result.logMessage).to.equal(
expect(result.logMessage).toBe(
[
"Successfully scheduled runs on 2 repositories. See https://github.com/org/name/actions/runs/123.",
"",
@@ -134,14 +133,14 @@ describe("parseResponse", () => {
},
});
expect(result.popupMessage).to.equal(
expect(result.popupMessage).toBe(
[
"Successfully scheduled runs on 2 repositories. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
"",
"Some repositories could not be scheduled. See extension log for details.",
].join(os.EOL),
);
expect(result.logMessage).to.equal(
expect(result.logMessage).toBe(
[
"Successfully scheduled runs on 2 repositories. See https://github.com/org/name/actions/runs/123.",
"",
@@ -166,14 +165,14 @@ describe("parseResponse", () => {
},
});
expect(result.popupMessage).to.equal(
expect(result.popupMessage).toBe(
[
"Successfully scheduled runs on 2 repositories. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
"",
"Some repositories could not be scheduled. See extension log for details.",
].join(os.EOL),
);
expect(result.logMessage).to.equal(
expect(result.logMessage).toBe(
[
"Successfully scheduled runs on 2 repositories. See https://github.com/org/name/actions/runs/123.",
"",
@@ -198,14 +197,14 @@ describe("parseResponse", () => {
},
});
expect(result.popupMessage).to.equal(
expect(result.popupMessage).toBe(
[
"Successfully scheduled runs on 2 repositories. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
"",
"Some repositories could not be scheduled. See extension log for details.",
].join(os.EOL),
);
expect(result.logMessage).to.equal(
expect(result.logMessage).toBe(
[
"Successfully scheduled runs on 2 repositories. See https://github.com/org/name/actions/runs/123.",
"",
@@ -237,14 +236,14 @@ describe("parseResponse", () => {
},
});
expect(result.popupMessage).to.equal(
expect(result.popupMessage).toBe(
[
"Successfully scheduled runs on 1 repository. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
"",
"Some repositories could not be scheduled. See extension log for details.",
].join(os.EOL),
);
expect(result.logMessage).to.equal(
expect(result.logMessage).toBe(
[
"Successfully scheduled runs on 1 repository. See https://github.com/org/name/actions/runs/123.",
"",

View File

@@ -1,11 +1,11 @@
import * as fs from "fs-extra";
import * as path from "path";
import * as sinon from "sinon";
import { expect } from "chai";
import {
CancellationToken,
ExtensionContext,
TextDocument,
TextEditor,
Uri,
window,
workspace,
@@ -32,62 +32,57 @@ import { VariantAnalysisManager } from "../../../remote-queries/variant-analysis
* Tests for remote queries and how they interact with the query history manager.
*/
describe("Remote queries and query history manager", function () {
describe("Remote queries and query history manager", () => {
const EXTENSION_PATH = path.join(__dirname, "../../../../");
const STORAGE_DIR = Uri.file(path.join(tmpDir.name, "remote-queries")).fsPath;
const asyncNoop = async () => {
/** noop */
};
let sandbox: sinon.SinonSandbox;
let qhm: QueryHistoryManager;
let localQueriesResultsViewStub: ResultsView;
let remoteQueriesManagerStub: RemoteQueriesManager;
let variantAnalysisManagerStub: VariantAnalysisManager;
const localQueriesResultsViewStub = {
showResults: jest.fn(),
} as any as ResultsView;
let rawQueryHistory: any;
let remoteQueryResult0: RemoteQueryResult;
let remoteQueryResult1: RemoteQueryResult;
let disposables: DisposableBucket;
let showTextDocumentSpy: sinon.SinonSpy;
let openTextDocumentSpy: sinon.SinonSpy;
let rehydrateRemoteQueryStub: sinon.SinonStub;
let removeRemoteQueryStub: sinon.SinonStub;
let openRemoteQueryResultsStub: sinon.SinonStub;
const rehydrateRemoteQueryStub = jest.fn();
const removeRemoteQueryStub = jest.fn();
const openRemoteQueryResultsStub = jest.fn();
beforeEach(async function () {
const remoteQueriesManagerStub = {
onRemoteQueryAdded: jest.fn(),
onRemoteQueryRemoved: jest.fn(),
onRemoteQueryStatusUpdate: jest.fn(),
rehydrateRemoteQuery: rehydrateRemoteQueryStub,
removeRemoteQuery: removeRemoteQueryStub,
openRemoteQueryResults: openRemoteQueryResultsStub,
} as any as RemoteQueriesManager;
const variantAnalysisManagerStub = {
onVariantAnalysisAdded: jest.fn(),
onVariantAnalysisStatusUpdated: jest.fn(),
onVariantAnalysisRemoved: jest.fn(),
} as any as VariantAnalysisManager;
const showTextDocumentSpy = jest.spyOn(window, "showTextDocument");
const openTextDocumentSpy = jest.spyOn(workspace, "openTextDocument");
beforeEach(async () => {
// set a higher timeout since recursive delete below may take a while, expecially on Windows.
this.timeout(120000);
jest.setTimeout(120000);
// Since these tests change the state of the query history manager, we need to copy the original
// to a temporary folder where we can manipulate it for tests
await copyHistoryState();
sandbox = sinon.createSandbox();
disposables = new DisposableBucket();
localQueriesResultsViewStub = {
showResults: sandbox.stub(),
} as any as ResultsView;
rehydrateRemoteQueryStub = sandbox.stub();
removeRemoteQueryStub = sandbox.stub();
openRemoteQueryResultsStub = sandbox.stub();
remoteQueriesManagerStub = {
onRemoteQueryAdded: sandbox.stub(),
onRemoteQueryRemoved: sandbox.stub(),
onRemoteQueryStatusUpdate: sandbox.stub(),
rehydrateRemoteQuery: rehydrateRemoteQueryStub,
removeRemoteQuery: removeRemoteQueryStub,
openRemoteQueryResults: openRemoteQueryResultsStub,
} as any as RemoteQueriesManager;
variantAnalysisManagerStub = {
onVariantAnalysisAdded: sandbox.stub(),
onVariantAnalysisStatusUpdated: sandbox.stub(),
onVariantAnalysisRemoved: sandbox.stub(),
} as any as VariantAnalysisManager;
rehydrateRemoteQueryStub.mockReset();
removeRemoteQueryStub.mockReset();
openRemoteQueryResultsStub.mockReset();
rawQueryHistory = fs.readJSONSync(
path.join(STORAGE_DIR, "workspace-query-history.json"),
@@ -129,33 +124,38 @@ describe("Remote queries and query history manager", function () {
);
disposables.push(qhm);
showTextDocumentSpy = sandbox.spy(window, "showTextDocument");
openTextDocumentSpy = sandbox.spy(workspace, "openTextDocument");
showTextDocumentSpy.mockResolvedValue(undefined as unknown as TextEditor);
openTextDocumentSpy.mockResolvedValue(undefined as unknown as TextDocument);
});
afterEach(function () {
afterEach(() => {
// set a higher timeout since recursive delete below may take a while, expecially on Windows.
this.timeout(120000);
jest.setTimeout(120000);
deleteHistoryState();
disposables.dispose(testDisposeHandler);
sandbox.restore();
});
it("should read query history", async () => {
await qhm.readQueryHistory();
// Should have added the query history. Contents are directly from the file
expect(rehydrateRemoteQueryStub).to.have.callCount(2);
expect(rehydrateRemoteQueryStub.getCall(0).args[1]).to.deep.eq(
expect(rehydrateRemoteQueryStub).toBeCalledTimes(2);
expect(rehydrateRemoteQueryStub).toHaveBeenNthCalledWith(
1,
rawQueryHistory[0].queryId,
rawQueryHistory[0].remoteQuery,
rawQueryHistory[0].status,
);
expect(rehydrateRemoteQueryStub.getCall(1).args[1]).to.deep.eq(
expect(rehydrateRemoteQueryStub).toHaveBeenNthCalledWith(
2,
rawQueryHistory[1].queryId,
rawQueryHistory[1].remoteQuery,
rawQueryHistory[1].status,
);
expect(qhm.treeDataProvider.allHistory[0]).to.deep.eq(rawQueryHistory[0]);
expect(qhm.treeDataProvider.allHistory[1]).to.deep.eq(rawQueryHistory[1]);
expect(qhm.treeDataProvider.allHistory.length).to.eq(2);
expect(qhm.treeDataProvider.allHistory[0]).toEqual(rawQueryHistory[0]);
expect(qhm.treeDataProvider.allHistory[1]).toEqual(rawQueryHistory[1]);
expect(qhm.treeDataProvider.allHistory.length).toBe(2);
});
it("should remove and then add query from history", async () => {
@@ -164,28 +164,32 @@ describe("Remote queries and query history manager", function () {
// Remove the first query
await qhm.handleRemoveHistoryItem(qhm.treeDataProvider.allHistory[0]);
expect(removeRemoteQueryStub).calledOnceWithExactly(
expect(removeRemoteQueryStub).toHaveBeenCalledWith(
rawQueryHistory[0].queryId,
);
expect(rehydrateRemoteQueryStub).to.have.callCount(2);
expect(rehydrateRemoteQueryStub.getCall(0).args[1]).to.deep.eq(
expect(rehydrateRemoteQueryStub).toBeCalledTimes(2);
expect(rehydrateRemoteQueryStub).toHaveBeenNthCalledWith(
1,
rawQueryHistory[0].queryId,
rawQueryHistory[0].remoteQuery,
rawQueryHistory[0].status,
);
expect(rehydrateRemoteQueryStub.getCall(1).args[1]).to.deep.eq(
expect(rehydrateRemoteQueryStub).toHaveBeenNthCalledWith(
2,
rawQueryHistory[1].queryId,
rawQueryHistory[1].remoteQuery,
rawQueryHistory[1].status,
);
expect(openRemoteQueryResultsStub).calledOnceWithExactly(
expect(openRemoteQueryResultsStub).toHaveBeenCalledWith(
rawQueryHistory[1].queryId,
);
expect(qhm.treeDataProvider.allHistory).to.deep.eq(
rawQueryHistory.slice(1),
);
expect(qhm.treeDataProvider.allHistory).toEqual(rawQueryHistory.slice(1));
// Add it back
qhm.addQuery(rawQueryHistory[0]);
expect(removeRemoteQueryStub).to.have.callCount(1);
expect(rehydrateRemoteQueryStub).to.have.callCount(2);
expect(qhm.treeDataProvider.allHistory).to.deep.eq([
expect(removeRemoteQueryStub).toBeCalledTimes(1);
expect(rehydrateRemoteQueryStub).toBeCalledTimes(2);
expect(qhm.treeDataProvider.allHistory).toEqual([
rawQueryHistory[1],
rawQueryHistory[0],
]);
@@ -201,19 +205,21 @@ describe("Remote queries and query history manager", function () {
qhm.treeDataProvider.allHistory[0],
]);
expect(removeRemoteQueryStub.callCount).to.eq(2);
expect(removeRemoteQueryStub.getCall(0).args[0]).to.eq(
expect(removeRemoteQueryStub).toHaveBeenCalledTimes(2);
expect(removeRemoteQueryStub).toHaveBeenNthCalledWith(
1,
rawQueryHistory[1].queryId,
);
expect(removeRemoteQueryStub.getCall(1).args[0]).to.eq(
expect(removeRemoteQueryStub).toHaveBeenNthCalledWith(
2,
rawQueryHistory[0].queryId,
);
expect(qhm.treeDataProvider.allHistory).to.deep.eq([]);
expect(qhm.treeDataProvider.allHistory).toEqual([]);
// also, both queries should be removed from on disk storage
expect(
fs.readJSONSync(path.join(STORAGE_DIR, "workspace-query-history.json")),
).to.deep.eq({
).toEqual({
version: 2,
queries: [],
});
@@ -223,7 +229,7 @@ describe("Remote queries and query history manager", function () {
await qhm.readQueryHistory();
await qhm.handleItemClicked(qhm.treeDataProvider.allHistory[0], []);
expect(openRemoteQueryResultsStub).calledOnceWithExactly(
expect(openRemoteQueryResultsStub).toHaveBeenCalledWith(
rawQueryHistory[0].queryId,
);
});
@@ -232,14 +238,14 @@ describe("Remote queries and query history manager", function () {
await qhm.readQueryHistory();
await qhm.handleShowQueryText(qhm.treeDataProvider.allHistory[0], []);
expect(showTextDocumentSpy).to.have.been.calledOnce;
expect(openTextDocumentSpy).to.have.been.calledOnce;
expect(showTextDocumentSpy).toBeCalledTimes(1);
expect(openTextDocumentSpy).toBeCalledTimes(1);
const uri: Uri = openTextDocumentSpy.getCall(0).args[0];
expect(uri.scheme).to.eq("codeql");
const uri: Uri = openTextDocumentSpy.mock.calls[0][0] as Uri;
expect(uri.scheme).toBe("codeql");
const params = new URLSearchParams(uri.query);
expect(params.get("isQuickEval")).to.eq("false");
expect(params.get("queryText")).to.eq(
expect(params.get("isQuickEval")).toBe("false");
expect(params.get("queryText")).toBe(
rawQueryHistory[0].remoteQuery.queryText,
);
});
@@ -253,19 +259,19 @@ describe("Remote queries and query history manager", function () {
beforeEach(() => {
mockOctokit = {
request: sandbox.stub(),
request: jest.fn(),
};
mockCredentials = {
getOctokit: () => mockOctokit,
};
mockLogger = {
log: sandbox.spy(),
log: jest.fn(),
};
mockCliServer = {
bqrsInfo: sandbox.spy(),
bqrsDecode: sandbox.spy(),
bqrsInfo: jest.fn(),
bqrsDecode: jest.fn(),
};
sandbox.stub(Credentials, "initialize").resolves(mockCredentials);
jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials);
arm = new AnalysesResultsManager(
{} as ExtensionContext,
@@ -277,54 +283,60 @@ describe("Remote queries and query history manager", function () {
it("should avoid re-downloading an analysis result", async () => {
// because the analysis result is already in on disk, it should not be downloaded
const publisher = sandbox.spy();
const publisher = jest.fn();
const analysisSummary = remoteQueryResult0.analysisSummaries[0];
await arm.downloadAnalysisResults(analysisSummary, publisher);
// Should not have made the request since the analysis result is already on disk
expect(mockOctokit.request).to.not.have.been.called;
expect(mockOctokit.request).not.toBeCalled();
// result should have been published twice
expect(publisher).toHaveBeenCalledTimes(2);
// first time, it is in progress
expect(publisher.getCall(0).args[0][0]).to.include({
nwo: "github/vscode-codeql",
status: "InProgress",
// interpretedResults: ... avoid checking the interpretedResults object since it is complex
});
expect(publisher).toHaveBeenNthCalledWith(1, [
expect.objectContaining({
nwo: "github/vscode-codeql",
status: "InProgress",
interpretedResults: expect.anything(), // avoid checking the interpretedResults object since it is complex
}),
]);
// second time, it has the path to the sarif file.
expect(publisher.getCall(1).args[0][0]).to.include({
nwo: "github/vscode-codeql",
status: "Completed",
// interpretedResults: ... avoid checking the interpretedResults object since it is complex
});
expect(publisher).to.have.been.calledTwice;
expect(publisher).toHaveBeenNthCalledWith(2, [
expect.objectContaining({
nwo: "github/vscode-codeql",
status: "Completed",
interpretedResults: expect.anything(), // avoid checking the interpretedResults object since it is complex
}),
]);
// result should be stored in the manager
expect(arm.getAnalysesResults(rawQueryHistory[0].queryId)[0]).to.include({
expect(
arm.getAnalysesResults(rawQueryHistory[0].queryId)[0],
).toMatchObject({
nwo: "github/vscode-codeql",
status: "Completed",
// interpretedResults: ... avoid checking the interpretedResults object since it is complex
});
publisher.resetHistory();
publisher.mockClear();
// now, let's try to download it again. This time, since it's already in memory,
// it should not even be re-published
await arm.downloadAnalysisResults(analysisSummary, publisher);
expect(publisher).to.not.have.been.called;
expect(publisher).not.toBeCalled();
});
it("should download two artifacts at once", async () => {
const publisher = sandbox.spy();
const publisher = jest.fn();
const analysisSummaries = [
remoteQueryResult0.analysisSummaries[0],
remoteQueryResult0.analysisSummaries[1],
];
await arm.loadAnalysesResults(analysisSummaries, undefined, publisher);
const trimmed = publisher
.getCalls()
.map((call) => call.args[0])
const trimmed = publisher.mock.calls
.map((call) => call[0])
.map((args) => {
args.forEach(
(analysisResult: any) => delete analysisResult.interpretedResults,
@@ -333,7 +345,7 @@ describe("Remote queries and query history manager", function () {
});
// As before, but now both summaries should have been published
expect(trimmed[0]).to.deep.eq([
expect(trimmed[0]).toEqual([
{
nwo: "github/vscode-codeql",
status: "InProgress",
@@ -343,7 +355,7 @@ describe("Remote queries and query history manager", function () {
},
]);
expect(trimmed[1]).to.deep.eq([
expect(trimmed[1]).toEqual([
{
nwo: "github/vscode-codeql",
status: "InProgress",
@@ -364,7 +376,7 @@ describe("Remote queries and query history manager", function () {
// github/vscode-codeql is completed first or other/hucairz is.
// There is not much point in trying to test it if the other calls are correct.
expect(trimmed[3]).to.deep.eq([
expect(trimmed[3]).toEqual([
{
nwo: "github/vscode-codeql",
status: "Completed",
@@ -381,11 +393,11 @@ describe("Remote queries and query history manager", function () {
},
]);
expect(publisher).to.have.callCount(4);
expect(publisher).toBeCalledTimes(4);
});
it("should avoid publishing when the request is cancelled", async () => {
const publisher = sandbox.spy();
const publisher = jest.fn();
const analysisSummaries = [...remoteQueryResult0.analysisSummaries];
try {
@@ -396,16 +408,16 @@ describe("Remote queries and query history manager", function () {
} as CancellationToken,
publisher,
);
expect.fail("Should have thrown");
fail("Should have thrown");
} catch (e) {
expect(getErrorMessage(e)).to.contain("cancelled");
expect(getErrorMessage(e)).toContain("cancelled");
}
expect(publisher).not.to.have.been.called;
expect(publisher).not.toBeCalled();
});
it("should get the analysis results", async () => {
const publisher = sandbox.spy();
const publisher = jest.fn();
const analysisSummaries0 = [
remoteQueryResult0.analysisSummaries[0],
remoteQueryResult0.analysisSummaries[1],
@@ -419,18 +431,18 @@ describe("Remote queries and query history manager", function () {
const result0Again = arm.getAnalysesResults(rawQueryHistory[0].queryId);
// Shoule be equal, but not equivalent
expect(result0).to.deep.eq(result0Again);
expect(result0).not.to.eq(result0Again);
expect(result0).toEqual(result0Again);
expect(result0).not.toBe(result0Again);
const result1 = arm.getAnalysesResults(rawQueryHistory[1].queryId);
const result1Again = arm.getAnalysesResults(rawQueryHistory[1].queryId);
expect(result1).to.deep.eq(result1Again);
expect(result1).not.to.eq(result1Again);
expect(result1).toEqual(result1Again);
expect(result1).not.toBe(result1Again);
});
// This test is failing on windows in CI.
it.skip("should read sarif", async () => {
const publisher = sandbox.spy();
const publisher = jest.fn();
const analysisSummaries0 = [remoteQueryResult0.analysisSummaries[0]];
await arm.loadAnalysesResults(analysisSummaries0, undefined, publisher);
@@ -447,7 +459,11 @@ describe("Remote queries and query history manager", function () {
.flatMap((run: any) => run.results)
.map((result: any) => ({ message: result.message.text }));
expect(publisher.getCall(1).args[0][0].results).to.deep.eq(queryResults);
expect(publisher).toHaveBeenNthCalledWith(2, [
{
results: queryResults,
},
]);
});
it("should check if an artifact is downloaded and not in memory", async () => {
@@ -462,21 +478,21 @@ describe("Remote queries and query history manager", function () {
await (arm as any).isAnalysisDownloaded(
remoteQueryResult0.analysisSummaries[0],
),
).to.be.true;
).toBe(true);
// in memory
expect(
await (arm as any).isAnalysisDownloaded(
remoteQueryResult0.analysisSummaries[1],
),
).to.be.true;
).toBe(true);
// not downloaded
expect(
await (arm as any).isAnalysisDownloaded(
remoteQueryResult0.analysisSummaries[2],
),
).to.be.false;
).toBe(false);
});
it("should load downloaded artifacts", async () => {
@@ -486,9 +502,9 @@ describe("Remote queries and query history manager", function () {
.getAnalysesResults(queryId)
.map((ar) => ar.nwo)
.sort();
expect(analysesResultsNwos[0]).to.eq("github/vscode-codeql");
expect(analysesResultsNwos[1]).to.eq("other/hucairz");
expect(analysesResultsNwos.length).to.eq(2);
expect(analysesResultsNwos[0]).toBe("github/vscode-codeql");
expect(analysesResultsNwos[1]).toBe("other/hucairz");
expect(analysesResultsNwos.length).toBe(2);
});
});

View File

@@ -1,96 +1,82 @@
import * as sinon from "sinon";
import { expect } from "chai";
import { window } from "vscode";
import * as pq from "proxyquire";
import { QuickPickItem, window } from "vscode";
import * as fs from "fs-extra";
import { UserCancellationException } from "../../../commandRunner";
const proxyquire = pq.noPreserveCache();
import * as config from "../../../config";
import { getRepositorySelection } from "../../../remote-queries/repository-selection";
describe("repository selection", async () => {
let sandbox: sinon.SinonSandbox;
describe("repository selection", () => {
const quickPickSpy = jest.spyOn(window, "showQuickPick");
const showInputBoxSpy = jest.spyOn(window, "showInputBox");
let quickPickSpy: sinon.SinonStub;
let showInputBoxSpy: sinon.SinonStub;
const getRemoteRepositoryListsSpy = jest.spyOn(
config,
"getRemoteRepositoryLists",
);
const getRemoteRepositoryListsPathSpy = jest.spyOn(
config,
"getRemoteRepositoryListsPath",
);
let getRemoteRepositoryListsSpy: sinon.SinonStub;
let getRemoteRepositoryListsPathSpy: sinon.SinonStub;
let pathExistsStub: sinon.SinonStub;
let fsStatStub: sinon.SinonStub;
let fsReadFileStub: sinon.SinonStub;
let mod: any;
const pathExistsStub = jest.spyOn(fs, "pathExists");
const fsStatStub = jest.spyOn(fs, "stat");
const fsReadFileStub = jest.spyOn(fs, "readFile");
beforeEach(() => {
sandbox = sinon.createSandbox();
quickPickSpy.mockReset().mockResolvedValue(undefined);
showInputBoxSpy.mockReset().mockResolvedValue(undefined);
quickPickSpy = sandbox.stub(window, "showQuickPick");
showInputBoxSpy = sandbox.stub(window, "showInputBox");
getRemoteRepositoryListsSpy.mockReset().mockReturnValue(undefined);
getRemoteRepositoryListsPathSpy.mockReset().mockReturnValue(undefined);
getRemoteRepositoryListsSpy = sandbox.stub();
getRemoteRepositoryListsPathSpy = sandbox.stub();
pathExistsStub = sandbox.stub(fs, "pathExists");
fsStatStub = sandbox.stub(fs, "stat");
fsReadFileStub = sandbox.stub(fs, "readFile");
mod = proxyquire("../../../remote-queries/repository-selection", {
"../config": {
getRemoteRepositoryLists: getRemoteRepositoryListsSpy,
getRemoteRepositoryListsPath: getRemoteRepositoryListsPathSpy,
},
"fs-extra": {
pathExists: pathExistsStub,
stat: fsStatStub,
readFile: fsReadFileStub,
},
});
pathExistsStub.mockReset().mockImplementation(() => false);
fsStatStub.mockReset().mockRejectedValue(new Error("not found"));
fsReadFileStub.mockReset().mockRejectedValue(new Error("not found"));
});
afterEach(() => {
sandbox.restore();
});
describe("repo lists from settings", async () => {
describe("repo lists from settings", () => {
it("should allow selection from repo lists from your pre-defined config", async () => {
// Fake return values
quickPickSpy.resolves({ repositories: ["foo/bar", "foo/baz"] });
getRemoteRepositoryListsSpy.returns({
quickPickSpy.mockResolvedValue({
repositories: ["foo/bar", "foo/baz"],
} as unknown as QuickPickItem);
getRemoteRepositoryListsSpy.mockReturnValue({
list1: ["foo/bar", "foo/baz"],
list2: [],
});
// Make the function call
const repoSelection = await mod.getRepositorySelection();
const repoSelection = await getRepositorySelection();
// Check that the return value is correct
expect(repoSelection.repositoryLists).to.be.undefined;
expect(repoSelection.owners).to.be.undefined;
expect(repoSelection.repositories).to.deep.eq(["foo/bar", "foo/baz"]);
expect(repoSelection.repositoryLists).toBeUndefined();
expect(repoSelection.owners).toBeUndefined();
expect(repoSelection.repositories).toEqual(["foo/bar", "foo/baz"]);
});
});
describe("system level repo lists", async () => {
describe("system level repo lists", () => {
it("should allow selection from repo lists defined at the system level", async () => {
// Fake return values
quickPickSpy.resolves({ repositoryList: "top_100" });
getRemoteRepositoryListsSpy.returns({
quickPickSpy.mockResolvedValue({
repositoryList: "top_100",
} as unknown as QuickPickItem);
getRemoteRepositoryListsSpy.mockReturnValue({
list1: ["foo/bar", "foo/baz"],
list2: [],
});
// Make the function call
const repoSelection = await mod.getRepositorySelection();
const repoSelection = await getRepositorySelection();
// Check that the return value is correct
expect(repoSelection.repositories).to.be.undefined;
expect(repoSelection.owners).to.be.undefined;
expect(repoSelection.repositoryLists).to.deep.eq(["top_100"]);
expect(repoSelection.repositories).toBeUndefined();
expect(repoSelection.owners).toBeUndefined();
expect(repoSelection.repositoryLists).toEqual(["top_100"]);
});
});
describe("custom owner", async () => {
describe("custom owner", () => {
// Test the owner regex in various "good" cases
const goodOwners = [
"owner",
@@ -102,17 +88,19 @@ describe("repository selection", async () => {
goodOwners.forEach((owner) => {
it(`should run on a valid owner that you enter in the text box: ${owner}`, async () => {
// Fake return values
quickPickSpy.resolves({ useAllReposOfOwner: true });
getRemoteRepositoryListsSpy.returns({}); // no pre-defined repo lists
showInputBoxSpy.resolves(owner);
quickPickSpy.mockResolvedValue({
useAllReposOfOwner: true,
} as unknown as QuickPickItem);
getRemoteRepositoryListsSpy.mockReturnValue({}); // no pre-defined repo lists
showInputBoxSpy.mockResolvedValue(owner);
// Make the function call
const repoSelection = await mod.getRepositorySelection();
const repoSelection = await getRepositorySelection();
// Check that the return value is correct
expect(repoSelection.repositories).to.be.undefined;
expect(repoSelection.repositoryLists).to.be.undefined;
expect(repoSelection.owners).to.deep.eq([owner]);
expect(repoSelection.repositories).toBeUndefined();
expect(repoSelection.repositoryLists).toBeUndefined();
expect(repoSelection.owners).toEqual([owner]);
});
});
@@ -121,33 +109,38 @@ describe("repository selection", async () => {
badOwners.forEach((owner) => {
it(`should show an error message if you enter an invalid owner in the text box: ${owner}`, async () => {
// Fake return values
quickPickSpy.resolves({ useAllReposOfOwner: true });
getRemoteRepositoryListsSpy.returns({}); // no pre-defined repo lists
showInputBoxSpy.resolves(owner);
quickPickSpy.mockResolvedValue({
useAllReposOfOwner: true,
} as unknown as QuickPickItem);
getRemoteRepositoryListsSpy.mockReturnValue({}); // no pre-defined repo lists
showInputBoxSpy.mockResolvedValue(owner);
// Function call should throw a UserCancellationException
await expect(mod.getRepositorySelection()).to.be.rejectedWith(
Error,
await expect(getRepositorySelection()).rejects.toThrow(
`Invalid user or organization: ${owner}`,
);
});
});
it("should be ok for the user to change their mind", async () => {
quickPickSpy.resolves({ useAllReposOfOwner: true });
getRemoteRepositoryListsSpy.returns({});
quickPickSpy.mockResolvedValue({
useAllReposOfOwner: true,
} as unknown as QuickPickItem);
getRemoteRepositoryListsSpy.mockReturnValue({});
// The user pressed escape to cancel the operation
showInputBoxSpy.resolves(undefined);
showInputBoxSpy.mockResolvedValue(undefined);
await expect(mod.getRepositorySelection()).to.be.rejectedWith(
UserCancellationException,
await expect(getRepositorySelection()).rejects.toThrow(
"No repositories selected",
);
await expect(getRepositorySelection()).rejects.toThrow(
UserCancellationException,
);
});
});
describe("custom repo", async () => {
describe("custom repo", () => {
// Test the repo regex in various "good" cases
const goodRepos = [
"owner/repo",
@@ -157,17 +150,19 @@ describe("repository selection", async () => {
goodRepos.forEach((repo) => {
it(`should run on a valid repo that you enter in the text box: ${repo}`, async () => {
// Fake return values
quickPickSpy.resolves({ useCustomRepo: true });
getRemoteRepositoryListsSpy.returns({}); // no pre-defined repo lists
showInputBoxSpy.resolves(repo);
quickPickSpy.mockResolvedValue({
useCustomRepo: true,
} as unknown as QuickPickItem);
getRemoteRepositoryListsSpy.mockReturnValue({}); // no pre-defined repo lists
showInputBoxSpy.mockResolvedValue(repo);
// Make the function call
const repoSelection = await mod.getRepositorySelection();
const repoSelection = await getRepositorySelection();
// Check that the return value is correct
expect(repoSelection.repositoryLists).to.be.undefined;
expect(repoSelection.owners).to.be.undefined;
expect(repoSelection.repositories).to.deep.equal([repo]);
expect(repoSelection.repositoryLists).toBeUndefined();
expect(repoSelection.owners).toBeUndefined();
expect(repoSelection.repositories).toEqual([repo]);
});
});
@@ -181,120 +176,129 @@ describe("repository selection", async () => {
badRepos.forEach((repo) => {
it(`should show an error message if you enter an invalid repo in the text box: ${repo}`, async () => {
// Fake return values
quickPickSpy.resolves({ useCustomRepo: true });
getRemoteRepositoryListsSpy.returns({}); // no pre-defined repo lists
showInputBoxSpy.resolves(repo);
quickPickSpy.mockResolvedValue({
useCustomRepo: true,
} as unknown as QuickPickItem);
getRemoteRepositoryListsSpy.mockReturnValue({}); // no pre-defined repo lists
showInputBoxSpy.mockResolvedValue(repo);
// Function call should throw a UserCancellationException
await expect(mod.getRepositorySelection()).to.be.rejectedWith(
UserCancellationException,
await expect(getRepositorySelection()).rejects.toThrow(
"Invalid repository format",
);
await expect(getRepositorySelection()).rejects.toThrow(
UserCancellationException,
);
});
});
it("should be ok for the user to change their mind", async () => {
quickPickSpy.resolves({ useCustomRepo: true });
getRemoteRepositoryListsSpy.returns({});
quickPickSpy.mockResolvedValue({
useCustomRepo: true,
} as unknown as QuickPickItem);
getRemoteRepositoryListsSpy.mockReturnValue({});
// The user pressed escape to cancel the operation
showInputBoxSpy.resolves(undefined);
showInputBoxSpy.mockResolvedValue(undefined);
await expect(mod.getRepositorySelection()).to.be.rejectedWith(
UserCancellationException,
await expect(getRepositorySelection()).rejects.toThrow(
"No repositories selected",
);
await expect(getRepositorySelection()).rejects.toThrow(
UserCancellationException,
);
});
});
describe("external repository lists file", async () => {
describe("external repository lists file", () => {
it("should fail if path does not exist", async () => {
const fakeFilePath = "/path/that/does/not/exist.json";
getRemoteRepositoryListsPathSpy.returns(fakeFilePath);
pathExistsStub.resolves(false);
getRemoteRepositoryListsPathSpy.mockReturnValue(fakeFilePath);
pathExistsStub.mockImplementation(() => false);
await expect(mod.getRepositorySelection()).to.be.rejectedWith(
Error,
await expect(getRepositorySelection()).rejects.toThrow(
`External repository lists file does not exist at ${fakeFilePath}`,
);
});
it("should fail if path points to directory", async () => {
const fakeFilePath = "/path/to/dir";
getRemoteRepositoryListsPathSpy.returns(fakeFilePath);
pathExistsStub.resolves(true);
fsStatStub.resolves({ isDirectory: () => true } as any);
getRemoteRepositoryListsPathSpy.mockReturnValue(fakeFilePath);
pathExistsStub.mockImplementation(() => true);
fsStatStub.mockResolvedValue({ isDirectory: () => true } as any);
await expect(mod.getRepositorySelection()).to.be.rejectedWith(
Error,
await expect(getRepositorySelection()).rejects.toThrow(
"External repository lists path should not point to a directory",
);
});
it("should fail if file does not have valid JSON", async () => {
const fakeFilePath = "/path/to/file.json";
getRemoteRepositoryListsPathSpy.returns(fakeFilePath);
pathExistsStub.resolves(true);
fsStatStub.resolves({ isDirectory: () => false } as any);
fsReadFileStub.resolves("not-json" as any as Buffer);
getRemoteRepositoryListsPathSpy.mockReturnValue(fakeFilePath);
pathExistsStub.mockImplementation(() => true);
fsStatStub.mockResolvedValue({ isDirectory: () => false } as any);
fsReadFileStub.mockResolvedValue("not-json" as any as Buffer);
await expect(mod.getRepositorySelection()).to.be.rejectedWith(
Error,
await expect(getRepositorySelection()).rejects.toThrow(
"Invalid repository lists file. It should contain valid JSON.",
);
});
it("should fail if file contains array", async () => {
const fakeFilePath = "/path/to/file.json";
getRemoteRepositoryListsPathSpy.returns(fakeFilePath);
pathExistsStub.resolves(true);
fsStatStub.resolves({ isDirectory: () => false } as any);
fsReadFileStub.resolves("[]" as any as Buffer);
getRemoteRepositoryListsPathSpy.mockReturnValue(fakeFilePath);
pathExistsStub.mockImplementation(() => true);
fsStatStub.mockResolvedValue({ isDirectory: () => false } as any);
fsReadFileStub.mockResolvedValue("[]" as any as Buffer);
await expect(mod.getRepositorySelection()).to.be.rejectedWith(
Error,
await expect(getRepositorySelection()).rejects.toThrow(
"Invalid repository lists file. It should be an object mapping names to a list of repositories.",
);
});
it("should fail if file does not contain repo lists in the right format", async () => {
const fakeFilePath = "/path/to/file.json";
getRemoteRepositoryListsPathSpy.returns(fakeFilePath);
pathExistsStub.resolves(true);
fsStatStub.resolves({ isDirectory: () => false } as any);
getRemoteRepositoryListsPathSpy.mockReturnValue(fakeFilePath);
pathExistsStub.mockImplementation(() => true);
fsStatStub.mockResolvedValue({ isDirectory: () => false } as any);
const repoLists = {
list1: "owner1/repo1",
};
fsReadFileStub.resolves(JSON.stringify(repoLists) as any as Buffer);
fsReadFileStub.mockResolvedValue(
JSON.stringify(repoLists) as any as Buffer,
);
await expect(mod.getRepositorySelection()).to.be.rejectedWith(
Error,
await expect(getRepositorySelection()).rejects.toThrow(
"Invalid repository lists file. It should contain an array of repositories for each list.",
);
});
it("should get repo lists from file", async () => {
const fakeFilePath = "/path/to/file.json";
getRemoteRepositoryListsPathSpy.returns(fakeFilePath);
pathExistsStub.resolves(true);
fsStatStub.resolves({ isDirectory: () => false } as any);
getRemoteRepositoryListsPathSpy.mockReturnValue(fakeFilePath);
pathExistsStub.mockImplementation(() => true);
fsStatStub.mockResolvedValue({ isDirectory: () => false } as any);
const repoLists = {
list1: ["owner1/repo1", "owner2/repo2"],
list2: ["owner3/repo3"],
};
fsReadFileStub.resolves(JSON.stringify(repoLists) as any as Buffer);
getRemoteRepositoryListsSpy.returns({
fsReadFileStub.mockResolvedValue(
JSON.stringify(repoLists) as any as Buffer,
);
getRemoteRepositoryListsSpy.mockReturnValue({
list3: ["onwer4/repo4"],
list4: [],
});
quickPickSpy.resolves({ repositories: ["owner3/repo3"] });
quickPickSpy.mockResolvedValue({
repositories: ["owner3/repo3"],
} as unknown as QuickPickItem);
const repoSelection = await mod.getRepositorySelection();
const repoSelection = await getRepositorySelection();
expect(repoSelection.repositoryLists).to.be.undefined;
expect(repoSelection.owners).to.be.undefined;
expect(repoSelection.repositories).to.deep.eq(["owner3/repo3"]);
expect(repoSelection.repositoryLists).toBeUndefined();
expect(repoSelection.owners).toBeUndefined();
expect(repoSelection.repositories).toEqual(["owner3/repo3"]);
});
});
});

View File

@@ -1,9 +1,14 @@
import * as fs from "fs-extra";
import * as path from "path";
import * as sinon from "sinon";
import { expect } from "chai";
import { ExtensionContext, Uri, window, workspace } from "vscode";
import {
ExtensionContext,
TextDocument,
TextEditor,
Uri,
window,
workspace,
} from "vscode";
import { QueryHistoryConfig } from "../../../config";
import { DatabaseManager } from "../../../databases";
import { tmpDir } from "../../../helpers";
@@ -22,7 +27,7 @@ import { VariantAnalysisManager } from "../../../remote-queries/variant-analysis
* Tests for variant analyses and how they interact with the query history manager.
*/
describe("Variant Analyses and QueryHistoryManager", function () {
describe("Variant Analyses and QueryHistoryManager", () => {
const EXTENSION_PATH = path.join(__dirname, "../../../../");
const STORAGE_DIR = Uri.file(
path.join(tmpDir.name, "variant-analysis"),
@@ -31,55 +36,50 @@ describe("Variant Analyses and QueryHistoryManager", function () {
/** noop */
};
let sandbox: sinon.SinonSandbox;
let qhm: QueryHistoryManager;
let localQueriesResultsViewStub: ResultsView;
let remoteQueriesManagerStub: RemoteQueriesManager;
let variantAnalysisManagerStub: VariantAnalysisManager;
let rawQueryHistory: any;
let disposables: DisposableBucket;
let showTextDocumentSpy: sinon.SinonSpy;
let openTextDocumentSpy: sinon.SinonSpy;
let rehydrateVariantAnalysisStub: sinon.SinonStub;
let removeVariantAnalysisStub: sinon.SinonStub;
let showViewStub: sinon.SinonStub;
const rehydrateVariantAnalysisStub = jest.fn();
const removeVariantAnalysisStub = jest.fn();
const showViewStub = jest.fn();
beforeEach(async function () {
const localQueriesResultsViewStub = {
showResults: jest.fn(),
} as any as ResultsView;
const remoteQueriesManagerStub = {
onRemoteQueryAdded: jest.fn(),
onRemoteQueryRemoved: jest.fn(),
onRemoteQueryStatusUpdate: jest.fn(),
rehydrateRemoteQuery: jest.fn(),
removeRemoteQuery: jest.fn(),
openRemoteQueryResults: jest.fn(),
} as any as RemoteQueriesManager;
const variantAnalysisManagerStub = {
onVariantAnalysisAdded: jest.fn(),
onVariantAnalysisRemoved: jest.fn(),
removeVariantAnalysis: removeVariantAnalysisStub,
rehydrateVariantAnalysis: rehydrateVariantAnalysisStub,
onVariantAnalysisStatusUpdated: jest.fn(),
showView: showViewStub,
} as any as VariantAnalysisManager;
const showTextDocumentSpy = jest.spyOn(window, "showTextDocument");
const openTextDocumentSpy = jest.spyOn(workspace, "openTextDocument");
beforeEach(async () => {
// set a higher timeout since recursive delete below may take a while, expecially on Windows.
this.timeout(120000);
jest.setTimeout(120000);
// Since these tests change the state of the query history manager, we need to copy the original
// to a temporary folder where we can manipulate it for tests
await copyHistoryState();
sandbox = sinon.createSandbox();
disposables = new DisposableBucket();
localQueriesResultsViewStub = {
showResults: sandbox.stub(),
} as any as ResultsView;
rehydrateVariantAnalysisStub = sandbox.stub();
removeVariantAnalysisStub = sandbox.stub();
showViewStub = sandbox.stub();
remoteQueriesManagerStub = {
onRemoteQueryAdded: sandbox.stub(),
onRemoteQueryRemoved: sandbox.stub(),
onRemoteQueryStatusUpdate: sandbox.stub(),
rehydrateRemoteQuery: sandbox.stub(),
openRemoteQueryResults: sandbox.stub(),
} as any as RemoteQueriesManager;
variantAnalysisManagerStub = {
onVariantAnalysisAdded: sandbox.stub(),
onVariantAnalysisRemoved: sandbox.stub(),
removeVariantAnalysis: removeVariantAnalysisStub,
rehydrateVariantAnalysis: rehydrateVariantAnalysisStub,
onVariantAnalysisStatusUpdated: sandbox.stub(),
showView: showViewStub,
} as any as VariantAnalysisManager;
rehydrateVariantAnalysisStub.mockReset();
removeVariantAnalysisStub.mockReset();
showViewStub.mockReset();
rawQueryHistory = fs.readJSONSync(
path.join(STORAGE_DIR, "workspace-query-history.json"),
@@ -105,30 +105,31 @@ describe("Variant Analyses and QueryHistoryManager", function () {
);
disposables.push(qhm);
showTextDocumentSpy = sandbox.spy(window, "showTextDocument");
openTextDocumentSpy = sandbox.spy(workspace, "openTextDocument");
showTextDocumentSpy.mockResolvedValue(undefined as unknown as TextEditor);
openTextDocumentSpy.mockResolvedValue(undefined as unknown as TextDocument);
});
afterEach(function () {
afterEach(() => {
deleteHistoryState();
disposables.dispose(testDisposeHandler);
sandbox.restore();
});
it("should read query history that has variant analysis history items", async () => {
await qhm.readQueryHistory();
expect(rehydrateVariantAnalysisStub).to.have.callCount(2);
expect(rehydrateVariantAnalysisStub.getCall(0).args[0]).to.deep.eq(
expect(rehydrateVariantAnalysisStub).toBeCalledTimes(2);
expect(rehydrateVariantAnalysisStub).toHaveBeenNthCalledWith(
1,
rawQueryHistory[0].variantAnalysis,
);
expect(rehydrateVariantAnalysisStub.getCall(1).args[0]).to.deep.eq(
expect(rehydrateVariantAnalysisStub).toHaveBeenNthCalledWith(
2,
rawQueryHistory[1].variantAnalysis,
);
expect(qhm.treeDataProvider.allHistory[0]).to.deep.eq(rawQueryHistory[0]);
expect(qhm.treeDataProvider.allHistory[1]).to.deep.eq(rawQueryHistory[1]);
expect(qhm.treeDataProvider.allHistory.length).to.eq(2);
expect(qhm.treeDataProvider.allHistory[0]).toEqual(rawQueryHistory[0]);
expect(qhm.treeDataProvider.allHistory[1]).toEqual(rawQueryHistory[1]);
expect(qhm.treeDataProvider.allHistory.length).toBe(2);
});
it("should remove the variant analysis history item", async () => {
@@ -139,9 +140,9 @@ describe("Variant Analyses and QueryHistoryManager", function () {
// Add it back to the history
qhm.addQuery(rawQueryHistory[0]);
expect(removeVariantAnalysisStub).to.have.callCount(1);
expect(rehydrateVariantAnalysisStub).to.have.callCount(2);
expect(qhm.treeDataProvider.allHistory).to.deep.eq([
expect(removeVariantAnalysisStub).toBeCalledTimes(1);
expect(rehydrateVariantAnalysisStub).toBeCalledTimes(2);
expect(qhm.treeDataProvider.allHistory).toEqual([
rawQueryHistory[1],
rawQueryHistory[0],
]);
@@ -157,19 +158,21 @@ describe("Variant Analyses and QueryHistoryManager", function () {
qhm.treeDataProvider.allHistory[0],
]);
expect(removeVariantAnalysisStub.callCount).to.eq(2);
expect(removeVariantAnalysisStub.getCall(0).args[0]).to.deep.eq(
expect(removeVariantAnalysisStub).toHaveBeenCalledTimes(2);
expect(removeVariantAnalysisStub).toHaveBeenNthCalledWith(
1,
rawQueryHistory[1].variantAnalysis,
);
expect(removeVariantAnalysisStub.getCall(1).args[0]).to.deep.eq(
expect(removeVariantAnalysisStub).toHaveBeenNthCalledWith(
2,
rawQueryHistory[0].variantAnalysis,
);
expect(qhm.treeDataProvider.allHistory).to.deep.eq([]);
expect(qhm.treeDataProvider.allHistory).toEqual([]);
// also, both queries should be removed from disk storage
expect(
fs.readJSONSync(path.join(STORAGE_DIR, "workspace-query-history.json")),
).to.deep.eq({
).toEqual({
version: 2,
queries: [],
});
@@ -179,23 +182,21 @@ describe("Variant Analyses and QueryHistoryManager", function () {
await qhm.readQueryHistory();
await qhm.handleItemClicked(qhm.treeDataProvider.allHistory[0], []);
expect(showViewStub).calledOnceWithExactly(
rawQueryHistory[0].variantAnalysis.id,
);
expect(showViewStub).toBeCalledWith(rawQueryHistory[0].variantAnalysis.id);
});
it("should get the query text", async () => {
await qhm.readQueryHistory();
await qhm.handleShowQueryText(qhm.treeDataProvider.allHistory[0], []);
expect(showTextDocumentSpy).to.have.been.calledOnce;
expect(openTextDocumentSpy).to.have.been.calledOnce;
expect(showTextDocumentSpy).toBeCalledTimes(1);
expect(openTextDocumentSpy).toBeCalledTimes(1);
const uri: Uri = openTextDocumentSpy.getCall(0).args[0];
expect(uri.scheme).to.eq("codeql");
const uri: Uri = openTextDocumentSpy.mock.calls[0][0] as Uri;
expect(uri.scheme).toBe("codeql");
const params = new URLSearchParams(uri.query);
expect(params.get("isQuickEval")).to.eq("false");
expect(params.get("queryText")).to.eq(
expect(params.get("isQuickEval")).toBe("false");
expect(params.get("queryText")).toBe(
rawQueryHistory[0].variantAnalysis.query.text,
);
});

View File

@@ -1,7 +1,5 @@
import { expect } from "chai";
import * as path from "path";
import * as fs from "fs-extra";
import * as sinon from "sinon";
import { Uri } from "vscode";
import {
@@ -20,55 +18,56 @@ import { LegacyQueryRunner } from "../../legacy-query-server/legacyRunner";
import { DatabaseItem } from "../../databases";
describe("run-queries", () => {
let sandbox: sinon.SinonSandbox;
const isCanarySpy = jest.spyOn(config, "isCanary");
beforeEach(() => {
sandbox = sinon.createSandbox();
sandbox.stub(config, "isCanary").returns(false);
});
afterEach(() => {
sandbox.restore();
isCanarySpy.mockReset().mockReturnValue(false);
});
it("should create a QueryEvaluationInfo", () => {
const saveDir = "query-save-dir";
const info = createMockQueryInfo(true, saveDir);
expect(info.compiledQueryPath).to.eq(
expect(info.compiledQueryPath).toBe(
path.join(saveDir, "compiledQuery.qlo"),
);
expect(info.queryEvalInfo.dilPath).to.eq(path.join(saveDir, "results.dil"));
expect(info.queryEvalInfo.resultsPaths.resultsPath).to.eq(
expect(info.queryEvalInfo.dilPath).toBe(path.join(saveDir, "results.dil"));
expect(info.queryEvalInfo.resultsPaths.resultsPath).toBe(
path.join(saveDir, "results.bqrs"),
);
expect(info.queryEvalInfo.resultsPaths.interpretedResultsPath).to.eq(
expect(info.queryEvalInfo.resultsPaths.interpretedResultsPath).toBe(
path.join(saveDir, "interpretedResults.sarif"),
);
expect(info.dbItemPath).to.eq(Uri.file("/abc").fsPath);
expect(info.dbItemPath).toBe(Uri.file("/abc").fsPath);
});
it("should check if interpreted results can be created", async () => {
const info = createMockQueryInfo(true);
expect(info.queryEvalInfo.canHaveInterpretedResults(), "1").to.eq(true);
// "1"
expect(info.queryEvalInfo.canHaveInterpretedResults()).toBe(true);
(info.queryEvalInfo as any).databaseHasMetadataFile = false;
expect(info.queryEvalInfo.canHaveInterpretedResults(), "2").to.eq(false);
// "2"
expect(info.queryEvalInfo.canHaveInterpretedResults()).toBe(false);
(info.queryEvalInfo as any).databaseHasMetadataFile = true;
info.metadata!.kind = undefined;
expect(info.queryEvalInfo.canHaveInterpretedResults(), "3").to.eq(false);
// "3"
expect(info.queryEvalInfo.canHaveInterpretedResults()).toBe(false);
info.metadata!.kind = "table";
expect(info.queryEvalInfo.canHaveInterpretedResults(), "4").to.eq(false);
// "4"
expect(info.queryEvalInfo.canHaveInterpretedResults()).toBe(false);
// Graphs are not interpreted unless canary is set
info.metadata!.kind = "graph";
expect(info.queryEvalInfo.canHaveInterpretedResults(), "5").to.eq(false);
// "5"
expect(info.queryEvalInfo.canHaveInterpretedResults()).toBe(false);
(config.isCanary as sinon.SinonStub).returns(true);
expect(info.queryEvalInfo.canHaveInterpretedResults(), "6").to.eq(true);
isCanarySpy.mockReturnValueOnce(true);
// "6"
expect(info.queryEvalInfo.canHaveInterpretedResults()).toBe(true);
});
[SELECT_QUERY_NAME, "other"].forEach((resultSetName) => {
@@ -106,16 +105,17 @@ describe("run-queries", () => {
);
const result = await promise;
expect(result).to.eq(true);
expect(result).toBe(true);
const csv = fs.readFileSync(csvLocation, "utf8");
expect(csv).to.eq('a,"b"\nc,"d"\n"a",b,c\n');
expect(csv).toBe('a,"b"\nc,"d"\n"a",b,c\n');
// now verify that we are using the expected result set
expect((cliServer.bqrsDecode as sinon.SinonStub).callCount).to.eq(2);
expect(
(cliServer.bqrsDecode as sinon.SinonStub).getCall(0).args[1],
).to.eq(resultSetName);
expect(cliServer.bqrsDecode).toHaveBeenCalledWith(
expect.anything(),
resultSetName,
expect.anything(),
);
});
});
@@ -145,17 +145,18 @@ describe("run-queries", () => {
const promise = info.queryEvalInfo.exportCsvResults(cliServer, csvLocation);
const result = await promise;
expect(result).to.eq(true);
expect(result).toBe(true);
const csv = fs.readFileSync(csvLocation, "utf8");
expect(csv).to.eq(
expect(csv).toBe(
'"a","""b"""\nc,xxx,"d,yyy"\naaa " bbb,"ccc "" ddd"\ntrue,"false"\n123,"456"\n123.98,"456.99"\n',
);
// now verify that we are using the expected result set
expect((cliServer.bqrsDecode as sinon.SinonStub).callCount).to.eq(1);
expect((cliServer.bqrsDecode as sinon.SinonStub).getCall(0).args[1]).to.eq(
expect(cliServer.bqrsDecode).toHaveBeenCalledWith(
expect.anything(),
SELECT_QUERY_NAME,
expect.anything(),
);
});
@@ -169,7 +170,7 @@ describe("run-queries", () => {
cliServer,
csvLocation,
);
expect(result).to.eq(false);
expect(result).toBe(false);
});
describe("compile", () => {
@@ -191,11 +192,10 @@ describe("run-queries", () => {
mockCancel as any,
);
expect(results).to.deep.eq([
{ message: "err", severity: Severity.ERROR },
]);
expect(results).toEqual([{ message: "err", severity: Severity.ERROR }]);
expect(qs.sendRequest).to.have.been.calledOnceWith(
expect(qs.sendRequest).toHaveBeenCalledTimes(1);
expect(qs.sendRequest).toHaveBeenCalledWith(
compileQuery,
{
compilationOptions: {
@@ -246,7 +246,8 @@ describe("run-queries", () => {
dbItem,
);
expect(qs.sendRequest).to.have.been.calledOnceWith(
expect(qs.sendRequest).toHaveBeenCalledTimes(1);
expect(qs.sendRequest).toHaveBeenCalledWith(
registerDatabases,
{
databases: [
@@ -284,7 +285,8 @@ describe("run-queries", () => {
dbItem,
);
expect(qs.sendRequest).to.have.been.calledOnceWith(
expect(qs.sendRequest).toHaveBeenCalledTimes(1);
expect(qs.sendRequest).toHaveBeenCalledWith(
deregisterDatabases,
{
databases: [
@@ -320,7 +322,7 @@ describe("run-queries", () => {
mockCancel as any,
dbItem,
);
expect(qs.sendRequest).not.to.have.been.called;
expect(qs.sendRequest).not.toHaveBeenCalled();
});
it("should not deregister if unsupported", async () => {
@@ -344,7 +346,7 @@ describe("run-queries", () => {
mockCancel as any,
dbItem,
);
expect(qs.sendRequest).not.to.have.been.called;
expect(qs.sendRequest).not.toBeCalled();
});
});
@@ -372,18 +374,14 @@ describe("run-queries", () => {
config: {
timeoutSecs: 5,
},
sendRequest: sandbox.stub().returns(
new Promise((resolve) => {
resolve({
messages: [
{ message: "err", severity: Severity.ERROR },
{ message: "warn", severity: Severity.WARNING },
],
});
}),
),
sendRequest: jest.fn().mockResolvedValue({
messages: [
{ message: "err", severity: Severity.ERROR },
{ message: "warn", severity: Severity.WARNING },
],
}),
logger: {
log: sandbox.spy(),
log: jest.fn(),
},
cliServer,
} as unknown as QueryServerClient;
@@ -394,9 +392,9 @@ describe("run-queries", () => {
): CodeQLCliServer {
const mockServer: Record<string, any> = {};
for (const [operation, returns] of Object.entries(mockOperations)) {
mockServer[operation] = sandbox.stub();
returns.forEach((returnValue, i) => {
mockServer[operation].onCall(i).resolves(returnValue);
mockServer[operation] = jest.fn();
returns.forEach((returnValue) => {
mockServer[operation].mockResolvedValueOnce(returnValue);
});
}

View File

@@ -1,27 +1,22 @@
import * as path from "path";
import * as chai from "chai";
import * as chaiAsPromised from "chai-as-promised";
import { sarifParser } from "../../sarif-parser";
chai.use(chaiAsPromised);
const expect = chai.expect;
describe("sarif parser", function () {
describe("sarif parser", () => {
const sarifDir = path.join(__dirname, "data/sarif");
it("should parse a valid SARIF file", async () => {
const result = await sarifParser(path.join(sarifDir, "validSarif.sarif"));
expect(result.version).to.exist;
expect(result.runs).to.exist;
expect(result.runs[0].tool).to.exist;
expect(result.runs[0].tool.driver).to.exist;
expect(result.runs.length).to.be.at.least(1);
expect(result.version).toBeDefined();
expect(result.runs).toBeDefined();
expect(result.runs[0].tool).toBeDefined();
expect(result.runs[0].tool.driver).toBeDefined();
expect(result.runs.length).toBeGreaterThanOrEqual(1);
});
it("should return an empty array if there are no results", async () => {
const result = await sarifParser(
path.join(sarifDir, "emptyResultsSarif.sarif"),
);
expect(result.runs[0].results).to.be.empty;
expect(result.runs[0].results).toHaveLength(0);
});
});

View File

@@ -1,5 +1,3 @@
import { expect } from "chai";
import * as sinon from "sinon";
import TelemetryReporter from "vscode-extension-telemetry";
import {
ExtensionContext,
@@ -16,12 +14,10 @@ import { fail } from "assert";
import { ENABLE_TELEMETRY } from "../../config";
import { createMockExtensionContext } from "./index";
const sandbox = sinon.createSandbox();
describe("telemetry reporting", function () {
describe("telemetry reporting", () => {
// setting preferences can trigger lots of background activity
// so need to bump up the timeout of this test.
this.timeout(10000);
jest.setTimeout(10000);
let originalTelemetryExtension: boolean | undefined;
let originalTelemetryGlobal: boolean | undefined;
@@ -29,6 +25,21 @@ describe("telemetry reporting", function () {
let ctx: ExtensionContext;
let telemetryListener: TelemetryListener;
const sendTelemetryEventSpy = jest.spyOn(
TelemetryReporter.prototype,
"sendTelemetryEvent",
);
const sendTelemetryExceptionSpy = jest.spyOn(
TelemetryReporter.prototype,
"sendTelemetryException",
);
const disposeSpy = jest.spyOn(TelemetryReporter.prototype, "dispose");
const showInformationMessageSpy = jest.spyOn(
window,
"showInformationMessage",
);
beforeEach(async () => {
try {
// in case a previous test has accidentally activated this extension,
@@ -39,9 +50,11 @@ describe("telemetry reporting", function () {
ctx = createMockExtensionContext();
sandbox.stub(TelemetryReporter.prototype, "sendTelemetryEvent");
sandbox.stub(TelemetryReporter.prototype, "sendTelemetryException");
sandbox.stub(TelemetryReporter.prototype, "dispose");
sendTelemetryEventSpy.mockReset().mockReturnValue(undefined);
sendTelemetryExceptionSpy.mockReset().mockReturnValue(undefined);
disposeSpy.mockReset().mockResolvedValue(undefined);
showInformationMessageSpy.mockReset().mockResolvedValue(undefined);
originalTelemetryExtension = workspace
.getConfiguration()
@@ -73,7 +86,6 @@ describe("telemetry reporting", function () {
telemetryListener?.dispose();
// await wait(100);
try {
sandbox.restore();
await enableTelemetry("telemetry", originalTelemetryGlobal);
await enableTelemetry("codeQL.telemetry", originalTelemetryExtension);
} catch (e) {
@@ -84,22 +96,22 @@ describe("telemetry reporting", function () {
it("should initialize telemetry when both options are enabled", async () => {
await telemetryListener.initialize();
expect(telemetryListener._reporter).not.to.be.undefined;
expect(telemetryListener._reporter).toBeDefined();
const reporter: any = telemetryListener._reporter;
expect(reporter.extensionId).to.eq("my-id");
expect(reporter.extensionVersion).to.eq("1.2.3");
expect(reporter.userOptIn).to.eq(true); // enabled
expect(reporter.extensionId).toBe("my-id");
expect(reporter.extensionVersion).toBe("1.2.3");
expect(reporter.userOptIn).toBe(true); // enabled
});
it("should initialize telemetry when global option disabled", async () => {
try {
await enableTelemetry("telemetry", false);
await telemetryListener.initialize();
expect(telemetryListener._reporter).not.to.be.undefined;
expect(telemetryListener._reporter).toBeDefined();
const reporter: any = telemetryListener._reporter;
expect(reporter.userOptIn).to.eq(false); // disabled
expect(reporter.userOptIn).toBe(false); // disabled
} catch (e) {
fail(e as Error);
}
@@ -110,7 +122,7 @@ describe("telemetry reporting", function () {
await enableTelemetry("codeQL.telemetry", false);
await telemetryListener.initialize();
expect(telemetryListener._reporter).to.be.undefined;
expect(telemetryListener._reporter).toBeUndefined();
} catch (e) {
fail(e as Error);
}
@@ -120,52 +132,52 @@ describe("telemetry reporting", function () {
await enableTelemetry("codeQL.telemetry", false);
await enableTelemetry("telemetry", false);
await telemetryListener.initialize();
expect(telemetryListener._reporter).to.be.undefined;
expect(telemetryListener._reporter).toBeUndefined();
});
it("should dispose telemetry object when re-initializing and should not add multiple", async () => {
await telemetryListener.initialize();
expect(telemetryListener._reporter).not.to.be.undefined;
expect(telemetryListener._reporter).toBeDefined();
const firstReporter = telemetryListener._reporter;
await telemetryListener.initialize();
expect(telemetryListener._reporter).not.to.be.undefined;
expect(telemetryListener._reporter).not.to.eq(firstReporter);
expect(telemetryListener._reporter).toBeDefined();
expect(telemetryListener._reporter).not.toBe(firstReporter);
expect(TelemetryReporter.prototype.dispose).to.have.been.calledOnce;
expect(disposeSpy).toBeCalledTimes(1);
// initializing a third time continues to dispose
await telemetryListener.initialize();
expect(TelemetryReporter.prototype.dispose).to.have.been.calledTwice;
expect(disposeSpy).toBeCalledTimes(2);
});
it("should reinitialize reporter when extension setting changes", async () => {
await telemetryListener.initialize();
expect(TelemetryReporter.prototype.dispose).not.to.have.been.called;
expect(telemetryListener._reporter).not.to.be.undefined;
expect(disposeSpy).not.toBeCalled();
expect(telemetryListener._reporter).toBeDefined();
// this disables the reporter
await enableTelemetry("codeQL.telemetry", false);
expect(telemetryListener._reporter).to.be.undefined;
expect(telemetryListener._reporter).toBeUndefined();
expect(TelemetryReporter.prototype.dispose).to.have.been.calledOnce;
expect(disposeSpy).toBeCalledTimes(1);
// creates a new reporter, but does not dispose again
await enableTelemetry("codeQL.telemetry", true);
expect(telemetryListener._reporter).not.to.be.undefined;
expect(TelemetryReporter.prototype.dispose).to.have.been.calledOnce;
expect(telemetryListener._reporter).toBeDefined();
expect(disposeSpy).toBeCalledTimes(1);
});
it("should set userOprIn to false when global setting changes", async () => {
await telemetryListener.initialize();
const reporter: any = telemetryListener._reporter;
expect(reporter.userOptIn).to.eq(true); // enabled
expect(reporter.userOptIn).toBe(true); // enabled
await enableTelemetry("telemetry", false);
expect(reporter.userOptIn).to.eq(false); // disabled
expect(reporter.userOptIn).toBe(false); // disabled
});
it("should send an event", async () => {
@@ -173,9 +185,7 @@ describe("telemetry reporting", function () {
telemetryListener.sendCommandUsage("command-id", 1234, undefined);
expect(
TelemetryReporter.prototype.sendTelemetryEvent,
).to.have.been.calledOnceWith(
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
"command-usage",
{
name: "command-id",
@@ -185,8 +195,7 @@ describe("telemetry reporting", function () {
{ executionTime: 1234 },
);
expect(TelemetryReporter.prototype.sendTelemetryException).not.to.have.been
.called;
expect(sendTelemetryExceptionSpy).not.toBeCalled();
});
it("should send a command usage event with an error", async () => {
@@ -198,9 +207,7 @@ describe("telemetry reporting", function () {
new UserCancellationException(),
);
expect(
TelemetryReporter.prototype.sendTelemetryEvent,
).to.have.been.calledOnceWith(
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
"command-usage",
{
name: "command-id",
@@ -210,8 +217,7 @@ describe("telemetry reporting", function () {
{ executionTime: 1234 },
);
expect(TelemetryReporter.prototype.sendTelemetryException).not.to.have.been
.called;
expect(sendTelemetryExceptionSpy).not.toBeCalled();
});
it("should avoid sending an event when telemetry is disabled", async () => {
@@ -221,10 +227,8 @@ describe("telemetry reporting", function () {
telemetryListener.sendCommandUsage("command-id", 1234, undefined);
telemetryListener.sendCommandUsage("command-id", 1234, new Error());
expect(TelemetryReporter.prototype.sendTelemetryEvent).not.to.have.been
.called;
expect(TelemetryReporter.prototype.sendTelemetryException).not.to.have.been
.called;
expect(sendTelemetryEventSpy).not.toBeCalled();
expect(sendTelemetryExceptionSpy).not.toBeCalled();
});
it("should send an event when telemetry is re-enabled", async () => {
@@ -234,9 +238,7 @@ describe("telemetry reporting", function () {
telemetryListener.sendCommandUsage("command-id", 1234, undefined);
expect(
TelemetryReporter.prototype.sendTelemetryEvent,
).to.have.been.calledOnceWith(
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
"command-usage",
{
name: "command-id",
@@ -267,8 +269,8 @@ describe("telemetry reporting", function () {
},
};
const res = telemetryProcessor(envelop);
expect(res).to.eq(true);
expect(envelop).to.deep.eq({
expect(res).toBe(true);
expect(envelop).toEqual({
tags: {
other: true,
},
@@ -282,11 +284,16 @@ describe("telemetry reporting", function () {
});
});
it("should request permission if popup has never been seen before", async function () {
this.timeout(3000);
sandbox
.stub(window, "showInformationMessage")
.resolvesArg(3 /* "yes" item */);
const resolveArg =
(index: number) =>
(...args: any[]) =>
Promise.resolve(args[index]);
it("should request permission if popup has never been seen before", async () => {
jest.setTimeout(3000);
showInformationMessageSpy.mockImplementation(
resolveArg(3 /* "yes" item */),
);
await ctx.globalState.update("telemetry-request-viewed", false);
await enableTelemetry("codeQL.telemetry", false);
@@ -296,40 +303,36 @@ describe("telemetry reporting", function () {
await wait(500);
// Dialog opened, user clicks "yes" and telemetry enabled
expect(window.showInformationMessage).to.have.been.calledOnce;
expect(ENABLE_TELEMETRY.getValue()).to.eq(true);
expect(ctx.globalState.get("telemetry-request-viewed")).to.be.true;
expect(showInformationMessageSpy).toBeCalledTimes(1);
expect(ENABLE_TELEMETRY.getValue()).toBe(true);
expect(ctx.globalState.get("telemetry-request-viewed")).toBe(true);
});
it("should prevent telemetry if permission is denied", async () => {
sandbox
.stub(window, "showInformationMessage")
.resolvesArg(4 /* "no" item */);
showInformationMessageSpy.mockImplementation(resolveArg(4 /* "no" item */));
await ctx.globalState.update("telemetry-request-viewed", false);
await enableTelemetry("codeQL.telemetry", true);
await telemetryListener.initialize();
// Dialog opened, user clicks "no" and telemetry disabled
expect(window.showInformationMessage).to.have.been.calledOnce;
expect(ENABLE_TELEMETRY.getValue()).to.eq(false);
expect(ctx.globalState.get("telemetry-request-viewed")).to.be.true;
expect(showInformationMessageSpy).toBeCalledTimes(1);
expect(ENABLE_TELEMETRY.getValue()).toBe(false);
expect(ctx.globalState.get("telemetry-request-viewed")).toBe(true);
});
it("should unchange telemetry if permission dialog is dismissed", async () => {
sandbox
.stub(window, "showInformationMessage")
.resolves(undefined /* cancelled */);
showInformationMessageSpy.mockResolvedValue(undefined /* cancelled */);
await ctx.globalState.update("telemetry-request-viewed", false);
// this causes requestTelemetryPermission to be called
await enableTelemetry("codeQL.telemetry", false);
// Dialog opened, and user closes without interacting with it
expect(window.showInformationMessage).to.have.been.calledOnce;
expect(ENABLE_TELEMETRY.getValue()).to.eq(false);
expect(showInformationMessageSpy).toBeCalledTimes(1);
expect(ENABLE_TELEMETRY.getValue()).toBe(false);
// dialog was canceled, so should not have marked as viewed
expect(ctx.globalState.get("telemetry-request-viewed")).to.be.false;
expect(ctx.globalState.get("telemetry-request-viewed")).toBe(false);
});
it("should unchange telemetry if permission dialog is cancelled if starting as true", async () => {
@@ -337,9 +340,7 @@ describe("telemetry reporting", function () {
// as before, except start with telemetry enabled. It should _stay_ enabled if the
// dialog is canceled.
sandbox
.stub(window, "showInformationMessage")
.resolves(undefined /* cancelled */);
showInformationMessageSpy.mockResolvedValue(undefined /* cancelled */);
await ctx.globalState.update("telemetry-request-viewed", false);
// this causes requestTelemetryPermission to be called
@@ -347,10 +348,10 @@ describe("telemetry reporting", function () {
// Dialog opened, and user closes without interacting with it
// Telemetry state should not have changed
expect(window.showInformationMessage).to.have.been.calledOnce;
expect(ENABLE_TELEMETRY.getValue()).to.eq(true);
expect(showInformationMessageSpy).toBeCalledTimes(1);
expect(ENABLE_TELEMETRY.getValue()).toBe(true);
// dialog was canceled, so should not have marked as viewed
expect(ctx.globalState.get("telemetry-request-viewed")).to.be.false;
expect(ctx.globalState.get("telemetry-request-viewed")).toBe(false);
});
it("should avoid showing dialog if global telemetry is disabled", async () => {
@@ -362,12 +363,11 @@ describe("telemetry reporting", function () {
await enableTelemetry("telemetry", false);
await ctx.globalState.update("telemetry-request-viewed", false);
sandbox.stub(window, "showInformationMessage");
await telemetryListener.initialize();
// popup should not be shown even though we have initialized telemetry
expect(window.showInformationMessage).not.to.have.been.called;
expect(showInformationMessageSpy).not.toBeCalled();
});
// This test is failing because codeQL.canary is not a registered configuration.
@@ -381,16 +381,14 @@ describe("telemetry reporting", function () {
await enableTelemetry("codeQL.telemetry", false);
await ctx.globalState.update("telemetry-request-viewed", true);
await telemetryListener.initialize();
sandbox
.stub(window, "showInformationMessage")
.resolves(undefined /* cancelled */);
showInformationMessageSpy.mockResolvedValue(undefined /* cancelled */);
// set canary to true
await workspace.getConfiguration().update("codeQL.canary", true);
// now, we should have to click through the telemetry requestor again
expect(ctx.globalState.get("telemetry-request-viewed")).to.be.false;
expect(window.showInformationMessage).to.have.been.calledOnce;
expect(ctx.globalState.get("telemetry-request-viewed")).toBe(false);
expect(showInformationMessageSpy).toBeCalledTimes(1);
});
async function enableTelemetry(section: string, value: boolean | undefined) {

View File

@@ -1,7 +1,5 @@
import * as sinon from "sinon";
import * as fs from "fs-extra";
import { Uri, WorkspaceFolder } from "vscode";
import { expect } from "chai";
import { QLTestAdapter } from "../../test-adapter";
import { CodeQLCliServer } from "../../cli";
@@ -17,14 +15,13 @@ describe("test-adapter", () => {
let fakeDatabaseManager: DatabaseManager;
let currentDatabaseItem: DatabaseItem | undefined;
let databaseItems: DatabaseItem[] = [];
let openDatabaseSpy: sinon.SinonStub;
let removeDatabaseItemSpy: sinon.SinonStub;
let renameDatabaseItemSpy: sinon.SinonStub;
let setCurrentDatabaseItemSpy: sinon.SinonStub;
let runTestsSpy: sinon.SinonStub;
let resolveTestsSpy: sinon.SinonStub;
let resolveQlpacksSpy: sinon.SinonStub;
let sandox: sinon.SinonSandbox;
const openDatabaseSpy = jest.fn();
const removeDatabaseItemSpy = jest.fn();
const renameDatabaseItemSpy = jest.fn();
const setCurrentDatabaseItemSpy = jest.fn();
const runTestsSpy = jest.fn();
const resolveTestsSpy = jest.fn();
const resolveQlpacksSpy = jest.fn();
const preTestDatabaseItem = new DatabaseItemImpl(
Uri.file("/path/to/test/dir/dir.testproj"),
@@ -44,27 +41,28 @@ describe("test-adapter", () => {
);
beforeEach(() => {
sandox = sinon.createSandbox();
mockRunTests();
openDatabaseSpy = sandox.stub().resolves(postTestDatabaseItem);
removeDatabaseItemSpy = sandox.stub().resolves();
renameDatabaseItemSpy = sandox.stub().resolves();
setCurrentDatabaseItemSpy = sandox.stub().resolves();
resolveQlpacksSpy = sandox.stub().resolves({});
resolveTestsSpy = sandox.stub().resolves([]);
openDatabaseSpy.mockReset().mockResolvedValue(postTestDatabaseItem);
removeDatabaseItemSpy.mockReset().mockResolvedValue(undefined);
renameDatabaseItemSpy.mockReset().mockResolvedValue(undefined);
setCurrentDatabaseItemSpy.mockReset().mockResolvedValue(undefined);
resolveQlpacksSpy.mockReset().mockResolvedValue({});
resolveTestsSpy.mockReset().mockResolvedValue([]);
fakeDatabaseManager = {
currentDatabaseItem: undefined,
databaseItems: undefined,
openDatabase: openDatabaseSpy,
removeDatabaseItem: removeDatabaseItemSpy,
renameDatabaseItem: renameDatabaseItemSpy,
setCurrentDatabaseItem: setCurrentDatabaseItemSpy,
} as unknown as DatabaseManager;
sandox
.stub(fakeDatabaseManager, "currentDatabaseItem")
.get(() => currentDatabaseItem);
sandox.stub(fakeDatabaseManager, "databaseItems").get(() => databaseItems);
sandox.stub(preTestDatabaseItem, "isAffectedByTest").resolves(true);
Object.defineProperty(fakeDatabaseManager, "currentDatabaseItem", {
get: () => currentDatabaseItem,
});
Object.defineProperty(fakeDatabaseManager, "databaseItems", {
get: () => databaseItems,
});
jest.spyOn(preTestDatabaseItem, "isAffectedByTest").mockResolvedValue(true);
adapter = new QLTestAdapter(
{
name: "ABC",
@@ -79,12 +77,8 @@ describe("test-adapter", () => {
);
});
afterEach(() => {
sandox.restore();
});
it("should run some tests", async () => {
const listenerSpy = sandox.spy();
const listenerSpy = jest.fn();
adapter.testStates(listenerSpy);
const testsPath = Uri.parse("file:/ab/c").fsPath;
const dPath = Uri.parse("file:/ab/c/d.ql").fsPath;
@@ -93,72 +87,78 @@ describe("test-adapter", () => {
await adapter.run([testsPath]);
expect(listenerSpy.getCall(0).args).to.deep.eq([
{ type: "started", tests: [testsPath] },
]);
expect(listenerSpy.getCall(1).args).to.deep.eq([
{
type: "test",
state: "passed",
test: dPath,
message: undefined,
decorations: [],
},
]);
expect(listenerSpy.getCall(2).args).to.deep.eq([
{
type: "test",
state: "errored",
test: gPath,
message: `\ncompilation error: ${gPath}\nERROR: abc\n`,
decorations: [{ line: 1, message: "abc" }],
},
]);
expect(listenerSpy.getCall(3).args).to.deep.eq([
{
type: "test",
state: "failed",
test: hPath,
message: `\nfailed: ${hPath}\njkh\ntuv\n`,
decorations: [],
},
]);
expect(listenerSpy.getCall(4).args).to.deep.eq([{ type: "finished" }]);
expect(listenerSpy).to.have.callCount(5);
expect(listenerSpy).toBeCalledTimes(5);
expect(listenerSpy).toHaveBeenNthCalledWith(1, {
type: "started",
tests: [testsPath],
});
expect(listenerSpy).toHaveBeenNthCalledWith(2, {
type: "test",
state: "passed",
test: dPath,
message: undefined,
decorations: [],
});
expect(listenerSpy).toHaveBeenNthCalledWith(3, {
type: "test",
state: "errored",
test: gPath,
message: `\ncompilation error: ${gPath}\nERROR: abc\n`,
decorations: [{ line: 1, message: "abc" }],
});
expect(listenerSpy).toHaveBeenNthCalledWith(4, {
type: "test",
state: "failed",
test: hPath,
message: `\nfailed: ${hPath}\njkh\ntuv\n`,
decorations: [],
});
expect(listenerSpy).toHaveBeenNthCalledWith(5, { type: "finished" });
});
it("should reregister testproj databases around test run", async () => {
sandox.stub(fs, "access").resolves();
jest.spyOn(fs, "access").mockResolvedValue(undefined);
currentDatabaseItem = preTestDatabaseItem;
databaseItems = [preTestDatabaseItem];
await adapter.run(["/path/to/test/dir"]);
removeDatabaseItemSpy.getCall(0).calledBefore(runTestsSpy.getCall(0));
openDatabaseSpy.getCall(0).calledAfter(runTestsSpy.getCall(0));
renameDatabaseItemSpy.getCall(0).calledAfter(openDatabaseSpy.getCall(0));
setCurrentDatabaseItemSpy
.getCall(0)
.calledAfter(openDatabaseSpy.getCall(0));
expect(removeDatabaseItemSpy.mock.invocationCallOrder[0]).toBeGreaterThan(
runTestsSpy.mock.invocationCallOrder[0],
);
expect(openDatabaseSpy.mock.invocationCallOrder[0]).toBeGreaterThan(
runTestsSpy.mock.invocationCallOrder[0],
);
expect(renameDatabaseItemSpy.mock.invocationCallOrder[0]).toBeGreaterThan(
openDatabaseSpy.mock.invocationCallOrder[0],
);
expect(
setCurrentDatabaseItemSpy.mock.invocationCallOrder[0],
).toBeGreaterThan(openDatabaseSpy.mock.invocationCallOrder[0]);
sinon.assert.calledOnceWithExactly(
removeDatabaseItemSpy,
sinon.match.any,
sinon.match.any,
expect(removeDatabaseItemSpy).toBeCalledTimes(1);
expect(removeDatabaseItemSpy).toBeCalledWith(
expect.anything(),
expect.anything(),
preTestDatabaseItem,
);
sinon.assert.calledOnceWithExactly(
openDatabaseSpy,
sinon.match.any,
sinon.match.any,
expect(openDatabaseSpy).toBeCalledTimes(1);
expect(openDatabaseSpy).toBeCalledWith(
expect.anything(),
expect.anything(),
preTestDatabaseItem.databaseUri,
);
sinon.assert.calledOnceWithExactly(
renameDatabaseItemSpy,
expect(renameDatabaseItemSpy).toBeCalledTimes(1);
expect(renameDatabaseItemSpy).toBeCalledWith(
postTestDatabaseItem,
preTestDatabaseItem.name,
);
sinon.assert.calledOnceWithExactly(
setCurrentDatabaseItemSpy,
expect(setCurrentDatabaseItemSpy).toBeCalledTimes(1);
expect(setCurrentDatabaseItemSpy).toBeCalledWith(
postTestDatabaseItem,
true,
);
@@ -167,8 +167,7 @@ describe("test-adapter", () => {
function mockRunTests() {
// runTests is an async generator function. This is not directly supported in sinon
// However, we can pretend the same thing by just returning an async array.
runTestsSpy = sandox.stub();
runTestsSpy.returns(
runTestsSpy.mockReturnValue(
(async function* () {
yield Promise.resolve({
test: Uri.parse("file:/ab/c/d.ql").fsPath,