Skip to content

Commit

Permalink
add provide generated ecu.test reports step (#159)
Browse files Browse the repository at this point in the history
  • Loading branch information
MxEh-TT authored Nov 12, 2024
1 parent f32d008 commit 4945891
Show file tree
Hide file tree
Showing 19 changed files with 657 additions and 68 deletions.
25 changes: 13 additions & 12 deletions docs/AdvancedUsage.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ abstract class AbstractProvideExecutionFilesStep extends Step implements Seriali
}

ArrayList<String> reportPaths = []
reports.each {report ->
reports.each { report ->
String reportDirName = report.reportDir.split('/').last()
File reportZip = apiClient.downloadReportFolder(report.testReportId)
ArrayList<String> reportPath = step.processReport(reportZip, reportDirName, outDirPath, listener)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class GenerateReportsStep extends Step {
}

List<AdditionalSetting> getAdditionalSettings() {
return additionalSettings.collect({new AdditionalSetting(it)})
return additionalSettings.collect({ new AdditionalSetting(it) })
}

@DataBoundSetter
Expand Down Expand Up @@ -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<List<GenerationResult>, IOException> {

private static final long serialVersionUID = 1L

private final String generatorName
Expand Down Expand Up @@ -164,8 +164,8 @@ class GenerateReportsStep extends Step {

@Extension
static final class DescriptorImpl extends StepDescriptor {
private static final List<String> REPORT_GENERATORS = Arrays.asList('ATX', 'EXCEL', 'HTML', 'JSON', 'OMR',
'TestSpec', 'TRF-SPLIT', 'TXT', 'UNIT')
static final List<String> REPORT_GENERATORS = Arrays.asList('ATX', 'EXCEL', 'HTML', 'JSON',
'TRF-SPLIT', 'TXT', 'UNIT')

static ListBoxModel doFillGeneratorNameItems() {
ListBoxModel model = new ListBoxModel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<String> extractedFiles = ZipUtil.extractFilesByExtension(reportZip, [".trf"], "${outDirPath}/${reportDirName}")
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> processReport(File reportFile, String reportDirName, String outDirPath, TaskListener listener) {
ArrayList<String> generatedZipPaths = new ArrayList<>()
ZipFile reportZip = new ZipFile(reportFile)
Set<String> targetFolderPaths = new HashSet<>()

String reportTypes = selectedReportTypes.replace("*", "[^/\\\\]*")
List<String> 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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> extractFilesByExtension(File reportFolderZip, List<String> fileEndings, String saveToDirPath) {
ArrayList<String> extractedFilePaths = []
Set<String> fileEndingsSet = fileEndings.toSet()
Expand All @@ -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
}
Expand All @@ -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<String> includeEndings, File outputZip) {
new ZipInputStream(new FileInputStream(reportDirZip)).withCloseable { zipInputStream ->
new ZipOutputStream(new FileOutputStream(outputZip)).withCloseable { zipOutputStream ->

static String recreateWithEndings(File zip, List<String> includePaths, File outputZip) {
List<String> 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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ProvideFilesActionView implements Action {

@Override
String getUrlName() {
return dirName
return dirName.replace(" ", "-")
}

Run getRun() {
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:description>${%step.description}
<b>${%step.compatNote}</b>
</f:description>
<f:entry title="${%selectedReportTypes.title}" description="${%selectedReportTypes.description}"
field="selectedReportTypes">
<f:textbox value="${descriptor.getSelectedReportTypes()}"/>
</f:entry>
<f:advanced>
<f:property field="publishConfig"/>
</f:advanced>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -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.
9 changes: 9 additions & 0 deletions src/main/webapp/images/file/generateReport.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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"() {
Expand All @@ -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)
}
}
Loading

0 comments on commit 4945891

Please sign in to comment.