From 49458914cb16debe561c9db17711696eb2098489 Mon Sep 17 00:00:00 2001 From: MxEh-TT <137298204+MxEh-TT@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:37:48 +0100 Subject: [PATCH] add provide generated ecu.test reports step (#159) --- docs/AdvancedUsage.md | 25 +-- .../AbstractProvideExecutionFilesStep.groovy | 2 +- .../steps/GenerateReportsStep.groovy | 10 +- .../steps/ProvideExecutionLogsStep.groovy | 2 +- .../steps/ProvideExecutionReportsStep.groovy | 6 +- .../steps/ProvideGeneratedReportsStep.groovy | 104 ++++++++++ .../ecutestexecution/util/ZipUtil.groovy | 75 +++++--- .../views/ProvideFilesActionView.groovy | 4 +- .../ProvideGeneratedReportsStep/config.jelly | 14 ++ .../config.properties | 4 + .../webapp/images/file/generateReport.svg | 9 + .../ecutestexecution/ETV1ContainerTest.groovy | 33 +++- .../ecutestexecution/ETV2ContainerTest.groovy | 88 ++++++++- .../steps/ProvideExecutionLogsStepIT.groovy | 12 +- .../ProvideExecutionReportsStepIT.groovy | 12 +- .../ProvideExecutionReportsStepTest.groovy | 4 +- .../ProvideGeneratedReportsStepIT.groovy | 79 ++++++++ .../ProvideGeneratedReportsStepTest.groovy | 63 ++++++ .../ecutestexecution/util/ZipUtilTest.groovy | 179 ++++++++++++++++++ 19 files changed, 657 insertions(+), 68 deletions(-) create mode 100644 src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStep.groovy create mode 100644 src/main/resources/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStep/config.jelly create mode 100644 src/main/resources/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStep/config.properties create mode 100644 src/main/webapp/images/file/generateReport.svg create mode 100644 src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStepIT.groovy create mode 100644 src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStepTest.groovy create mode 100644 src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/util/ZipUtilTest.groovy diff --git a/docs/AdvancedUsage.md b/docs/AdvancedUsage.md index 9ac17daa..b25e4a8c 100644 --- a/docs/AdvancedUsage.md +++ b/docs/AdvancedUsage.md @@ -3,18 +3,19 @@ This advanced usage documentation will provide the full specs of all steps implemented in the ecu.test execution plugin. Additionally, further examples are provided. -| Step-Name | Parameters | Return | -|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------| -| **ttCheckPackage** | **testCasePath**: String - The path to the file that should be checked. Can be package or project

**executionConfig**: [ExecutionConfig](#executionconfig) - Contains settings to handle ecu.test executions | [CheckPackageResult](#checkpackageresult) | -| **ttGenerateReports** | **generatorName**: String - The name of the report generator to trigger, currently ATX, EXCEL, HTML, JSON, TRF-SPLIT, TXT and UNIT are supported

**additionalSettings**: List\<[AdditionalSetting](#additionalsetting)> - Additional settings for the chosen report generator.

**reportIds**: List\ - reportIds to generate a report for, ignore to generate all. | List\<[GenerationResult](#generationresult)> | -| **ttProvideLogs** | **publishConfig**: [PublishConfig](#publishconfig) - Contains settings to adjust how logs will be provided | / | -| **ttProvideReports** | **publishConfig**: [PublishConfig](#publishconfig) - Contains settings to adjust how reports will be provided | / | -| **ttRunPackage** | **testCasePath**: String - The path to the package file that should be started. A test case file can be a package, project, project archive or analysis job. The path must either be an absolute path or a relative path to the Packages directory in the workspace.

**testConfig**: [TestConfig](#testconfig) - Contains settings for the ecu.test configuration.

**executionConfig**: [ExecutionConfig](#executionconfig) - Contains settings to handle ecu.test executions

**packageConfig**: [PackageConfig](#packageconfig) - Contains package parameters

**analysisConfig**: [AnalysisConfig](#analysisconfig) - Contains settings for analysis execution. | [TestResult](#testresult) | -| **ttRunProject** | **testCasePath**: String - The path to the project file that should be started. A test case file can be a package, project, project archive or analysis job. The path must either be an absolute path or a relative path to the Packages directory in the workspace.

**testConfig**: [TestConfig](#testconfig) - Contains settings for the ecu.test configuration.

**executionConfig**: [ExecutionConfig](#executionconfig) - Contains settings to handle ecu.test executions | [TestResult](#testresult) | -| **ttRunTestFolder** | **testCasePath**: String - Absolute test folder path where packages/projects are located.

**testConfig**: [TestConfig](#testconfig) - Contains settings for the ecu.test configuration.

**executionConfig**: [ExecutionConfig](#executionconfig) - Contains settings to handle ecu.test executions

**scanMode**: [ScanMode](#scanmode) - Defines what types of files should be run (PACKAGES_ONLY, PROJECTS_ONLY, PACKAGES_AND_PROJECTS)

**failFast**: boolean - The first failed package or project execution will abort the test folder execution immediately.

**packageConfig**: [PackageConfig](#packageconfig) - Contains package parameters

**analysisConfig**: [AnalysisConfig](#analysisconfig) - Contains settings for analysis execution. | List\<[TestResult](#testresult)> | -| **ttStartTool** | **toolName**: String - Select a preconfigured ecu.test or trace.check installation

**workspaceDir**: String - ecu.test or trace.check workspace, relative to build workspace or absolute path.

**settingsDir**: String - ecu.test or trace.check settings directory, relative to build workspace or absolute path.

timeout: int - Maximum time in seconds starting and connecting to the selected tool.

**keepInstance**: boolean - Re-uses an already running ecu.test or trace.check instance with the currently loaded workspace instead of starting a new one.

stopUndefinedTools: boolean - It only has an impact if Keep Previous Instance is unselected. Additionally, all tracetronic tools that are not defined by the Jenkins ETInstallations are stopped. | / | -| **ttStopTool** | **toolName**: String - Select a preconfigured ecu.test or trace.check installation

**timeout**: int - Maximum time in seconds terminating the selected tool.

**stopUndefinedTools**: boolean - Additionally, all tracetronic tools that are not defined by the Jenkins ETInstallations are stopped. | / | -| **ttUploadReports** | **testGuideUrl**: String - The URL to the test.guide instance to connect to

**credentialsId**: String - Credentials for test.guide REST API.

**projectId**: int - The test.guide project ID to upload to.

useSettingsFromServer: boolean - Get and use upload settings from test.guide.

additionalSettings:List\<[AdditionalSetting](#additionalsetting)> - Additional ATX generator settings.

**reportIds**: List\ - reportIds to upload, ignore to upload all. | List\<[UploadResult](#uploadresult)> | +| Step-Name | Parameters | Return | +|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------| +| **ttCheckPackage** | **testCasePath**: String - The path to the file that should be checked. Can be package or project

**executionConfig**: [ExecutionConfig](#executionconfig) - Contains settings to handle ecu.test executions | [CheckPackageResult](#checkpackageresult) | +| **ttGenerateReports** | **generatorName**: String - The name of the report generator to trigger, currently ATX, EXCEL, HTML, JSON, TRF-SPLIT, TXT and UNIT are supported

**additionalSettings**: List\<[AdditionalSetting](#additionalsetting)> - Additional settings for the chosen report generator.

**reportIds**: List\ - reportIds to generate a report for, ignore to generate all. | List\<[GenerationResult](#generationresult)> | +| **ttProvideLogs** | **publishConfig**: [PublishConfig](#publishconfig) - Contains settings to adjust how logs will be provided | / | +| **ttProvideReports** | **publishConfig**: [PublishConfig](#publishconfig) - Contains settings to adjust how reports will be provided | / | +| **ttProvideGeneratedReports** | **selectedReportTypes**: String - Comma seperated names of generated report folders that should be included.

**publishConfig**: [PublishConfig](#publishconfig) - Contains settings to adjust how reports will be provided | / | +| **ttRunPackage** | **testCasePath**: String - The path to the package file that should be started. A test case file can be a package, project, project archive or analysis job. The path must either be an absolute path or a relative path to the Packages directory in the workspace.

**testConfig**: [TestConfig](#testconfig) - Contains settings for the ecu.test configuration.

**executionConfig**: [ExecutionConfig](#executionconfig) - Contains settings to handle ecu.test executions

**packageConfig**: [PackageConfig](#packageconfig) - Contains package parameters

**analysisConfig**: [AnalysisConfig](#analysisconfig) - Contains settings for analysis execution. | [TestResult](#testresult) | +| **ttRunProject** | **testCasePath**: String - The path to the project file that should be started. A test case file can be a package, project, project archive or analysis job. The path must either be an absolute path or a relative path to the Packages directory in the workspace.

**testConfig**: [TestConfig](#testconfig) - Contains settings for the ecu.test configuration.

**executionConfig**: [ExecutionConfig](#executionconfig) - Contains settings to handle ecu.test executions | [TestResult](#testresult) | +| **ttRunTestFolder** | **testCasePath**: String - Absolute test folder path where packages/projects are located.

**testConfig**: [TestConfig](#testconfig) - Contains settings for the ecu.test configuration.

**executionConfig**: [ExecutionConfig](#executionconfig) - Contains settings to handle ecu.test executions

**scanMode**: [ScanMode](#scanmode) - Defines what types of files should be run (PACKAGES_ONLY, PROJECTS_ONLY, PACKAGES_AND_PROJECTS)

**failFast**: boolean - The first failed package or project execution will abort the test folder execution immediately.

**packageConfig**: [PackageConfig](#packageconfig) - Contains package parameters

**analysisConfig**: [AnalysisConfig](#analysisconfig) - Contains settings for analysis execution. | List\<[TestResult](#testresult)> | +| **ttStartTool** | **toolName**: String - Select a preconfigured ecu.test or trace.check installation

**workspaceDir**: String - ecu.test or trace.check workspace, relative to build workspace or absolute path.

**settingsDir**: String - ecu.test or trace.check settings directory, relative to build workspace or absolute path.

timeout: int - Maximum time in seconds starting and connecting to the selected tool.

**keepInstance**: boolean - Re-uses an already running ecu.test or trace.check instance with the currently loaded workspace instead of starting a new one.

stopUndefinedTools: boolean - It only has an impact if Keep Previous Instance is unselected. Additionally, all tracetronic tools that are not defined by the Jenkins ETInstallations are stopped. | / | +| **ttStopTool** | **toolName**: String - Select a preconfigured ecu.test or trace.check installation

**timeout**: int - Maximum time in seconds terminating the selected tool.

**stopUndefinedTools**: boolean - Additionally, all tracetronic tools that are not defined by the Jenkins ETInstallations are stopped. | / | +| **ttUploadReports** | **testGuideUrl**: String - The URL to the test.guide instance to connect to

**credentialsId**: String - Credentials for test.guide REST API.

**projectId**: int - The test.guide project ID to upload to.

useSettingsFromServer: boolean - Get and use upload settings from test.guide.

additionalSettings:List\<[AdditionalSetting](#additionalsetting)> - Additional ATX generator settings.

**reportIds**: List\ - reportIds to upload, ignore to upload all. | List\<[UploadResult](#uploadresult)> | ## Advanced Pipeline Examples diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/AbstractProvideExecutionFilesStep.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/AbstractProvideExecutionFilesStep.groovy index c6b1192d..c687ae93 100644 --- a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/AbstractProvideExecutionFilesStep.groovy +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/AbstractProvideExecutionFilesStep.groovy @@ -136,7 +136,7 @@ abstract class AbstractProvideExecutionFilesStep extends Step implements Seriali } ArrayList reportPaths = [] - reports.each {report -> + reports.each { report -> String reportDirName = report.reportDir.split('/').last() File reportZip = apiClient.downloadReportFolder(report.testReportId) ArrayList reportPath = step.processReport(reportZip, reportDirName, outDirPath, listener) diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/GenerateReportsStep.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/GenerateReportsStep.groovy index 0d6f06d5..0c60a885 100644 --- a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/GenerateReportsStep.groovy +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/GenerateReportsStep.groovy @@ -47,7 +47,7 @@ class GenerateReportsStep extends Step { } List getAdditionalSettings() { - return additionalSettings.collect({new AdditionalSetting(it)}) + return additionalSettings.collect({ new AdditionalSetting(it) }) } @DataBoundSetter @@ -108,13 +108,13 @@ class GenerateReportsStep extends Step { } catch (Exception e) { context.get(TaskListener.class).error(e.message) context.get(Run.class).setResult(Result.FAILURE) - return [ new GenerationResult("A problem occured during the report generation. See caused exception for more details.", "", null) ] + return [new GenerationResult("A problem occured during the report generation. See caused exception for more details.", "", null)] } } } private static final class ExecutionCallable extends MasterToSlaveCallable, IOException> { - + private static final long serialVersionUID = 1L private final String generatorName @@ -164,8 +164,8 @@ class GenerateReportsStep extends Step { @Extension static final class DescriptorImpl extends StepDescriptor { - private static final List REPORT_GENERATORS = Arrays.asList('ATX', 'EXCEL', 'HTML', 'JSON', 'OMR', - 'TestSpec', 'TRF-SPLIT', 'TXT', 'UNIT') + static final List REPORT_GENERATORS = Arrays.asList('ATX', 'EXCEL', 'HTML', 'JSON', + 'TRF-SPLIT', 'TXT', 'UNIT') static ListBoxModel doFillGeneratorNameItems() { ListBoxModel model = new ListBoxModel() diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionLogsStep.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionLogsStep.groovy index 0d2793b1..724427a0 100644 --- a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionLogsStep.groovy +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionLogsStep.groovy @@ -17,7 +17,7 @@ import org.kohsuke.stapler.DataBoundConstructor class ProvideExecutionLogsStep extends AbstractProvideExecutionFilesStep { private static final String ICON_NAME = 'logFile' - private static final String OUT_DIR_NAME = "ecu.test-logs" + private static final String OUT_DIR_NAME = "ecu.test Logs" private static final String SUPPORT_VERSION = "2024.2" @DataBoundConstructor diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionReportsStep.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionReportsStep.groovy index ca483b36..6c32eb85 100644 --- a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionReportsStep.groovy +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionReportsStep.groovy @@ -15,9 +15,9 @@ import hudson.model.TaskListener import org.jenkinsci.plugins.workflow.steps.StepDescriptor import org.kohsuke.stapler.DataBoundConstructor -class ProvideExecutionReportsStep extends AbstractProvideExecutionFilesStep{ +class ProvideExecutionReportsStep extends AbstractProvideExecutionFilesStep { private static final String ICON_NAME = 'testreport' - private static final String OUT_DIR_NAME = "ecu.test-reports" + private static final String OUT_DIR_NAME = "ecu.test Reports" private static final String SUPPORT_VERSION = "2024.3" @DataBoundConstructor @@ -34,7 +34,7 @@ class ProvideExecutionReportsStep extends AbstractProvideExecutionFilesStep{ if (ZipUtil.containsFileOfType(reportZip, ".prf")) { def outputFile = new File("${outDirPath}/${reportDirName}/${reportDirName}.zip") outputFile.parentFile.mkdirs() - String zipPath = ZipUtil.recreateWithFilesOfType(reportZip, [".trf", ".prf"], outputFile) + String zipPath = ZipUtil.recreateWithEndings(reportZip, [".trf", ".prf"], outputFile) reportPaths.add(zipPath) } else { List extractedFiles = ZipUtil.extractFilesByExtension(reportZip, [".trf"], "${outDirPath}/${reportDirName}") diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStep.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStep.groovy new file mode 100644 index 00000000..222991ed --- /dev/null +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStep.groovy @@ -0,0 +1,104 @@ +/* +* Copyright (c) 2024 tracetronic GmbH +* +* SPDX-License-Identifier: BSD-3-Clause +*/ +package de.tracetronic.jenkins.plugins.ecutestexecution.steps + +import com.google.common.collect.ImmutableSet +import de.tracetronic.jenkins.plugins.ecutestexecution.util.ZipUtil +import hudson.EnvVars +import hudson.Extension +import hudson.Launcher +import hudson.model.TaskListener +import org.jenkinsci.plugins.workflow.steps.StepDescriptor +import org.kohsuke.stapler.DataBoundConstructor +import org.kohsuke.stapler.DataBoundSetter + +import java.nio.file.FileSystems +import java.nio.file.Path +import java.nio.file.PathMatcher +import java.nio.file.Paths +import java.util.zip.ZipFile + +class ProvideGeneratedReportsStep extends AbstractProvideExecutionFilesStep { + private static final String ICON_NAME = 'generateReport' + private static final String OUT_DIR_NAME = "Generated ecu.test Reports" + private static final String SUPPORT_VERSION = "2024.3" + private String selectedReportTypes + + @DataBoundConstructor + ProvideGeneratedReportsStep() { + super() + this.selectedReportTypes = DescriptorImpl.getSelectedReportTypes() + iconName = ICON_NAME + outDirName = OUT_DIR_NAME + supportVersion = SUPPORT_VERSION + } + + String getSelectedReportTypes() { + return this.selectedReportTypes + } + + @DataBoundSetter + void setSelectedReportTypes(String selectedReportTypes) { + this.selectedReportTypes = (selectedReportTypes != null) ? selectedReportTypes : DescriptorImpl.getSelectedReportTypes() + } + + protected ArrayList processReport(File reportFile, String reportDirName, String outDirPath, TaskListener listener) { + ArrayList generatedZipPaths = new ArrayList<>() + ZipFile reportZip = new ZipFile(reportFile) + Set targetFolderPaths = new HashSet<>() + + String reportTypes = selectedReportTypes.replace("*", "[^/\\\\]*") + List reportTypesList = reportTypes.split(",\\s*") + reportZip.entries().each { entry -> + Path entryPath = Paths.get(entry.name) + String path = entryPath.getParent().toString() + for (String reportTypeStr : reportTypesList) { + String pattern = "regex:(.+(/|\\\\))?${reportTypeStr}" + PathMatcher matcher = FileSystems.getDefault().getPathMatcher(pattern) + if (entryPath.getParent() && matcher.matches(entryPath.getParent())) { + targetFolderPaths.add(path) + } + } + } + + for (String path : targetFolderPaths) { + def outputFile = new File("${outDirPath}/${reportDirName}/${path}.zip") + outputFile.parentFile.mkdirs() + def zipPath = ZipUtil.recreateWithPath(reportFile, path, outputFile,true) + generatedZipPaths.add(zipPath) + } + + + if (generatedZipPaths.isEmpty()) { + listener.logger.println("[WARNING] Could not find any matching generated report files in ${reportDirName}!") + } + + return generatedZipPaths + } + + @Extension + static final class DescriptorImpl extends StepDescriptor { + + static String getSelectedReportTypes() { + return GenerateReportsStep.DescriptorImpl.REPORT_GENERATORS.collect { it + "*" }.join(", ") + } + + @Override + String getFunctionName() { + 'ttProvideGeneratedReports' + } + + @Override + String getDisplayName() { + '[TT] Provide generated ecu.test reports as job artifacts.' + } + + @Override + Set> getRequiredContext() { + return ImmutableSet.of(Launcher.class, EnvVars.class, TaskListener.class) + } + } +} diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/util/ZipUtil.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/util/ZipUtil.groovy index b76d6b55..04564d5f 100644 --- a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/util/ZipUtil.groovy +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/util/ZipUtil.groovy @@ -6,11 +6,30 @@ package de.tracetronic.jenkins.plugins.ecutestexecution.util +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.StandardCopyOption import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream class ZipUtil { + + static boolean containsFileOfType(File reportFolderZip, String fileEnding) { + boolean result = false + new ZipInputStream(new FileInputStream(reportFolderZip)).withCloseable { zipInputStream -> + ZipEntry entry + while ((entry = zipInputStream.getNextEntry()) != null) { + if (!entry.isDirectory() && entry.name.endsWith(fileEnding)) { + result = true + break + } + } + } + return result + } + static ArrayList extractFilesByExtension(File reportFolderZip, List fileEndings, String saveToDirPath) { ArrayList extractedFilePaths = [] Set fileEndingsSet = fileEndings.toSet() @@ -23,10 +42,10 @@ class ZipUtil { boolean shouldExtract = fileEndingsSet.any { ending -> entry.name.endsWith(ending) } - if (shouldExtract) { - File outputFile = new File(saveToDirPath, entry.name) - outputFile.parentFile.mkdirs() // Ensure the directory structure is created + String entryPath = entry.name.replace("\\", "/") + File outputFile = new File(saveToDirPath, entryPath) + outputFile.parentFile.mkdirs() outputFile.withOutputStream { outputStream -> outputStream << zipInputStream } @@ -35,37 +54,49 @@ class ZipUtil { } } } - return extractedFilePaths } - static boolean containsFileOfType(File reportFolderZip, String fileEnding) { - boolean result = false - new ZipInputStream(new FileInputStream(reportFolderZip)).withCloseable { zipInputStream -> - ZipEntry entry - while ((entry = zipInputStream.getNextEntry()) != null) { - if (!entry.isDirectory() && entry.name.endsWith(fileEnding)) { - result = true - break + + static String recreateWithPath(File zip, String target, File outputZip, boolean stripBasePath = false) { + Path targetPath = Paths.get(target.replace("\\", "/")).normalize() + new ZipInputStream(new FileInputStream(zip)).withCloseable { inputStream -> + new ZipOutputStream(new FileOutputStream(outputZip)).withCloseable { outPutStream -> + ZipEntry entry + while ((entry = inputStream.getNextEntry()) != null) { + Path entryPath = Paths.get(entry.name.replace("\\", "/")).normalize() + if (entryPath.startsWith(targetPath)) { + String outputEntryName = entry.name.replace("\\", "/") + if (stripBasePath) { + outputEntryName = targetPath.relativize(entryPath).toString() + } + outPutStream.putNextEntry(new ZipEntry(outputEntryName)) + outPutStream << inputStream + outPutStream.closeEntry() + } } } } - return result + return outputZip.path } - static String recreateWithFilesOfType(File reportDirZip, List includeEndings, File outputZip) { - new ZipInputStream(new FileInputStream(reportDirZip)).withCloseable { zipInputStream -> - new ZipOutputStream(new FileOutputStream(outputZip)).withCloseable { zipOutputStream -> + + static String recreateWithEndings(File zip, List includePaths, File outputZip) { + List normalizedIncludePaths = includePaths.collect { it.replace("\\", "/") } + new ZipInputStream(new FileInputStream(zip)).withCloseable { inputStream -> + new ZipOutputStream(new FileOutputStream(outputZip)).withCloseable { outPutStream -> ZipEntry entry - while ((entry = zipInputStream.getNextEntry()) != null) { - if (!entry.isDirectory() && includeEndings.any { entry.name.endsWith(it) }) { - zipOutputStream.putNextEntry(new ZipEntry(entry.name)) - zipOutputStream << zipInputStream - zipOutputStream.closeEntry() + while ((entry = inputStream.getNextEntry()) != null) { + String normalizedEntryName = entry.name.replace("\\", "/") + if (!entry.isDirectory() && normalizedIncludePaths.any { path -> normalizedEntryName.endsWith(path) }) { + outPutStream.putNextEntry(new ZipEntry(normalizedEntryName)) + outPutStream << inputStream + outPutStream.closeEntry() } } } } - return outputZip.getPath() + return outputZip.path } + } diff --git a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/views/ProvideFilesActionView.groovy b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/views/ProvideFilesActionView.groovy index 9d6d20d3..b5a61c3e 100644 --- a/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/views/ProvideFilesActionView.groovy +++ b/src/main/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/views/ProvideFilesActionView.groovy @@ -26,7 +26,7 @@ class ProvideFilesActionView implements Action { @Override String getUrlName() { - return dirName + return dirName.replace(" ", "-") } Run getRun() { @@ -62,7 +62,7 @@ class ProvideFilesActionView implements Action { parts = parts.drop(1) def currentLevel = map; - parts.eachWithIndex{ part, i -> + parts.eachWithIndex { part, i -> boolean isDirectory = (i < parts.length - 1); if (isDirectory) { diff --git a/src/main/resources/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStep/config.jelly b/src/main/resources/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStep/config.jelly new file mode 100644 index 00000000..9ebb0abb --- /dev/null +++ b/src/main/resources/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStep/config.jelly @@ -0,0 +1,14 @@ + + + + ${%step.description} + ${%step.compatNote} + + + + + + + + diff --git a/src/main/resources/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStep/config.properties b/src/main/resources/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStep/config.properties new file mode 100644 index 00000000..a6ac92f4 --- /dev/null +++ b/src/main/resources/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStep/config.properties @@ -0,0 +1,4 @@ +step.description=This step provides all custom generated ecu.test reports as job artifacts. +step.compatNote=This feature is only available for ecu.test 2024.3 or higher! +selectedReportTypes.title=Select Generated Reports +selectedReportTypes.description=Comma separated list of generated report folder names that should be provided as zip in Jenkins. It is possible to use * to match multiple reports. diff --git a/src/main/webapp/images/file/generateReport.svg b/src/main/webapp/images/file/generateReport.svg new file mode 100644 index 00000000..eca46240 --- /dev/null +++ b/src/main/webapp/images/file/generateReport.svg @@ -0,0 +1,9 @@ + + + + document_attachment icon + document_attachment icon from the IconExperience.com O-Collection. Copyright by INCORS GmbH (www.incors.com). + + + + 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 d4e9905f..af40e103 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETV1ContainerTest.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETV1ContainerTest.groovy @@ -55,9 +55,9 @@ class ETV1ContainerTest extends ETContainerTest { WorkflowRun run = jenkins.buildAndAssertStatus(Result.UNSTABLE, job) then: "expect log information about unstable pipeline run" - jenkins.assertLogContains("Providing ecu.test-logs to jenkins.", run) - jenkins.assertLogContains("Providing ecu.test-logs failed!", run) - jenkins.assertLogContains("Downloading ecu.test-logs is not supported for this ecu.test version. Please use ecu.test >= 2024.2 instead.", run) + jenkins.assertLogContains("Providing ecu.test Logs to jenkins.", run) + jenkins.assertLogContains("Providing ecu.test Logs failed!", run) + jenkins.assertLogContains("Downloading ecu.test Logs is not supported for this ecu.test version. Please use ecu.test >= 2024.2 instead.", run) } def "Perform provide reports step unsupported"() { @@ -76,8 +76,29 @@ class ETV1ContainerTest extends ETContainerTest { WorkflowRun run = jenkins.buildAndAssertStatus(Result.UNSTABLE, job) then: "expect log information about unstable pipeline run" - jenkins.assertLogContains("Providing ecu.test-reports to jenkins.", run) - jenkins.assertLogContains("Providing ecu.test-reports failed!", run) - jenkins.assertLogContains("Downloading ecu.test-reports is not supported for this ecu.test version. Please use ecu.test >= 2024.3 instead.", run) + jenkins.assertLogContains("Providing ecu.test Reports to jenkins.", run) + jenkins.assertLogContains("Providing ecu.test Reports failed!", run) + jenkins.assertLogContains("Downloading ecu.test Reports is not supported for this ecu.test version. Please use ecu.test >= 2024.3 instead.", run) } + + def "Perform provide generated reports step unsupported"() { + given: "a pipeline with test package and report provider" + String script = """ + node { + withEnv(['ET_API_HOSTNAME=${etContainer.host}', 'ET_API_PORT=${etContainer.getMappedPort(ET_PORT)}']) { + ttRunPackage testCasePath: 'test.pkg' + ttProvideGeneratedReports() + } + } + """.stripIndent() + WorkflowJob job = jenkins.createProject(WorkflowJob.class, "pipeline") + job.setDefinition(new CpsFlowDefinition(script, true)) + when: "scheduling a new build" + WorkflowRun run = jenkins.buildAndAssertStatus(Result.UNSTABLE, job) + + then: "expect log information about unstable pipeline run" + jenkins.assertLogContains("Providing Generated ecu.test Reports to jenkins.", run) + jenkins.assertLogContains("Providing Generated ecu.test Reports failed!", run) + jenkins.assertLogContains("Downloading Generated ecu.test Reports is not supported for this ecu.test version. Please use ecu.test >= 2024.3 instead.", 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 6a4c0d30..fb53d852 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETV2ContainerTest.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/ETV2ContainerTest.groovy @@ -21,8 +21,9 @@ import org.testcontainers.spock.Testcontainers class ETV2ContainerTest extends ETContainerTest { private static final Logger LOGGER = LoggerFactory.getLogger(ETV2ContainerTest.class) - private static final String etLogsFolderName = 'ecu.test-logs' - private static final String etReportsFolderName = 'ecu.test-reports' + private static final String etLogsFolderName = 'ecu.test Logs' + private static final String etReportsFolderName = 'ecu.test Reports' + private static final String etGeneratedReportsFolderName = 'Generated ecu.test Reports' GenericContainer getETContainer() { return new GenericContainer<>(ET_V2_IMAGE_NAME) @@ -161,4 +162,87 @@ class ETV2ContainerTest extends ETContainerTest { jenkins.assertLogContains("Providing $etReportsFolderName to jenkins.", run) jenkins.assertLogContains("Successfully added $etReportsFolderName to jenkins.", run) } + + def "Perform provide generated reports step with no reports"() { + given: "a pipeline reports provider" + String script = """ + node { + withEnv(['ET_API_HOSTNAME=${etContainer.host}', 'ET_API_PORT=${etContainer.getMappedPort(ET_PORT)}']) { + ttProvideGeneratedReports() + } + } + """.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 log information about failed pipeline run" + jenkins.assertLogContains("Providing $etGeneratedReportsFolderName to jenkins.", run) + jenkins.assertLogContains("[WARNING] No files found!", run) + jenkins.assertLogContains("ERROR: Build Result set to FAILURE due to missing $etGeneratedReportsFolderName. Adjust AllowMissing step property if this is not intended.", run) + } + + def "Perform provide generated reports step allow missing"() { + given: "a pipeline reports provider" + String script = """ + node { + withEnv(['ET_API_HOSTNAME=${etContainer.host}', 'ET_API_PORT=${etContainer.getMappedPort(ET_PORT)}']) { + ttProvideGeneratedReports(publishConfig: [allowMissing: true]) + } + } + """.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 log information about successful pipeline run" + jenkins.assertLogContains("Providing $etGeneratedReportsFolderName to jenkins.", run) + jenkins.assertLogContains("[WARNING] No files found!", run) + } + + def "Perform provide generated reports step with reports"() { + given: "a pipeline with test packages and report provider" + String script = """ + node { + withEnv(['ET_API_HOSTNAME=${etContainer.host}', 'ET_API_PORT=${etContainer.getMappedPort(ET_PORT)}']) { + ttRunPackage testCasePath: 'test.pkg' + ttGenerateReports 'HTML' + ttProvideGeneratedReports() + } + } + """.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 log information about successful pipeline run" + jenkins.assertLogContains("Providing $etGeneratedReportsFolderName to jenkins.", run) + jenkins.assertLogContains("Successfully added $etGeneratedReportsFolderName to jenkins.", run) + } + + def "Perform provide generated reports step with excluded reports"() { + given: "a pipeline with test packages and report provider" + String script = """ + node { + withEnv(['ET_API_HOSTNAME=${etContainer.host}', 'ET_API_PORT=${etContainer.getMappedPort(ET_PORT)}']) { + ttRunPackage testCasePath: 'test.pkg' + ttGenerateReports 'HTML' + ttProvideGeneratedReports publishConfig: [allowMissing: true], selectedReportTypes: 'NOMATCH' + } + } + """.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 log information about successful pipeline run" + jenkins.assertLogContains("Providing $etGeneratedReportsFolderName to jenkins.", run) + jenkins.assertLogContains("[WARNING] Could not find any matching generated report files", run) + jenkins.assertLogContains("[WARNING] No files found!", run) + } } diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionLogsStepIT.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionLogsStepIT.groovy index 8c694267..4e10f714 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionLogsStepIT.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionLogsStepIT.groovy @@ -48,9 +48,9 @@ class ProvideExecutionLogsStepIT extends IntegrationTestBase { job.setDefinition(new CpsFlowDefinition("node {ttProvideLogs()}", true)) expect: WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) - jenkins.assertLogContains("Providing ecu.test-logs to jenkins.", run) + jenkins.assertLogContains("Providing ecu.test Logs to jenkins.", run) jenkins.assertLogContains("[WARNING] No files found!", run) - jenkins.assertLogContains("ERROR: Build Result set to FAILURE due to missing ecu.test-logs. Adjust AllowMissing step property if this is not intended.", run) + jenkins.assertLogContains("ERROR: Build Result set to FAILURE due to missing ecu.test Logs. Adjust AllowMissing step property if this is not intended.", run) } def 'Run pipeline allow missing logs'() { @@ -59,9 +59,9 @@ class ProvideExecutionLogsStepIT extends IntegrationTestBase { job.setDefinition(new CpsFlowDefinition("node {ttProvideLogs(publishConfig: [allowMissing: true])}", true)) expect: WorkflowRun run = jenkins.assertBuildStatus(Result.SUCCESS, job.scheduleBuild2(0).get()) - jenkins.assertLogContains("Providing ecu.test-logs to jenkins.", run) + jenkins.assertLogContains("Providing ecu.test Logs to jenkins.", run) jenkins.assertLogContains("[WARNING] No files found!", run) - jenkins.assertLogNotContains("Successfully added ecu.test-logs to jenkins.", run) + jenkins.assertLogNotContains("Successfully added ecu.test Logs to jenkins.", run) } def 'Run pipeline exceeds timeout'() { @@ -71,8 +71,8 @@ class ProvideExecutionLogsStepIT extends IntegrationTestBase { job.setDefinition(new CpsFlowDefinition("node {ttProvideLogs(publishConfig: [timeout: ${timeout}])}", true)) expect: WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) - jenkins.assertLogContains("Providing ecu.test-logs to jenkins.", run) + jenkins.assertLogContains("Providing ecu.test Logs to jenkins.", run) jenkins.assertLogContains("Execution has exceeded the configured timeout of ${timeout} seconds", run) - jenkins.assertLogContains("Providing ecu.test-logs failed!", run) + jenkins.assertLogContains("Providing ecu.test Logs failed!", run) } } diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionReportsStepIT.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionReportsStepIT.groovy index 25c0ce7a..a2f7f631 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionReportsStepIT.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionReportsStepIT.groovy @@ -48,9 +48,9 @@ class ProvideExecutionReportsStepIT extends IntegrationTestBase { job.setDefinition(new CpsFlowDefinition("node {ttProvideReports()}", true)) expect: WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) - jenkins.assertLogContains("Providing ecu.test-reports to jenkins.", run) + jenkins.assertLogContains("Providing ecu.test Reports to jenkins.", run) jenkins.assertLogContains("[WARNING] No files found!", run) - jenkins.assertLogContains("ERROR: Build Result set to FAILURE due to missing ecu.test-reports. Adjust AllowMissing step property if this is not intended.", run) + jenkins.assertLogContains("ERROR: Build Result set to FAILURE due to missing ecu.test Reports. Adjust AllowMissing step property if this is not intended.", run) } def 'Run pipeline allow missing reports'() { @@ -59,9 +59,9 @@ class ProvideExecutionReportsStepIT extends IntegrationTestBase { job.setDefinition(new CpsFlowDefinition("node {ttProvideReports(publishConfig: [allowMissing: true])}", true)) expect: WorkflowRun run = jenkins.assertBuildStatus(Result.SUCCESS, job.scheduleBuild2(0).get()) - jenkins.assertLogContains("Providing ecu.test-reports to jenkins.", run) + jenkins.assertLogContains("Providing ecu.test Reports to jenkins.", run) jenkins.assertLogContains("[WARNING] No files found!", run) - jenkins.assertLogNotContains("Successfully added ecu.test-logs to jenkins.", run) + jenkins.assertLogNotContains("Successfully added ecu.test Reports to jenkins.", run) } def 'Run pipeline exceeds timeout'() { @@ -71,8 +71,8 @@ class ProvideExecutionReportsStepIT extends IntegrationTestBase { job.setDefinition(new CpsFlowDefinition("node {ttProvideReports(publishConfig: [timeout: ${timeout}])}", true)) expect: WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) - jenkins.assertLogContains("Providing ecu.test-reports to jenkins.", run) + jenkins.assertLogContains("Providing ecu.test Reports to jenkins.", run) jenkins.assertLogContains("Execution has exceeded the configured timeout of ${timeout} seconds", run) - jenkins.assertLogContains("Providing ecu.test-reports failed!", run) + jenkins.assertLogContains("Providing ecu.test Reports failed!", run) } } diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionReportsStepTest.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionReportsStepTest.groovy index cc4e1330..d6b9ea65 100644 --- a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionReportsStepTest.groovy +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideExecutionReportsStepTest.groovy @@ -21,8 +21,8 @@ class ProvideExecutionReportsStepTest extends Specification { and: GroovyMock(ZipUtil, global: true) GroovyMock(File, global: true) - ZipUtil.containsFileOfType(_, ".prf") >> containsPrf - ZipUtil.recreateWithFilesOfType(_, [".trf", ".prf"], _) >> expectedResult[0] + ZipUtil.containsFileOfType(_ , ".prf") >> containsPrf + ZipUtil.recreateWithEndings(_, [".trf", ".prf"], _) >> expectedResult[0] ZipUtil.extractFilesByExtension(_, [".trf"], _) >> expectedResult listener.logger >> logger new File(_) >> outputFile diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStepIT.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStepIT.groovy new file mode 100644 index 00000000..e99c5781 --- /dev/null +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStepIT.groovy @@ -0,0 +1,79 @@ +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.configs.PublishConfig +import hudson.Functions +import hudson.model.Result +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 + +class ProvideGeneratedReportsStepIT extends IntegrationTestBase { + def setup() { + ETInstallation.DescriptorImpl etDescriptor = jenkins.jenkins + .getDescriptorByType(ETInstallation.DescriptorImpl.class) + String executablePath = Functions.isWindows() ? 'C:\\ecu.test\\ECU-TEST.exe' : 'bin/ecu-test' + etDescriptor.setInstallations(new ETInstallation('ecu.test', executablePath, JenkinsRule.NO_PROPERTIES)) + } + + def 'Default config round trip'() { + given: + ProvideGeneratedReportsStep before = new ProvideGeneratedReportsStep() + when: + ProvideGeneratedReportsStep after = new StepConfigTester(jenkins).configRoundTrip(before) + then: + jenkins.assertEqualDataBoundBeans(before, after) + } + def 'Snippet generator'() { + given: + SnippetizerTester st = new SnippetizerTester(jenkins) + PublishConfig publishConfig = new PublishConfig() + publishConfig.setTimeout(10) + publishConfig.setKeepAll(false) + publishConfig.setAllowMissing(true) + ProvideGeneratedReportsStep step = new ProvideGeneratedReportsStep() + when: + step.setPublishConfig(publishConfig) + step.setSelectedReportTypes("ATX") + then: + st.assertRoundTrip(step, "ttProvideGeneratedReports publishConfig: [allowMissing: true, keepAll: false, timeout: 10], selectedReportTypes: 'ATX'") + } + + def 'Run pipeline default'() { + given: + WorkflowJob job = jenkins.createProject(WorkflowJob.class, 'pipeline') + job.setDefinition(new CpsFlowDefinition("node {ttProvideGeneratedReports()}", true)) + expect: + WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) + jenkins.assertLogContains("Providing Generated ecu.test Reports to jenkins.", run) + jenkins.assertLogContains("[WARNING] No files found!", run) + jenkins.assertLogContains("ERROR: Build Result set to FAILURE due to missing Generated ecu.test Reports. Adjust AllowMissing step property if this is not intended.", run) + } + + def 'Run pipeline allow missing reports'() { + given: + WorkflowJob job = jenkins.createProject(WorkflowJob.class, 'pipeline') + job.setDefinition(new CpsFlowDefinition("node {ttProvideGeneratedReports(publishConfig: [allowMissing: true])}", true)) + expect: + WorkflowRun run = jenkins.assertBuildStatus(Result.SUCCESS, job.scheduleBuild2(0).get()) + jenkins.assertLogContains("Providing Generated ecu.test Reports to jenkins.", run) + jenkins.assertLogContains("[WARNING] No files found!", run) + jenkins.assertLogNotContains("Successfully added Generated ecu.test Reports to jenkins.", run) + } + + def 'Run pipeline exceeds timeout'() { + int timeout = 1 + given: + WorkflowJob job = jenkins.createProject(WorkflowJob.class, 'pipeline') + job.setDefinition(new CpsFlowDefinition("node {ttProvideGeneratedReports(publishConfig: [timeout: ${timeout}])}", true)) + expect: + WorkflowRun run = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get()) + jenkins.assertLogContains("Providing Generated ecu.test Reports to jenkins.", run) + jenkins.assertLogContains("Execution has exceeded the configured timeout of ${timeout} seconds", run) + jenkins.assertLogContains("Providing Generated ecu.test Reports failed!", run) + } +} diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStepTest.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStepTest.groovy new file mode 100644 index 00000000..ed56b6da --- /dev/null +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/steps/ProvideGeneratedReportsStepTest.groovy @@ -0,0 +1,63 @@ +package de.tracetronic.jenkins.plugins.ecutestexecution.steps + +import spock.lang.Specification +import hudson.EnvVars +import hudson.Launcher +import hudson.model.TaskListener + +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +class ProvideGeneratedReportsStepTest extends Specification { + def createTestZip(){ + def reportZip = File.createTempFile("test", ".zip") + reportZip.deleteOnExit() + ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(reportZip)) + zos.putNextEntry(new ZipEntry("html/")) + zos.closeEntry() + zos.putNextEntry(new ZipEntry("html/file1.html")) + zos.closeEntry() + zos.putNextEntry(new ZipEntry("json/")) + zos.closeEntry() + zos.putNextEntry(new ZipEntry("json/file1.json")) + zos.closeEntry() + zos.putNextEntry(new ZipEntry("package/json/file1.json")) + zos.closeEntry() + zos.close() + return reportZip + } + def "Test processReport #scenario"() { + given: + def step = new ProvideGeneratedReportsStep() + step.setSelectedReportTypes(pattern) + def reportDirName = "testreport" + def outDirPath = "/tmp/output" + def listener = Mock(TaskListener) + def logger = Mock(PrintStream) + and: + listener.logger >> logger + + when: + def result = step.processReport(createTestZip(), reportDirName, outDirPath, listener) + + then: + result.collect { it.replaceAll("\\\\", "/") }.toSet() == extractedFiles.toSet() + loggerCalled * logger.println("[WARNING] Could not find any matching generated report files in testreport!") + + where: + scenario | pattern |extractedFiles | loggerCalled + "select one" | "html" |["/tmp/output/testreport/html.zip"] | 0 + "select all" | "html, json" |["/tmp/output/testreport/json.zip", "/tmp/output/testreport/html.zip", "/tmp/output/testreport/package/json.zip"]| 0 + "select all no space" | "html,json" |["/tmp/output/testreport/json.zip", "/tmp/output/testreport/html.zip", "/tmp/output/testreport/package/json.zip"]| 0 + "exclude all" | "nothing matches" |[] | 1 + } + + def "Test DescriptorImpl returns correct values"() { + given: + def descriptor = new ProvideGeneratedReportsStep.DescriptorImpl() + expect: + descriptor.functionName == 'ttProvideGeneratedReports' + descriptor.displayName == '[TT] Provide generated ecu.test reports as job artifacts.' + descriptor.requiredContext == [Launcher, EnvVars, TaskListener] as Set + } +} diff --git a/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/util/ZipUtilTest.groovy b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/util/ZipUtilTest.groovy new file mode 100644 index 00000000..b30c98d3 --- /dev/null +++ b/src/test/groovy/de/tracetronic/jenkins/plugins/ecutestexecution/util/ZipUtilTest.groovy @@ -0,0 +1,179 @@ +package de.tracetronic.jenkins.plugins.ecutestexecution.util + +import spock.lang.Specification +import spock.lang.TempDir +import java.nio.file.Path +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream + +class ZipUtilTest extends Specification { + @TempDir + Path tempDir + + File testZip + File outputDir + File outputZip + + def setup() { + outputDir = new File(tempDir.toFile(), "extracted") + outputZip = new File(tempDir.toFile(), "output.zip") + testZip = new File(tempDir.toFile(), "test.zip") + createTestZip(testZip) + } + + private static void createTestZip(File zipFile) { + def zip = new ZipOutputStream(new FileOutputStream(zipFile)) + try { + zip.putNextEntry(new ZipEntry("test1.txt")) + zip.putNextEntry(new ZipEntry("test2.xml")) + zip.putNextEntry(new ZipEntry("folder/test3.txt")) + zip.putNextEntry(new ZipEntry("folder\\test4.xml")) + zip.putNextEntry(new ZipEntry("folder2/test5.json")) + zip.putNextEntry(new ZipEntry("report/result.html")) + } finally { + zip.close() + } + } + + def "should check for presence of file types"() { + expect: + ZipUtil.containsFileOfType(testZip, extension) == exists + + where: + extension | exists + ".txt" | true + ".xml" | true + ".html" | true + ".pdf" | false + } + + + + def "extract files by extension"() { + given: "a list of file extensions to extract" + def fileEndings = [".txt"] + + when: "extracting files by extension" + def extractedFiles = ZipUtil.extractFilesByExtension( + testZip, + fileEndings, + outputDir.path + ) + + then: + extractedFiles.size() == 2 + } + + def "should extract files by multiple extensions"() { + given: + def fileEndings = [".txt", ".xml"] + + when: "extracting files by extensions" + def extractedFiles = ZipUtil.extractFilesByExtension( + testZip, + fileEndings, + outputDir.path + ) + + then: + extractedFiles.size() == 4 + } + def "should handle empty zip file"() { + given: + def emptyZip = new File(tempDir.toFile(), "empty.zip") + new ZipOutputStream(new FileOutputStream(emptyZip)).close() + + when: + def extractedFiles = ZipUtil.extractFilesByExtension( + emptyZip, + [".txt"], + outputDir.absolutePath + ) + + then: + extractedFiles.isEmpty() + } + + def "should extract nested directory structure"() { + given: + def fileEndings = [".txt", ".xml"] + + when: + def extractedFiles = ZipUtil.extractFilesByExtension( + testZip, + fileEndings, + outputDir.absolutePath + ) + + then: + new File(outputDir, "folder").exists() + new File(outputDir, "folder").isDirectory() + new File(outputDir, "folder/test3.txt").exists() + new File(outputDir, "folder/test4.xml").exists() + } + + + + def "should recreate zip with full file paths given as endings"() { + given: + def includePaths = ["folder/test3.txt", "folder\\test4.xml"] + + when: + def newZipPath = ZipUtil.recreateWithEndings(testZip, includePaths, outputZip) + def entriesInNewZip = [] + new ZipInputStream(new FileInputStream(newZipPath)).withCloseable { zipInputStream -> + ZipEntry entry + while ((entry = zipInputStream.getNextEntry()) != null) { + entriesInNewZip.add(entry.name) + } + } + + then: + entriesInNewZip.size() == 2 + entriesInNewZip.contains("folder/test3.txt") + entriesInNewZip.contains("folder/test4.xml") + } + + + + def "should recreate zip with files at path"() { + given: + def path = "folder" + + when: + def newZipPath = ZipUtil.recreateWithPath(testZip, path, outputZip, false) + def entriesInNewZip = [] + new ZipInputStream(new FileInputStream(newZipPath)).withCloseable { zipInputStream -> + ZipEntry entry + while ((entry = zipInputStream.getNextEntry()) != null) { + entriesInNewZip.add(entry.name) + } + } + + then: + entriesInNewZip.size() == 2 + entriesInNewZip.contains("folder/test3.txt") + entriesInNewZip.contains("folder/test4.xml") + } + + def "should recreate zip with files at striped path"() { + given: + def path = "folder" + + when: + def newZipPath = ZipUtil.recreateWithPath(testZip, path, outputZip, true) + def entriesInNewZip = [] + new ZipInputStream(new FileInputStream(newZipPath)).withCloseable { zipInputStream -> + ZipEntry entry + while ((entry = zipInputStream.getNextEntry()) != null) { + entriesInNewZip.add(entry.name) + } + } + + then: + entriesInNewZip.size() == 2 + entriesInNewZip.contains("test3.txt") + entriesInNewZip.contains("test4.xml") + } +}