From ababa35b803e11effd278bae33fe4448b75359ac Mon Sep 17 00:00:00 2001 From: Alan Ly Date: Wed, 29 Jul 2020 15:16:09 +1000 Subject: [PATCH] Validate code coverage for individual test classes in TriggerApexTest task --- .../TriggerApexTestTask/TriggerApexTest.ts | 5 ++- .../BuildTasks/TriggerApexTestTask/task.json | 19 ++++++++++- .../src/sfdxwrappers/TriggerApexTestImpl.ts | 33 +++++++++++++++++-- .../messages/trigger_apex_test.json | 4 ++- .../sfpowerscripts/TriggerApexTest.ts | 6 +++- 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/packages/azpipelines/BuildTasks/TriggerApexTestTask/TriggerApexTest.ts b/packages/azpipelines/BuildTasks/TriggerApexTestTask/TriggerApexTest.ts index 7458a7afb..7433b7dae 100644 --- a/packages/azpipelines/BuildTasks/TriggerApexTestTask/TriggerApexTest.ts +++ b/packages/azpipelines/BuildTasks/TriggerApexTestTask/TriggerApexTest.ts @@ -9,12 +9,11 @@ async function run() { try { const target_org: string = tl.getInput("target_org", true); - test_options["wait_time"] = tl.getInput("wait_time", true); - - test_options["testlevel"] = tl.getInput("testlevel", true); test_options["synchronous"] = tl.getBoolInput("synchronous", false); + test_options["isValidateCoverage"] = tl.getBoolInput("isValidateCoverage", false); + test_options["coverageThreshold"] = tl.getInput("coverageThreshold", false); if (test_options["testlevel"] == "RunSpecifiedTests") test_options["specified_tests"] = tl.getInput("specified_tests", true); diff --git a/packages/azpipelines/BuildTasks/TriggerApexTestTask/task.json b/packages/azpipelines/BuildTasks/TriggerApexTestTask/task.json index 8717b4dd5..1aaf51ae7 100644 --- a/packages/azpipelines/BuildTasks/TriggerApexTestTask/task.json +++ b/packages/azpipelines/BuildTasks/TriggerApexTestTask/task.json @@ -64,6 +64,23 @@ "helpMarkDown": "Select an option if the tests are to be run synchronously", "required": false }, + { + "name": "isValidateCoverage", + "type": "boolean", + "label": "Validate code coverage of individual test classes", + "defaultValue": false, + "helpMarkDown": "When enabled, verifies whether indvidual test classes meet minimum code coverage requirement", + "required": false + }, + { + "name": "coverageThreshold", + "type": "string", + "label": "Minimum percentage coverage required per test class", + "defaultValue": "75", + "helpMarkDown": "Minimum coverage required per test class, in order for the task to succeed", + "required":true, + "visibleRule": "isValidateCoverage = true" + }, { "name": "wait_time", "type": "string", @@ -84,4 +101,4 @@ "argumentFormat": "" } } -} \ No newline at end of file +} diff --git a/packages/core/src/sfdxwrappers/TriggerApexTestImpl.ts b/packages/core/src/sfdxwrappers/TriggerApexTestImpl.ts index 9a2bebab1..aee1edcec 100644 --- a/packages/core/src/sfdxwrappers/TriggerApexTestImpl.ts +++ b/packages/core/src/sfdxwrappers/TriggerApexTestImpl.ts @@ -6,7 +6,10 @@ let fs = require("fs-extra"); let path = require("path"); export default class TriggerApexTestImpl { - public constructor(private target_org: string, private test_options: any) {} + public constructor( + private target_org: string, + private test_options: any, + ) {} public async exec(): Promise<{ id: string; @@ -84,8 +87,32 @@ export default class TriggerApexTestImpl { test_result.result = false; console.error(output); } else { - test_result.message = `${test_report_json.summary.passing} Tests passed with overall Test Run Coverage of ${test_report_json.summary.testRunCoverage}`; - test_result.result = true; + const classesWithInvalidCoverage: string[] = []; + + if (this.test_options["isValidateCoverage"]) { + let code_coverage = fs.readFileSync( + path.join( + this.test_options["outputdir"], + `test-result-codecoverage.json` + ), + "utf8" + ); + let code_coverage_json = JSON.parse(code_coverage); + + for (let testClass of code_coverage_json) { + if (testClass["coveredPercent"] < this.test_options["coverageThreshold"]) { + classesWithInvalidCoverage.push(testClass["name"]); + } + } + } + + if (classesWithInvalidCoverage.length == 0) { + test_result.message = `${test_report_json.summary.passing} Tests passed with overall Test Run Coverage of ${test_report_json.summary.testRunCoverage}`; + test_result.result = true; + } else { + test_result.message=`The test classes ${classesWithInvalidCoverage.toString()} do not meet the required code coverage of ${this.test_options["coverageThreshold"]}`; + test_result.result = false; + } console.log(output); } diff --git a/packages/sfpowerscripts-cli/messages/trigger_apex_test.json b/packages/sfpowerscripts-cli/messages/trigger_apex_test.json index 7b9a2adec..ac2b0a734 100644 --- a/packages/sfpowerscripts-cli/messages/trigger_apex_test.json +++ b/packages/sfpowerscripts-cli/messages/trigger_apex_test.json @@ -5,5 +5,7 @@ "synchronousFlagDescription": "Select an option if the tests are to be run synchronously", "specifiedTestsFlagDescription": "comma-separated list of Apex test class names or IDs and, if applicable, test methods to run", "apexTestSuiteFlagDescription": "comma-separated list of Apex test suite names to run", + "validateCoverageFlagDescription": "Enable code coverage validation for individual test classes", + "coveragePercentFlagDescription": "Minimum coverage percentage required for each test class", "waitTimeFlagDescription": "wait time for command to finish in minutes" -} \ No newline at end of file +} diff --git a/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/TriggerApexTest.ts b/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/TriggerApexTest.ts index c06e6fa90..a9dbc181e 100644 --- a/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/TriggerApexTest.ts +++ b/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/TriggerApexTest.ts @@ -25,6 +25,8 @@ export default class TriggerApexTest extends SfdxCommand { synchronous: flags.boolean({char: 's', description: messages.getMessage('synchronousFlagDescription')}), specifiedtests: flags.string({description: messages.getMessage('specifiedTestsFlagDescription')}), apextestsuite: flags.string({description: messages.getMessage('apexTestSuiteFlagDescription')}), + validatecoverage: flags.boolean({char: 'c', description: messages.getMessage('validateCoverageFlagDescription')}), + coveragepercent: flags.integer({char: 'p', description: messages.getMessage('coveragePercentFlagDescription'), dependsOn: ['validatecoverage'], default: 75}), waittime: flags.string({description: messages.getMessage('waitTimeFlagDescription'), default: '60'}) }; @@ -38,6 +40,8 @@ export default class TriggerApexTest extends SfdxCommand { test_options["wait_time"] = this.flags.waittime; test_options["testlevel"] = this.flags.testlevel; test_options["synchronous"] = this.flags.synchronous; + test_options["isValidateCoverage"] = this.flags.validatecoverage; + test_options["coverageThreshold"] = this.flags.coveragepercent; if (test_options["testlevel"] == "RunSpecifiedTests") test_options["specified_tests"] = this.flags.specifiedtests; @@ -63,7 +67,7 @@ export default class TriggerApexTest extends SfdxCommand { } } catch(err) { // AppInsights.trackExcepiton("sfpwowerscript-triggerapextest-task",err); - console.log(err); + console.error(err); // Fail the task when an error occurs process.exit(1); }