From f4e23a70d0e4276a273a1e36c55165de55d1fca8 Mon Sep 17 00:00:00 2001 From: Joshua Feingold Date: Fri, 1 Mar 2024 13:55:25 -0600 Subject: [PATCH 1/2] CHANGE (CodeAnalyzer): @W-14645433@: Refactored CPD-related list tests. --- test/TestUtils.ts | 2 +- .../DefaultCatalogFixture.json | 23 +++++++++++ test/commands/scanner/e2e.cpd.test.ts | 32 +-------------- test/lib/actions/RuleListAction.test.ts | 33 ++++++++++++++- test/lib/services/LocalCatalog.test.ts | 41 ++++++++++++++----- 5 files changed, 86 insertions(+), 45 deletions(-) diff --git a/test/TestUtils.ts b/test/TestUtils.ts index 678ec2356..a2192de8b 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -14,7 +14,7 @@ import LocalCatalog from '../src/lib/services/LocalCatalog'; const CATALOG_FIXTURE_PATH = path.join('test', 'catalog-fixtures', 'DefaultCatalogFixture.json'); -export const CATALOG_FIXTURE_RULE_COUNT = 15; +export const CATALOG_FIXTURE_RULE_COUNT = 16; export const CATALOG_FIXTURE_DEFAULT_ENABLED_RULE_COUNT = 11; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/test/catalog-fixtures/DefaultCatalogFixture.json b/test/catalog-fixtures/DefaultCatalogFixture.json index dea489e9e..39b262bbe 100644 --- a/test/catalog-fixtures/DefaultCatalogFixture.json +++ b/test/catalog-fixtures/DefaultCatalogFixture.json @@ -53,6 +53,11 @@ "paths": [ "https://eslint.org/docs/rules/no-inner-declarations" ] + }, + { + "name": "Copy/Paste Detected", + "engine": "cpd", + "paths": [] } ], "rulesets": [ @@ -332,6 +337,24 @@ ], "message": "Avoid using if statements without curly braces", "engine": "pmd" + }, + { + "rulesets": [], + "defaultEnabled": true, + "sourcepackage": "cpd", + "languages": [ + "apex", + "java", + "visualforce", + "xml" + ], + "name": "copy-paste-detected", + "description": "Identify duplicate code blocks.", + "categories": [ + "Copy/Paste Detected" + ], + "message": "", + "engine": "cpd" } ] } diff --git a/test/commands/scanner/e2e.cpd.test.ts b/test/commands/scanner/e2e.cpd.test.ts index 2fef5f599..eb5418295 100644 --- a/test/commands/scanner/e2e.cpd.test.ts +++ b/test/commands/scanner/e2e.cpd.test.ts @@ -1,10 +1,9 @@ import { expect } from "chai"; -// @ts-ignore import { runCommand } from "../../TestUtils"; import path = require("path"); import { ENGINE } from "../../../src/Constants"; import { RuleResult } from "../../../src/types"; -import { CpdLanguagesSupported, CpdRuleCategory, CpdRuleDescription, CpdRuleName, CpdViolationSeverity } from "../../../src/lib/cpd/CpdEngine"; +import {CpdRuleCategory, CpdRuleName, CpdViolationSeverity } from "../../../src/lib/cpd/CpdEngine"; const Cpd_Test_Code_Path = path.join("test", "code-fixtures", "cpd"); const Vf_File1 = path.join(Cpd_Test_Code_Path, "myVfPage1.page"); @@ -112,35 +111,6 @@ describe("End to end tests for CPD engine", () => { }); }); }); - - describe("Integration with `scanner rule list` command", () => { - describe("Invoking CPD engine", () => { - it("CPD engine rules should not be displayed by default", () => { - const output = runCommand(`scanner rule list --json`); - const results = output.jsonOutput.result as any[]; - expect(results.length).to.be.greaterThan(0); - - const cpdCatalogs = results.filter(row => row.engine === ENGINE.CPD); - expect(cpdCatalogs).to.have.lengthOf(0); - }); - - it("CPD engine rules should be displayed when using `--engine cpd`", () => { - const output = runCommand(`scanner rule list --engine cpd --json`); - const results = output.jsonOutput.result as any[]; - expect(results.length).equals(1); - - // Verify properties of rule. - const rule = results[0]; - expect(rule.engine).equals(ENGINE.CPD); - expect(rule.sourcepackage).equals(ENGINE.CPD); - expect(rule.name).equals(CpdRuleName); - expect(rule.description).equals(CpdRuleDescription); - expect(rule.categories).contains(CpdRuleCategory); - expect(rule.languages).has.same.members(CpdLanguagesSupported); - expect(rule.defaultEnabled).equals(true); - }); - }); - }); }); function verifyEnvVarIsUsedForMinimumTokens(ctx) { const Minimum_Tokens_50 = [Apex_File1, Apex_File2, Vf_File1, Vf_File2].sort(); diff --git a/test/lib/actions/RuleListAction.test.ts b/test/lib/actions/RuleListAction.test.ts index ef25c6d64..99f4238fb 100644 --- a/test/lib/actions/RuleListAction.test.ts +++ b/test/lib/actions/RuleListAction.test.ts @@ -26,20 +26,29 @@ describe("Tests for RuleListAction", () => { describe('Filtering logic', () => { beforeEach(() => { - sinon.stub(Config.prototype, 'isEngineEnabled').callThrough().withArgs(ENGINE.ESLINT_LWC).resolves(false); + sinon.stub(Config.prototype, 'isEngineEnabled') + .callThrough() + .withArgs(ENGINE.ESLINT_LWC).resolves(false) + .withArgs(ENGINE.CPD).resolves(true); }); afterEach(() => { sinon.restore(); }); - it('Test Case: Without filters, all rules for enabled engines are returned', async () => { + it('Test Case: Without filters, all rules for enabled and default-runnable engines are returned', async () => { await ruleListAction.run([]); let tableData: Ux.Table.Data[] = display.getLastTableData(); for (const rowData of tableData) { expect(rowData.engine).to.not.equal('eslint-lwc', 'Should not return rules for disabled engine'); + // NOTE: Currently, CPD has the unique behavior of only running/listing rules when it's explicitly + // requested via the --engine parameter, even if it's listed as enabled. So since it wasn't + // explicitly requested, it shouldn't be included. + // This behavior is something of an anomaly, and should not be taken as ironclad. If it becomes + // advantageous or convenient to change it, we should take the opportunity to do so. + expect(rowData.engine).to.not.equal('cpd', 'Should not return rule for unrequested CPD engine'); } }); @@ -58,6 +67,26 @@ describe("Tests for RuleListAction", () => { } }); + // NOTE: Currently, CPD has the unique behavior of only running/listing rules when it's explicitly requested via + // the --engine parameter, even if it's listed as enabled. So since it's explicitly requested here, it should + // be included. + // This behavior is something of an anomaly, and should not be taken as ironclad. If it becomes + // advantageous or convenient to change it, we should take the opportunity to do so. + it('Test Case: Filtering explicitly for a default non-runnable engine will return its rules', async () => { + const inputs: Inputs = { + engine: ['cpd'] + }; + + await ruleListAction.run(inputs); + + let tableData: Ux.Table.Data[] = display.getLastTableData(); + expect(tableData).to.have.length(1); + + for (const rowData of tableData) { + expect(rowData.engine).to.equal('cpd'); + } + }); + it('Edge Case: No matching rules causes empty table', async () => { const inputs: Inputs = { category: ['beebleborp'] diff --git a/test/lib/services/LocalCatalog.test.ts b/test/lib/services/LocalCatalog.test.ts index 74d897825..3392105dd 100644 --- a/test/lib/services/LocalCatalog.test.ts +++ b/test/lib/services/LocalCatalog.test.ts @@ -1,12 +1,12 @@ import Sinon = require('sinon'); import * as TestOverrides from '../../test-related-lib/TestOverrides'; import * as TestUtils from '../../TestUtils'; -import { RuleCatalog } from '../../../src/lib/services/RuleCatalog'; -import { CategoryFilter, EngineFilter, LanguageFilter, RuleFilter, RulesetFilter } from '../../../src/lib/RuleFilter'; -import { ENGINE, LANGUAGE } from '../../../src/Constants'; -import { Rule, RuleGroup } from '../../../src/types'; +import {RuleCatalog} from '../../../src/lib/services/RuleCatalog'; +import {CategoryFilter, EngineFilter, LanguageFilter, RuleFilter, RulesetFilter} from '../../../src/lib/RuleFilter'; +import {ENGINE, LANGUAGE} from '../../../src/Constants'; +import {Rule, RuleGroup} from '../../../src/types'; import LocalCatalog from '../../../src/lib/services/LocalCatalog'; -import { expect } from 'chai'; +import {expect} from 'chai'; import {Controller} from '../../../src/Controller'; TestOverrides.initializeTestSetup(); @@ -63,6 +63,14 @@ describe('LocalCatalog', () => { validatePmdRuleGroup(mappedRuleGroups, name, languages, 'category'); }; + const validateCpdCategory = (mappedRuleGroups: Map, name: string, languages: string[]): void => { + const ruleGroup = mappedRuleGroups.get(`${ENGINE.CPD}:${name}`); + expect(ruleGroup).to.not.be.undefined; + expect(ruleGroup.name).to.equal(name); + expect(ruleGroup.engine).to.equal(ENGINE.CPD); + expect(ruleGroup.paths, TestUtils.prettyPrint(ruleGroup.paths)).to.be.lengthOf(0); + } + const validateEslintBestPractices = (mappedRuleGroups: Map): void => { for (const engine of [ENGINE.ESLINT, ENGINE.ESLINT_TYPESCRIPT]) { const ruleGroup = mappedRuleGroups.get(`${engine}:Best Practices`); @@ -174,12 +182,13 @@ describe('LocalCatalog', () => { // ASSERTIONS // ESLint and ESLint-typescript should both have one category, and PMD should have two. - expect(ruleGroups, TestUtils.prettyPrint(ruleGroups)).to.be.lengthOf(4); + expect(ruleGroups, TestUtils.prettyPrint(ruleGroups)).to.be.lengthOf(5); const mappedRuleGroups = mapRuleGroups(ruleGroups); validateEslintPossibleErrors(mappedRuleGroups); validatePmdCategory(mappedRuleGroups, 'Design', [LANGUAGE.APEX, LANGUAGE_ECMASCRIPT]); validatePmdCategory(mappedRuleGroups, 'Error Prone', [LANGUAGE.APEX, LANGUAGE_ECMASCRIPT]); + validateCpdCategory(mappedRuleGroups, 'Copy/Paste Detected', [LANGUAGE.APEX, LANGUAGE.JAVA, LANGUAGE.VISUALFORCE, LANGUAGE.XML]); }); it('Correctly filters by multiple values', async () => { @@ -193,11 +202,12 @@ describe('LocalCatalog', () => { // ASSERTIONS // ESLint, ESLint-Typescript, and PMD should each have one category. - expect(ruleGroups, TestUtils.prettyPrint(ruleGroups)).to.be.lengthOf(3); + expect(ruleGroups, TestUtils.prettyPrint(ruleGroups)).to.be.lengthOf(4); const mappedRuleGroups = mapRuleGroups(ruleGroups); validateEslintPossibleErrors(mappedRuleGroups); validatePmdCategory(mappedRuleGroups, 'Error Prone', [LANGUAGE.APEX, LANGUAGE_ECMASCRIPT]); + validateCpdCategory(mappedRuleGroups, 'Copy/Paste Detected', [LANGUAGE.APEX, LANGUAGE.JAVA, LANGUAGE.VISUALFORCE, LANGUAGE.XML]); }); }); }); @@ -226,7 +236,7 @@ describe('LocalCatalog', () => { describe('Edge Cases', () => { const validateRuleGroups = (ruleGroups: RuleGroup[]) => { - expect(ruleGroups, TestUtils.prettyPrint(ruleGroups)).to.be.lengthOf(7); + expect(ruleGroups, TestUtils.prettyPrint(ruleGroups)).to.be.lengthOf(8); const mappedRuleGroups = mapRuleGroups(ruleGroups); validateEslintBestPractices(mappedRuleGroups); @@ -234,6 +244,7 @@ describe('LocalCatalog', () => { validatePmdCategory(mappedRuleGroups, 'Best Practices', [LANGUAGE_ECMASCRIPT, LANGUAGE.APEX]); validatePmdCategory(mappedRuleGroups, 'Design', [LANGUAGE_ECMASCRIPT, LANGUAGE.APEX]); validatePmdCategory(mappedRuleGroups, 'Error Prone', [LANGUAGE_ECMASCRIPT, LANGUAGE.APEX]); + validateCpdCategory(mappedRuleGroups, 'Copy/Paste Detected', [LANGUAGE.APEX, LANGUAGE.JAVA, LANGUAGE.VISUALFORCE, LANGUAGE.XML]); } it('Returns all categories for eligible engines when given no filters', async () => { @@ -293,6 +304,11 @@ describe('LocalCatalog', () => { validateRule(mappedRules, names, categories, languages, [ENGINE.PMD]); }; + const validateCpdRule = (mappedRules: Map, names: string[], categories: string[], languages: string[]): void => { + validateRule(mappedRules, names, categories, languages, [ENGINE.CPD]); + } + + const validateEslintRule = (mappedRules: Map, names: string[], categories: string[], engines=[ENGINE.ESLINT, ENGINE.ESLINT_TYPESCRIPT]): void => { for (const engine of engines) { const languages = engine === ENGINE.ESLINT ? [LANGUAGE.JAVASCRIPT] : [LANGUAGE.TYPESCRIPT]; @@ -330,22 +346,24 @@ describe('LocalCatalog', () => { it ('Single Value', async () => { const filter: RuleFilter = new CategoryFilter(['!Possible Errors']); const rules: Rule[] = catalog.getRulesMatchingFilters([filter]); - expect(rules, TestUtils.prettyPrint(rules)).to.be.lengthOf(7); + expect(rules, TestUtils.prettyPrint(rules)).to.be.lengthOf(8); const mappedRules = mapRules(rules); validatePmdRule(mappedRules, ['AvoidDeeplyNestedIfStmts', 'ExcessiveClassLength'], ['Design'], [LANGUAGE.APEX]); validatePmdRule(mappedRules, ['AvoidWithStatement', 'ConsistentReturn'], ['Best Practices'], [LANGUAGE.JAVASCRIPT]); validatePmdRule(mappedRules, ['ForLoopsMustUseBraces', 'IfElseStmtsMustUseBraces', 'IfStmtsMustUseBraces'], ['Code Style'], [LANGUAGE.JAVASCRIPT]); + validateCpdRule(mappedRules, ['copy-paste-detected'], ['Copy/Paste Detected'], [LANGUAGE.APEX, LANGUAGE.JAVA, LANGUAGE.VISUALFORCE, LANGUAGE.XML]); }); it ('Multiple Values', async () => { const filter: RuleFilter = new CategoryFilter(['!Possible Errors', '!Code Style']); const rules: Rule[] = catalog.getRulesMatchingFilters([filter]); - expect(rules, TestUtils.prettyPrint(rules)).to.be.lengthOf(4); + expect(rules, TestUtils.prettyPrint(rules)).to.be.lengthOf(5); const mappedRules = mapRules(rules); validatePmdRule(mappedRules, ['AvoidDeeplyNestedIfStmts', 'ExcessiveClassLength'], ['Design'], [LANGUAGE.APEX]); validatePmdRule(mappedRules, ['AvoidWithStatement', 'ConsistentReturn'], ['Best Practices'], [LANGUAGE.JAVASCRIPT]); + validateCpdRule(mappedRules, ['copy-paste-detected'], ['Copy/Paste Detected'], [LANGUAGE.APEX, LANGUAGE.JAVA, LANGUAGE.VISUALFORCE, LANGUAGE.XML]); }); }); }); @@ -434,10 +452,11 @@ describe('LocalCatalog', () => { const categoryFilter: RuleFilter = new CategoryFilter(['!Possible Errors', '!Code Style']); const languageFilter: RuleFilter = new LanguageFilter([LANGUAGE.APEX]); const rules: Rule[] = catalog.getRulesMatchingFilters([categoryFilter, languageFilter]); - expect(rules, TestUtils.prettyPrint(rules)).to.be.lengthOf(2); + expect(rules, TestUtils.prettyPrint(rules)).to.be.lengthOf(3); const mappedRules = mapRules(rules); validatePmdRule(mappedRules, ['AvoidDeeplyNestedIfStmts', 'ExcessiveClassLength'], ['Design'], [LANGUAGE.APEX]); + validateCpdRule(mappedRules, ['copy-paste-detected'], ['Copy/Paste Detected'], [LANGUAGE.APEX, LANGUAGE.JAVA, LANGUAGE.VISUALFORCE, LANGUAGE.XML]); }); }); }); From 18ef5bcf56332a9aba5075a3483b675868af04d7 Mon Sep 17 00:00:00 2001 From: Joshua Feingold Date: Fri, 1 Mar 2024 16:17:38 -0600 Subject: [PATCH 2/2] CHANGE (CodeAnalyzer): @W-14645433@: Refactored some scanner run tests. --- test/commands/scanner/run.test.ts | 89 --------- ...nAction.test.ts => RunAction.pmd7.test.ts} | 0 test/lib/actions/RunAction.target.test.ts | 169 ++++++++++++++++++ 3 files changed, 169 insertions(+), 89 deletions(-) rename test/lib/actions/{RunAction.test.ts => RunAction.pmd7.test.ts} (100%) create mode 100644 test/lib/actions/RunAction.target.test.ts diff --git a/test/commands/scanner/run.test.ts b/test/commands/scanner/run.test.ts index 23f3b1715..8a006a58c 100644 --- a/test/commands/scanner/run.test.ts +++ b/test/commands/scanner/run.test.ts @@ -6,11 +6,8 @@ import {ENV_VAR_NAMES} from "../../../src/Constants"; import fs = require('fs'); import path = require('path'); import process = require('process'); -import tildify = require('tildify'); -const pathToApexFolder = path.join('test', 'code-fixtures', 'apex'); const pathToSomeTestClass = path.join('test', 'code-fixtures', 'apex', 'SomeTestClass.cls'); -const pathToSomeOtherTestClass = path.join('test', 'code-fixtures', 'apex', 'SomeOtherTestClass.cls'); const pathToAnotherTestClass = path.join('test', 'code-fixtures', 'apex', 'AnotherTestClass.cls'); const pathToYetAnotherTestClass = path.join('test', 'code-fixtures', 'apex', 'YetAnotherTestClass.cls'); @@ -30,78 +27,12 @@ describe('scanner run', function () { validateXmlOutput(output.shellOutput.stdout); }); - it('Target path may be relative or absolute', () => { - const output = runCommand(`scanner run --target ${path.join('.', pathToSomeTestClass)} --ruleset ApexUnit --format xml`); - validateXmlOutput(output.shellOutput.stdout); - }); - it('When the file contains no violations, a message is logged to the console', () => { const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --ruleset ApexUnit --format xml`); expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.noViolationsDetected', ['pmd, retire-js'])); }); }); - describe('Test Case: Running rules against multiple specified files', () => { - it('Both files are evaluated, and any violations are logged', () => { - const output = runCommand(`scanner run --target "${pathToSomeTestClass},${pathToSomeOtherTestClass}" --ruleset ApexUnit --format xml`); - // We'll split the output by the tag first, so we can get each file that violated rules. - const results = output.shellOutput.stdout.split(' tag so we can inspect individual violations. - const firstFileViolations = results[0].split(' { - it('Any violations in the folder are logged as an XML', () => { - const output = runCommand(`scanner run --target ${pathToApexFolder} --ruleset ApexUnit --format xml`); - // We'll split the output by the tag first, so we can get each file that violated rules. - const results = output.shellOutput.stdout.split(' tag so we can inspect individual violations. - const firstFileViolations = results[0].split(' { it('Violations from each rule are logged as an XML', () => { const output = runCommand(`scanner run --target ${pathToAnotherTestClass} --ruleset ApexUnit,Style --format xml`); @@ -425,26 +356,6 @@ describe('scanner run', function () { }) }); - describe('Dynamic Input', () => { - - describe('Test Case: Using ~/ shorthand in target', () => { - const pathWithTilde = tildify(path.join(process.cwd(), 'test', 'code-fixtures', 'apex', 'SomeTestClass.cls')); - - it('Tilde is expanded to full directory', () => { - const output = runCommand(`scanner run --target ${pathWithTilde} --ruleset ApexUnit --format xml`); - // We'll split the output by the tag, so we can get individual violations. - const violations = output.shellOutput.stdout.split(' { describe('Test case: No output specified', () => { it('When no format is specified, we default to a TABLE', () => { diff --git a/test/lib/actions/RunAction.test.ts b/test/lib/actions/RunAction.pmd7.test.ts similarity index 100% rename from test/lib/actions/RunAction.test.ts rename to test/lib/actions/RunAction.pmd7.test.ts diff --git a/test/lib/actions/RunAction.target.test.ts b/test/lib/actions/RunAction.target.test.ts new file mode 100644 index 000000000..5031326b1 --- /dev/null +++ b/test/lib/actions/RunAction.target.test.ts @@ -0,0 +1,169 @@ +import path = require('path'); +import {Logger} from '@salesforce/core'; +import {expect} from 'chai'; +import tildify = require('tildify'); + +import {Results} from '../../../src/lib/output/Results'; +import {Inputs, RuleResult} from '../../../src/types'; +import {RunAction} from '../../../src/lib/actions/RunAction'; +import {InputProcessorImpl} from '../../../src/lib/InputProcessor'; +import {RuleFilterFactoryImpl} from '../../../src/lib/RuleFilterFactory'; +import {RunEngineOptionsFactory} from '../../../src/lib/EngineOptionsFactory'; + +import {initializeTestSetup} from '../../test-related-lib/TestOverrides'; +import {FakeDisplay} from '../FakeDisplay'; +import {FakeResultsProcessorFactory, RawResultsProcessor} from './fakes'; + + +describe('scanner run, targeting capabilities', () => { + const PATH_TO_TEST_FOLDER = path.join('.', 'test', 'code-fixtures', 'apex'); + const PATH_TO_SOME_TEST_CLASS = path.join('.', 'test', 'code-fixtures', 'apex', 'SomeTestClass.cls'); + const PATH_TO_SOME_OTHER_TEST_CLASS = path.join('.', 'test', 'code-fixtures', 'apex', 'SomeOtherTestClass.cls'); + const PATH_TO_SOQL_IN_LOOP = path.join('.', 'test', 'code-fixtures', 'apex', 'SoqlInLoop.cls'); + + let logger: Logger; + let display: FakeDisplay; + let inputProcessor: InputProcessorImpl; + let resultsProcessor: RawResultsProcessor; + let runAction: RunAction; + + beforeEach(async () => { + initializeTestSetup(); + logger = await Logger.child('RunAction.target.test.ts'); + display = new FakeDisplay(); + inputProcessor = new InputProcessorImpl('testing', display); + resultsProcessor = new RawResultsProcessor(); + runAction = new RunAction( + logger, + display, + inputProcessor, + new RuleFilterFactoryImpl(), + new RunEngineOptionsFactory(inputProcessor), + new FakeResultsProcessorFactory(resultsProcessor) + ); + }); + + describe('Test Case: Can target a single file...', () => { + it('...with a relative path', async () => { + // Prepare Input + const target = [PATH_TO_SOME_TEST_CLASS]; + const inputs: Inputs = { + target, + engine: ['pmd'], + category: ['Performance'], + format: 'xml' + }; + + // Invoke tested method + await runAction.run(inputs); + + // Assert against results + const results: Results = resultsProcessor.getResults(); + assertPerformanceViolations(results, target.map(p => path.resolve(p))); + }); + + it('... with an absolute path', async () => { + // Prepare Input + const target = [path.resolve(PATH_TO_SOME_TEST_CLASS)]; + const inputs: Inputs = { + target, + engine: ['pmd'], + category: ['Performance'], + format: 'xml' + }; + + // Invoke tested method + await runAction.run(inputs); + + // Assert against results + const results: Results = resultsProcessor.getResults(); + assertPerformanceViolations(results, target); + }); + + it('With a tilde-style path', async () => { + // Prepare Input + const target = [tildify(path.resolve(PATH_TO_SOME_TEST_CLASS))]; + const inputs: Inputs = { + target, + engine: ['pmd'], + category: ['Performance'], + format: 'xml' + }; + + // Invoke tested method + await runAction.run(inputs); + + // Assert against results + const results: Results = resultsProcessor.getResults(); + assertPerformanceViolations(results, [path.resolve(PATH_TO_SOME_TEST_CLASS)]); + }); + }); + + it('Test Case: Can target a list of files', async () => { + // Prepare Input + const target = [PATH_TO_SOME_TEST_CLASS, PATH_TO_SOME_OTHER_TEST_CLASS]; + const inputs: Inputs = { + target, + engine: ['pmd'], + category: ['Performance'], + format: 'xml' + }; + + // Invoke tested method + await runAction.run(inputs); + + // Assert against results + const results: Results = resultsProcessor.getResults(); + assertPerformanceViolations(results, target.map(p => path.resolve(p))); + }); + + it('Test Case: Can target a whole folder', async () => { + // Prepare Input + const inputs: Inputs = { + target: [PATH_TO_TEST_FOLDER], + engine: ['pmd'], + category: ['Performance'], + format: 'xml' + }; + + // Invoke tested method + await runAction.run(inputs); + + // Assert against results + const expectedTargets = [PATH_TO_SOME_TEST_CLASS, PATH_TO_SOME_OTHER_TEST_CLASS, PATH_TO_SOQL_IN_LOOP].map(p => path.resolve(p)); + const results: Results = resultsProcessor.getResults(); + assertPerformanceViolations(results, expectedTargets); + }); + + it('Test Case: Can target a glob', async () => { + // Prepare Input + const inputs: Inputs = { + target: [path.join(PATH_TO_TEST_FOLDER, 'Some*.cls')], + engine: ['pmd'], + category: ['Performance'], + format: 'xml' + }; + + // Invoke tested method + await runAction.run(inputs); + + // Assert against results + const expectedTargets = [PATH_TO_SOME_TEST_CLASS, PATH_TO_SOME_OTHER_TEST_CLASS].map(p => path.resolve(p)); + const results: Results = resultsProcessor.getResults(); + assertPerformanceViolations(results, expectedTargets); + }); + + function assertPerformanceViolations(results: Results, fileList: string[]): void { + expect(results.getExecutedEngines().size).to.equal(1, 'Wrong executedEngines count'); + expect(results.getExecutedEngines()).to.contain('pmd', 'Wrong engines executed'); + const ruleResults: RuleResult[] = results.getRuleResults(); + expect(ruleResults).to.have.length(fileList.length, 'Wrong number of results'); + for (const ruleResult of ruleResults) { + const fullFileName = path.resolve(ruleResult.fileName); + expect(fileList).to.contain(fullFileName, `Violations in unexpected file ${fullFileName}`); + for (const violation of ruleResult.violations) { + expect(violation.category).to.equal('Performance', `Wrong category of violation found in ${ruleResult.fileName}`); + } + } + } +});