Skip to content

Commit

Permalink
feat: More wide ranging logging (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
Danielku15 authored Apr 3, 2024
1 parent 0630140 commit 3b8f9f8
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 38 deletions.
74 changes: 46 additions & 28 deletions src/configurationFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class ConfigurationFile implements vscode.Disposable {
public readonly onDidChange = this.didChangeEmitter.event;

constructor(
private readonly logChannel: vscode.LogOutputChannel,
public readonly uri: vscode.Uri,
public readonly wf: vscode.WorkspaceFolder,
) {
Expand Down Expand Up @@ -96,7 +97,16 @@ export class ConfigurationFile implements vscode.Disposable {
// We cannot use process.execPath as this points to code.exe which is an electron application
// also with ELECTRON_RUN_AS_NODE this can lead to errors (e.g. with the --import option)
// we prefer to use the system level node
this._pathToNode ??= (await which('node', { nothrow: true })) ?? process.execPath;
if (!this._pathToNode) {
this.logChannel.debug('Resolving Node.js executable');
this._pathToNode = await which('node', { nothrow: true });
if (this._pathToNode) {
this.logChannel.debug(`Found Node.js in PATH at '${this._pathToNode}'`);
} else {
this._pathToNode = process.execPath;
this.logChannel.debug(`Node.js not found in PATH using '${this._pathToNode}' as fallback`);
}
}
return this._pathToNode;
}

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

private async _resolveLocalMochaPath(suffix?: string): Promise<string> {
this._resolver ??= resolveModule.ResolverFactory.createResolver({
fileSystem: new resolveModule.CachedInputFileSystem(fs, 4000),
conditionNames: ['node', 'require', 'module'],
});
if (!this._resolver) {
this.logChannel.debug('Creating new resolver for resolving Mocha');
this._resolver ??= resolveModule.ResolverFactory.createResolver({
fileSystem: new resolveModule.CachedInputFileSystem(fs, 4000),
conditionNames: ['node', 'require', 'module'],
});
}

return new Promise<string>((resolve, reject) =>
this._resolver!.resolve(
{},
path.dirname(this.uri.fsPath),
'mocha' + (suffix ?? ''),
{},
(err, res) => {
if (err) {
reject(
new HumanError(
`Could not find mocha in working directory '${path.dirname(
this.uri.fsPath,
)}', please install mocha to run tests.`,
),
);
} else {
resolve(res as string);
}
},
),
);
return new Promise<string>((resolve, reject) => {
const dir = path.dirname(this.uri.fsPath);
this.logChannel.debug(`resolving 'mocha${suffix}' via ${dir}`);
this._resolver!.resolve({}, dir, 'mocha' + (suffix ?? ''), {}, (err, res) => {
if (err) {
this.logChannel.error(`resolving 'mocha${suffix}' failed with error ${err}`);
reject(
new HumanError(
`Could not find mocha in working directory '${path.dirname(
this.uri.fsPath,
)}', please install mocha to run tests.`,
),
);
} else {
this.logChannel.debug(`'mocha${suffix}' resolved to '${res}'`);
resolve(res as string);
}
});
});
}

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

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

config = this._optionsModule.loadOptions();
this.logChannel.debug(`Loaded mocharc via Mocha`);
} finally {
this.logChannel.debug(`Reading mocharc, changing working directory back to ${currentCwd}`);
process.chdir(currentCwd);
}

return new ConfigurationList(this.uri, config, this.wf);
return new ConfigurationList(this.logChannel, this.uri, config, this.wf);
}

/**
Expand All @@ -196,6 +211,7 @@ export class ConfigurationList {
)[];

constructor(
private readonly logChannel: vscode.LogOutputChannel,
public readonly uri: vscode.Uri,
public readonly value: IResolvedConfiguration,
wf: vscode.WorkspaceFolder,
Expand Down Expand Up @@ -238,6 +254,8 @@ export class ConfigurationList {
}),
);
}

this.logChannel.debug(`Loaded mocharc via '${uri.fsPath}', with patterns`, this.patterns);
}

/**
Expand Down
79 changes: 72 additions & 7 deletions src/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,49 @@ import { TsConfigStore } from './tsconfig-store';

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

type TestNodeCountKind = '+' | '~' | '-';
class TestNodeCounter {
private counts: Map<NodeKind, Map<TestNodeCountKind, number>> = new Map();

public add(kind: NodeKind) {
this.increment(kind, '+');
}

public update(kind: NodeKind) {
this.increment(kind, '~');
}

public remove(kind: NodeKind) {
this.increment(kind, '-');
}

increment(nodeKind: NodeKind, countKind: TestNodeCountKind) {
let counts = this.counts.get(nodeKind);
if (!counts) {
counts = new Map([
['+', 0],
['~', 0],
['-', 0],
]);
this.counts.set(nodeKind, counts);
}
counts.set(countKind, (counts.get(countKind) ?? 0) + 1);
}

toString() {
const s = [];
for (const [nodeKind, nodeKindV] of this.counts) {
const prefix = `${NodeKind[nodeKind]}:`;
const values = [];
for (const [countKind, count] of nodeKindV) {
values.push(`${countKind}${count}`);
}
s.push(`${prefix} ${values.join(' ')}`);
}
return s.join('; ');
}
}

export class Controller {
private readonly disposables = new DisposableStore();
public readonly configFile: ConfigurationFile;
Expand Down Expand Up @@ -82,20 +125,21 @@ export class Controller {
configFileUri.fsPath,
);
this.disposables.add(ctrl);
this.configFile = this.disposables.add(new ConfigurationFile(configFileUri, wf));
this.configFile = this.disposables.add(new ConfigurationFile(logChannel, configFileUri, wf));
this.onDidDelete = this.configFile.onDidDelete;

this.recreateDiscoverer();

const rescan = () => {
const rescan = (reason: string) => {
logChannel.info(`Rescan of tests triggered (${reason})`);
this.recreateDiscoverer();
this.scanFiles();
};
this.disposables.add(this.configFile.onDidChange(rescan));
this.disposables.add(this.settings.onDidChange(rescan));
this.disposables.add(this.configFile.onDidChange(() => rescan('mocharc changed')));
this.disposables.add(this.settings.onDidChange(() => rescan('settings changed')));
ctrl.refreshHandler = () => {
this.configFile.forget();
rescan();
rescan('user');
};
this.scanFiles();
}
Expand Down Expand Up @@ -167,12 +211,15 @@ export class Controller {
}

if (!tree.length) {
this.logChannel.info(`No tests found in '${uri.fsPath}'`);
this.deleteFileTests(uri.toString());
return;
}

const smMaintainer = previous?.sourceMap ?? this.smStore.maintain(uri);
const sourceMap = await smMaintainer.refresh(contents);

const counter = new TestNodeCounter();
const add = (
parent: vscode.TestItem,
node: IParsedNode,
Expand All @@ -182,10 +229,13 @@ export class Controller {
let item = parent.children.get(node.name);
if (!item) {
item = this.ctrl.createTestItem(node.name, node.name, start.uri);
counter.add(node.kind);
testMetadata.set(item, {
type: node.kind === NodeKind.Suite ? ItemType.Suite : ItemType.Test,
});
parent.children.add(item);
} else {
counter.update(node.kind);
}
item.range = new vscode.Range(start.range.start, end.range.end);
item.error = node.error;
Expand All @@ -206,9 +256,15 @@ export class Controller {
seen.set(child.name, add(item, child, start, end));
}

for (const [id] of item.children) {
for (const [id, child] of item.children) {
if (!seen.has(id)) {
const meta = testMetadata.get(child);
item.children.delete(id);
if (meta?.type === ItemType.Test) {
counter.remove(NodeKind.Test);
} else if (meta?.type === ItemType.Suite) {
counter.remove(NodeKind.Suite);
}
}
}

Expand All @@ -234,11 +290,19 @@ export class Controller {
if (previous) {
for (const [id, test] of previous.items) {
if (!newTestsInFile.has(id)) {
const meta = testMetadata.get(test);
(test.parent?.children ?? this.ctrl.items).delete(id);
if (meta?.type === ItemType.Test) {
counter.remove(NodeKind.Test);
} else if (meta?.type === ItemType.Suite) {
counter.remove(NodeKind.Suite);
}
}
}
}

this.logChannel.info(`Reloaded tests from '${uri.fsPath}' ${counter}`);

this.testsInFiles.set(uri.toString(), { items: newTestsInFile, hash, sourceMap: smMaintainer });
this.didChangeEmitter.fire();
}
Expand Down Expand Up @@ -353,7 +417,8 @@ export class Controller {
let configs: ConfigurationList;
try {
configs = await this.configFile.read();
} catch {
} catch (e) {
this.logChannel.error(e as Error, 'Failed to read config file');
this.handleScanError();
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/discoverer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export interface IExtensionSettings {
extractTimeout: number;
}

export const enum NodeKind {
export enum NodeKind {
Suite,
Test,
}
Expand Down
4 changes: 2 additions & 2 deletions src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export class TestRunner {
const ds = new DisposableStore();

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

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

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

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

0 comments on commit 3b8f9f8

Please sign in to comment.