Skip to content

Commit 3b8f9f8

Browse files
authored
feat: More wide ranging logging (#22)
1 parent 0630140 commit 3b8f9f8

File tree

4 files changed

+121
-38
lines changed

4 files changed

+121
-38
lines changed

src/configurationFile.ts

+46-28
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export class ConfigurationFile implements vscode.Disposable {
5151
public readonly onDidChange = this.didChangeEmitter.event;
5252

5353
constructor(
54+
private readonly logChannel: vscode.LogOutputChannel,
5455
public readonly uri: vscode.Uri,
5556
public readonly wf: vscode.WorkspaceFolder,
5657
) {
@@ -96,7 +97,16 @@ export class ConfigurationFile implements vscode.Disposable {
9697
// We cannot use process.execPath as this points to code.exe which is an electron application
9798
// also with ELECTRON_RUN_AS_NODE this can lead to errors (e.g. with the --import option)
9899
// we prefer to use the system level node
99-
this._pathToNode ??= (await which('node', { nothrow: true })) ?? process.execPath;
100+
if (!this._pathToNode) {
101+
this.logChannel.debug('Resolving Node.js executable');
102+
this._pathToNode = await which('node', { nothrow: true });
103+
if (this._pathToNode) {
104+
this.logChannel.debug(`Found Node.js in PATH at '${this._pathToNode}'`);
105+
} else {
106+
this._pathToNode = process.execPath;
107+
this.logChannel.debug(`Node.js not found in PATH using '${this._pathToNode}' as fallback`);
108+
}
109+
}
100110
return this._pathToNode;
101111
}
102112

@@ -113,32 +123,33 @@ export class ConfigurationFile implements vscode.Disposable {
113123
}
114124

115125
private async _resolveLocalMochaPath(suffix?: string): Promise<string> {
116-
this._resolver ??= resolveModule.ResolverFactory.createResolver({
117-
fileSystem: new resolveModule.CachedInputFileSystem(fs, 4000),
118-
conditionNames: ['node', 'require', 'module'],
119-
});
126+
if (!this._resolver) {
127+
this.logChannel.debug('Creating new resolver for resolving Mocha');
128+
this._resolver ??= resolveModule.ResolverFactory.createResolver({
129+
fileSystem: new resolveModule.CachedInputFileSystem(fs, 4000),
130+
conditionNames: ['node', 'require', 'module'],
131+
});
132+
}
120133

121-
return new Promise<string>((resolve, reject) =>
122-
this._resolver!.resolve(
123-
{},
124-
path.dirname(this.uri.fsPath),
125-
'mocha' + (suffix ?? ''),
126-
{},
127-
(err, res) => {
128-
if (err) {
129-
reject(
130-
new HumanError(
131-
`Could not find mocha in working directory '${path.dirname(
132-
this.uri.fsPath,
133-
)}', please install mocha to run tests.`,
134-
),
135-
);
136-
} else {
137-
resolve(res as string);
138-
}
139-
},
140-
),
141-
);
134+
return new Promise<string>((resolve, reject) => {
135+
const dir = path.dirname(this.uri.fsPath);
136+
this.logChannel.debug(`resolving 'mocha${suffix}' via ${dir}`);
137+
this._resolver!.resolve({}, dir, 'mocha' + (suffix ?? ''), {}, (err, res) => {
138+
if (err) {
139+
this.logChannel.error(`resolving 'mocha${suffix}' failed with error ${err}`);
140+
reject(
141+
new HumanError(
142+
`Could not find mocha in working directory '${path.dirname(
143+
this.uri.fsPath,
144+
)}', please install mocha to run tests.`,
145+
),
146+
);
147+
} else {
148+
this.logChannel.debug(`'mocha${suffix}' resolved to '${res}'`);
149+
resolve(res as string);
150+
}
151+
});
152+
});
142153
}
143154

144155
private async _read() {
@@ -154,7 +165,9 @@ export class ConfigurationFile implements vscode.Disposable {
154165
// TODO[mocha]: allow specifying the cwd in loadOptions()
155166
const currentCwd = process.cwd();
156167
try {
157-
process.chdir(path.dirname(this.uri.fsPath));
168+
const configSearchPath = path.dirname(this.uri.fsPath);
169+
this.logChannel.debug(`Reading mocharc, changing working directory to ${configSearchPath}`);
170+
process.chdir(configSearchPath);
158171

159172
// we need to ensure a reload for javascript files
160173
// as they are in the require cache https://github.com/mochajs/mocha/blob/e263c7a722b8c2fcbe83596836653896a9e0258b/lib/cli/config.js#L37
@@ -167,11 +180,13 @@ export class ConfigurationFile implements vscode.Disposable {
167180
}
168181

169182
config = this._optionsModule.loadOptions();
183+
this.logChannel.debug(`Loaded mocharc via Mocha`);
170184
} finally {
185+
this.logChannel.debug(`Reading mocharc, changing working directory back to ${currentCwd}`);
171186
process.chdir(currentCwd);
172187
}
173188

174-
return new ConfigurationList(this.uri, config, this.wf);
189+
return new ConfigurationList(this.logChannel, this.uri, config, this.wf);
175190
}
176191

177192
/**
@@ -196,6 +211,7 @@ export class ConfigurationList {
196211
)[];
197212

198213
constructor(
214+
private readonly logChannel: vscode.LogOutputChannel,
199215
public readonly uri: vscode.Uri,
200216
public readonly value: IResolvedConfiguration,
201217
wf: vscode.WorkspaceFolder,
@@ -238,6 +254,8 @@ export class ConfigurationList {
238254
}),
239255
);
240256
}
257+
258+
this.logChannel.debug(`Loaded mocharc via '${uri.fsPath}', with patterns`, this.patterns);
241259
}
242260

243261
/**

src/controller.ts

+72-7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,49 @@ import { TsConfigStore } from './tsconfig-store';
2424

2525
const diagnosticCollection = vscode.languages.createDiagnosticCollection('ext-test-duplicates');
2626

27+
type TestNodeCountKind = '+' | '~' | '-';
28+
class TestNodeCounter {
29+
private counts: Map<NodeKind, Map<TestNodeCountKind, number>> = new Map();
30+
31+
public add(kind: NodeKind) {
32+
this.increment(kind, '+');
33+
}
34+
35+
public update(kind: NodeKind) {
36+
this.increment(kind, '~');
37+
}
38+
39+
public remove(kind: NodeKind) {
40+
this.increment(kind, '-');
41+
}
42+
43+
increment(nodeKind: NodeKind, countKind: TestNodeCountKind) {
44+
let counts = this.counts.get(nodeKind);
45+
if (!counts) {
46+
counts = new Map([
47+
['+', 0],
48+
['~', 0],
49+
['-', 0],
50+
]);
51+
this.counts.set(nodeKind, counts);
52+
}
53+
counts.set(countKind, (counts.get(countKind) ?? 0) + 1);
54+
}
55+
56+
toString() {
57+
const s = [];
58+
for (const [nodeKind, nodeKindV] of this.counts) {
59+
const prefix = `${NodeKind[nodeKind]}:`;
60+
const values = [];
61+
for (const [countKind, count] of nodeKindV) {
62+
values.push(`${countKind}${count}`);
63+
}
64+
s.push(`${prefix} ${values.join(' ')}`);
65+
}
66+
return s.join('; ');
67+
}
68+
}
69+
2770
export class Controller {
2871
private readonly disposables = new DisposableStore();
2972
public readonly configFile: ConfigurationFile;
@@ -82,20 +125,21 @@ export class Controller {
82125
configFileUri.fsPath,
83126
);
84127
this.disposables.add(ctrl);
85-
this.configFile = this.disposables.add(new ConfigurationFile(configFileUri, wf));
128+
this.configFile = this.disposables.add(new ConfigurationFile(logChannel, configFileUri, wf));
86129
this.onDidDelete = this.configFile.onDidDelete;
87130

88131
this.recreateDiscoverer();
89132

90-
const rescan = () => {
133+
const rescan = (reason: string) => {
134+
logChannel.info(`Rescan of tests triggered (${reason})`);
91135
this.recreateDiscoverer();
92136
this.scanFiles();
93137
};
94-
this.disposables.add(this.configFile.onDidChange(rescan));
95-
this.disposables.add(this.settings.onDidChange(rescan));
138+
this.disposables.add(this.configFile.onDidChange(() => rescan('mocharc changed')));
139+
this.disposables.add(this.settings.onDidChange(() => rescan('settings changed')));
96140
ctrl.refreshHandler = () => {
97141
this.configFile.forget();
98-
rescan();
142+
rescan('user');
99143
};
100144
this.scanFiles();
101145
}
@@ -167,12 +211,15 @@ export class Controller {
167211
}
168212

169213
if (!tree.length) {
214+
this.logChannel.info(`No tests found in '${uri.fsPath}'`);
170215
this.deleteFileTests(uri.toString());
171216
return;
172217
}
173218

174219
const smMaintainer = previous?.sourceMap ?? this.smStore.maintain(uri);
175220
const sourceMap = await smMaintainer.refresh(contents);
221+
222+
const counter = new TestNodeCounter();
176223
const add = (
177224
parent: vscode.TestItem,
178225
node: IParsedNode,
@@ -182,10 +229,13 @@ export class Controller {
182229
let item = parent.children.get(node.name);
183230
if (!item) {
184231
item = this.ctrl.createTestItem(node.name, node.name, start.uri);
232+
counter.add(node.kind);
185233
testMetadata.set(item, {
186234
type: node.kind === NodeKind.Suite ? ItemType.Suite : ItemType.Test,
187235
});
188236
parent.children.add(item);
237+
} else {
238+
counter.update(node.kind);
189239
}
190240
item.range = new vscode.Range(start.range.start, end.range.end);
191241
item.error = node.error;
@@ -206,9 +256,15 @@ export class Controller {
206256
seen.set(child.name, add(item, child, start, end));
207257
}
208258

209-
for (const [id] of item.children) {
259+
for (const [id, child] of item.children) {
210260
if (!seen.has(id)) {
261+
const meta = testMetadata.get(child);
211262
item.children.delete(id);
263+
if (meta?.type === ItemType.Test) {
264+
counter.remove(NodeKind.Test);
265+
} else if (meta?.type === ItemType.Suite) {
266+
counter.remove(NodeKind.Suite);
267+
}
212268
}
213269
}
214270

@@ -234,11 +290,19 @@ export class Controller {
234290
if (previous) {
235291
for (const [id, test] of previous.items) {
236292
if (!newTestsInFile.has(id)) {
293+
const meta = testMetadata.get(test);
237294
(test.parent?.children ?? this.ctrl.items).delete(id);
295+
if (meta?.type === ItemType.Test) {
296+
counter.remove(NodeKind.Test);
297+
} else if (meta?.type === ItemType.Suite) {
298+
counter.remove(NodeKind.Suite);
299+
}
238300
}
239301
}
240302
}
241303

304+
this.logChannel.info(`Reloaded tests from '${uri.fsPath}' ${counter}`);
305+
242306
this.testsInFiles.set(uri.toString(), { items: newTestsInFile, hash, sourceMap: smMaintainer });
243307
this.didChangeEmitter.fire();
244308
}
@@ -353,7 +417,8 @@ export class Controller {
353417
let configs: ConfigurationList;
354418
try {
355419
configs = await this.configFile.read();
356-
} catch {
420+
} catch (e) {
421+
this.logChannel.error(e as Error, 'Failed to read config file');
357422
this.handleScanError();
358423
return;
359424
}

src/discoverer/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export interface IExtensionSettings {
2626
extractTimeout: number;
2727
}
2828

29-
export const enum NodeKind {
29+
export enum NodeKind {
3030
Suite,
3131
Test,
3232
}

src/runner.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ export class TestRunner {
238238
const ds = new DisposableStore();
239239

240240
const spawnArgs = await config.getMochaSpawnArgs(args);
241-
this.logChannel.debug('Start test debugging with args', spawnArgs);
241+
this.logChannel.info('Start test debugging with args', spawnArgs);
242242

243243
return new Promise<void>((resolve, reject) => {
244244
const sessionKey = randomUUID();
@@ -324,7 +324,7 @@ export class TestRunner {
324324

325325
private async runWithoutDebug({ args, config, onLine, token }: ISpawnOptions) {
326326
const spawnArgs = await config.getMochaSpawnArgs(args);
327-
this.logChannel.debug('Start test execution with args', spawnArgs);
327+
this.logChannel.info('Start test execution with args', spawnArgs);
328328

329329
const cli = await new Promise<ChildProcessWithoutNullStreams>((resolve, reject) => {
330330
const p = spawn(spawnArgs[0], spawnArgs.slice(1), {

0 commit comments

Comments
 (0)