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 extends Class>> 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")
+ }
+}