Skip to content

Commit

Permalink
executable cloning
Browse files Browse the repository at this point in the history
  • Loading branch information
matepek committed Dec 16, 2023
1 parent 9f3c7bf commit 09b2d9b
Show file tree
Hide file tree
Showing 17 changed files with 93 additions and 27 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]

## [4.7.0]

### Added

- `testMate.test.advancedExecutables` - `executableCloning`: (experimental) If enabled it creates a copy of the test executable before listing or running the tests. NOTE: discovery (`--help`) still uses the original file.

## [4.6.3] - 2023-11-17

### Fixed
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,4 @@ bcomp "./out/latest_checked.vscode.d.ts" "./out/latest.vscode.d.ts"
### TODOs

- 1. run skipped directly; 2. run skipped by parent; see status: not resetted, shouldn't be
- 2. security concern note
3 changes: 2 additions & 1 deletion documents/configuration/test.advancedExecutables.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ If it is an object it can contains the following properties:
| `parallelizationLimit` | The variable maximize the number of the parallel execution of one executable instance. Note: `testMate.cpp.test.parallelExecutionLimit` is a global limit. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md) |
| `strictPattern` | Test loading fails if one of the files matched by `pattern` is not a test executable. (Helps noticing unexpected crashes/problems under test loading.) |
| `markAsSkipped` | If true then all the tests related to the pattern are skipped. They can be run manually though. |
| `waitForBuildProcess` | (experimental) Prevents the extension of auto-reloading. With this linking failure might can be avoided. Can be true to use a default pattern that works for most cases, or a string to pass your own search pattern (regex) for processes. |
| `executableCloning` | (experimental) If enabled it creates a copy of the test executable before listing or running the tests. NOTE: discovery (`--help`) still uses the original file. |
| `waitForBuildProcess` | Prevents the extension of auto-reloading. With this linking failure might can be avoided. Can be true to use a default pattern that works for most cases, or a string to pass your own search pattern (regex) for processes. |
| `catch2` | Object with framework specific settings. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#framework-specific-settings) |
| `gtest` | Object with framework specific settings. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#framework-specific-settings) |
| `doctest` | Object with framework specific settings. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#framework-specific-settings) |
Expand Down
4 changes: 4 additions & 0 deletions documents/support.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ BUT before that read ALL this document.

### Link / Build process fails because the executable is locked by this extension.

> Check `testMate.cpp.test.advancedExecutables` -> `executableCloning`.
and

