Skip to content

Commit

Permalink
Implementing try catch for execution and publishing in AzureTestPlanV0 (
Browse files Browse the repository at this point in the history
#19748)

* Implementing try catch for execution and publishing

* Adding Promise to applicable places

* Upgrading Task Version

* Removing extra lines

* Addressing comments

---------

Co-authored-by: triptijain2112 <92331194+triptijain2112@users.noreply.github.com>
  • Loading branch information
adityashahms and triptijain2112 authored Apr 10, 2024
1 parent fd95edd commit f113906
Show file tree
Hide file tree
Showing 31 changed files with 376 additions and 253 deletions.
5 changes: 2 additions & 3 deletions Tasks/AzureTestPlanV0/Invokers/gradleinvoker.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { spawn } from '../testexecutor'
import tl = require('azure-pipelines-task-lib/task');
import utils = require('../utils');
import constants = require('../constants');
import { execGradleBuild } from '../testLibExecutor';

export async function executegradletests(testsToBeExecuted: string[]) {
export async function executeGradleTests(testsToBeExecuted: string[]):Promise<number> {

//public doc link: https://docs.gradle.org/current/userguide/command_line_interface.html
//gradle command like "gradlew clean test --tests <package.className.testName> --tests <package.className.testName>"
Expand All @@ -26,5 +25,5 @@ export async function executegradletests(testsToBeExecuted: string[]) {

var status = await execGradleBuild(args);

return { exitCode: status ?? 1 }
return status ?? 1;
}
6 changes: 4 additions & 2 deletions Tasks/AzureTestPlanV0/Invokers/maveninvoker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import utils = require('../utils');
import constants = require('../constants');
import { execMavenBuild } from '../testLibExecutor';

export async function executemaventests(testsToBeExecuted: string[]) {
export async function executeMavenTests(testsToBeExecuted: string[]):Promise<number> {

//public doc link: https://maven.apache.org/surefire/maven-surefire-plugin/examples/single-test.html
//maven command like "mvn test -Dtest=<package.className#testName>,<package.className#testName1>"
Expand All @@ -31,5 +31,7 @@ export async function executemaventests(testsToBeExecuted: string[]) {
tl.debug("Executing java maven tests with executable : " + executable);
tl.debug("Executing java maven tests with args :" + args);

await execMavenBuild(args);
let status = await execMavenBuild(args);

return status ?? 1;
}
10 changes: 5 additions & 5 deletions Tasks/AzureTestPlanV0/Invokers/pythoninvoker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { spawn, SpawnResult } from '../testexecutor';
import tl = require('azure-pipelines-task-lib/task');
import constants = require('../constants');

export async function executepythontests(testsToBeExecuted: string[]) {
export async function executePythonTests(testsToBeExecuted: string[]):Promise<number> {
// Perform test discovery
const discoveryArgs: string[] = ['--collect-only'];
const discoveryResult = await runPytestCommand(discoveryArgs);

if (discoveryResult.status !== 0) {
tl.error("Error occurred during test discovery: " + (discoveryResult.error ? discoveryResult.error.message : "Unknown error"));
return { exitCode: 1 };
return 1;
}

// Extract discovered tests from stdout
Expand All @@ -20,7 +20,7 @@ export async function executepythontests(testsToBeExecuted: string[]) {

if (testsToRun.length === 0) {
tl.warning("No common tests found between specified tests and discovered tests.");
return { exitCode: 0 };
return 0;
}

console.log(`Found ${testsToRun.length} tests to run`);
Expand All @@ -34,10 +34,10 @@ export async function executepythontests(testsToBeExecuted: string[]) {

if (error) {
tl.error("Error executing pytest command: " + error.message);
return { exitCode: 1 };
return 1;
}

return { exitCode: status ?? 1 };
return status ?? 1;
}

async function runPytestCommand(args: string[]): Promise<SpawnResult> {
Expand Down
27 changes: 18 additions & 9 deletions Tasks/AzureTestPlanV0/automatedTestInvoker.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import * as tl from 'azure-pipelines-task-lib/task'
import { executepythontests } from './Invokers/pythoninvoker'
import { executemaventests } from './Invokers/maveninvoker'
import { executegradletests } from './Invokers/gradleinvoker'
import { executePythonTests } from './Invokers/pythoninvoker'
import { executeMavenTests } from './Invokers/maveninvoker'
import { executeGradleTests } from './Invokers/gradleinvoker'

export async function testInvoker(testsToBeExecuted: string[]) {
export async function testInvoker(testsToBeExecuted: string[]): Promise<number> {

const testLanguageStrings = tl.getDelimitedInput('testLanguageInput', ',', true);

let exitStatusCode = 0;

for (const testLanguage of testLanguageStrings) {
let exitCode = 0;

if (testLanguage === null || testLanguage === undefined) {
console.log("Please select the test framework language from the task dropdown list to execute automated tests");
Expand All @@ -16,21 +19,27 @@ export async function testInvoker(testsToBeExecuted: string[]) {

switch (testLanguage) {
case 'Java-Maven':
await executemaventests(testsToBeExecuted);
exitCode = await executeMavenTests(testsToBeExecuted);
tl.debug(`Execution Status Code for Maven: ${exitCode}`);
break;

case 'Java-Gradle':
await executegradletests(testsToBeExecuted);
exitCode = await executeGradleTests(testsToBeExecuted);
tl.debug(`Execution Status Code for Gradle: ${exitCode}`);
break;

case 'Python':
await executepythontests(testsToBeExecuted);
exitCode = await executePythonTests(testsToBeExecuted);
tl.debug(`Execution Status Code for Python: ${exitCode}`);
break;

default:
console.log('Invalid test Language Input selected.');
}


exitStatusCode = exitStatusCode || exitCode;
}


tl.debug(`Execution Status Code for Automated Execution Flow: ${exitStatusCode}`);
return exitStatusCode;
}
51 changes: 31 additions & 20 deletions Tasks/AzureTestPlanV0/automatedTests.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
import tl = require('azure-pipelines-task-lib/task');
import { testInvoker } from './automatedTestInvoker'
import { TestPlanData } from './testPlanData'
import { publishAutomatedTestResult } from './publishAutomatedTests'
import { testInvoker } from './automatedTestInvoker';
import { TestPlanData } from './testPlanData';
import { publishAutomatedTestResult } from './publishAutomatedTests';

export async function automatedTestsFlow(testPlanInfo: TestPlanData, testSelectorInput: string): Promise<number> {
let listOfTestsToBeExecuted: string[] = testPlanInfo.listOfFQNOfTestCases;
let testInvokerStatusCode = 0;

export async function automatedTestsFlow(testPlanInfo: TestPlanData, testSelectorInput: string) {
if (listOfTestsToBeExecuted !== null && listOfTestsToBeExecuted !== undefined && listOfTestsToBeExecuted.length > 0) {
tl.debug('Invoking test execution for tests: ' + listOfTestsToBeExecuted);

let listOfTestsToBeExecuted: string[] = testPlanInfo.listOfFQNOfTestCases;

console.log(tl.loc('automatedTestTriggered'));

if (listOfTestsToBeExecuted !== null && listOfTestsToBeExecuted !== undefined && listOfTestsToBeExecuted.length > 0) {
tl.debug("Invoking test execution for tests: " + listOfTestsToBeExecuted);
await testInvoker(listOfTestsToBeExecuted);
publishAutomatedTestResult(JSON.stringify(testPlanInfo.listOfAutomatedTestPoints));
try {
testInvokerStatusCode = await testInvoker(listOfTestsToBeExecuted);
} catch (err) {
tl.debug(`Unable to invoke automated test execution. Err:( ${err} )`);
testInvokerStatusCode = 1;
}
else {
console.log("No automated tests found for given test plan inputs ");
if (testSelectorInput === 'automatedTests') {
tl.setResult(tl.TaskResult.Failed, tl.loc('ErrorFailTaskOnNoAutomatedTestsFound'));
}
else {
tl.setResult(tl.TaskResult.Succeeded, "Successfully triggered manual test execution");
}

try {
await publishAutomatedTestResult(JSON.stringify(testPlanInfo.listOfAutomatedTestPoints));
} catch (err) {
tl.error(`Error while publishing automated Test Results with err : ( ${err} )`);
return 1;
}

tl.debug(`Execution Status Code for test Invoker: ${testInvokerStatusCode}`);
return testInvokerStatusCode;
} else {
console.log('No automated tests found for given test plan inputs ');
if (testSelectorInput === 'automatedTests') {
tl.setResult(tl.TaskResult.Failed, tl.loc('ErrorFailTaskOnNoAutomatedTestsFound'));
return 1;
} else {
tl.setResult(tl.TaskResult.Succeeded, 'Successfully triggered manual test execution');
return 0;
}
}
}
13 changes: 10 additions & 3 deletions Tasks/AzureTestPlanV0/manualTests.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import tl = require('azure-pipelines-task-lib/task');
import { TestPlanData, createManualTestRun, ManualTestRunData } from './testPlanData';

export async function manualTestsFlow(testPlanInfo: TestPlanData) {
export async function manualTestsFlow(testPlanInfo: TestPlanData):Promise<number> {

let manualTestRun: ManualTestRunData = { testRunId: 0, runUrl: "" };

manualTestRun = await createManualTestRun(testPlanInfo);

try{
manualTestRun = await createManualTestRun(testPlanInfo);
}
catch (err){
tl.debug(`Unable to create Manual Test Run. Err:( ${err} )`);
return 1;
}

console.log('Test run id created: ', manualTestRun.testRunId);
console.log('Test run url: ', manualTestRun.runUrl);

return 0;
}
13 changes: 11 additions & 2 deletions Tasks/AzureTestPlanV0/runTestPlan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,21 @@ export async function run() {

const testPlanInfo = await getTestPlanData();

let manualTestFlowReturnCode = 0;
let automatedTestFlowReturnCode = 0;

// trigger manual, automated or both tests based on user's input
if (testSelectorInput.includes('manualTests')) {
await manualTestsFlow(testPlanInfo);
manualTestFlowReturnCode = await manualTestsFlow(testPlanInfo);
tl.debug(`Execution Status Code for Manual Test Flow is ${manualTestFlowReturnCode}`);
}
if (testSelectorInput.includes('automatedTests')) {
await automatedTestsFlow(testPlanInfo, testSelectorInput);
automatedTestFlowReturnCode = await automatedTestsFlow(testPlanInfo, testSelectorInput);
tl.debug(`Execution Status Code for Automated Test Flow is ${automatedTestFlowReturnCode}`);
}

if( manualTestFlowReturnCode || automatedTestFlowReturnCode){
tl.setResult(tl.TaskResult.Failed, "Faced error in execution.");
}
}

Expand Down
2 changes: 1 addition & 1 deletion Tasks/AzureTestPlanV0/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"version": {
"Major": 0,
"Minor": 238,
"Patch": 6
"Patch": 8
},
"preview": true,
"demands": [],
Expand Down
2 changes: 1 addition & 1 deletion Tasks/AzureTestPlanV0/task.loc.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"version": {
"Major": 0,
"Minor": 238,
"Patch": 6
"Patch": 8
},
"preview": true,
"demands": [],
Expand Down
74 changes: 39 additions & 35 deletions Tasks/AzureTestPlanV0/testLibExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,35 +54,32 @@ function getExecOptions(): tr.IExecOptions {
* @param args Arguments to execute via mvn
* @returns execution Status Code
*/
export async function execMavenBuild(args: string[]) {

export async function execMavenBuild(args: string[]): Promise<number> {
var mvnExec = getMavenExec();

// Setup tool runner that executes Maven only to retrieve its version
var mvnGetVersion = tl.tool(mvnExec);
mvnGetVersion.arg('-version');

// 1. Check that Maven exists by executing it to retrieve its version.
await mvnGetVersion.exec()
.fail(function (err) {
console.error("Maven is not installed on the agent");
tl.setResult(tl.TaskResult.Failed, "Maven is not installed."); // tl.exit sets the step result but does not stop execution
process.exit(1);
})
.then(async function (code) {
// Setup Maven Executable to run list of test runs provided as input
var mvnRun = tl.tool(mvnExec);
mvnRun.arg('-ntp');
mvnRun.arg(args);

// 3. Run Maven. Compilation or test errors will cause this to fail.
return mvnRun.exec(getExecOptions());
})
.fail(function (err) {
console.error(err.message);
tl.setResult(tl.TaskResult.Failed, "Build failed."); // tl.exit sets the step result but does not stop execution
process.exit(1);
});
try {
// 1. Check that Maven exists by executing it to retrieve its version.
await mvnGetVersion.exec();

// Setup Maven Executable to run list of test runs provided as input
var mvnRun = tl.tool(mvnExec);
mvnRun.arg('-ntp');
mvnRun.arg(args);

// 3. Run Maven. Compilation or test errors will cause this to fail.
await mvnRun.exec(getExecOptions());

// Maven build succeeded
return 0; // Return 0 indicating success
} catch (err) {
console.error(err.message);
tl.setResult(tl.TaskResult.Failed, "Build failed.");
return 1; // Return 1 indicating failure
}
}

function getGradlewExec() {
Expand All @@ -105,14 +102,16 @@ function getGradlewExec() {

if (gradlewPath.length == 0) {
tl.setResult(tl.TaskResult.Failed, "Missing gradlew file");
return "";
}

var gradlewExec: string = gradlewPath[0];

if (gradlewPath.length > 1) {
tl.warning(tl.loc('MultipleMatchingGradlewFound'));
tl.debug(gradlewExec);
}

var gradlewExec: string = gradlewPath[0];

if (isWindows) {
tl.debug('Append .bat extension name to gradlew script.');
gradlewExec += '.bat';
Expand All @@ -136,22 +135,27 @@ function getGradlewExec() {
* @param args Arguments to execute via mvn
* @returns execution Status Code
*/
export async function execGradleBuild(args: string[]) {
export async function execGradleBuild(args: string[]): Promise<number> {
var gradleExec = getGradlewExec();

// Setup tool runner that executes Maven only to retrieve its version
if (!gradleExec || gradleExec == "") {
return 1; // Return 1 indicating failure
}

// Setup tool runner that executes Gradle
var gradleRunner = tl.tool(gradleExec);

// Add args prepared by invoker for executing individual test cases
gradleRunner.arg('clean');
gradleRunner.arg(args);

var statusCode = await gradleRunner.exec(getExecOptions())
.fail(function (err) {
console.error(err.message);
tl.setResult(tl.TaskResult.Failed, "Build failed."); // tl.exit sets the step result but does not stop execution
process.exit(1);
});

return statusCode;
try {
await gradleRunner.exec(getExecOptions());
// Gradle build succeeded
return 0; // Return 0 indicating success
} catch (err) {
console.error(err.message);
tl.setResult(tl.TaskResult.Failed, "Build failed."); // Set the step result to Failed
return 1; // Return 1 indicating failure
}
}
4 changes: 2 additions & 2 deletions _generated/AzureTestPlanV0.versionmap.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Default|0.238.6
Node20-225|0.238.7
Default|0.238.8
Node20-225|0.238.9
5 changes: 2 additions & 3 deletions _generated/AzureTestPlanV0/Invokers/gradleinvoker.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { spawn } from '../testexecutor'
import tl = require('azure-pipelines-task-lib/task');
import utils = require('../utils');
import constants = require('../constants');
import { execGradleBuild } from '../testLibExecutor';

export async function executegradletests(testsToBeExecuted: string[]) {
export async function executeGradleTests(testsToBeExecuted: string[]):Promise<number> {

//public doc link: https://docs.gradle.org/current/userguide/command_line_interface.html
//gradle command like "gradlew clean test --tests <package.className.testName> --tests <package.className.testName>"
Expand All @@ -26,5 +25,5 @@ export async function executegradletests(testsToBeExecuted: string[]) {

var status = await execGradleBuild(args);

return { exitCode: status ?? 1 }
return status ?? 1;
}
Loading

0 comments on commit f113906

Please sign in to comment.