From 4ea18a8d33ffe5815bd363f57031d283529a84e8 Mon Sep 17 00:00:00 2001 From: MxEh-TT <137298204+MxEh-TT@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:52:30 +0200 Subject: [PATCH] response handling for idle state (#121) (#126) --- .../builder/AbstractTestBuilder.groovy | 84 +++++++----- .../clients/RestApiClient.groovy | 25 +++- .../clients/RestApiClientFactory.groovy | 24 +++- .../clients/RestApiClientV1.groovy | 57 ++++---- .../clients/RestApiClientV2.groovy | 128 +++++++++--------- .../RestApiClientV2WithIdleHandle.groovy | 52 +++++++ .../clients/model/AdditionalSettings.groovy | 8 +- ...ontrollerToAgentCallableWithTimeout.groovy | 53 ++++++++ .../steps/CheckPackageStep.groovy | 53 +++++--- .../ecutestexecution/ETContainerTest.groovy | 47 +++++-- .../client/MockApiResponse.groovy | 32 +++++ .../MockRestApiClient.groovy} | 11 +- .../steps/CheckPackageStepIT.groovy | 57 +++++++- .../steps/GenerateReportsStepIT.groovy | 31 ++++- .../steps/RunPackageStepIT.groovy | 71 +++++++++- .../steps/RunProjectStepIT.groovy | 52 +++++-- .../steps/RunTestFolderStepIT.groovy | 13 +- .../steps/StartToolStepIT.groovy | 3 +- .../steps/UploadReportsStepIT.groovy | 32 ++++- 19 files changed, 633 insertions(+), 200 deletions(-) create mode 100644 src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientV2WithIdleHandle.groovy create mode 100644 src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/security/ControllerToAgentCallableWithTimeout.groovy create mode 100644 src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/client/MockApiResponse.groovy rename src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/{steps/TestRestApiClient.groovy => client/MockRestApiClient.groovy} (80%) diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/builder/AbstractTestBuilder.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/builder/AbstractTestBuilder.groovy index 0e0b76ba..ea819b1a 100644 --- a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/builder/AbstractTestBuilder.groovy +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/builder/AbstractTestBuilder.groovy @@ -5,28 +5,27 @@ */ package de.tracetronic.jenkins.plugins.ecutestexecution.builder + import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClient import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClientFactory -import de.tracetronic.jenkins.plugins.ecutestexecution.clients.model.ApiException +import de.tracetronic.jenkins.plugins.ecutestexecution.security.ControllerToAgentCallableWithTimeout import de.tracetronic.jenkins.plugins.ecutestexecution.clients.model.ExecutionOrder import de.tracetronic.jenkins.plugins.ecutestexecution.clients.model.ReportInfo import de.tracetronic.jenkins.plugins.ecutestexecution.configs.ExecutionConfig import de.tracetronic.jenkins.plugins.ecutestexecution.configs.TestConfig import de.tracetronic.jenkins.plugins.ecutestexecution.model.CheckPackageResult -import de.tracetronic.jenkins.plugins.ecutestexecution.model.GenerationResult import de.tracetronic.jenkins.plugins.ecutestexecution.model.TestResult import de.tracetronic.jenkins.plugins.ecutestexecution.model.ToolInstallations -import de.tracetronic.jenkins.plugins.ecutestexecution.steps.CheckPackageStep import de.tracetronic.jenkins.plugins.ecutestexecution.util.LogConfigUtil import hudson.EnvVars import hudson.Launcher import hudson.model.Result import hudson.model.Run import hudson.model.TaskListener -import jenkins.security.MasterToSlaveCallable import org.apache.commons.lang.StringUtils import org.jenkinsci.plugins.workflow.steps.StepContext + /** * Common base class for all test related steps implemented in this plugin. */ @@ -37,7 +36,9 @@ abstract class AbstractTestBuilder implements Serializable { final StepContext context protected abstract String getTestArtifactName() + protected abstract LogConfigUtil getLogConfig() + protected abstract ExecutionOrderBuilder getExecutionOrderBuilder() AbstractTestBuilder(String testCasePath, TestConfig testConfig, ExecutionConfig executionConfig, @@ -58,41 +59,31 @@ abstract class AbstractTestBuilder implements Serializable { } /** - * Performs CheckPackageStep if executePackageCheck option was set in the execution config and calls the execution - * of the package. + * Calls the execution of the package. * @return TestResult * results of the test execution */ TestResult runTest() { TaskListener listener = context.get(TaskListener.class) ToolInstallations toolInstallations = new ToolInstallations(context) - - if (executionConfig.executePackageCheck){ - CheckPackageStep step = new CheckPackageStep(testCasePath) - step.setExecutionConfig(executionConfig) - CheckPackageResult check_result = step.start(context).run() - if (executionConfig.stopOnError && check_result.result == "ERROR") { - listener.logger.println( - "Skipping execution of ${testArtifactName} ${testCasePath} due to failed package checks" - ) - return new TestResult(null, "ERROR",null) - } - } - + TestResult result try { - return context.get(Launcher.class).getChannel().call(new RunTestCallable(testCasePath, + result = context.get(Launcher.class).getChannel().call(new RunTestCallable(testCasePath, context.get(EnvVars.class), listener, executionConfig, getTestArtifactName(), getLogConfig(), getExecutionOrderBuilder(), toolInstallations)) } catch (Exception e) { - context.get(TaskListener.class).error(e.message) + listener.logger.println("Executing ${testArtifactName} '${testCasePath}' failed!") + listener.error(e.message) context.get(Run.class).setResult(Result.FAILURE) - return new TestResult(null, "A problem occured during the report generation. See caused exception for more details.", null) + result = new TestResult(null, "A problem occurred during the report generation. See caused exception for more details.", null) } + listener.logger.flush() + return result } - private static final class RunTestCallable extends MasterToSlaveCallable { - - private static final long serialVersionUID = 1L + private static final class RunTestCallable extends ControllerToAgentCallableWithTimeout { + + private static final long serialVersionUID = 1L private final String testCasePath private final EnvVars envVars @@ -102,10 +93,12 @@ abstract class AbstractTestBuilder implements Serializable { private final String testArtifactName private final LogConfigUtil configUtil private final ToolInstallations toolInstallations + private RestApiClient apiClient RunTestCallable(final String testCasePath, EnvVars envVars, TaskListener listener, ExecutionConfig executionConfig, String testArtifactName, LogConfigUtil configUtil, ExecutionOrderBuilder executionOrderBuilder, ToolInstallations toolInstallations) { + super(executionConfig.timeout, listener) this.testCasePath = testCasePath this.envVars = envVars this.listener = listener @@ -114,17 +107,35 @@ abstract class AbstractTestBuilder implements Serializable { this.configUtil = configUtil this.executionOrderBuilder = executionOrderBuilder this.toolInstallations = toolInstallations + } + /** + * Performs CheckPackageStep if executePackageCheck option was set in the execution config and + * executes the package + * @return TestResult results of the test execution + * @throws IOException + */ @Override - TestResult call() throws IOException { + TestResult execute() throws IOException { + listener.logger.println("Executing ${testArtifactName} '${testCasePath}'...") + apiClient = RestApiClientFactory.getRestApiClient(envVars.get('ET_API_HOSTNAME'), envVars.get('ET_API_PORT')) + if (executionConfig.executePackageCheck) { + listener.logger.println("Executing package checks for '${testCasePath}'") + CheckPackageResult check_result = apiClient.runPackageCheck(testCasePath) + listener.logger.println(check_result.toString()) + if (executionConfig.stopOnError && check_result.result == "ERROR") { + listener.logger.println( + "Skipping execution of ${testArtifactName} '${testCasePath}' due to failed package checks" + ) + listener.logger.flush() + return new TestResult(null, "ERROR", null) + } + } ExecutionOrder executionOrder = executionOrderBuilder.build() - RestApiClient apiClient = RestApiClientFactory.getRestApiClient(envVars.get('ET_API_HOSTNAME'), envVars.get('ET_API_PORT')) - - listener.logger.println("Executing ${testArtifactName} ${testCasePath}...") configUtil.log() - ReportInfo reportInfo = apiClient.runTest(executionOrder, executionConfig.timeout) + ReportInfo reportInfo = apiClient.runTest(executionOrder) TestResult result if (reportInfo) { @@ -132,7 +143,7 @@ abstract class AbstractTestBuilder implements Serializable { listener.logger.println("${StringUtils.capitalize(testArtifactName)} executed successfully.") } else { result = new TestResult(null, 'ERROR', null) - listener.logger.println("Executing ${testArtifactName} failed!") + listener.logger.println("Executing ${testArtifactName} '${testCasePath}' failed!") if (executionConfig.stopOnError) { toolInstallations.stopToolInstances(executionConfig.timeout) if (executionConfig.stopUndefinedTools) { @@ -141,7 +152,18 @@ abstract class AbstractTestBuilder implements Serializable { } } listener.logger.println(result.toString()) + listener.logger.flush() return result } + + /** + * Cancels the package check because it exceeded the configured timeout + * If the RestApiClientFactory has not return an apiClient for this class yet, it will be canceled there + */ + @Override + void cancel() { + listener.logger.println("Canceling ${testArtifactName} execution!") + !apiClient ? RestApiClientFactory.setTimeoutExceeded() : apiClient.setTimeoutExceeded() + } } } diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClient.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClient.groovy index 8209a00d..7e862e52 100644 --- a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClient.groovy +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClient.groovy @@ -18,33 +18,44 @@ import java.util.concurrent.TimeoutException interface RestApiClient { + /** + * Sets the timeoutExceeded to true, which will stop the execution at the next check + */ + abstract void setTimeoutExceeded() /** * Waits until the ecu.test REST api is alive or timeout is reached. * @param timeout time in seconds to wait for alive check * @return boolean: * true, if the the ecu.test API sends an alive signal within the timeout range * false, otherwise + * @throws TimeoutException if the execution time exceeded the timeout */ - abstract boolean waitForAlive(int timeout) + abstract boolean waitForAlive(int timeout) throws TimeoutException /** * This method performs the package check for the given test package or project via REST api. + * The method will abort upon a thread interruption signal used by the TimeoutControllerToAgentCallable to handle the + * timeout from outside this class. + * {@see de.tracetronic.jenkins.plugins.ecutestexecution.security.TimeoutControllerToAgentCallable} * @param testPkgPath the path to the package or project to be checked - * @param timeout Time in seconds until the check package execution will be aborted * @return CheckPackageResult with the result of the check - * @throws ApiException on error status codes - * @throws TimeoutException on timeout exceeded + * @throws ApiException on error status codes (except 409 (busy) where it will wait until success or timeout) + * @throws TimeoutException if the execution time exceeded the timeout */ - abstract CheckPackageResult runPackageCheck(String testPkgPath, int timeout) throws ApiException, TimeoutException + abstract CheckPackageResult runPackageCheck(String testPkgPath) throws ApiException, TimeoutException /** * Executes the test package or project of the given ExecutionOrder via REST api. + * The method will abort upon a thread interruption signal used by the TimeoutControllerToAgentCallable to handle the + * timeout from outside this class. + * {@see de.tracetronic.jenkins.plugins.ecutestexecution.security.TimeoutControllerToAgentCallable} * @param executionOrder is an ExecutionOrder object which defines the test environment and even the test package * or project - * @param timeout Time in seconds until the test execution will be aborted * @return ReportInfo with report information about the test execution + * @throws ApiException on error status codes (except 409 (busy) where it will wait until success or timeout) + * @throws TimeoutException if the execution time exceeded the timeout */ - abstract ReportInfo runTest(ExecutionOrder executionOrder, int timeout) + abstract ReportInfo runTest(ExecutionOrder executionOrder) throws ApiException, TimeoutException /** * Generates a report for a given report ID. The report has the format defined by the ReportGenerationOrder diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientFactory.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientFactory.groovy index 6c8150cc..fc3966a4 100644 --- a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientFactory.groovy +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientFactory.groovy @@ -8,11 +8,13 @@ package de.tracetronic.jenkins.plugins.ecutestexecution.clients import de.tracetronic.jenkins.plugins.ecutestexecution.clients.model.ApiException import org.apache.commons.lang.StringUtils +import java.util.concurrent.TimeoutException + class RestApiClientFactory { private static int DEFAULT_TIMEOUT = 10 private static final String DEFAULT_HOSTNAME = 'localhost' private static final String DEFAULT_PORT = '5050' - + private static RestApiClient apiClient /** * Determine the client for the highest REST api version of the given ecu.test. * @param hostName (optional) set if ecu.test is hosted on a custom host @@ -25,16 +27,24 @@ class RestApiClientFactory { hostName = StringUtils.isBlank(hostName) ? DEFAULT_HOSTNAME : StringUtils.trim(hostName) port = StringUtils.isBlank(port) ? DEFAULT_PORT : StringUtils.trim(port) - RestApiClient apiClientV2 = new RestApiClientV2(hostName, port) - if (apiClientV2.waitForAlive(timeout)) { - return apiClientV2 + apiClient = new RestApiClientV2(hostName, port) + if (apiClient.waitForAlive(timeout)) { + return apiClient } - RestApiClient apiClientV1 = new RestApiClientV1(hostName, port) - if (apiClientV1.waitForAlive(timeout)) { - return apiClientV1 + apiClient = new RestApiClientV1(hostName, port) + if (apiClient.waitForAlive(timeout)) { + return apiClient } throw new ApiException("Could not find a ecu.test REST api for host: ${hostName}:${port}") } + + /** + * Sets the executionTimedOut of the current ApiClient to true, which will stop the execution of ApiCalls and + * throw a TimeoutException + */ + static setTimeoutExceeded() { + apiClient.setTimeoutExceeded() + } } diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientV1.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientV1.groovy index 8110d30f..db6aa6b5 100644 --- a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientV1.groovy +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientV1.groovy @@ -32,28 +32,37 @@ import de.tracetronic.jenkins.plugins.ecutestexecution.model.UploadResult import java.util.concurrent.TimeoutException -class RestApiClientV1 implements RestApiClient{ +class RestApiClientV1 implements RestApiClient { private ApiClient apiClient + private boolean timeoutExceeded = false RestApiClientV1(String hostName, String port) { apiClient = Configuration.getDefaultApiClient() apiClient.setBasePath(String.format('http://%s:%s/api/v1', hostName, port)) } + /** + * Sets the timeoutExceeded to true, which will stop the execution at the next check + */ + void setTimeoutExceeded() { + timeoutExceeded = true + } + /** * Waits until the ecu.test API is alive or timeout is reached. It uses the api "apiStatus" to get a simple ping * @param timeout time in seconds to wait for alive check * @return boolean: * true, if the the ecu.test API sends an alive signal within the timeout range * false, otherwise + * @throws TimeoutException if the execution time exceeded the timeout */ - boolean waitForAlive(int timeout = 60) { + boolean waitForAlive(int timeout = 60) throws TimeoutException { ApiStatusApi statusApi = new ApiStatusApi(apiClient) boolean alive = false long endTimeMillis = System.currentTimeMillis() + (long) timeout * 1000L - while (System.currentTimeMillis() < endTimeMillis) { + while (System.currentTimeMillis() < endTimeMillis && !timeoutExceeded) { try { alive = statusApi.isAlive().message == 'Alive' if (alive) { @@ -63,6 +72,9 @@ class RestApiClientV1 implements RestApiClient{ sleep(1000) } } + if (timeoutExceeded) { + throw new TimeoutException("Could not find a ecu.test REST api for host: ${apiClient.getBasePath()}") + } return alive } @@ -70,12 +82,11 @@ class RestApiClientV1 implements RestApiClient{ * This method performs the package check for the given test package or project. It creates a check execution order * to get the execution ID and execute the package check for this ID. * @param testPkgPath the path to the package or project to be checked - * @param timeout Time in seconds until the check package execution will be aborted * @return CheckPackageResult with the result of the check * @throws ApiException on error status codes - * @throws TimeoutException on timeout exceeded + * @throws TimeoutException if the execution time exceeded the timeout */ - CheckPackageResult runPackageCheck(String testPkgPath, int timeout) throws ApiException, TimeoutException { + CheckPackageResult runPackageCheck(String testPkgPath) throws ApiException, TimeoutException { def issues = [] try { ChecksApi apiInstance = new ChecksApi(apiClient) @@ -86,20 +97,14 @@ class RestApiClientV1 implements RestApiClient{ response?.status in [null, 'WAITING', 'RUNNING'] } - CheckExecutionStatus checkPackageStatus - long endTimeMillis = System.currentTimeMillis() + (long) timeout * 1000L - while (checkStatus(checkPackageStatus = apiInstance.getCheckExecutionStatus(checkExecutionId))) { - if (timeout > 0 && System.currentTimeMillis() > endTimeMillis) { - break - } + while (!timeoutExceeded && checkStatus(apiInstance.getCheckExecutionStatus(checkExecutionId))) { sleep(1000) } - - println("CheckStatus is: " + checkPackageStatus.status) - if (checkPackageStatus.status != 'FINISHED' ) { - throw new TimeoutException("Timeout: check package '${testPkgPath}' took longer than ${timeout} seconds") + if (timeoutExceeded) { + throw new TimeoutException("Timeout exceeded during the package checks execution of '${testPkgPath}'") } + CheckReport checkReport = apiInstance.getCheckResult(checkExecutionId) for (CheckFinding issue : checkReport.issues) { def issueMap = [filename: issue.fileName, message: issue.message] @@ -116,12 +121,12 @@ class RestApiClientV1 implements RestApiClient{ /** * Executes the test package or project of the given ExecutionOrder via REST api. * @param executionOrder is an ExecutionOrder object which defines the test environment and even the test package - * or project - * @param timeout Time in seconds until the test execution will be aborted + * or project * @return ReportInfo with report information about the test execution + * @throws ApiException on error status codes (except 409 (busy) where it will wait until success or timeout) + * @throws TimeoutException if the execution time exceeded the timeout */ - ReportInfo runTest(ExecutionOrder executionOrder, int timeout) { - + ReportInfo runTest(ExecutionOrder executionOrder) throws ApiException, TimeoutException { de.tracetronic.cxs.generated.et.client.model.v1.ExecutionOrder executionOrderV1 executionOrderV1 = executionOrder.toExecutionOrderV1() ExecutionApi apiInstance = new ExecutionApi(apiClient) @@ -132,15 +137,15 @@ class RestApiClientV1 implements RestApiClient{ } Execution execution - long endTimeMillis = System.currentTimeMillis() + (long) timeout * 1000L - while (checkStatus(execution = apiInstance.currentExecution)) { - if (timeout > 0 && System.currentTimeMillis() > endTimeMillis) { + while (!timeoutExceeded && checkStatus(execution = apiInstance.currentExecution)) { + sleep(1000) + } + if (timeoutExceeded) { + if (apiInstance.currentExecution.order == executionOrderV1) { apiInstance.abortExecution() - break } - sleep(1000) + throw new TimeoutException("Timeout exceeded during the execution of '${executionOrder.testCasePath}'") } - if (execution.result == null) { // tests are not running return null diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientV2.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientV2.groovy index 694aa2a5..49287736 100644 --- a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientV2.groovy +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientV2.groovy @@ -25,9 +25,7 @@ import de.tracetronic.cxs.generated.et.client.model.v2.TGUpload import de.tracetronic.cxs.generated.et.client.model.v2.TGUploadStatus import de.tracetronic.cxs.generated.et.client.model.v2.TestConfiguration import de.tracetronic.cxs.generated.et.client.model.v2.TestbenchConfiguration -import de.tracetronic.cxs.generated.et.client.v2.ApiClient import de.tracetronic.cxs.generated.et.client.v2.ApiResponse -import de.tracetronic.cxs.generated.et.client.v2.Configuration import de.tracetronic.jenkins.plugins.ecutestexecution.clients.model.ApiException import de.tracetronic.jenkins.plugins.ecutestexecution.clients.model.ReportGenerationOrder import de.tracetronic.jenkins.plugins.ecutestexecution.clients.model.ReportInfo @@ -39,23 +37,28 @@ import de.tracetronic.jenkins.plugins.ecutestexecution.clients.model.ExecutionOr import java.util.concurrent.TimeoutException -class RestApiClientV2 implements RestApiClient{ - - private ApiClient apiClient +class RestApiClientV2 extends RestApiClientV2WithIdleHandle implements RestApiClient { RestApiClientV2(String hostName, String port) { - apiClient = Configuration.getDefaultApiClient() - apiClient.setBasePath(String.format('http://%s:%s/api/v2', hostName, port)) + super(hostName, port) + } + + /** + * Sets the timeoutExceeded to true, which will stop the execution of ApiCalls and throw a TimeoutException + */ + void setTimeoutExceeded() { + timeoutExceeded = true } /** - * Waits until the ecu.test API is alive or timeout is reached. It uses the api "apiStatus" to get a simple ping + * Waits until the ecu.test API is alive or timeout is reached. It uses the api "StatusApi" to get a simple ping * @param timeout time in seconds to wait for alive check * @return boolean: * true, if the the ecu.test API sends an alive signal within the timeout range * false, otherwise + * @throws TimeoutException during api calls if the execution time exceeded the timeout */ - boolean waitForAlive(int timeout = 60) { + boolean waitForAlive(int timeout = 60) throws TimeoutException { StatusApi statusApi = new StatusApi(apiClient) boolean alive = false @@ -68,6 +71,8 @@ class RestApiClientV2 implements RestApiClient{ } } catch (de.tracetronic.cxs.generated.et.client.v2.ApiException ignored) { sleep(1000) + } catch (TimeoutException ignored) { + throw new TimeoutException("Could not find a ecu.test REST api for host: ${apiClient.getBasePath()}") } } return alive @@ -77,56 +82,48 @@ class RestApiClientV2 implements RestApiClient{ * This method performs the package check for the given test package or project. It creates a check execution order * to get the execution ID and execute the package check for this ID. * @param testPkgPath the path to the package or project to be checked - * @param timeout Time in seconds until the check package execution will be aborted * @return CheckPackageResult with the result of the check - * @throws ApiException on error status codes - * @throws TimeoutException on timeout exceeded + * @throws ApiException on error status codes (except 409 (busy) where it will wait until success or timeout) + * @throws TimeoutException during api calls if the execution time exceeded the timeout + */ - CheckPackageResult runPackageCheck(String testPkgPath, int timeout) throws ApiException, TimeoutException { + CheckPackageResult runPackageCheck(String testPkgPath) throws ApiException, TimeoutException { def issues = [] + ChecksApi apiInstance = new ChecksApi(apiClient) + CheckExecutionOrder order = new CheckExecutionOrder().filePath(testPkgPath) + String checkExecutionId try { - ChecksApi apiInstance = new ChecksApi(apiClient) - CheckExecutionOrder order = new CheckExecutionOrder().filePath(testPkgPath) - String checkExecutionId = apiInstance.createCheckExecutionOrder(order).getCheckExecutionId() - - Closure checkStatus = { CheckExecutionStatus response -> - response?.status in [null, 'WAITING', 'RUNNING'] - } - - CheckExecutionStatus checkPackageStatus - long endTimeMillis = System.currentTimeMillis() + (long) timeout * 1000L - while (checkStatus(checkPackageStatus = apiInstance.getCheckExecutionStatus(checkExecutionId))) { - if (timeout > 0 && System.currentTimeMillis() > endTimeMillis) { - break - } - sleep(1000) - } - - if (checkPackageStatus.status != 'FINISHED' ) { - throw new TimeoutException("Timeout: check package '${testPkgPath}' took longer than ${timeout} seconds") - } - - CheckReport checkReport = apiInstance.getCheckResult(checkExecutionId) - for (CheckFinding issue : checkReport.issues) { - def issueMap = [filename: issue.fileName, message: issue.message] - issues.add(issueMap) - } + checkExecutionId = apiInstance.createCheckExecutionOrder(order).getCheckExecutionId() } catch (de.tracetronic.cxs.generated.et.client.v2.ApiException rethrow) { - throw new ApiException('An error occurs during runPackageCheck. See stacktrace below:\n' + + throw new ApiException('An error occurred during runPackageCheck. See stacktrace below:\n' + rethrow.getMessage()) } + Closure checkStatus = { CheckExecutionStatus response -> + response?.status in [null, 'WAITING', 'RUNNING'] + } + + while (checkStatus(apiInstance.getCheckExecutionStatus(checkExecutionId))) { + sleep(1000) + } + + CheckReport checkReport = apiInstance.getCheckResult(checkExecutionId) + for (CheckFinding issue : checkReport.issues) { + def issueMap = [filename: issue.fileName, message: issue.message] + issues.add(issueMap) + } return new CheckPackageResult(testPkgPath, issues) } /** * Executes the test package or project of the given ExecutionOrder via REST api. * @param executionOrder is an ExecutionOrder object which defines the test environment and even the test package - * or project - * @param timeout Time in seconds until the test execution will be aborted + * or project * @return ReportInfo with report information about the test execution + * @throws ApiException on error status codes (except 409 (busy) where it will wait until success or timeout) + * @throws TimeoutException during api calls if the execution time exceeded the timeout */ - ReportInfo runTest(ExecutionOrder executionOrder, int timeout) { + ReportInfo runTest(ExecutionOrder executionOrder) throws ApiException, TimeoutException { de.tracetronic.cxs.generated.et.client.model.v2.ExecutionOrder executionOrderV2 executionOrderV2 = executionOrder.toExecutionOrderV2() @@ -143,34 +140,34 @@ class RestApiClientV2 implements RestApiClient{ if (executionOrder.tbcPath != null || executionOrder.tcfPath != null) { ConfigurationApi configApi = new ConfigurationApi(apiClient) - ApiResponse status = configApi.manageConfigurationWithHttpInfo(configOrder) - if (status.statusCode != 200) { - throw new ApiException("Configuration could not be loaded!") - } + ApiResponse status + configApi.manageConfigurationWithHttpInfo(configOrder) } - ExecutionApi executionApi = new ExecutionApi(apiClient) executionApi.createExecution(executionOrderV2) - Closure checkStatus = { Execution execution -> execution?.status?.key in [null, ExecutionStatus.KeyEnum.WAITING, ExecutionStatus.KeyEnum.RUNNING] } - Execution execution - long endTimeMillis = System.currentTimeMillis() + (long) timeout * 1000L - while (checkStatus(execution = executionApi.currentExecution)) { - if (timeout > 0 && System.currentTimeMillis() > endTimeMillis) { - executionApi.abortExecution() - break + try { + Execution execution + while (checkStatus(execution = executionApi.currentExecution)) { + sleep(1000) + } + if (execution.result == null) { + // tests are not running + return null + } + return ReportInfo.fromReportInfo(execution.result) + } catch (TimeoutException ignored) { + if (timeoutExceeded) { + timeoutExceeded = false + if (executionApi.currentExecution.order == executionOrderV2) { + executionApi.abortExecution() + } + throw new TimeoutException("Timeout exceeded during the execution of '${executionOrder.testCasePath}'") } - sleep(1000) - } - - if (execution.result == null) { - // tests are not running - return null } - return ReportInfo.fromReportInfo(execution.result) } /** @@ -183,7 +180,6 @@ class RestApiClientV2 implements RestApiClient{ de.tracetronic.cxs.generated.et.client.model.v2.ReportGenerationOrder orderV2 = order.toReportGenerationOrderV2() ReportApi apiInstance = new ReportApi(apiClient) apiInstance.createReportGeneration(reportId, orderV2) - Closure checkStatus = { ReportGeneration generation -> generation?.status?.key in [null, ReportGenerationStatus.KeyEnum.WAITING, ReportGenerationStatus.KeyEnum.RUNNING] @@ -207,10 +203,11 @@ class RestApiClientV2 implements RestApiClient{ UploadResult uploadReport(String reportId, TGUploadOrder order) { de.tracetronic.cxs.generated.et.client.model.v2.TGUploadOrder uploadOrderV2 uploadOrderV2 = order.toTGUploadOrderV2() + ReportApi apiInstance = new ReportApi(apiClient) apiInstance.createUpload(reportId, uploadOrderV2) - - Closure checkStatus = { TGUpload upload -> upload?.status?.key in [null, TGUploadStatus.KeyEnum.WAITING, TGUploadStatus.KeyEnum.RUNNING] + Closure checkStatus = { TGUpload upload -> + upload?.status?.key in [null, TGUploadStatus.KeyEnum.WAITING, TGUploadStatus.KeyEnum.RUNNING] } TGUpload upload @@ -235,4 +232,5 @@ class RestApiClientV2 implements RestApiClient{ List reports = apiInstance.getAllReports() return reports*.testReportId } + } diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientV2WithIdleHandle.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientV2WithIdleHandle.groovy new file mode 100644 index 00000000..421225a7 --- /dev/null +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/RestApiClientV2WithIdleHandle.groovy @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 tracetronic GmbH + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package de.tracetronic.jenkins.plugins.ecutestexecution.clients + +import de.tracetronic.cxs.generated.et.client.v2.ApiClient +import de.tracetronic.cxs.generated.et.client.v2.ApiException +import de.tracetronic.cxs.generated.et.client.v2.ApiResponse +import okhttp3.Call + +import java.lang.reflect.Type +import java.util.concurrent.TimeoutException + +class RestApiClientV2WithIdleHandle { + public ApiClient apiClient + public boolean timeoutExceeded = false + + RestApiClientV2WithIdleHandle(String hostName, String port) { + apiClient = new ApiClient() { + /** + * Execute HTTP call and handle the status 409 (ecu.test is busy) by waiting and trying again. + * Run until success or timeout handled by RestApiClient and ControllerToAgentCallableWithTimeout + * {@see de.tracetronic.jenkins.plugins.ecutestexecution.security.ControllerToAgentCallableWithTimeout} + * @param returnType : The return type used to deserialize HTTP response body + * @param call Call + * @return ApiResponse object containing response status, headers and + * data, which is a Java object deserialized from response body and would be null + * when returnType is null. + * @throws ApiException on error status codes (except 409 (busy) where it will wait until success or + * timeout + * @throws TimeoutException when the execution time exceeded the timeout + */ + @Override + ApiResponse execute(Call call, Type returnType) throws ApiException, TimeoutException { + while (!timeoutExceeded) { + try { + return super.execute(call.clone(), returnType) + } catch (ApiException e) { + if (e.code != 409) { + throw e + } + sleep(1000) + } + } + throw new TimeoutException("ecu.test could not process the request within the timeout") + } + } + apiClient.setBasePath(String.format('http://%s:%s/api/v2', hostName, port)) + } +} diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/model/AdditionalSettings.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/model/AdditionalSettings.groovy index 13d47e7c..5c413b07 100644 --- a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/model/AdditionalSettings.groovy +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/clients/model/AdditionalSettings.groovy @@ -7,8 +7,8 @@ package de.tracetronic.jenkins.plugins.ecutestexecution.clients.model class AdditionalSettings { private List recording - private String mapping - private String analysisName + private String mapping = '' + private String analysisName = '' private Boolean forceConfigurationReload private List packageParameter @@ -28,7 +28,7 @@ class AdditionalSettings { * Convert the abstract AdditionalSettings object to a ecu.test REST api object of the api version V1 * @return AdditionalSettings for ecu.test REST api in version V1 */ - de.tracetronic.cxs.generated.et.client.model.v1.AdditionalSettings toAdditionalSettingsV1(){ + de.tracetronic.cxs.generated.et.client.model.v1.AdditionalSettings toAdditionalSettingsV1() { List recordingsV1 = [] for (Recording recording : this.recording) { recordingsV1.add(recording.toRecordingV1()) @@ -51,7 +51,7 @@ class AdditionalSettings { * Convert the abstract AdditionalSettings object to a ecu.test REST api object of the api version V2 * @return AdditionalSettings for ecu.test REST api in version V2 */ - de.tracetronic.cxs.generated.et.client.model.v2.AdditionalSettings toAdditionalSettingsV2(){ + de.tracetronic.cxs.generated.et.client.model.v2.AdditionalSettings toAdditionalSettingsV2() { List recordingsV2 = [] for (Recording recording : this.recording) { recordingsV2.add(recording.toRecordingV2()) diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/security/ControllerToAgentCallableWithTimeout.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/security/ControllerToAgentCallableWithTimeout.groovy new file mode 100644 index 00000000..0eb79d2a --- /dev/null +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/security/ControllerToAgentCallableWithTimeout.groovy @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 tracetronic GmbH + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package de.tracetronic.jenkins.plugins.ecutestexecution.security + +import hudson.model.TaskListener +import jenkins.security.MasterToSlaveCallable +import jenkins.util.Timer + +import java.util.concurrent.Callable +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException + + +abstract class ControllerToAgentCallableWithTimeout extends MasterToSlaveCallable { + + private long timeout + private final TaskListener listener + + ControllerToAgentCallableWithTimeout(long timeout, TaskListener listener) { + this.timeout = timeout + this.listener = listener + } + + abstract V execute() throws Exception + + abstract void cancel() + + /** + * Call the execution of a step in the build and return the results + * Cancels the execution of the step upon reaching the given timeout by setting the apiClient's variable + * executionTimedOut to true + * @return V + */ + @Override + V call() throws T { + try { + ScheduledExecutorService exe = Timer.get() + V result + exe.schedule({ !result ? cancel() : { return } } as Callable, timeout, TimeUnit.SECONDS) + result = execute() + } catch (Exception e) { + if (e instanceof TimeoutException) { + listener.error("Execution has exceeded the configured timeout of ${timeout} seconds") + listener.logger.flush() + } + throw e + } + } +} diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/CheckPackageStep.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/CheckPackageStep.groovy index 6523f4d0..9ede023e 100644 --- a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/CheckPackageStep.groovy +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/CheckPackageStep.groovy @@ -8,6 +8,7 @@ package de.tracetronic.jenkins.plugins.ecutestexecution.steps import com.google.common.collect.ImmutableSet import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClient import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClientFactory +import de.tracetronic.jenkins.plugins.ecutestexecution.security.ControllerToAgentCallableWithTimeout import de.tracetronic.jenkins.plugins.ecutestexecution.clients.model.ApiException import de.tracetronic.jenkins.plugins.ecutestexecution.configs.ExecutionConfig import de.tracetronic.jenkins.plugins.ecutestexecution.model.CheckPackageResult @@ -18,7 +19,6 @@ import hudson.Launcher import hudson.model.Result import hudson.model.Run import hudson.model.TaskListener -import jenkins.security.MasterToSlaveCallable import org.apache.commons.lang.StringUtils import org.jenkinsci.plugins.workflow.steps.* import org.kohsuke.stapler.DataBoundConstructor @@ -114,22 +114,29 @@ class CheckPackageStep extends Step { */ @Override CheckPackageResult run() throws Exception { + CheckPackageResult result + TaskListener listener = context.get(TaskListener.class) try { - return getContext().get(Launcher.class).getChannel().call( - new PackageCheckCallable (step.testCasePath, context, step.executionConfig) + result = getContext().get(Launcher.class).getChannel().call( + new PackageCheckCallable(step.testCasePath, context, step.executionConfig) ) } catch (Exception e) { - context.get(TaskListener.class).error(e.message) + listener.logger.println('Executing package checks failed!') + listener.error(e.message) context.get(Run.class).setResult(Result.FAILURE) - return new CheckPackageResult(null, null) + result = new CheckPackageResult(null, null) } + + listener.logger.println(result.toString()) + listener.logger.flush() + return result } } /** * Callable providing the execution of the step in the build */ - private static final class PackageCheckCallable extends MasterToSlaveCallable { + private static final class PackageCheckCallable extends ControllerToAgentCallableWithTimeout { private static final long serialVersionUID = 1L @@ -139,6 +146,7 @@ class CheckPackageStep extends Step { private final TaskListener listener private final ExecutionConfig executionConfig private final ToolInstallations toolInstallations + private RestApiClient apiClient /** * Instantiates a new [ExecutionCallable]. * @@ -152,6 +160,7 @@ class CheckPackageStep extends Step { * ArrayList of strings containing tool installations, which depending on execution cfg will be stopped */ PackageCheckCallable(String testCasePath, StepContext context, ExecutionConfig executionConfig) { + super(executionConfig.timeout, context.get(TaskListener.class)) this.testCasePath = testCasePath this.context = context this.envVars = context.get(EnvVars.class) @@ -168,27 +177,23 @@ class CheckPackageStep extends Step { * @return the results of the package check */ @Override - CheckPackageResult call() throws Exception { - listener.logger.println('Executing Package Checks for: ' + testCasePath + ' ...') - RestApiClient apiClient = RestApiClientFactory.getRestApiClient(envVars.get('ET_API_HOSTNAME'), envVars.get('ET_API_PORT')) - + CheckPackageResult execute() throws Exception { + listener.logger.println("Executing package checks for '${testCasePath}'...") + apiClient = RestApiClientFactory.getRestApiClient(envVars.get('ET_API_HOSTNAME'), envVars.get('ET_API_PORT')) CheckPackageResult result try { - result = apiClient.runPackageCheck(testCasePath, executionConfig.timeout) + result = apiClient.runPackageCheck(testCasePath) } catch (Exception e) { - listener.logger.println('Executing Package Checks failed!') - if (e instanceof TimeoutException || e instanceof ApiException) { - listener.logger.println(e.message) + if (e instanceof ApiException) { + listener.logger.println('Executing package checks failed!') + listener.error(e.message) result = new CheckPackageResult(null, null) - } - else { + } else { throw e } } - - listener.logger.println(result.toString()) - if (result.result == "ERROR" && executionConfig.stopOnError){ + if (result.result == "ERROR" && executionConfig.stopOnError) { toolInstallations.stopToolInstances(executionConfig.timeout) if (executionConfig.stopUndefinedTools) { toolInstallations.stopTTInstances(executionConfig.timeout) @@ -196,6 +201,16 @@ class CheckPackageStep extends Step { } return result } + + /** + * Cancels the package checks because it exceeded the configured timeout + * If the RestApiClientFactory has not return an apiClient for this class yet, it will be canceled there + */ + @Override + void cancel() { + listener.logger.println("Canceling package checks execution!") + !apiClient ? RestApiClientFactory.setTimeoutExceeded() : apiClient.setTimeoutExceeded() + } } /** diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETContainerTest.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETContainerTest.groovy index 3d7bd0cc..f3884ebd 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETContainerTest.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETContainerTest.groovy @@ -47,7 +47,7 @@ abstract class ETContainerTest extends ContainerTest { WorkflowRun run = jenkins.buildAndAssertStatus(Result.SUCCESS, job) then: "expect successful test completion" - jenkins.assertLogContains("Executing Package Checks for: test.pkg", run) + jenkins.assertLogContains("Executing package checks for 'test.pkg'", run) jenkins.assertLogContains("-> result: SUCCESS", run) } @@ -67,7 +67,7 @@ abstract class ETContainerTest extends ContainerTest { WorkflowRun run = jenkins.buildAndAssertStatus(Result.SUCCESS, job) then: "expect error" - jenkins.assertLogContains("Executing Package Checks failed!", run) + jenkins.assertLogContains("Executing package checks failed!", run) jenkins.assertLogContains("-> result: ERROR", run) // ecu.test 2024.1 and newer returns case sensitive messages assertThat(jenkins.getLog(run), containsStringIgnoringCase("BAD REQUEST")) @@ -89,7 +89,7 @@ abstract class ETContainerTest extends ContainerTest { WorkflowRun run = jenkins.buildAndAssertStatus(Result.SUCCESS, job) then: "expect error" - jenkins.assertLogContains("Executing Package Checks for: invalid_package_desc.pkg", run) + jenkins.assertLogContains("Executing package checks for 'invalid_package_desc.pkg'", run) jenkins.assertLogContains("-> result: ERROR", run) jenkins.assertLogContains("--> invalid_package_desc.pkg: Description must not be empty!", run) } @@ -109,12 +109,12 @@ abstract class ETContainerTest extends ContainerTest { job.setDefinition(new CpsFlowDefinition(script, true)) when: "scheduling a new build" - WorkflowRun run = jenkins.buildAndAssertStatus(Result.SUCCESS, job) + WorkflowRun run = jenkins.buildAndAssertStatus(Result.FAILURE, job) then: "expect error" - jenkins.assertLogContains("Executing Package Checks for: invalid_package_desc.pkg", run) - jenkins.assertLogContains("Executing Package Checks failed!", run) - jenkins.assertLogContains("Timeout: check package '${testPkg}' took longer than ${timeout} seconds", run) + jenkins.assertLogContains("Executing package checks for 'invalid_package_desc.pkg'", run) + jenkins.assertLogContains("Executing package checks failed!", run) + jenkins.assertLogContains("Execution has exceeded the configured timeout of ${timeout} seconds", run) jenkins.assertLogContains("-> result: ERROR", run) } @@ -133,7 +133,7 @@ abstract class ETContainerTest extends ContainerTest { WorkflowRun run = jenkins.buildAndAssertStatus(Result.SUCCESS, job) then: "expect successful test completion" - jenkins.assertLogContains("Executing Package Checks for: test.prj", run) + jenkins.assertLogContains("Executing package checks for 'test.prj'", run) jenkins.assertLogContains("-> result: SUCCESS", run) } @@ -152,7 +152,7 @@ abstract class ETContainerTest extends ContainerTest { WorkflowRun run = jenkins.buildAndAssertStatus(Result.SUCCESS, job) then: "expect error" - jenkins.assertLogContains("Executing Package Checks for: invalid_package_desc.prj", run) + jenkins.assertLogContains("Executing package checks for 'invalid_package_desc.prj'", run) jenkins.assertLogContains("-> result: ERROR", run) jenkins.assertLogContains("--> invalid_package_desc.pkg: Description must not be empty!", run) } @@ -202,7 +202,7 @@ abstract class ETContainerTest extends ContainerTest { then: "expect error" jenkins.assertLogContains("-> result: ERROR", run) - jenkins.assertLogContains("Executing package failed!", run) + jenkins.assertLogContains("Executing package 'testDoesNotExist.pkg' failed!", run) } def "Execute test case including package check"() { @@ -226,14 +226,37 @@ abstract class ETContainerTest extends ContainerTest { WorkflowRun run = jenkins.buildAndAssertStatus(Result.SUCCESS, job) then: "expect successful test completion" - jenkins.assertLogContains("Executing Package Checks for: invalid_package_desc.pkg", run) + jenkins.assertLogContains("Executing package checks for 'invalid_package_desc.pkg'", run) jenkins.assertLogContains("-> result: ERROR", run) jenkins.assertLogContains("--> invalid_package_desc.pkg: Description must not be empty!", run) - jenkins.assertLogContains("Executing package invalid_package_desc.pkg", run) + jenkins.assertLogContains("Executing package 'invalid_package_desc.pkg'", run) jenkins.assertLogContains("-> result: SUCCESS", run) jenkins.assertLogContains("-> reportDir: ${ET_WS_PATH}/TestReports/invalid_package_desc_", run) } + def "Execute package with timeout"() { + given: "a test execution pipeline" + int timeout = 1 + String testPkg = 'test.pkg' + String script = """ + node { + withEnv(['ET_API_HOSTNAME=${etContainer.host}', 'ET_API_PORT=${etContainer.getMappedPort(ET_PORT)}']) { + ttRunPackage executionConfig: [stopOnError: false, stopUndefinedTools: false, timeout: ${timeout}], testCasePath: '${testPkg}' + } + } + """.stripIndent() + WorkflowJob job = jenkins.createProject(WorkflowJob.class, "pipeline") + job.setDefinition(new CpsFlowDefinition(script, true)) + + when: "scheduling a new build" + WorkflowRun run = jenkins.buildAndAssertStatus(Result.FAILURE, job) + + then: "expect error" + jenkins.assertLogContains("Executing package '${testPkg}'", run) + jenkins.assertLogContains("Executing package '${testPkg}' failed!", run) + jenkins.assertLogContains("Execution has exceeded the configured timeout of ${timeout} seconds", run) + } + def "Generate report format"() { given: "a test execution and report generation pipeline" String script = """ diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/client/MockApiResponse.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/client/MockApiResponse.groovy new file mode 100644 index 00000000..521134da --- /dev/null +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/client/MockApiResponse.groovy @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 tracetronic GmbH + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package de.tracetronic.jenkins.plugins.ecutestexecution.client + +import okhttp3.MediaType +import okhttp3.Protocol +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody + +class MockApiResponse { + static Response getResponseUnauthorized(){ + return new Response.Builder() + .request(new Request.Builder().url('http://example.com').build()) + .protocol(Protocol.HTTP_1_1) + .code(401).message('unauthorized') + .body(ResponseBody.create("{}", MediaType.parse('application/json; charset=utf-8') + )).build() + } + + static Response getResponseBusy(){ + return new Response.Builder() + .request(new Request.Builder().url('http://example.com').build()) + .protocol(Protocol.HTTP_1_1) + .code(409).message('ecu.test is busy') + .body(ResponseBody.create("{}", MediaType.parse('application/json; charset=utf-8') + )).build() + } +} diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/TestRestApiClient.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/client/MockRestApiClient.groovy similarity index 80% rename from src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/TestRestApiClient.groovy rename to src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/client/MockRestApiClient.groovy index 9fcb470f..3de4dff7 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/TestRestApiClient.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/client/MockRestApiClient.groovy @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: BSD-3-Clause */ -package de.tracetronic.jenkins.plugins.ecutestexecution.steps +package de.tracetronic.jenkins.plugins.ecutestexecution.client import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClient import de.tracetronic.jenkins.plugins.ecutestexecution.clients.model.ApiException @@ -17,7 +17,10 @@ import de.tracetronic.jenkins.plugins.ecutestexecution.model.UploadResult import java.util.concurrent.TimeoutException -class TestRestApiClient implements RestApiClient { +class MockRestApiClient implements RestApiClient { + + @Override + void setTimeoutExceeded() {} @Override boolean waitForAlive(int timeout) { @@ -25,12 +28,12 @@ class TestRestApiClient implements RestApiClient { } @Override - CheckPackageResult runPackageCheck(String testPkgPath, int timeout) throws ApiException, TimeoutException { + CheckPackageResult runPackageCheck(String testPkgPath) throws ApiException, TimeoutException { return null } @Override - ReportInfo runTest(ExecutionOrder executionOrder, int timeout) { + ReportInfo runTest(ExecutionOrder executionOrder) { return null } diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/CheckPackageStepIT.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/CheckPackageStepIT.groovy index 9488965a..be4ad547 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/CheckPackageStepIT.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/CheckPackageStepIT.groovy @@ -5,16 +5,26 @@ */ package de.tracetronic.jenkins.plugins.ecutestexecution.steps +import com.google.gson.reflect.TypeToken +import de.tracetronic.cxs.generated.et.client.api.v2.ChecksApi +import de.tracetronic.cxs.generated.et.client.model.v2.AcceptedCheckExecutionOrder import de.tracetronic.jenkins.plugins.ecutestexecution.ETInstallation import de.tracetronic.jenkins.plugins.ecutestexecution.IntegrationTestBase +import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClientFactory +import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClientV2 import hudson.Functions import hudson.model.Result +import okhttp3.Call +import okhttp3.HttpUrl +import okhttp3.Request import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition import org.jenkinsci.plugins.workflow.cps.SnippetizerTester import org.jenkinsci.plugins.workflow.job.WorkflowJob import org.jenkinsci.plugins.workflow.job.WorkflowRun import org.jenkinsci.plugins.workflow.steps.StepConfigTester + import org.jvnet.hudson.test.JenkinsRule +import de.tracetronic.jenkins.plugins.ecutestexecution.client.MockApiResponse class CheckPackageStepIT extends IntegrationTestBase { @@ -49,7 +59,52 @@ class CheckPackageStepIT extends IntegrationTestBase { job.setDefinition(new CpsFlowDefinition("node {ttCheckPackage testCasePath: 'test.pkg'}", true)) expect: WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) - jenkins.assertLogContains("Executing Package Checks for: test.pkg", run) + jenkins.assertLogContains("Executing package checks for 'test.pkg'", run) + } + + def 'Run pipeline: with 409 handling'() { + given: + GroovyMock(RestApiClientFactory, global: true) + def restApiClient = new RestApiClientV2('', '') + RestApiClientFactory.getRestApiClient(*_) >> restApiClient + def mockCall = Mock(Call) + mockCall.clone() >> mockCall + mockCall.execute() >> MockApiResponse.getResponseBusy() >> MockApiResponse.getResponseUnauthorized() + GroovySpy(ChecksApi, global: true) { + createCheckExecutionOrder(_) >> { restApiClient.apiClient.execute(mockCall, new TypeToken() {}.getType()) } + } + + WorkflowJob job = jenkins.createProject(WorkflowJob.class, 'pipeline') + job.setDefinition(new CpsFlowDefinition("node {ttCheckPackage testCasePath: 'test.pkg'}", true)) + when: + WorkflowRun run = jenkins.assertBuildStatus(Result.SUCCESS, job.scheduleBuild2(0).get()) + then: + jenkins.assertLogContains("Executing package checks for 'test.pkg'", run) + jenkins.assertLogNotContains('ecu.test is busy', run) + jenkins.assertLogContains('unauthorized', run) + + } + + def 'Run pipeline: timeout by busy ecu.test'() { + given: + GroovyMock(RestApiClientFactory, global: true) + def restApiClient = new RestApiClientV2('', '') + RestApiClientFactory.getRestApiClient(*_) >> restApiClient + def mockCall = Mock(Call) + mockCall.clone() >> mockCall + mockCall.execute() >> MockApiResponse.getResponseBusy() + + GroovySpy(ChecksApi, global: true) { + createCheckExecutionOrder(*_) >> { restApiClient.apiClient.execute(mockCall, null) } + } + WorkflowJob job = jenkins.createProject(WorkflowJob.class, 'pipeline') + job.setDefinition(new CpsFlowDefinition("node {ttCheckPackage testCasePath: 'test.pkg', executionConfig:[timeout: 2]}", true)) + expect: + WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) + jenkins.assertLogContains("Executing package checks for 'test.pkg'", run) + jenkins.assertLogNotContains('ecu.test is busy', run) + jenkins.assertLogContains("Execution has exceeded the configured timeout of 2 seconds", run) } + } diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/GenerateReportsStepIT.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/GenerateReportsStepIT.groovy index 35a176da..9e1f8a81 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/GenerateReportsStepIT.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/GenerateReportsStepIT.groovy @@ -5,11 +5,15 @@ */ package de.tracetronic.jenkins.plugins.ecutestexecution.steps - +import de.tracetronic.jenkins.plugins.ecutestexecution.client.MockRestApiClient +import de.tracetronic.jenkins.plugins.ecutestexecution.client.MockApiResponse +import de.tracetronic.cxs.generated.et.client.api.v2.ReportApi import de.tracetronic.jenkins.plugins.ecutestexecution.IntegrationTestBase import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClientFactory +import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClientV2 import de.tracetronic.jenkins.plugins.ecutestexecution.model.AdditionalSetting import hudson.model.Result +import okhttp3.Call import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition import org.jenkinsci.plugins.workflow.cps.SnippetizerTester import org.jenkinsci.plugins.workflow.job.WorkflowJob @@ -47,7 +51,7 @@ class GenerateReportsStepIT extends IntegrationTestBase { when: step.setAdditionalSettings(Arrays.asList(new AdditionalSetting('javascript', 'False'))) then: - st.assertRoundTrip(step, "ttGenerateReports additionalSettings: [" + + st.assertRoundTrip(step, 'ttGenerateReports additionalSettings: [' + "[name: 'javascript', value: 'False']], generatorName: 'HTML'") } @@ -58,9 +62,30 @@ class GenerateReportsStepIT extends IntegrationTestBase { // assume RestApiClient is available GroovyMock(RestApiClientFactory, global: true) - RestApiClientFactory.getRestApiClient() >> new TestRestApiClient() + RestApiClientFactory.getRestApiClient() >> new MockRestApiClient() + expect: + WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) + jenkins.assertLogContains('Generating HTML reports...', run) + } + + def 'Run pipeline: with 409 handling'() { + given: + GroovyMock(RestApiClientFactory, global: true) + def restApiClient = new RestApiClientV2('','') + RestApiClientFactory.getRestApiClient(*_) >> restApiClient + def mockCall = Mock(Call) + mockCall.clone() >> mockCall + mockCall.execute() >> MockApiResponse.getResponseBusy() >> MockApiResponse.getResponseUnauthorized() + GroovySpy(ReportApi, global: true){ + createReportGeneration(*_) >> {restApiClient.apiClient.execute(mockCall, null)} + getAllReports(*_) >> {restApiClient.apiClient.execute(mockCall, null)} + } + WorkflowJob job = jenkins.createProject(WorkflowJob.class, 'pipeline') + job.setDefinition(new CpsFlowDefinition("node { ttGenerateReports 'HTML' }", true)) expect: WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) jenkins.assertLogContains('Generating HTML reports...', run) + jenkins.assertLogNotContains('ecu.test is busy', run) + jenkins.assertLogContains('unauthorized', run) } } diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/RunPackageStepIT.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/RunPackageStepIT.groovy index c952ed9b..1b420c24 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/RunPackageStepIT.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/RunPackageStepIT.groovy @@ -5,9 +5,16 @@ */ package de.tracetronic.jenkins.plugins.ecutestexecution.steps +import de.tracetronic.jenkins.plugins.ecutestexecution.client.MockRestApiClient +import de.tracetronic.jenkins.plugins.ecutestexecution.client.MockApiResponse +import com.google.gson.reflect.TypeToken +import de.tracetronic.cxs.generated.et.client.api.v2.ConfigurationApi +import de.tracetronic.cxs.generated.et.client.api.v2.ExecutionApi +import de.tracetronic.cxs.generated.et.client.model.v2.SimpleMessage import de.tracetronic.jenkins.plugins.ecutestexecution.ETInstallation import de.tracetronic.jenkins.plugins.ecutestexecution.IntegrationTestBase import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClientFactory +import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClientV2 import de.tracetronic.jenkins.plugins.ecutestexecution.configs.AnalysisConfig import de.tracetronic.jenkins.plugins.ecutestexecution.configs.ExecutionConfig import de.tracetronic.jenkins.plugins.ecutestexecution.configs.PackageConfig @@ -17,6 +24,9 @@ import de.tracetronic.jenkins.plugins.ecutestexecution.model.PackageParameter import de.tracetronic.jenkins.plugins.ecutestexecution.model.RecordingAsSetting import hudson.Functions import hudson.model.Result +import okhttp3.Call +import okhttp3.HttpUrl +import okhttp3.Request import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition import org.jenkinsci.plugins.workflow.cps.SnippetizerTester import org.jenkinsci.plugins.workflow.job.WorkflowJob @@ -152,14 +162,17 @@ class RunPackageStepIT extends IntegrationTestBase { // assume RestApiClient is available GroovyMock(RestApiClientFactory, global: true) - RestApiClientFactory.getRestApiClient() >> new TestRestApiClient() + RestApiClientFactory.getRestApiClient() >> new MockRestApiClient() expect: WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) - jenkins.assertLogContains('Executing package test.pkg...', run) + jenkins.assertLogContains("Executing package 'test.pkg'", run) } def 'Run pipeline with package check'(){ given: + GroovyMock(RestApiClientFactory, global: true) + def restApiClient = new RestApiClientV2('','') + RestApiClientFactory.getRestApiClient(*_) >> restApiClient WorkflowJob job = jenkins.createProject(WorkflowJob.class, 'pipeline') job.setDefinition(new CpsFlowDefinition("node { " + "ttRunPackage testCasePath:'test.pkg', executionConfig: [executePackageCheck: true]}", @@ -167,6 +180,58 @@ class RunPackageStepIT extends IntegrationTestBase { ) expect: WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) - jenkins.assertLogContains('Executing Package Checks for: test.pkg ...', run) + jenkins.assertLogContains("Executing package checks for 'test.pkg'", run) + } + + def 'Run pipeline: with 409 handling'() { + given: + GroovyMock(RestApiClientFactory, global: true) + def restApiClient = new RestApiClientV2('','') + RestApiClientFactory.getRestApiClient(*_) >> restApiClient + def mockCall = Mock(Call) + mockCall.clone() >> mockCall + mockCall.execute() >> MockApiResponse.getResponseBusy() >> MockApiResponse.getResponseUnauthorized() + GroovySpy(ConfigurationApi, global: true){ + manageConfigurationWithHttpInfo(*_) >> { + restApiClient.apiClient.execute(mockCall,new TypeToken(){}.getType()) + } + } + GroovySpy(ExecutionApi, global: true){ + createExecution(*_) >> {restApiClient.apiClient.execute(mockCall, null)} + } + WorkflowJob job = jenkins.createProject(WorkflowJob.class, 'pipeline') + job.setDefinition(new CpsFlowDefinition("node { ttRunPackage 'test.pkg' }", true)) + when: + WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) + then: + jenkins.assertLogContains("Executing package 'test.pkg'", run) + jenkins.assertLogNotContains('ecu.test is busy', run) + jenkins.assertLogContains('unauthorized', run) + + } + + def 'Run pipeline: timeout by busy ecu.test'() { + given: + GroovyMock(RestApiClientFactory, global: true) + def restApiClient = new RestApiClientV2('','') + RestApiClientFactory.getRestApiClient(*_) >> restApiClient + def mockCall = Mock(Call) + mockCall.clone() >> mockCall + mockCall.execute() >> MockApiResponse.getResponseBusy() + GroovySpy(ConfigurationApi, global: true){ + manageConfigurationWithHttpInfo(*_) >> { + restApiClient.apiClient.execute(mockCall, null) + } + } + GroovySpy(ExecutionApi, global: true){ + createExecution(*_) >> {restApiClient.apiClient.execute(mockCall, null)} + } + WorkflowJob job = jenkins.createProject(WorkflowJob.class, 'pipeline') + job.setDefinition(new CpsFlowDefinition("node { ttRunPackage testCasePath:'test.pkg', executionConfig:[timeout: 2]}", true)) + expect: + WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) + jenkins.assertLogContains("Executing package 'test.pkg'", run) + jenkins.assertLogNotContains('ecu.test is busy', run) + jenkins.assertLogContains("Execution has exceeded the configured timeout of 2 seconds", run) } } diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/RunProjectStepIT.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/RunProjectStepIT.groovy index 5208002f..7adb3192 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/RunProjectStepIT.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/RunProjectStepIT.groovy @@ -5,14 +5,20 @@ */ package de.tracetronic.jenkins.plugins.ecutestexecution.steps +import de.tracetronic.jenkins.plugins.ecutestexecution.client.MockRestApiClient +import de.tracetronic.jenkins.plugins.ecutestexecution.client.MockApiResponse +import de.tracetronic.cxs.generated.et.client.api.v2.ConfigurationApi +import de.tracetronic.cxs.generated.et.client.api.v2.ExecutionApi import de.tracetronic.jenkins.plugins.ecutestexecution.ETInstallation import de.tracetronic.jenkins.plugins.ecutestexecution.IntegrationTestBase import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClientFactory +import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClientV2 import de.tracetronic.jenkins.plugins.ecutestexecution.configs.ExecutionConfig import de.tracetronic.jenkins.plugins.ecutestexecution.configs.TestConfig import de.tracetronic.jenkins.plugins.ecutestexecution.model.Constant import hudson.Functions import hudson.model.Result +import okhttp3.Call import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition import org.jenkinsci.plugins.workflow.cps.SnippetizerTester import org.jenkinsci.plugins.workflow.job.WorkflowJob @@ -101,21 +107,49 @@ class RunProjectStepIT extends IntegrationTestBase { // assume RestApiClient is available GroovyMock(RestApiClientFactory, global: true) - RestApiClientFactory.getRestApiClient() >> new TestRestApiClient() + RestApiClientFactory.getRestApiClient() >> new MockRestApiClient() expect: WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) - jenkins.assertLogContains('Executing project test.prj...', run) + jenkins.assertLogContains("Executing project 'test.prj'", run) } def 'Run pipeline with package check'() { given: - WorkflowJob job = jenkins.createProject(WorkflowJob.class, 'pipeline') - job.setDefinition(new CpsFlowDefinition("node { " + - "ttRunProject testCasePath: 'test.prj', executionConfig: [executePackageCheck: true]}", - true) - ) + GroovyMock(RestApiClientFactory, global: true) + def restApiClient = new RestApiClientV2('','') + RestApiClientFactory.getRestApiClient(*_) >> restApiClient + WorkflowJob job = jenkins.createProject(WorkflowJob.class, 'pipeline') + job.setDefinition(new CpsFlowDefinition("node { " + + "ttRunProject testCasePath: 'test.prj', executionConfig: [executePackageCheck: true]}", + true) + ) expect: - WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) - jenkins.assertLogContains('Executing Package Checks for: test.prj ...', run) + WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) + jenkins.assertLogContains("Executing package checks for 'test.prj'", run) + } + + def 'Run pipeline: with 409 handling'() { + given: + GroovyMock(RestApiClientFactory, global: true) + def restApiClient = new RestApiClientV2('','') + RestApiClientFactory.getRestApiClient(*_) >> restApiClient + def mockCall = Mock(Call) + mockCall.clone() >> mockCall + mockCall.execute() >> MockApiResponse.getResponseBusy() >> MockApiResponse.getResponseUnauthorized() + GroovySpy(ConfigurationApi, global: true){ + manageConfigurationWithHttpInfo(*_) >> { + restApiClient.apiClient.execute(mockCall, null) + } + } + GroovySpy(ExecutionApi, global: true){ + createExecution(*_) >> {restApiClient.apiClient.execute(mockCall, null)} + } + WorkflowJob job = jenkins.createProject(WorkflowJob.class, 'pipeline') + job.setDefinition(new CpsFlowDefinition("node { ttRunProject 'test.prj' }", true)) + expect: + WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) + jenkins.assertLogContains("Executing project 'test.prj'", run) + jenkins.assertLogNotContains('ecu.test is busy', run) + jenkins.assertLogContains('unauthorized', run) } } diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/RunTestFolderStepIT.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/RunTestFolderStepIT.groovy index dd8060bd..52da7536 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/RunTestFolderStepIT.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/RunTestFolderStepIT.groovy @@ -7,6 +7,7 @@ package de.tracetronic.jenkins.plugins.ecutestexecution.steps import de.tracetronic.jenkins.plugins.ecutestexecution.ETInstallation import de.tracetronic.jenkins.plugins.ecutestexecution.IntegrationTestBase +import de.tracetronic.jenkins.plugins.ecutestexecution.client.MockRestApiClient import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClientFactory import de.tracetronic.jenkins.plugins.ecutestexecution.configs.AnalysisConfig import de.tracetronic.jenkins.plugins.ecutestexecution.configs.ExecutionConfig @@ -179,13 +180,13 @@ class RunTestFolderStepIT extends IntegrationTestBase { // assume RestApiClient is available GroovyMock(RestApiClientFactory, global: true) - RestApiClientFactory.getRestApiClient() >> new TestRestApiClient() + RestApiClientFactory.getRestApiClient() >> new MockRestApiClient() expect: WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) jenkins.assertLogContains('Found 1 package(s)', run) jenkins.assertLogContains('Found 1 project(s)', run) // packages will be execute first - jenkins.assertLogContains("Executing package ${testPackage.getAbsolutePath()}...", run) + jenkins.assertLogContains("Executing package '${testPackage.getAbsolutePath()}'", run) } def 'Run recursive scan pipeline'() { @@ -199,13 +200,13 @@ class RunTestFolderStepIT extends IntegrationTestBase { // assume RestApiClient is available GroovyMock(RestApiClientFactory, global: true) - RestApiClientFactory.getRestApiClient() >> new TestRestApiClient() + RestApiClientFactory.getRestApiClient() >> new MockRestApiClient() expect: WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) jenkins.assertLogContains('Found 3 package(s)', run) jenkins.assertLogContains('Found 3 project(s)', run) // packages in subfolder will be execute first - jenkins.assertLogContains("Executing package ${subPackage.getAbsolutePath()}...", run) + jenkins.assertLogContains("Executing package '${subPackage.getAbsolutePath()}'", run) } def 'Run scan mode pipeline'() { @@ -219,14 +220,14 @@ class RunTestFolderStepIT extends IntegrationTestBase { // assume RestApiClient is available GroovyMock(RestApiClientFactory, global: true) - RestApiClientFactory.getRestApiClient() >> new TestRestApiClient() + RestApiClientFactory.getRestApiClient() >> new MockRestApiClient() expect: WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) jenkins.assertLogNotContains('No packages found!', run) jenkins.assertLogNotContains('Found 1 packages(s)', run) jenkins.assertLogContains('Found 1 project(s)', run) // packages will be execute first - jenkins.assertLogContains("Executing project ${testProject.getAbsolutePath()}...", run) + jenkins.assertLogContains("Executing project '${testProject.getAbsolutePath()}'", run) } void setupTestFolder() { diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/StartToolStepIT.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/StartToolStepIT.groovy index b756beb9..50989906 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/StartToolStepIT.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/StartToolStepIT.groovy @@ -7,7 +7,6 @@ package de.tracetronic.jenkins.plugins.ecutestexecution.steps import de.tracetronic.jenkins.plugins.ecutestexecution.ETInstallation import de.tracetronic.jenkins.plugins.ecutestexecution.IntegrationTestBase -import de.tracetronic.jenkins.plugins.ecutestexecution.util.ProcessUtil import hudson.Functions import hudson.model.Result import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition @@ -133,7 +132,7 @@ class StartToolStepIT extends IntegrationTestBase { String workspaceDir = tempDir.getPath().replace('\\', '/') WorkflowJob job = jenkins.createProject(WorkflowJob.class, 'pipeline') job.setDefinition(new CpsFlowDefinition("node { ttStartTool toolName: 'ecu.test', " + - "workspaceDir: '${workspaceDir}', settingsDir: '${workspaceDir}', keepInstance: true }", true)) + "workspaceDir: '${workspaceDir}', settingsDir: '${workspaceDir}', keepInstance: true, timeout: 5 }", true)) expect: WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) jenkins.assertLogContains('Re-using running instance ', run) diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/UploadReportsStepIT.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/UploadReportsStepIT.groovy index 76bd66e3..407b2321 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/UploadReportsStepIT.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/UploadReportsStepIT.groovy @@ -5,14 +5,19 @@ */ package de.tracetronic.jenkins.plugins.ecutestexecution.steps +import de.tracetronic.jenkins.plugins.ecutestexecution.client.MockRestApiClient +import de.tracetronic.jenkins.plugins.ecutestexecution.client.MockApiResponse import com.cloudbees.plugins.credentials.CredentialsProvider import com.cloudbees.plugins.credentials.CredentialsScope import com.cloudbees.plugins.credentials.domains.Domain import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl +import de.tracetronic.cxs.generated.et.client.api.v2.ReportApi import de.tracetronic.jenkins.plugins.ecutestexecution.IntegrationTestBase import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClientFactory +import de.tracetronic.jenkins.plugins.ecutestexecution.clients.RestApiClientV2 import de.tracetronic.jenkins.plugins.ecutestexecution.model.AdditionalSetting import hudson.model.Result +import okhttp3.Call import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition import org.jenkinsci.plugins.workflow.cps.SnippetizerTester import org.jenkinsci.plugins.workflow.job.WorkflowJob @@ -77,9 +82,34 @@ class UploadReportsStepIT extends IntegrationTestBase { // assume RestApiClient is available GroovyMock(RestApiClientFactory, global: true) - RestApiClientFactory.getRestApiClient() >> new TestRestApiClient() + RestApiClientFactory.getRestApiClient() >> new MockRestApiClient() expect: WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) jenkins.assertLogContains('Uploading reports to test.guide http://localhost:8085...', run) } + + + def 'Run pipeline: with 409 handling'() { + given: + GroovyMock(RestApiClientFactory, global: true) + def restApiClient = new RestApiClientV2('','') + RestApiClientFactory.getRestApiClient(*_) >> restApiClient + def mockCall = Mock(Call) + mockCall.clone() >> mockCall + mockCall.execute() >> MockApiResponse.getResponseBusy() >> MockApiResponse.getResponseUnauthorized() + GroovySpy(ReportApi, global: true){ + createUpload(*_) >> {restApiClient.apiClient.execute(mockCall, null)} + getAllReports(*_) >> {restApiClient.apiClient.execute(mockCall, null)} + } + WorkflowJob job = jenkins.createProject(WorkflowJob.class, 'pipeline') + job.setDefinition(new CpsFlowDefinition( + "node { ttUploadReports credentialsId: 'authKey', " + + "testGuideUrl: 'http://localhost:8085' }", true)) + + expect: + WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) + jenkins.assertLogContains('Uploading reports to test.guide http://localhost:8085...', run) + jenkins.assertLogNotContains('ecu.test is busy', run) + jenkins.assertLogContains('unauthorized', run) + } }