Skip to content

Commit 5164475

Browse files
authored
NEW @W-19397402@ Added hidden root_working_folder config property (#370)
1 parent c94ddbd commit 5164475

File tree

6 files changed

+115
-4
lines changed

6 files changed

+115
-4
lines changed

packages/code-analyzer-core/src/code-analyzer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export class CodeAnalyzer {
116116
constructor(config: CodeAnalyzerConfig, fileSystem: FileSystem = new RealFileSystem(), nodeVersion: string = process.version) {
117117
this.validateEnvironment(nodeVersion);
118118
this.config = config;
119-
this.tempFolder = new TempFolder(fileSystem);
119+
this.tempFolder = new TempFolder(fileSystem, this.config.getRootWorkingFolder());
120120
/* istanbul ignore next */
121121
process.addListener('exit', async () => {
122122
// Note that on node exit there is no more event loop, so removal must take place synchronously

packages/code-analyzer-core/src/config.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const FIELDS = {
1313
CONFIG_ROOT: 'config_root',
1414
LOG_FOLDER: 'log_folder',
1515
LOG_LEVEL: 'log_level',
16+
ROOT_WORKING_FOLDER: 'root_working_folder', // Hidden
1617
CUSTOM_ENGINE_PLUGIN_MODULES: 'custom_engine_plugin_modules', // Hidden
1718
PRESERVE_ALL_WORKING_FOLDERS: 'preserve_all_working_folders', // Hidden
1819
RULES: 'rules',
@@ -42,6 +43,7 @@ type TopLevelConfig = {
4243
log_level: LogLevel
4344
rules: Record<string, RuleOverrides>
4445
engines: Record<string, EngineOverrides>
46+
root_working_folder: string, // INTERNAL USE ONLY
4547
preserve_all_working_folders: boolean // INTERNAL USE ONLY
4648
custom_engine_plugin_modules: string[] // INTERNAL USE ONLY
4749
}
@@ -53,6 +55,7 @@ export const DEFAULT_CONFIG: TopLevelConfig = {
5355
log_level: LogLevel.Debug,
5456
rules: {},
5557
engines: {},
58+
root_working_folder: os.tmpdir(), // INTERNAL USE ONLY
5659
preserve_all_working_folders: false, // INTERNAL USE ONLY
5760
custom_engine_plugin_modules: [], // INTERNAL USE ONLY
5861
};
@@ -139,7 +142,7 @@ export class CodeAnalyzerConfig {
139142
configRoot = !rawConfig.config_root ? (configRoot ?? process.cwd()) :
140143
validateAbsoluteFolder(rawConfig.config_root, FIELDS.CONFIG_ROOT);
141144
const configExtractor: engApi.ConfigValueExtractor = new engApi.ConfigValueExtractor(rawConfig, '', configRoot);
142-
configExtractor.addKeysThatBypassValidation([FIELDS.CUSTOM_ENGINE_PLUGIN_MODULES, FIELDS.PRESERVE_ALL_WORKING_FOLDERS]); // Hidden fields bypass validation
145+
configExtractor.addKeysThatBypassValidation([FIELDS.CUSTOM_ENGINE_PLUGIN_MODULES, FIELDS.PRESERVE_ALL_WORKING_FOLDERS, FIELDS.ROOT_WORKING_FOLDER]); // Hidden fields bypass validation
143146
configExtractor.validateContainsOnlySpecifiedKeys([FIELDS.CONFIG_ROOT, FIELDS.LOG_FOLDER, FIELDS.LOG_LEVEL ,FIELDS.RULES, FIELDS.ENGINES]);
144147
const config: TopLevelConfig = {
145148
config_root: configRoot,
@@ -148,6 +151,7 @@ export class CodeAnalyzerConfig {
148151
custom_engine_plugin_modules: configExtractor.extractArray(FIELDS.CUSTOM_ENGINE_PLUGIN_MODULES,
149152
engApi.ValueValidator.validateString,
150153
DEFAULT_CONFIG.custom_engine_plugin_modules)!,
154+
root_working_folder: !rawConfig.root_working_folder ? os.tmpdir() : validateAbsoluteFolder(rawConfig.root_working_folder, FIELDS.ROOT_WORKING_FOLDER),
151155
preserve_all_working_folders: configExtractor.extractBoolean(FIELDS.PRESERVE_ALL_WORKING_FOLDERS, DEFAULT_CONFIG.preserve_all_working_folders)!,
152156
rules: extractRulesValue(configExtractor),
153157
engines: extractEnginesValue(configExtractor)
@@ -239,6 +243,15 @@ export class CodeAnalyzerConfig {
239243
return this.config.preserve_all_working_folders;
240244
}
241245

246+
247+
/**
248+
* Returns the absolute path to a folder that will serve as the root for all temporary working folders associated with
249+
* this execution.
250+
*/
251+
public getRootWorkingFolder(): string {
252+
return this.config.root_working_folder;
253+
}
254+
242255
/**
243256
* Returns a {@link RuleOverrides} instance containing the user specified overrides for all rules associated with the specified engine
244257
* @param engineName name of the engine

packages/code-analyzer-core/src/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ export class TempFolder {
6464
private rootFolder?: string;
6565
private relPathsToKeep: Set<string> = new Set();
6666

67-
constructor(fileSystem: FileSystem = new RealFileSystem(), rootFolderPrefix: string = path.join(os.tmpdir(), 'code-analyzer-')) {
67+
constructor(fileSystem: FileSystem = new RealFileSystem(), rootFolderPath: string = os.tmpdir()) {
6868
this.fileSystem = fileSystem;
69-
this.rootFolderPrefix = rootFolderPrefix;
69+
this.rootFolderPrefix = path.join(rootFolderPath, 'code-analyzer-');
7070
}
7171

7272
async getPath(...subfolderPathSegments: string[]): Promise<string> {

packages/code-analyzer-core/test/code-analyzer.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,38 @@ describe("Tests for the run method of CodeAnalyzer", () => {
865865
expect(fileSystem.files).not.toContain(expectedRunWorkingFolderForStubEngine3);
866866
});
867867

868+
it("When running rules, the root_working_folder config property designates where working folders are created", async () => {
869+
await setupCodeAnalyzerWithStubs(CodeAnalyzerConfig.fromObject({
870+
root_working_folder: path.resolve(__dirname, 'test-data')
871+
}));
872+
await codeAnalyzer.run(selection, sampleRunOptions);
873+
874+
const expectedRunWorkingFolderRoot: string = path.resolve(__dirname, 'test-data','code-analyzer-0','run-' + clock.formatToDateTimeString());
875+
const expectedRunWorkingFolderForStubEngine1: string = path.join(expectedRunWorkingFolderRoot, 'stubEngine1');
876+
const expectedRunWorkingFolderForStubEngine2: string = path.join(expectedRunWorkingFolderRoot, 'stubEngine2');
877+
const expectedRunWorkingFolderForStubEngine3: string = path.join(expectedRunWorkingFolderRoot, 'stubEngine3');
878+
879+
// First confirm that the root folder and all 3 engines run working folders were created
880+
const createdFolders: string[] = fileSystem.mkdirCallHistory.map(args => args.absPath.toString());
881+
expect(createdFolders).toContain(expectedRunWorkingFolderRoot);
882+
expect(createdFolders).toContain(expectedRunWorkingFolderForStubEngine1);
883+
expect(createdFolders).toContain(expectedRunWorkingFolderForStubEngine2);
884+
expect(createdFolders).toContain(expectedRunWorkingFolderForStubEngine3);
885+
886+
// Confirm that the root folder and all 3 engines run working folders were removed (because none of them errored during run)
887+
const removedFolders: string[] = fileSystem.rmCallHistory.map(args => args.absPath.toString());
888+
expect(removedFolders).toContain(expectedRunWorkingFolderRoot);
889+
expect(removedFolders).toContain(expectedRunWorkingFolderForStubEngine1);
890+
expect(removedFolders).toContain(expectedRunWorkingFolderForStubEngine2);
891+
expect(removedFolders).toContain(expectedRunWorkingFolderForStubEngine3);
892+
893+
// Verify end result
894+
expect(fileSystem.files).not.toContain(expectedRunWorkingFolderRoot);
895+
expect(fileSystem.files).not.toContain(expectedRunWorkingFolderForStubEngine1);
896+
expect(fileSystem.files).not.toContain(expectedRunWorkingFolderForStubEngine2);
897+
expect(fileSystem.files).not.toContain(expectedRunWorkingFolderForStubEngine3);
898+
});
899+
868900

869901
it("When running rules, if the top-level preserve_all_working_folders flag is true, all run working folders are preserved and a log is issued", async () => {
870902
await setupCodeAnalyzerWithStubs(CodeAnalyzerConfig.fromObject({

packages/code-analyzer-core/test/config.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe("Tests for creating and accessing configuration values", () => {
2020
expect(conf.getLogLevel()).toEqual(LogLevel.Debug);
2121
expect(conf.getCustomEnginePluginModules()).toEqual([]);
2222
expect(conf.getPreserveAllWorkingFolders()).toEqual(false);
23+
expect(conf.getRootWorkingFolder()).toEqual(os.tmpdir());
2324
expect(conf.getRuleOverridesFor("stubEngine1")).toEqual({});
2425
expect(conf.getEngineOverridesFor("stubEngine1")).toEqual({});
2526
expect(conf.getRuleOverridesFor("stubEngine2")).toEqual({});
@@ -83,6 +84,7 @@ describe("Tests for creating and accessing configuration values", () => {
8384
expect(conf.getLogFolder()).toEqual(os.tmpdir());
8485
expect(conf.getCustomEnginePluginModules()).toEqual(['dummy_plugin_module_path']);
8586
expect(conf.getPreserveAllWorkingFolders()).toEqual(true);
87+
expect(conf.getRootWorkingFolder()).toEqual(os.tmpdir());
8688
expect(conf.getRuleOverridesFor('stubEngine1')).toEqual({});
8789
expect(conf.getRuleOverridesFor('stubEngine2')).toEqual({
8890
stub2RuleC: {
@@ -306,6 +308,27 @@ describe("Tests for creating and accessing configuration values", () => {
306308
getMessageFromCatalog(SHARED_MESSAGE_CATALOG, 'ConfigValueMustBeOfType','preserve_all_working_folders', 'boolean', 'string'));
307309
})
308310

311+
it("When supplied root_working_folder is a valid absolute path, then we use it", () => {
312+
const workingFoldersRootValue: string = path.join(TEST_DATA_DIR, 'sampleWorkspace');
313+
const conf: CodeAnalyzerConfig = CodeAnalyzerConfig.fromObject({root_working_folder: workingFoldersRootValue});
314+
expect(conf.getRootWorkingFolder()).toEqual(workingFoldersRootValue);
315+
});
316+
317+
it("When supplied root_working_folder does not exist, then we error", () => {
318+
expect(() => CodeAnalyzerConfig.fromObject({root_working_folder: path.resolve('doesNotExist')})).toThrow(
319+
getMessageFromCatalog(SHARED_MESSAGE_CATALOG, 'ConfigPathValueDoesNotExist', 'root_working_folder', path.resolve('doesNotExist')));
320+
});
321+
322+
it("When supplied root_working_folder is a file instead of a folder, then we error", () => {
323+
expect(() => CodeAnalyzerConfig.fromObject({root_working_folder: path.resolve('package.json')})).toThrow(
324+
getMessageFromCatalog(SHARED_MESSAGE_CATALOG, 'ConfigFolderValueMustNotBeFile', 'root_working_folder', path.resolve('package.json')));
325+
});
326+
327+
it("When supplied root_working_folder is a relative folder, then we error", () => {
328+
expect(() => CodeAnalyzerConfig.fromObject({root_working_folder: 'test/test-data'})).toThrow(
329+
getMessage('ConfigPathValueMustBeAbsolute', 'root_working_folder', 'test/test-data', path.resolve('test', 'test-data')));
330+
});
331+
309332
it("When supplied config_root path is a valid absolute path, then we use it", () => {
310333
const configRootValue: string = path.join(TEST_DATA_DIR, 'sampleWorkspace');
311334
const conf: CodeAnalyzerConfig = CodeAnalyzerConfig.fromObject({config_root: configRootValue});

packages/code-analyzer-core/test/rule-selection.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,49 @@ describe('Tests for selecting rules', () => {
641641
expect(relevantLogMsgs.filter(m => m.endsWith(expectedRulesWorkingFolderForStubEngine2))).toHaveLength(1);
642642
});
643643

644+
it("When selecting rules, the root_working_folder config property designates where the working folders are created", async () => {
645+
await setupCodeAnalyzerWithStubPlugin(CodeAnalyzerConfig.fromObject({
646+
root_working_folder: path.resolve(__dirname, 'test-data')
647+
}));
648+
649+
const logEvents: LogEvent[] = [];
650+
codeAnalyzer.onEvent(EventType.LogEvent, (event: LogEvent) => logEvents.push(event));
651+
652+
await codeAnalyzer.selectRules(['all']);
653+
654+
const expectedRulesWorkingFolderRoot: string = path.resolve(__dirname, 'test-data', 'code-analyzer-0', 'rules-' + clock.formatToDateTimeString());
655+
const expectedRulesWorkingFolderForStubEngine1: string = path.join(expectedRulesWorkingFolderRoot, 'stubEngine1');
656+
const expectedRulesWorkingFolderForStubEngine2: string = path.join(expectedRulesWorkingFolderRoot, 'stubEngine2');
657+
const expectedRulesWorkingFolderForStubEngine3: string = path.join(expectedRulesWorkingFolderRoot, 'stubEngine3');
658+
659+
// First confirm that the root folder and all 3 engine rule working folders were created
660+
const createdFolders: string[] = fileSystem.mkdirCallHistory.map(args => args.absPath.toString());
661+
expect(createdFolders).toContain(expectedRulesWorkingFolderRoot);
662+
expect(createdFolders).toContain(expectedRulesWorkingFolderForStubEngine1);
663+
expect(createdFolders).toContain(expectedRulesWorkingFolderForStubEngine2);
664+
expect(createdFolders).toContain(expectedRulesWorkingFolderForStubEngine3);
665+
666+
// Next confirm that the root folder and 2 of the engine working folders were kept while 1 was removed (because all issued errors except for stubEngine1)
667+
const removedFolders: string[] = fileSystem.rmCallHistory.map(args => args.absPath.toString());
668+
expect(removedFolders).not.toContain(expectedRulesWorkingFolderRoot);
669+
expect(removedFolders).toContain(expectedRulesWorkingFolderForStubEngine1);
670+
expect(removedFolders).not.toContain(expectedRulesWorkingFolderForStubEngine2);
671+
expect(removedFolders).not.toContain(expectedRulesWorkingFolderForStubEngine3);
672+
673+
// Verify end result
674+
expect(fileSystem.files).toContain(expectedRulesWorkingFolderRoot);
675+
expect(fileSystem.files).not.toContain(expectedRulesWorkingFolderForStubEngine1);
676+
expect(fileSystem.files).toContain(expectedRulesWorkingFolderForStubEngine2);
677+
expect(fileSystem.files).toContain(expectedRulesWorkingFolderForStubEngine3);
678+
679+
// Verify log lines
680+
const relevantLogMsgs: string[] = logEvents.filter(e => e.logLevel === LogLevel.Debug &&
681+
e.message.includes('the following temporary working folder will not be removed')).map(e => e.message);
682+
expect(relevantLogMsgs.filter(m => m.endsWith(expectedRulesWorkingFolderForStubEngine1))).toHaveLength(0);
683+
expect(relevantLogMsgs.filter(m => m.endsWith(expectedRulesWorkingFolderForStubEngine2))).toHaveLength(1);
684+
expect(relevantLogMsgs.filter(m => m.endsWith(expectedRulesWorkingFolderForStubEngine2))).toHaveLength(1);
685+
});
686+
644687
it("When selecting rules, if preserve_all_working_folders is true, then all working folders are kept regardless of failures and the root is logged", async () => {
645688
await setupCodeAnalyzerWithStubPlugin(CodeAnalyzerConfig.fromObject({
646689
preserve_all_working_folders: true

0 commit comments

Comments
 (0)