Skip to content

Commit d8e7670

Browse files
authored
Add unit tests for lldb.ts (#1110)
1 parent b69ee94 commit d8e7670

File tree

4 files changed

+214
-103
lines changed

4 files changed

+214
-103
lines changed

src/debugger/lldb.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export async function getLLDBLibPath(toolchain: SwiftToolchain): Promise<Result<
6262
}
6363
}
6464

65-
async function findLibLLDB(pathHint: string): Promise<string | undefined> {
65+
export async function findLibLLDB(pathHint: string): Promise<string | undefined> {
6666
const stat = await fs.stat(pathHint);
6767
if (stat.isFile()) {
6868
return pathHint;
@@ -92,7 +92,7 @@ async function findLibLLDB(pathHint: string): Promise<string | undefined> {
9292
return undefined;
9393
}
9494

95-
async function findFileByPattern(path: string, pattern: RegExp): Promise<string | null> {
95+
export async function findFileByPattern(path: string, pattern: RegExp): Promise<string | null> {
9696
try {
9797
const files = await fs.readdir(path);
9898
for (const file of files) {

test/unit-tests/debugger/attachDebugger.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ suite("attachDebugger Unit Test Suite", () => {
6161
await attachDebugger(instance(mockContext));
6262

6363
// Verify startDebugging was called with the right pid.
64-
// Integration level check needed: actual config return a fulfilled promise.
64+
// NB(separate itest): actual config return a fulfilled promise.
6565
expect(debugMock.startDebugging).to.have.been.calledOnce;
6666
expect(debugMock.startDebugging.args[0][1]).to.containSubset({ pid: 1234 });
6767
});

test/unit-tests/debugger/getLldbProcess.test.ts

-100
This file was deleted.

test/unit-tests/debugger/lldb.test.ts

+211
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the VS Code Swift open source project
4+
//
5+
// Copyright (c) 2024 the VS Code Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import * as vscode from "vscode";
16+
import * as util from "../../../src/utilities/utilities";
17+
import * as lldb from "../../../src/debugger/lldb";
18+
import * as fs from "fs/promises";
19+
import * as sinon from "sinon";
20+
import { expect } from "chai";
21+
import {
22+
instance,
23+
MockedObject,
24+
mockFn,
25+
mockGlobalModule,
26+
mockGlobalObject,
27+
mockObject,
28+
MockedFunction,
29+
} from "../../MockUtils";
30+
import { SwiftToolchain } from "../../../src/toolchain/toolchain";
31+
import { WorkspaceContext } from "../../../src/WorkspaceContext";
32+
33+
suite("debugger.lldb Tests", () => {
34+
suite("getLLDBLibPath Tests", () => {
35+
let mockToolchain: MockedObject<SwiftToolchain>;
36+
let mockFindLibLLDB: MockedFunction<(typeof lldb)["findLibLLDB"]>;
37+
const mockUtil = mockGlobalModule(util);
38+
39+
setup(() => {
40+
mockFindLibLLDB = sinon.stub();
41+
mockToolchain = mockObject<SwiftToolchain>({
42+
getLLDB: mockFn(),
43+
swiftFolderPath: "",
44+
});
45+
});
46+
47+
test("should return failure if toolchain.getLLDB() throws an error", async () => {
48+
mockToolchain.getLLDB.rejects(new Error("Failed to get LLDB"));
49+
const result = await lldb.getLLDBLibPath(instance(mockToolchain));
50+
expect(result.failure).to.have.property("message", "Failed to get LLDB");
51+
});
52+
53+
test("should return failure when execFile throws an error on windows", async function () {
54+
if (process.platform !== "win32") {
55+
this.skip();
56+
} else {
57+
mockToolchain.getLLDB.resolves("/path/to/lldb");
58+
mockUtil.execFile.rejects(new Error("execFile failed"));
59+
const result = await lldb.getLLDBLibPath(instance(mockToolchain));
60+
// specific behaviour: return success and failure both undefined
61+
expect(result.failure).to.equal(undefined);
62+
expect(result.success).to.equal(undefined);
63+
}
64+
});
65+
66+
test("should return failure if findLibLLDB returns falsy values", async () => {
67+
mockToolchain.getLLDB.resolves("/path/to/lldb");
68+
mockUtil.execFile.resolves({ stdout: "", stderr: "" });
69+
mockFindLibLLDB.onFirstCall().resolves(undefined);
70+
71+
let result = await lldb.getLLDBLibPath(instance(mockToolchain));
72+
expect(result.failure).to.not.equal(undefined);
73+
74+
mockFindLibLLDB.onSecondCall().resolves("");
75+
76+
result = await lldb.getLLDBLibPath(instance(mockToolchain));
77+
expect(result.failure).to.not.equal(undefined);
78+
});
79+
// NB(separate itest): contract test with toolchains of various platforms
80+
});
81+
82+
suite("findLibLLDB Tests", () => {
83+
const fsMock = mockGlobalModule(fs);
84+
85+
test("should return undefined if no file matches the pattern", async () => {
86+
fsMock.readdir.resolves(["file1", "file2"] as any);
87+
fsMock.stat.resolves({ isFile: () => false } as any);
88+
89+
const result = await lldb.findLibLLDB("/path/hint");
90+
91+
expect(result).to.be.undefined;
92+
});
93+
94+
test("should return path if file exists", async () => {
95+
fsMock.stat.resolves({ isFile: () => true } as any);
96+
97+
const result = await lldb.findLibLLDB("/path/hint");
98+
99+
expect(result).to.equal("/path/hint");
100+
});
101+
// NB(separate itest): contract test with toolchains of various platforms
102+
});
103+
104+
suite("findFileByPattern Tests", () => {
105+
const fsMock = mockGlobalModule(fs);
106+
107+
test("should return null if no file matches the pattern", async () => {
108+
fsMock.readdir.resolves(["file1", "file2"] as any);
109+
110+
const result = await lldb.findFileByPattern("/some/path", /pattern/);
111+
112+
expect(result).to.be.null;
113+
});
114+
115+
test("should return the first match if one file matches the pattern", async () => {
116+
fsMock.readdir.resolves(["match1", "nomatch"] as any);
117+
118+
const result = await lldb.findFileByPattern("/some/path", /match1/);
119+
120+
expect(result).to.equal("match1");
121+
});
122+
123+
test("should return the first match if multiple files match the pattern", async () => {
124+
fsMock.readdir.resolves(["match1", "match2"] as any);
125+
126+
const result = await lldb.findFileByPattern("/some/path", /match/);
127+
128+
expect(result).to.equal("match1");
129+
});
130+
131+
test("should return null if directory reading fails", async () => {
132+
fsMock.readdir.rejects(new Error("Some error"));
133+
134+
const result = await lldb.findFileByPattern("/some/path", /pattern/);
135+
136+
expect(result).to.be.null;
137+
});
138+
});
139+
140+
suite("getLldbProcess Unit Test Suite", () => {
141+
const utilMock = mockGlobalModule(util);
142+
const windowMock = mockGlobalObject(vscode, "window");
143+
144+
let mockContext: MockedObject<WorkspaceContext>;
145+
let mockToolchain: MockedObject<SwiftToolchain>;
146+
147+
setup(() => {
148+
mockToolchain = mockObject<SwiftToolchain>({
149+
getLLDB: mockFn(s => s.resolves("/path/to/lldb")),
150+
});
151+
mockContext = mockObject<WorkspaceContext>({
152+
toolchain: instance(mockToolchain),
153+
});
154+
});
155+
156+
test("should return an empty list when no processes are found", async () => {
157+
utilMock.execFile.resolves({ stdout: "", stderr: "" });
158+
159+
const result = await lldb.getLldbProcess(instance(mockContext));
160+
161+
expect(result).to.be.an("array").that.is.empty;
162+
});
163+
164+
test("should return a list with one process", async () => {
165+
utilMock.execFile.resolves({
166+
stdout: `1234 5678 user1 group1 SingleProcess\n`,
167+
stderr: "",
168+
});
169+
170+
const result = await lldb.getLldbProcess(instance(mockContext));
171+
172+
expect(result).to.deep.equal([{ pid: 1234, label: "1234: SingleProcess" }]);
173+
});
174+
175+
test("should return a list with many processes", async () => {
176+
const manyProcessesOutput = Array(1000)
177+
.fill(0)
178+
.map((_, i) => {
179+
return `${1000 + i} 2000 user${i} group${i} Process${i}`;
180+
})
181+
.join("\n");
182+
utilMock.execFile.resolves({
183+
stdout: manyProcessesOutput,
184+
stderr: "",
185+
});
186+
187+
const result = await lldb.getLldbProcess(instance(mockContext));
188+
189+
// Assert that the result is an array with 1000 processes
190+
const expected = Array(1000)
191+
.fill(0)
192+
.map((_, i) => ({
193+
pid: 1000 + i,
194+
label: `${1000 + i}: Process${i}`,
195+
}));
196+
expect(result).to.deep.equal(expected);
197+
});
198+
199+
test("should handle errors correctly", async () => {
200+
utilMock.execFile.rejects(new Error("LLDB Error"));
201+
utilMock.getErrorDescription.returns("LLDB Error");
202+
203+
const result = await lldb.getLldbProcess(instance(mockContext));
204+
205+
expect(result).to.equal(undefined);
206+
expect(windowMock.showErrorMessage).to.have.been.calledWith(
207+
"Failed to run LLDB: LLDB Error"
208+
);
209+
});
210+
});
211+
});

0 commit comments

Comments
 (0)