Skip to content

Commit e5967c3

Browse files
committed
First pass on converting cli-integration tests to Jest
This is a first pass on converting the cli-integration tests to Jest. It uses a custom Jest runner (based on the jest-runner-vscode) to install required extensions. It will also necessary to move some code for installating the CLI to ensure it was possible to install the CLI from outside VSCode. Most of the conversion has been done by `npx jest-codemods`, but all migration of Sinon has been done manually.
1 parent c31635f commit e5967c3

29 files changed

Lines changed: 1046 additions & 915 deletions

extensions/ql-vscode/jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module.exports = {
88
projects: [
99
"<rootDir>/src/view",
1010
"<rootDir>/test",
11+
"<rootDir>/out/vscode-tests/cli-integration",
1112
"<rootDir>/out/vscode-tests/no-workspace",
1213
],
1314
};

extensions/ql-vscode/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1275,7 +1275,7 @@
12751275
"integration": "npm-run-all integration:*",
12761276
"integration:no-workspace": "jest --projects out/vscode-tests/no-workspace",
12771277
"integration:minimal-workspace": "node ./out/vscode-tests/run-integration-tests.js minimal-workspace",
1278-
"cli-integration": "node ./out/vscode-tests/run-integration-tests.js cli-integration",
1278+
"cli-integration": "jest --config=out/vscode-tests/cli-integration/jest.config.js out/vscode-tests/cli-integration",
12791279
"update-vscode": "node ./node_modules/vscode/bin/install",
12801280
"format": "prettier --write **/*.{ts,tsx} && eslint . --ext .ts,.tsx --fix",
12811281
"lint": "eslint . --ext .ts,.tsx --max-warnings=0",

extensions/ql-vscode/src/distribution.ts

