From b2aefe7de6206d81e350d6401b1b12c593b2b866 Mon Sep 17 00:00:00 2001 From: maxe Date: Thu, 11 Jul 2024 15:59:19 +0200 Subject: [PATCH] check for old report folders (#143) --- .../clients/RestApiClient.groovy | 14 ++- .../clients/RestApiClientV1.groovy | 21 +++- .../clients/RestApiClientV2.groovy | 18 +++- .../steps/ProvideReportLogsStep.groovy | 95 ++++++++++++------- .../ecutestexecution/ETContainerTest.groovy | 39 -------- .../ecutestexecution/ETV1ContainerTest.groovy | 25 ++++- .../ecutestexecution/ETV2ContainerTest.groovy | 44 ++++++++- .../client/MockRestApiClient.groovy | 6 ++ 8 files changed, 181 insertions(+), 81 deletions(-) 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 8180f505..65c389bc 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 @@ -73,11 +73,23 @@ interface RestApiClient { */ abstract UploadResult uploadReport(String reportId, TGUploadOrder order) + /** + * Get de.tracetronic.jenkins.plugins.ecutestexecution.clients.model.ReportInfo + * of all available test reports in the ecu.test instance. + * @return List of ReportInfo with report IDs + */ + abstract List getAllReports() + /** * Get the IDs of all available test reports in the ecu.test instance. * @return List of strings with report IDs */ abstract List getAllReportIds() - + + /** + * Download the report folder of the given reportId from ecu.test + * Only available in api v2 + * @return File + */ abstract File downloadReportFolder(String reportID) } 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 1d07a35c..ccc1655d 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 @@ -206,17 +206,30 @@ class RestApiClientV1 implements RestApiClient { "Report upload for ${reportId} failed", '') } + /** + * Get de.tracetronic.jenkins.plugins.ecutestexecution.clients.model.ReportInfo + * of all available test reports in the ecu.test instance. + * @return List of ReportInfo with report IDs + */ + List getAllReports() { + de.tracetronic.cxs.generated.et.client.api.v2.ReportApi apiInstance = new de.tracetronic.cxs.generated.et.client.api.v2.ReportApi(apiClient) + return apiInstance.getAllReports() + } + /** * Get the IDs of all available test reports in the ecu.test instance. * @return List of strings with report IDs */ List getAllReportIds() { - ReportApi apiInstance = new ReportApi(apiClient) - List reports = apiInstance.getAllReports() - return reports*.testReportId + return getAllReports()*.testReportId } + /** + * Download the report folder of the given reportId from ecu.test + * Only available in api v2 + * @return File + */ File downloadReportFolder(String reportID) { - throw new Exception("Downloading report folders is not supported by api v1") + throw new de.tracetronic.cxs.generated.et.client.v1.ApiException("Downloading report folders is not supported by api v1. Use ecu.test > 2024.2 with api v2 instead.") } } 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 d917664c..eeaac132 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 @@ -223,16 +223,28 @@ class RestApiClientV2 extends RestApiClientV2WithIdleHandle implements RestApiCl "Report upload for ${reportId} failed", '') } + /** + * Get de.tracetronic.jenkins.plugins.ecutestexecution.clients.model.ReportInfo + * of all available test reports in the ecu.test instance. + * @return List of ReportInfo with report IDs + */ + List getAllReports() { + ReportApi apiInstance = new ReportApi(apiClient) + return apiInstance.getAllReports() + } + /** * Get the IDs of all available test reports in the ecu.test instance. * @return List of strings with report IDs */ List getAllReportIds() { - ReportApi apiInstance = new ReportApi(apiClient) - List reports = apiInstance.getAllReports() - return reports*.testReportId + return getAllReports()*.testReportId } + /** + * Download the report folder of the given reportId from ecu.test + * @return File + */ File downloadReportFolder(String reportID) { ReportApi apiInstance = new ReportApi(apiClient) return apiInstance.reportDownload(reportID) diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideReportLogsStep.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideReportLogsStep.groovy index 7c7d23fa..9efc9d66 100644 --- a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideReportLogsStep.groovy +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideReportLogsStep.groovy @@ -9,20 +9,19 @@ 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.model.GenerationResult import de.tracetronic.jenkins.plugins.ecutestexecution.actions.ProvideReportLogsAction +import de.tracetronic.jenkins.plugins.ecutestexecution.clients.model.ApiException +import de.tracetronic.jenkins.plugins.ecutestexecution.clients.model.ReportInfo import de.tracetronic.jenkins.plugins.ecutestexecution.util.PathUtil import hudson.EnvVars import hudson.Extension import hudson.FilePath import hudson.Launcher -import hudson.model.Executor import hudson.model.Run import hudson.model.TaskListener import hudson.util.ListBoxModel -import io.jenkins.cli.shaded.org.apache.commons.io.FileUtils -import jenkins.model.StandardArtifactManager +import jenkins.model.Jenkins import jenkins.security.MasterToSlaveCallable import org.apache.commons.lang.StringUtils import org.jenkinsci.plugins.workflow.steps.Step @@ -31,8 +30,8 @@ import org.jenkinsci.plugins.workflow.steps.StepDescriptor import org.jenkinsci.plugins.workflow.steps.StepExecution import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution import org.kohsuke.stapler.DataBoundConstructor -import org.kohsuke.stapler.DataBoundSetter +import java.text.SimpleDateFormat import java.util.zip.ZipEntry import java.util.zip.ZipInputStream @@ -78,10 +77,9 @@ class ProvideReportLogsStep extends Step { TaskListener listener = context.get(TaskListener.class) if (workspace.child(logDirName).list().size() > 0) { - listener.logger.println("[WARNING] workspace report folder includes old files") + listener.logger.println("[WARNING] jenkins workspace report folder includes old files") } - - long startTimeMillis = run.getTimeInMillis() + long startTimeMillis = run.getStartTimeInMillis() def logDirPath = PathUtil.makeAbsoluteInPipelineHome("${logDirName}", context) // Download report logs to workspace @@ -106,7 +104,7 @@ class ProvideReportLogsStep extends Step { run.addAction(new ProvideReportLogsAction(run)) } - listener.logger.println("Cleaning report folder in workspace") + listener.logger.println("Cleaning report folder in jenkins workspace") workspace.child(logDirName).deleteContents() listener.logger.flush() } @@ -141,40 +139,73 @@ class ProvideReportLogsStep extends Step { listener.logger.println("Providing ecu.test report logs to jenkins.") List logs = [] RestApiClient apiClient = RestApiClientFactory.getRestApiClient(envVars.get('ET_API_HOSTNAME'), envVars.get('ET_API_PORT')) - reportIds = apiClient.getAllReportIds() + List reports = apiClient.getAllReports() - if (reportIds == null || reportIds.isEmpty()) { + if (reports == null || reports.isEmpty()) { listener.logger.println("[WARNING] No report files returned by ecu.test") } - reportIds.each { reportId -> + reports*.reportDir.each { String reportDir -> + if (!checkReportFolderCreationDate(reportDir)) { + listener.logger.println("[WARNING] ecu.test report folder includes files older than this run. ${reportDir}") + } + } + + reports*.testReportId.each { String reportId -> listener.logger.println("Downloading reportFolder for ${reportId}") - File reportFolderZip = apiClient.downloadReportFolder(reportId) - def fileNameToExtract = "test/ecu.test_out.log" - ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(reportFolderZip)) - ZipEntry entry - - while ((entry = zipInputStream.nextEntry) != null) { - if (entry.name == fileNameToExtract) { - def outputFile = new File("${logDirPath}/${reportId}.log") - outputFile.parentFile.mkdirs() - - def outputStream = new FileOutputStream(outputFile) - try { - outputStream << zipInputStream - } finally { - outputStream.close() - } - - listener.logger.println("Extracted ${fileNameToExtract} to ${logDirPath}") - logs.add(outputFile.name) + try { + File reportFolderZip = apiClient.downloadReportFolder(reportId) + File log = extractLogFileFromZip(reportFolderZip, "test/ecu.test_out.log", reportId) + logs.add(log.name) + } + catch (ApiException e) { + if (e instanceof de.tracetronic.cxs.generated.et.client.v2.ApiException && e.code == 404) { + throw new de.tracetronic.cxs.generated.et.client.v2.ApiException("[ERROR] Downloading reportFolder is not available for ecu.test < 2024.2!") + } else { + throw e } } - zipInputStream.close() } listener.logger.flush() return logs } + + boolean checkReportFolderCreationDate(String reportDir) { + def df = "yyyy-MM-dd_HHmmss" + def pattern = /\d{4}-\d{2}-\d{2}_\d{6}/ + def matcher = reportDir =~ pattern + if (matcher) { + def date = matcher[0] + Date dateTime1 = new Date().parse(df, date) + Date dateTime2 = new Date(startTimeMillis) + return dateTime1 > dateTime2 + } else { + throw new Exception("No date and time found in the input string.") + } + } + + File extractLogFileFromZip(File reportFolderZip, String fileNameToExtract, String newName) { + ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(reportFolderZip)) + ZipEntry entry + while ((entry = zipInputStream.nextEntry) != null) { + newName + if (entry.name == fileNameToExtract) { + File outputFile = new File("${logDirPath}/${newName}.log") + outputFile.parentFile.mkdirs() + + def outputStream = new FileOutputStream(outputFile) + try { + outputStream << zipInputStream + } finally { + outputStream.close() + } + listener.logger.println("Extracted ${fileNameToExtract} to ${logDirPath}") + zipInputStream.close() + return outputFile + } + } + throw new Exception("No ecu.test log not found in given report zip!") + } } @Extension 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 99ac9f15..f3884ebd 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETContainerTest.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETContainerTest.groovy @@ -302,43 +302,4 @@ abstract class ETContainerTest extends ContainerTest { then: "expect successful test and upload completion" jenkins.assertLogContains("-> FINISHED", run) } - - def "Perform provide report logs step with no reports"() { - given: "a test execution pipeline" - String script = """ - node { - withEnv(['ET_API_HOSTNAME=${etContainer.host}', 'ET_API_PORT=${etContainer.getMappedPort(ET_PORT)}']) { - ttProvideReportLogs() - } - } - """.stripIndent() - WorkflowJob job = jenkins.createProject(WorkflowJob.class, "pipeline") - job.setDefinition(new CpsFlowDefinition(script, true)) - when: "scheduling a new build" - WorkflowRun run = jenkins.buildAndAssertStatus(Result.SUCCESS, job) - - then: "expect successful test completion" - jenkins.assertLogContains("Providing ecu.test report logs to jenkins.", run) - jenkins.assertLogContains("[WARNING] No report files returned by ecu.test", run) - } - - def "Perform provide report logs step with reports"() { - given: "a test execution pipeline" - String script = """ - node { - withEnv(['ET_API_HOSTNAME=${etContainer.host}', 'ET_API_PORT=${etContainer.getMappedPort(ET_PORT)}']) { - ttRunPackage testCasePath: 'test.pkg' - ttProvideReportLogs() - } - } - """.stripIndent() - WorkflowJob job = jenkins.createProject(WorkflowJob.class, "pipeline") - job.setDefinition(new CpsFlowDefinition(script, true)) - when: "scheduling a new build" - WorkflowRun run = jenkins.buildAndAssertStatus(Result.SUCCESS, job) - - then: "expect successful test completion" - jenkins.assertLogContains("Providing ecu.test report logs to jenkins.", run) - jenkins.assertLogContains("Adding report logs to artifacts", run) - } } diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETV1ContainerTest.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETV1ContainerTest.groovy index 33e22905..a9ff46bc 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETV1ContainerTest.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETV1ContainerTest.groovy @@ -5,7 +5,10 @@ */ package de.tracetronic.jenkins.plugins.ecutestexecution - +import hudson.model.Result +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition +import org.jenkinsci.plugins.workflow.job.WorkflowJob +import org.jenkinsci.plugins.workflow.job.WorkflowRun import org.slf4j.Logger import org.slf4j.LoggerFactory import org.testcontainers.containers.BindMode @@ -35,4 +38,24 @@ class ETV1ContainerTest extends ETContainerTest { .withLogConsumer(new Slf4jLogConsumer(LOGGER)) .waitingFor(Wait.forHttp("/api/v1/live")) } + + def "Perform provide report logs step with reports"() { + given: "a test execution pipeline" + String script = """ + node { + withEnv(['ET_API_HOSTNAME=${etContainer.host}', 'ET_API_PORT=${etContainer.getMappedPort(ET_PORT)}']) { + ttRunPackage testCasePath: 'test.pkg' + ttProvideReportLogs() + } + } + """.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 successful test completion" + jenkins.assertLogContains("Providing ecu.test report logs to jenkins.", run) + jenkins.assertLogContains("Adding report logs to artifacts", run) + } } diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETV2ContainerTest.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETV2ContainerTest.groovy index 26430df7..6568dbff 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETV2ContainerTest.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETV2ContainerTest.groovy @@ -5,7 +5,10 @@ */ package de.tracetronic.jenkins.plugins.ecutestexecution - +import hudson.model.Result +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition +import org.jenkinsci.plugins.workflow.job.WorkflowJob +import org.jenkinsci.plugins.workflow.job.WorkflowRun import org.slf4j.Logger import org.slf4j.LoggerFactory import org.testcontainers.containers.BindMode @@ -35,4 +38,43 @@ class ETV2ContainerTest extends ETContainerTest { .withLogConsumer(new Slf4jLogConsumer(LOGGER)) .waitingFor(Wait.forHttp("/api/v2/live")) } + + def "Perform provide report logs step with no reports"() { + given: "a test execution pipeline" + String script = """ + node { + withEnv(['ET_API_HOSTNAME=${etContainer.host}', 'ET_API_PORT=${etContainer.getMappedPort(ET_PORT)}']) { + ttProvideReportLogs() + } + } + """.stripIndent() + WorkflowJob job = jenkins.createProject(WorkflowJob.class, "pipeline") + job.setDefinition(new CpsFlowDefinition(script, true)) + when: "scheduling a new build" + WorkflowRun run = jenkins.buildAndAssertStatus(Result.SUCCESS, job) + + then: "expect successful test completion" + jenkins.assertLogContains("Providing ecu.test report logs to jenkins.", run) + jenkins.assertLogContains("[WARNING] No report files returned by ecu.test", run) + } + + def "Perform provide report logs step with reports"() { + given: "a test execution pipeline" + String script = """ + node { + withEnv(['ET_API_HOSTNAME=${etContainer.host}', 'ET_API_PORT=${etContainer.getMappedPort(ET_PORT)}']) { + ttRunPackage testCasePath: 'test.pkg' + ttProvideReportLogs() + } + } + """.stripIndent() + WorkflowJob job = jenkins.createProject(WorkflowJob.class, "pipeline") + job.setDefinition(new CpsFlowDefinition(script, true)) + when: "scheduling a new build" + WorkflowRun run = jenkins.buildAndAssertStatus(Result.SUCCESS, job) + + then: "expect successful test completion" + jenkins.assertLogContains("Providing ecu.test report logs to jenkins.", run) + jenkins.assertLogContains("Adding report logs to artifacts", run) + } } diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/client/MockRestApiClient.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/client/MockRestApiClient.groovy index 0b6e5ae1..0a3dfe28 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/client/MockRestApiClient.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/client/MockRestApiClient.groovy @@ -47,10 +47,16 @@ class MockRestApiClient implements RestApiClient { return null } + @Override + List getAllReports(){ + return null + } + @Override List getAllReportIds() { return null } + @Override File downloadReportFolder(String reportID){ return null