From 09b2d9bcddcd3d32fd1cf4ad15de6b1093a07e64 Mon Sep 17 00:00:00 2001 From: Mate Pek Date: Sat, 16 Dec 2023 13:40:03 +0700 Subject: [PATCH] executable cloning --- CHANGELOG.md | 6 +++ CONTRIBUTING.md | 1 + .../configuration/test.advancedExecutables.md | 3 +- documents/support.md | 4 ++ package.json | 9 +++- src/AdvancedExecutableInterface.ts | 1 + src/ConfigOfExecGroup.ts | 6 +++ src/Configurations.ts | 6 ++- src/WorkspaceShared.ts | 5 -- src/framework/AbstractExecutable.ts | 50 +++++++++++++++++-- src/framework/Catch2/Catch2Executable.ts | 8 ++- src/framework/ExecutableFactory.ts | 2 + .../GoogleBenchmarkExecutable.ts | 5 +- .../GoogleTest/GoogleTestExecutable.ts | 6 +-- src/framework/SharedVarOfExec.ts | 1 + src/framework/doctest/DOCExecutable.ts | 5 +- src/util/BuildProcessChecker.ts | 2 +- 17 files changed, 93 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58199e3c..c8defab2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 11a00452..32c882b7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 diff --git a/documents/configuration/test.advancedExecutables.md b/documents/configuration/test.advancedExecutables.md index 17cadc0c..44f2d6bf 100644 --- a/documents/configuration/test.advancedExecutables.md +++ b/documents/configuration/test.advancedExecutables.md @@ -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) | diff --git a/documents/support.md b/documents/support.md index 18c1d715..fdee290e 100644 --- a/documents/support.md +++ b/documents/support.md @@ -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 diff --git a/package.json b/package.json index 59e97d39..f3cedde7 100644 --- a/package.json +++ b/package.json @@ -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" @@ -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 diff --git a/src/AdvancedExecutableInterface.ts b/src/AdvancedExecutableInterface.ts index 6b885ed1..87625cca 100644 --- a/src/AdvancedExecutableInterface.ts +++ b/src/AdvancedExecutableInterface.ts @@ -17,6 +17,7 @@ export type AdvancedExecutableConfig = { parallelizationLimit?: number; strictPattern?: boolean; markAsSkipped?: boolean; + executableCloning?: boolean; waitForBuildProcess?: boolean | string; catch2?: FrameworkSpecificConfig; gtest?: FrameworkSpecificConfig; diff --git a/src/ConfigOfExecGroup.ts b/src/ConfigOfExecGroup.ts index c7a9e056..55d08456 100644 --- a/src/ConfigOfExecGroup.ts +++ b/src/ConfigOfExecGroup.ts @@ -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'; /// @@ -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, @@ -352,6 +354,7 @@ export class ConfigOfExecGroup implements vscode.Disposable { varToValue, this._parallelizationLimit, this._markAsSkipped === true, + this._executableCloning === true, this._runTask, spawner, resolvedSourceFileMap, @@ -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; } diff --git a/src/Configurations.ts b/src/Configurations.ts index c21ee45a..be2471cc 100644 --- a/src/Configurations.ts +++ b/src/Configurations.ts @@ -440,7 +440,8 @@ export class Configurations { [], { before: [], beforeEach: [], after: [], afterEach: [] }, defaultParallelExecutionOfExecLimit, - false, + undefined, + undefined, undefined, undefined, undefined, @@ -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; @@ -578,6 +581,7 @@ export class Configurations { parallelizationLimit, strictPattern, markAsSkipped, + executableCloning, waitForBuildProcess, spawnerConfig, sourceFileMap, diff --git a/src/WorkspaceShared.ts b/src/WorkspaceShared.ts index a1b69fe2..a85f4059 100644 --- a/src/WorkspaceShared.ts +++ b/src/WorkspaceShared.ts @@ -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 { @@ -64,7 +63,3 @@ export class WorkspaceShared { readonly onDidChangeExecRunningTimeout = this._execRunningTimeoutChangeEmitter.event; } - -export interface ExecutableShared extends Readonly { - readonly log: Logger; -} diff --git a/src/framework/AbstractExecutable.ts b/src/framework/AbstractExecutable.ts index 7f00ca45..ff26473c 100644 --- a/src/framework/AbstractExecutable.ts +++ b/src/framework/AbstractExecutable.ts @@ -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'; @@ -633,20 +634,45 @@ export abstract class AbstractExecutable { + 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 { 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); @@ -662,7 +688,7 @@ export abstract class AbstractExecutable { - this.shared.log.info('proc close:', this.shared.path, args); + this.shared.log.info('proc close:', pathForExecution, args); trigger('closed'); }); @@ -759,7 +785,7 @@ export abstract class AbstractExecutable { 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 diff --git a/src/framework/ExecutableFactory.ts b/src/framework/ExecutableFactory.ts index 5a62e2f4..7fed0514 100644 --- a/src/framework/ExecutableFactory.ts +++ b/src/framework/ExecutableFactory.ts @@ -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, @@ -65,6 +66,7 @@ export class ExecutableFactory { frameworkSpecific, this._parallelizationLimit, this._markAsSkipped, + this._executableCloning, this._runTask, this._spawner, this._resolvedSourceFileMap, diff --git a/src/framework/GoogleBenchmark/GoogleBenchmarkExecutable.ts b/src/framework/GoogleBenchmark/GoogleBenchmarkExecutable.ts index fadba5ad..973f9239 100644 --- a/src/framework/GoogleBenchmark/GoogleBenchmarkExecutable.ts +++ b/src/framework/GoogleBenchmark/GoogleBenchmarkExecutable.ts @@ -74,8 +74,9 @@ export class GoogleBenchmarkExecutable extends AbstractExecutable googleBenchmarkTestListOutput.stderr: ', listOutput); diff --git a/src/framework/GoogleTest/GoogleTestExecutable.ts b/src/framework/GoogleTest/GoogleTestExecutable.ts index 7b9eadd6..a17c7046 100644 --- a/src/framework/GoogleTest/GoogleTestExecutable.ts +++ b/src/framework/GoogleTest/GoogleTestExecutable.ts @@ -162,9 +162,9 @@ export class GoogleTestExecutable extends AbstractExecutable { `--${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 => { const hasXmlFile = await promisify(fs.exists)(cacheFile); diff --git a/src/framework/SharedVarOfExec.ts b/src/framework/SharedVarOfExec.ts index 976ead36..6a196067 100644 --- a/src/framework/SharedVarOfExec.ts +++ b/src/framework/SharedVarOfExec.ts @@ -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, diff --git a/src/framework/doctest/DOCExecutable.ts b/src/framework/doctest/DOCExecutable.ts index ea88cab7..3f311cd2 100644 --- a/src/framework/doctest/DOCExecutable.ts +++ b/src/framework/doctest/DOCExecutable.ts @@ -121,8 +121,9 @@ export class DOCExecutable extends AbstractExecutable { '--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( diff --git a/src/util/BuildProcessChecker.ts b/src/util/BuildProcessChecker.ts index 7bfb09d6..3d102613 100644 --- a/src/util/BuildProcessChecker.ts +++ b/src/util/BuildProcessChecker.ts @@ -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