> Check `testMate.cpp.test.advancedExecutables` -> `waitForBuildProcess`.
### Debug button doesn't work / stopped working
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,13 @@
"type": "boolean",
"default": false
},
"executableCloning": {
"markdownDescription": "(experimental) If enabled it creates a copy of the test executable before listing or running the tests. NOTE: discovery (`--help`) still uses the original file.",
"type": "boolean",
"default": false
},
"waitForBuildProcess": {
"markdownDescription": "(experimental) Prevents the extension of auto-reloading. With this linking failure might can be avoided. Can be true to use a default pattern that works for most cases, or a string to pass your own search pattern (regex) for processes.",
"markdownDescription": "Prevents the extension of auto-reloading. With this linking failure might can be avoided. Can be true to use a default pattern that works for most cases, or a string to pass your own search pattern (regex) for processes.",
"type": [
"boolean",
"string"
Expand Down Expand Up @@ -1416,7 +1421,7 @@
"maximum": 900
},
"testMate.cpp.discovery.testListCaching": {
"markdownDescription": "(Experimental) In case your executable took too much time to list the tests, one can set this. It will preserve the output of `--gtest_list_tests --gtest_output=xml:...`. (Beware: Older Google Test doesn't support xml test list format.)",
"markdownDescription": "In case your executable took too much time to list the tests, one can set this. It will preserve the output of `--gtest_list_tests --gtest_output=xml:...`. (Beware: Older Google Test doesn't support xml test list format.)",
"scope": "resource",
"type": "boolean",
"default": false
Expand Down
1 change: 1 addition & 0 deletions src/AdvancedExecutableInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type AdvancedExecutableConfig = {
parallelizationLimit?: number;
strictPattern?: boolean;
markAsSkipped?: boolean;
executableCloning?: boolean;
waitForBuildProcess?: boolean | string;
catch2?: FrameworkSpecificConfig;
gtest?: FrameworkSpecificConfig;
Expand Down
6 changes: 6 additions & 0 deletions src/ConfigOfExecGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { FrameworkType } from './framework/Framework';
import { readFileSync } from 'fs';
import { getModiTime } from './Util';
import { SubProgressReporter } from './util/ProgressReporter';
import { ExecCloner } from './framework/AbstractExecutable';

///

Expand All @@ -39,6 +40,7 @@ export class ConfigOfExecGroup implements vscode.Disposable {
private readonly _parallelizationLimit: number,
private readonly _strictPattern: boolean | undefined,
private readonly _markAsSkipped: boolean | undefined,
private readonly _executableCloning: boolean | undefined,
private readonly _waitForBuildProcess: boolean | string | undefined,
private readonly _executionWrapper: ExecutionWrapperConfig | undefined,
private readonly _sourceFileMap: Record<string, string>,
Expand Down Expand Up @@ -352,6 +354,7 @@ export class ConfigOfExecGroup implements vscode.Disposable {
varToValue,
this._parallelizationLimit,
this._markAsSkipped === true,
this._executableCloning === true,
this._runTask,
spawner,
resolvedSourceFileMap,
Expand Down Expand Up @@ -508,6 +511,9 @@ export class ConfigOfExecGroup implements vscode.Disposable {
// cmake fetches the dependencies here. we dont care about it 🤞
this._shared.log.info('skipping because it is under "/CMakeFiles/"', filePath);
return true;
} else if (filePath.endsWith(ExecCloner.suffix)) {
this._shared.log.info('skipping because it is part of the cloning feature of this extension', filePath);
return true;
} else {
return false;
}
Expand Down
6 changes: 5 additions & 1 deletion src/Configurations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,8 @@ export class Configurations {
[],
{ before: [], beforeEach: [], after: [], afterEach: [] },
defaultParallelExecutionOfExecLimit,
false,
undefined,
undefined,
undefined,
undefined,
undefined,
Expand Down Expand Up @@ -547,6 +548,8 @@ export class Configurations {

const markAsSkipped: boolean | undefined = obj.markAsSkipped;

const executableCloning: boolean | undefined = obj.executableCloning;

const waitForBuildProcess: boolean | string | undefined = obj.waitForBuildProcess;

const defaultTestGrouping = obj.testGrouping;
Expand Down Expand Up @@ -578,6 +581,7 @@ export class Configurations {
parallelizationLimit,
strictPattern,
markAsSkipped,
executableCloning,
waitForBuildProcess,
spawnerConfig,
sourceFileMap,
Expand Down
5 changes: 0 additions & 5 deletions src/WorkspaceShared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { ResolveRuleAsync } from './util/ResolveRule';
import { BuildProcessChecker } from './util/BuildProcessChecker';
import { CancellationToken } from './Util';
import { TestItemManager } from './TestItemManager';
import { FrameworkSpecificConfig } from './AdvancedExecutableInterface';
import { AbstractExecutable } from './framework/AbstractExecutable';

export class WorkspaceShared {
Expand Down Expand Up @@ -64,7 +63,3 @@ export class WorkspaceShared {

readonly onDidChangeExecRunningTimeout = this._execRunningTimeoutChangeEmitter.event;
}

export interface ExecutableShared extends Readonly<FrameworkSpecificConfig> {
readonly log: Logger;
}
50 changes: 45 additions & 5 deletions src/framework/AbstractExecutable.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as pathlib from 'path';
import * as vscode from 'vscode';
import * as fs from 'fs';
import { EOL } from 'os';

import { SharedVarOfExec } from './SharedVarOfExec';
Expand Down Expand Up @@ -633,20 +634,45 @@ export abstract class AbstractExecutable<TestT extends AbstractTest = AbstractTe
});
}

/**
* since the cloning executable feature for test listing and test running (not for discovery)
* we should use this function which will make sure we are not working on the original file
* if the feature is enabled
*/
protected async _getPathForExecution(): Promise<string> {
if (!this.shared.executableCloning) {
return this.shared.path;
}
const origModiTime = await getModiTime(this.shared.path);
if (origModiTime === undefined) {
return this.shared.path; // no file exists, nothing to do
}

const clonePath = ExecCloner.generateClonePath(this.shared.path);
const cloneModiTime = await getModiTime(clonePath);

if (cloneModiTime === undefined || cloneModiTime < origModiTime) {
await fs.promises.copyFile(this.shared.path, clonePath, fs.constants.COPYFILE_FICLONE);
}

return clonePath;
}

private async _runProcess(testRun: vscode.TestRun, childrenToRun: readonly AbstractTest[]): Promise<void> {
const execParams = this._getRunParams(childrenToRun);

this.shared.log.info('proc starting', this.shared.path, execParams);
const pathForExecution = await this._getPathForExecution();
this.shared.log.info('proc starting', pathForExecution, execParams, this.shared.path);

const runInfo = await RunningExecutable.create(
new SpawnBuilder(this.shared.spawner, this.shared.path, execParams, this.shared.options, undefined),
new SpawnBuilder(this.shared.spawner, pathForExecution, execParams, this.shared.options, undefined),
childrenToRun,
testRun.token,
);

testRun.appendOutput(runInfo.getProcStartLine());

this.shared.log.info('proc started', runInfo.process.pid, this.shared.path, this.shared, execParams);
this.shared.log.info('proc started', runInfo.process.pid, pathForExecution, this.shared, execParams);

runInfo.setPriorityAsync(this.shared.log);

Expand All @@ -662,7 +688,7 @@ export abstract class AbstractExecutable<TestT extends AbstractTest = AbstractTe
});

runInfo.process.once('close', (...args) => {
this.shared.log.info('proc close:', this.shared.path, args);
this.shared.log.info('proc close:', pathForExecution, args);
trigger('closed');
});

Expand Down Expand Up @@ -759,7 +785,7 @@ export abstract class AbstractExecutable<TestT extends AbstractTest = AbstractTe
debugBreak(); // we really shouldnt be here
this.shared.log.exceptionS(e);
} finally {
this.shared.log.info('proc finished:', this.shared.path);
this.shared.log.info('proc finished:', pathForExecution);
}
}

Expand Down Expand Up @@ -929,3 +955,17 @@ export class TestsToRun {
for (const i of this.parent) yield i;
}
}

///

export class ExecCloner {
private constructor() {}

public static readonly prefix = '.';
public static readonly suffix = '.TestMate.execClone.tmp';

public static generateClonePath(path: string): string {
const { dir, base } = pathlib.parse(path);
return pathlib.join(dir, this.prefix + base + this.suffix);
}
}
8 changes: 3 additions & 5 deletions src/framework/Catch2/Catch2Executable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,9 @@ export class Catch2Executable extends AbstractExecutable<Catch2Test> {
if (this._catch2Version && this._catch2Version.major >= 3) args.push('--reporter', 'xml');
else args.push('--use-colour', 'no');

this.shared.log.info('discovering tests', this.shared.path, args, this.shared.options.cwd);

//const process = await this.sharedVarOfExec.spawner.spawn(this.sharedVarOfExec.path, args, this.sharedVarOfExec.options);

const catch2TestListingProcess = await this.shared.spawner.spawn(this.shared.path, args, this.shared.options);
const pathForExecution = await this._getPathForExecution();
this.shared.log.info('discovering tests', this.shared.path, pathForExecution, args, this.shared.options.cwd);
const catch2TestListingProcess = await this.shared.spawner.spawn(pathForExecution, args, this.shared.options);

const result =
this._catch2Version && this._catch2Version.major >= 3
Expand Down
2 changes: 2 additions & 0 deletions src/framework/ExecutableFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class ExecutableFactory {
private readonly _varToValue: ResolveRuleAsync[],
private readonly _parallelizationLimit: number,
private readonly _markAsSkipped: boolean,
private readonly _executableCloning: boolean,
private readonly _runTask: RunTaskConfig,
private readonly _spawner: Spawner,
private readonly _resolvedSourceFileMap: Record<string, string>,
Expand Down Expand Up @@ -65,6 +66,7 @@ export class ExecutableFactory {
frameworkSpecific,
this._parallelizationLimit,
this._markAsSkipped,
this._executableCloning,
this._runTask,
this._spawner,
this._resolvedSourceFileMap,
Expand Down
5 changes: 3 additions & 2 deletions src/framework/GoogleBenchmark/GoogleBenchmarkExecutable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ export class GoogleBenchmarkExecutable extends AbstractExecutable<GoogleBenchmar

const args = this.shared.prependTestListingArgs.concat([`--benchmark_list_tests=true`]);

this.shared.log.info('discovering tests', this.shared.path, args, this.shared.options.cwd);
const listOutput = await this.shared.spawner.spawnAsync(this.shared.path, args, this.shared.options, 30000);
const pathForExecution = await this._getPathForExecution();
this.shared.log.info('discovering tests', this.shared.path, pathForExecution, args, this.shared.options.cwd);
const listOutput = await this.shared.spawner.spawnAsync(pathForExecution, args, this.shared.options, 30000);

if (listOutput.stderr && !this.shared.ignoreTestEnumerationStdErr) {
this.shared.log.warn('reloadChildren -> googleBenchmarkTestListOutput.stderr: ', listOutput);
Expand Down
6 changes: 3 additions & 3 deletions src/framework/GoogleTest/GoogleTestExecutable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,9 @@ export class GoogleTestExecutable extends AbstractExecutable<GoogleTestTest> {
`--${this._argumentPrefix}output=xml:${cacheFile}`,
]);

this.shared.log.info('discovering tests', this.shared.path, args, this.shared.options.cwd);

const googleTestListProcess = await this.shared.spawner.spawn(this.shared.path, args, this.shared.options);
const pathForExecution = await this._getPathForExecution();
this.shared.log.info('discovering tests', this.shared.path, pathForExecution, args, this.shared.options.cwd);
const googleTestListProcess = await this.shared.spawner.spawn(pathForExecution, args, this.shared.options);

const loadFromFileIfHas = async (): Promise<boolean> => {
const hasXmlFile = await promisify(fs.exists)(cacheFile);
Expand Down
1 change: 1 addition & 0 deletions src/framework/SharedVarOfExec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class SharedVarOfExec {
private readonly _frameworkSpecific: FrameworkSpecificConfig,
_parallelizationLimit: number,
readonly markAsSkipped: boolean,
readonly executableCloning: boolean,
readonly runTask: RunTaskConfig,
readonly spawner: Spawner,
readonly resolvedSourceFileMap: Record<string, string>,
Expand Down
5 changes: 3 additions & 2 deletions src/framework/doctest/DOCExecutable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,9 @@ export class DOCExecutable extends AbstractExecutable<DOCTest> {
'--no-color=true',
]);

this.shared.log.info('discovering tests', this.shared.path, args, this.shared.options.cwd);
const docTestListOutput = await this.shared.spawner.spawnAsync(this.shared.path, args, this.shared.options, 30000);
const pathForExecution = await this._getPathForExecution();
this.shared.log.info('discovering tests', this.shared.path, pathForExecution, args, this.shared.options.cwd);
const docTestListOutput = await this.shared.spawner.spawnAsync(pathForExecution, args, this.shared.options, 30000);

if (docTestListOutput.stderr && !this.shared.ignoreTestEnumerationStdErr) {
this.shared.log.warn(
Expand Down
2 changes: 1 addition & 1 deletion src/util/BuildProcessChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class BuildProcessChecker {
private readonly _checkIntervalMillis = 2000;
// https://en.wikipedia.org/wiki/List_of_compilers#C++_compilers
private readonly _defaultPattern =
/(^|[/\\])(cmake|make|ninja|cl|c\+\+|ld|clang|clang\+\+|gcc|g\+\+|link|icc|armcc|armclang)(-[^/\\]+)?(\.exe)?$/;
/(^|[/\\])(bazel|cmake|make|ninja|cl|c\+\+|ld|clang|clang\+\+|gcc|g\+\+|link|icc|armcc|armclang)(-[^/\\]+)?(\.exe)?$/;
private _lastChecked = 0;
private _finishedP = Promise.resolve();
private _finishedResolver = (): void => {}; // eslint-disable-line
Expand Down

0 comments on commit 09b2d9b

Please sign in to comment.