Lines changed: 8 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import * as fs from "fs-extra";
33
import * as os from "os";
44
import * as path from "path";
55
import * as semver from "semver";
6-
import * as unzipper from "unzipper";
76
import * as url from "url";
87
import { ExtensionContext, Event } from "vscode";
98
import { DistributionConfig } from "./config";
@@ -16,6 +15,12 @@ import {
1615
import { logger } from "./logging";
1716
import { getCodeQlCliVersion } from "./cli-version";
1817
import { ProgressCallback, reportStreamProgress } from "./commandRunner";
18+
import {
19+
codeQlLauncherName,
20+
deprecatedCodeQlLauncherName,
21+
extractZipArchive,
22+
getRequiredAssetName,
23+
} from "./pure/distribution";
1924

2025
/**
2126
* distribution.ts
@@ -55,22 +60,6 @@ export interface DistributionProvider {
5560
}
5661

5762
export class DistributionManager implements DistributionProvider {
58-
/**
59-
* Get the name of the codeql cli installation we prefer to install, based on our current platform.
60-
*/
61-
public static getRequiredAssetName(): string {
62-
switch (os.platform()) {
63-
case "linux":
64-
return "codeql-linux64.zip";
65-
case "darwin":
66-
return "codeql-osx64.zip";
67-
case "win32":
68-
return "codeql-win64.zip";
69-
default:
70-
return "codeql.zip";
71-
}
72-
}
73-
7463
constructor(
7564
public readonly config: DistributionConfig,
7665
private readonly versionRange: semver.Range,
@@ -361,7 +350,7 @@ class ExtensionSpecificDistributionManager {
361350
}
362351

363352
// Filter assets to the unique one that we require.
364-
const requiredAssetName = DistributionManager.getRequiredAssetName();
353+
const requiredAssetName = getRequiredAssetName();
365354
const assets = release.assets.filter(
366355
(asset) => asset.name === requiredAssetName,
367356
);
@@ -431,7 +420,7 @@ class ExtensionSpecificDistributionManager {
431420
}
432421

433422
private async getLatestRelease(): Promise<Release> {
434-
const requiredAssetName = DistributionManager.getRequiredAssetName();
423+
const requiredAssetName = getRequiredAssetName();
435424
void logger.log(
436425
`Searching for latest release including ${requiredAssetName}.`,
437426
);
@@ -683,39 +672,6 @@ export class ReleasesApiConsumer {
683672
private static readonly _maxRedirects = 20;
684673
}
685674

686-
export async function extractZipArchive(
687-
archivePath: string,
688-
outPath: string,
689-
): Promise<void> {
690-
const archive = await unzipper.Open.file(archivePath);
691-
await archive.extract({
692-
concurrency: 4,
693-
path: outPath,
694-
});
695-
// Set file permissions for extracted files
696-
await Promise.all(
697-
archive.files.map(async (file) => {
698-
// Only change file permissions if within outPath (path.join normalises the path)
699-
const extractedPath = path.join(outPath, file.path);
700-
if (
701-
extractedPath.indexOf(outPath) !== 0 ||
702-
!(await fs.pathExists(extractedPath))
703-
) {
704-
return Promise.resolve();
705-
}
706-
return fs.chmod(extractedPath, file.externalFileAttributes >>> 16);
707-
}),
708-
);
709-
}
710-
711-
export function codeQlLauncherName(): string {
712-
return os.platform() === "win32" ? "codeql.exe" : "codeql";
713-
}
714-
715-
function deprecatedCodeQlLauncherName(): string | undefined {
716-
return os.platform() === "win32" ? "codeql.cmd" : undefined;
717-
}
718-
719675
function isRedirectStatusCode(statusCode: number): boolean {
720676
return (
721677
statusCode === 301 ||
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import * as os from "os";
2+
import * as unzipper from "unzipper";
3+
import * as path from "path";
4+
import * as fs from "fs-extra";
5+
6+
/**
7+
* Get the name of the codeql cli installation we prefer to install, based on our current platform.
8+
*/
9+
export function getRequiredAssetName(): string {
10+
switch (os.platform()) {
11+
case "linux":
12+
return "codeql-linux64.zip";
13+
case "darwin":
14+
return "codeql-osx64.zip";
15+
case "win32":
16+
return "codeql-win64.zip";
17+
default:
18+
return "codeql.zip";
19+
}
20+
}
21+
22+
export async function extractZipArchive(
23+
archivePath: string,
24+
outPath: string,
25+
): Promise<void> {
26+
const archive = await unzipper.Open.file(archivePath);
27+
await archive.extract({
28+
concurrency: 4,
29+
path: outPath,
30+
});
31+
// Set file permissions for extracted files
32+
await Promise.all(
33+
archive.files.map(async (file) => {
34+
// Only change file permissions if within outPath (path.join normalises the path)
35+
const extractedPath = path.join(outPath, file.path);
36+
if (
37+
extractedPath.indexOf(outPath) !== 0 ||
38+
!(await fs.pathExists(extractedPath))
39+
) {
40+
return Promise.resolve();
41+
}
42+
return fs.chmod(extractedPath, file.externalFileAttributes >>> 16);
43+
}),
44+
);
45+
}
46+
47+
export function codeQlLauncherName(): string {
48+
return os.platform() === "win32" ? "codeql.exe" : "codeql";
49+
}
50+
51+
export function deprecatedCodeQlLauncherName(): string | undefined {
52+
return os.platform() === "win32" ? "codeql.cmd" : undefined;
53+
}

extensions/ql-vscode/src/vscode-tests/cli-integration/databases.test.ts

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import * as sinon from "sinon";
21
import * as path from "path";
3-
import { fail } from "assert";
4-
import { expect } from "chai";
52
import { extensions, CancellationToken, Uri, window } from "vscode";
63

74
import { CodeQLExtensionInterface } from "../../extension";
@@ -12,33 +9,29 @@ import {
129
importArchiveDatabase,
1310
promptImportInternetDatabase,
1411
} from "../../databaseFetcher";
15-
import { ProgressCallback } from "../../commandRunner";
1612
import { cleanDatabases, dbLoc, DB_URL, storagePath } from "./global.helper";
1713

14+
jest.setTimeout(60_000);
15+
1816
/**
1917
* Run various integration tests for databases
2018
*/
21-
describe("Databases", function () {
22-
this.timeout(60000);
23-
19+
describe("Databases", () => {
2420
const LGTM_URL =
2521
"https://lgtm.com/projects/g/aeisenberg/angular-bind-notifier/";
2622

2723
let databaseManager: DatabaseManager;
28-
let sandbox: sinon.SinonSandbox;
29-
let inputBoxStub: sinon.SinonStub;
24+
const inputBoxStub = jest.spyOn(window, "showInputBox");
3025
let cli: CodeQLCliServer;
31-
let progressCallback: ProgressCallback;
26+
const progressCallback = jest.fn();
27+
28+
jest.spyOn(window, "showErrorMessage").mockResolvedValue(undefined);
29+
jest.spyOn(window, "showInformationMessage").mockResolvedValue(undefined);
3230

3331
beforeEach(async () => {
3432
try {
35-
sandbox = sinon.createSandbox();
36-
// the uri.fsPath function on windows returns a lowercase drive letter
37-
// so, force the storage path string to be lowercase, too.
38-
progressCallback = sandbox.spy();
39-
inputBoxStub = sandbox.stub(window, "showInputBox");
40-
sandbox.stub(window, "showErrorMessage");
41-
sandbox.stub(window, "showInformationMessage");
33+
inputBoxStub.mockReset().mockResolvedValue(undefined);
34+
progressCallback.mockReset();
4235

4336
const extension = await extensions
4437
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
@@ -61,15 +54,13 @@ describe("Databases", function () {
6154

6255
afterEach(async () => {
6356
try {
64-
sandbox.restore();
6557
await cleanDatabases(databaseManager);
6658
} catch (e) {
6759
fail(e as Error);
6860
}
6961
});
7062

7163
it("should add a database from a folder", async () => {
72-
const progressCallback = sandbox.spy() as ProgressCallback;
7364
const uri = Uri.file(dbLoc);
7465
let dbItem = await importArchiveDatabase(
7566
uri.toString(true),
@@ -79,27 +70,27 @@ describe("Databases", function () {
7970
{} as CancellationToken,
8071
cli,
8172
);
82-
expect(dbItem).to.be.eq(databaseManager.currentDatabaseItem);
83-
expect(dbItem).to.be.eq(databaseManager.databaseItems[0]);
84-
expect(dbItem).not.to.be.undefined;
73+
expect(dbItem).toBe(databaseManager.currentDatabaseItem);
74+
expect(dbItem).toBe(databaseManager.databaseItems[0]);
75+
expect(dbItem).toBeDefined();
8576
dbItem = dbItem!;
86-
expect(dbItem.name).to.eq("db");
87-
expect(dbItem.databaseUri.fsPath).to.eq(path.join(storagePath, "db", "db"));
77+
expect(dbItem.name).toBe("db");
78+
expect(dbItem.databaseUri.fsPath).toBe(path.join(storagePath, "db", "db"));
8879
});
8980

9081
it("should add a database from lgtm with only one language", async () => {
91-
inputBoxStub.resolves(LGTM_URL);
82+
inputBoxStub.mockResolvedValue(LGTM_URL);
9283
let dbItem = await promptImportLgtmDatabase(
9384
databaseManager,
9485
storagePath,
9586
progressCallback,
9687
{} as CancellationToken,
9788
cli,
9889
);
99-
expect(dbItem).not.to.be.undefined;
90+
expect(dbItem).toBeDefined();
10091
dbItem = dbItem!;
101-
expect(dbItem.name).to.eq("aeisenberg_angular-bind-notifier_106179a");
102-
expect(dbItem.databaseUri.fsPath).to.eq(
92+
expect(dbItem.name).toBe("aeisenberg_angular-bind-notifier_106179a");
93+
expect(dbItem.databaseUri.fsPath).toBe(
10394
path.join(
10495
storagePath,
10596
"javascript",
@@ -109,7 +100,7 @@ describe("Databases", function () {
109100
});
110101

111102
it("should add a database from a url", async () => {
112-
inputBoxStub.resolves(DB_URL);
103+
inputBoxStub.mockResolvedValue(DB_URL);
113104

114105
let dbItem = await promptImportInternetDatabase(
115106
databaseManager,
@@ -118,10 +109,10 @@ describe("Databases", function () {
118109
{} as CancellationToken,
119110
cli,
120111
);
121-
expect(dbItem).not.to.be.undefined;
112+
expect(dbItem).toBeDefined();
122113
dbItem = dbItem!;
123-
expect(dbItem.name).to.eq("db");
124-
expect(dbItem.databaseUri.fsPath).to.eq(
114+
expect(dbItem.name).toBe("db");
115+
expect(dbItem.databaseUri.fsPath).toBe(
125116
path.join(storagePath, "simple-db", "db"),
126117
);
127118
});

0 commit comments

Comments
 (0)