Skip to content

Commit

Permalink
AzureTestPlan@V0 Code Refactoring. (#20816)
Browse files Browse the repository at this point in the history
* Refactored manual flow

* Changing Task Directory Structure

* Modified Maven and Gradle Executors in new flow

* Fixing Gradle User input GradlewFlow

* Fixing PythonTestExecutor

* Changed the old flow under a folder for efficient cleanup

* Building Task and Fixing Generated Task Folder

* Move discoverTests func to return IOperationResult

* Renaming symbol and removing extra semicolon

* Fixing variable to pass to Python Executor
  • Loading branch information
adityashahms authored Feb 3, 2025
1 parent f90ad8a commit f0eff63
Show file tree
Hide file tree
Showing 91 changed files with 4,822 additions and 2,083 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { ITestExecutor } from "../../Interface/ITestExecutor";
import { IOperationResult } from "../../Interface/IOperationResult";
import { ciDictionary } from "../../Common/ciEventLogger";
import * as constants from "../../Common/constants";
import { removeParenthesesFromEnd, replaceLastDotWithHash } from "../../Common/utils";
import * as tl from 'azure-pipelines-task-lib/task';
import { ToolRunner } from "azure-pipelines-task-lib/toolrunner";
import { SimpleTimer } from "../../Common/SimpleTimer";

export class GradleTestExecutor implements ITestExecutor {
testRunnerCLI: string = constants.GRADLE_EXECUTABLE;
toolRunnerPath: string;
toolRunner: ToolRunner;
gradlewFilePath: string;

/*
* Setup the test executor
*/
async setup(): Promise<IOperationResult> {
let operationResult: IOperationResult = { returnCode: 0, errorMessage: '' };
this.gradlewFilePath = tl.getInput('gradleFilePath');

try {
this.toolRunnerPath = tl.which(this.testRunnerCLI, true);
this.toolRunner = tl.tool(this.toolRunnerPath);
this.toolRunner.arg('-v');
operationResult.returnCode = await this.toolRunner.execAsync();
} catch (error) {
operationResult.returnCode = 1;
operationResult.errorMessage = error.message || String(error);
tl.debug("Error in Gradle setup: " + operationResult.errorMessage);
tl.debug("Looking for Gradlew file to install Gradle");
}

if(operationResult.returnCode === 1){
operationResult.returnCode = 0;
operationResult.errorMessage = '';

try {
this.toolRunnerPath = tl.which(this.gradlewFilePath, true);
} catch (error) {
operationResult.returnCode = 1;
operationResult.errorMessage = error.message || String(error);
tl.debug("Error while looking for user input Gradlew file: " + operationResult.errorMessage);
tl.debug("Looking for gradlew file in the repository");
}
}

if(operationResult.returnCode === 1){
operationResult.returnCode = 0;
operationResult.errorMessage = '';

try {
const gradlewExecFileSearchPattern: string = "**/gradlew";
let workingDirectory = tl.getVariable('System.DefaultWorkingDirectory');
let os = tl.getVariable('Agent.OS');
const gradlewPath = tl.findMatch(workingDirectory, gradlewExecFileSearchPattern);
this.toolRunnerPath = gradlewPath[0];

if (gradlewPath.length == 0) {
operationResult.returnCode = 1;
operationResult.errorMessage = tl.loc('GradlewNotFound');
tl.debug("Gradlew file not found in the repository");
return operationResult;
}

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

if (os == 'Windows_NT') {
tl.debug('Append .bat extension name to gradlew script for windows agent');
this.toolRunnerPath += '.bat';
}
} catch (error) {
operationResult.returnCode = 1;
operationResult.errorMessage = error.message || String(error);
tl.debug("Error while looking for gradlew file in the repository: " + operationResult.errorMessage);
}

return operationResult;
}


try {
operationResult.returnCode = await this.toolRunner.execAsync();
} catch (error) {
operationResult.errorMessage = error.message || String(error);
}
return operationResult;
}

async discoverTests(listOfTestsToBeExecuted: string[], ciData: ciDictionary, listOfTestsToBeRan: string[]): Promise<IOperationResult> {
let operationResult: IOperationResult = { returnCode: 0, errorMessage: '' };

listOfTestsToBeExecuted.forEach(element => {
listOfTestsToBeRan.push(element);
});

return operationResult;
}

async executeTests(testsToBeExecuted: string[], ciData: ciDictionary): Promise<IOperationResult> {
let operationResult: IOperationResult = { returnCode: 0, errorMessage: '' };
let executionTimer = new SimpleTimer(constants.AUTOMATED_EXECUTION);
executionTimer.start();

this.toolRunner = tl.tool(this.toolRunnerPath);
const args = []

args.push('test');

for (let testcase of testsToBeExecuted) {
// in some cases found that gradle is including () in test name
testcase = removeParenthesesFromEnd(testcase);
args.push('--tests');
args.push(testcase);
}

tl.debug("Executing gradle tests with args :" + args);
this.toolRunner.arg(args);

try {
operationResult.returnCode = await this.toolRunner.execAsync();
} catch (error) {
operationResult.errorMessage = error.message || String(error);
}
executionTimer.stop(ciData);

return operationResult;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { ITestExecutor } from "../../Interface/ITestExecutor";
import { IOperationResult } from "../../Interface/IOperationResult";
import { ciDictionary } from "../../Common/ciEventLogger";
import * as constants from "../../Common/constants";
import { replaceLastDotWithHash } from "../../Common/utils";
import * as tl from 'azure-pipelines-task-lib/task';
import { ToolRunner } from "azure-pipelines-task-lib/toolrunner";
import { SimpleTimer } from "../../Common/SimpleTimer";

export class MavenTestExecutor implements ITestExecutor {
testRunnerCLI: string = constants.MVN_EXECUTABLE;
toolRunnerPath: string;
toolRunner: ToolRunner;
pomFilePath: string;

async setup(): Promise<IOperationResult> {
let operationResult: IOperationResult = { returnCode: 0, errorMessage: '' };
this.pomFilePath = tl.getInput('pomFilePath');

try {
this.toolRunnerPath = tl.which(this.testRunnerCLI, true);
} catch (error) {
operationResult.returnCode = 1;
operationResult.errorMessage = error.message || String(error);
return operationResult;
}


this.toolRunner = tl.tool(this.toolRunnerPath);
this.toolRunner.arg('-version');

try {
operationResult.returnCode = await this.toolRunner.execAsync();
} catch (error) {
operationResult.errorMessage = error.message || String(error);
}
return operationResult;
}

async discoverTests(listOfTestsToBeExecuted: string[], ciData: ciDictionary, listOfTestsToBeRan: string[]): Promise<IOperationResult> {
let operationResult: IOperationResult = { returnCode: 0, errorMessage: '' };

listOfTestsToBeExecuted.forEach(element => {
listOfTestsToBeRan.push(element);
});

return operationResult;
}

async executeTests(testsToBeExecuted: string[], ciData: ciDictionary): Promise<IOperationResult> {
let operationResult: IOperationResult = { returnCode: 0, errorMessage: '' };
let executionTimer = new SimpleTimer(constants.AUTOMATED_EXECUTION);
executionTimer.start();

this.toolRunner = tl.tool(this.toolRunnerPath);
const args = []
const testsToRun =[]

for (let tests of testsToBeExecuted) {
const modifiedTest = replaceLastDotWithHash(tests);
testsToRun.push(modifiedTest);
}

if (testsToRun.length > 0)
{
const testsList = testsToRun.join(',')
const dtest = constants.MAVEN_DTEST;
const combinedTestArgs = dtest + testsList;

args.push('test');
args.push(combinedTestArgs);
}

args.push('-ntp');

if (this.pomFilePath) {
args.push('-f');
args.push(this.pomFilePath);
}

tl.debug("Executing java maven tests with args :" + args);
this.toolRunner.arg(args);

try {
operationResult.returnCode = await this.toolRunner.execAsync();
} catch (error) {
operationResult.errorMessage = error.message || String(error);
}
executionTimer.stop(ciData);

return operationResult;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { ITestExecutor } from "../../Interface/ITestExecutor";
import { IOperationResult } from "../../Interface/IOperationResult";
import { ciDictionary } from "../../Common/ciEventLogger";
import * as constants from "../../Common/constants";
import { replaceLastDotWithHash, extractPythonDiscoveredTests, getExecOptions, transformPythonTestStrings } from "../../Common/utils";
import * as tl from 'azure-pipelines-task-lib/task';
import { ToolRunner } from "azure-pipelines-task-lib/toolrunner";
import { SimpleTimer } from "../../Common/SimpleTimer";

export class PythonTestExecutor implements ITestExecutor {
testRunnerCLI: string = constants.PYTEST_EXECUTABLE;
toolRunnerPath: string;
toolRunner: ToolRunner;
pomFilePath: string;

async setup(): Promise<IOperationResult> {
let operationResult: IOperationResult = { returnCode: 0, errorMessage: '' };

try {
this.toolRunnerPath = tl.which(this.testRunnerCLI, true);
} catch (error) {
operationResult.returnCode = 1;
operationResult.errorMessage = error.message || String(error);
return operationResult;
}

this.toolRunner = tl.tool(this.toolRunnerPath);
this.toolRunner.arg('--version');

try {
operationResult.returnCode = await this.toolRunner.execAsync();
} catch (error) {
operationResult.errorMessage = error.message || String(error);
}
return operationResult;
}

async discoverTests(testsToBeExecuted: string[], ciData: ciDictionary, listOfTestsToBeRan: string[]): Promise<IOperationResult> {
let operationResult: IOperationResult = { returnCode: 0, errorMessage: '' };
const args: string[] = ['--collect-only', '-q'];
let discoveryResult = { stdout: ''};;
this.toolRunner = tl.tool(this.toolRunnerPath);
this.toolRunner.arg(args);

try {
operationResult.returnCode = await this.toolRunner.execAsync(getExecOptions(discoveryResult));
} catch (error) {
operationResult.errorMessage = error.message || String(error);
}

if(operationResult.returnCode === 0){
// Extract discovered tests from stdout
const discoveredTests: string[] = extractPythonDiscoveredTests(discoveryResult.stdout ?? '');
var testStringtoFQNMap: Map<string, string> = new Map<string, string>();

for(let test of discoveredTests){
testStringtoFQNMap.set(transformPythonTestStrings(test), test);
}

for(let test of testsToBeExecuted){
if(!testStringtoFQNMap.has(test)){
tl.debug(`Test ${test} not found in discovered tests`);
}
else{
listOfTestsToBeRan.push(testStringtoFQNMap.get(test));
}
}

// Variables for debug console logs
const testsToBeExecutedString: string = testsToBeExecuted.join(", ");
const testsToRunString: string = listOfTestsToBeRan.join(", ");

tl.debug(`Tests to executed are: ${testsToBeExecutedString}`);
tl.debug(`Tests to run are: ${testsToRunString}`);
console.log(`Found ${listOfTestsToBeRan.length} tests to run`);

return operationResult;
}
}

async executeTests(testsToBeExecuted: string[], ciData: ciDictionary): Promise<IOperationResult> {
let operationResult: IOperationResult = { returnCode: 0, errorMessage: '' };
let executionTimer = new SimpleTimer(constants.AUTOMATED_EXECUTION);
executionTimer.start();

this.toolRunner = tl.tool(this.toolRunnerPath);

tl.debug("Executing python pytest tests with args :" + testsToBeExecuted);
this.toolRunner.arg(testsToBeExecuted);
this.toolRunner.arg('--junitxml=TEST-python-junit.xml');

try {
operationResult.returnCode = await this.toolRunner.execAsync();
} catch (error) {
operationResult.errorMessage = error.message || String(error);
}
executionTimer.stop(ciData);

return operationResult;
}


}
Loading

0 comments on commit f0eff63

Please sign in to comment.