1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-11-06 09:09:19 +02:00

Add html processing to AUnit stage/step (#3302)

* change return after files are persisted & Change logging

* html processing

* Extend step parameters

* Add documentation

* html processing

* add generateHTML flag to general

* test adaptions

* step metadata corrections

* adapt formatting

* Update documentation/docs/pipelines/abapEnvironment/extensibility.md

Co-authored-by: Daniel Mieg <56156797+DanielMieg@users.noreply.github.com>

* Update documentation/docs/pipelines/abapEnvironment/extensibility.md

Co-authored-by: Daniel Mieg <56156797+DanielMieg@users.noreply.github.com>

* adapt unit tests

Co-authored-by: Daniel Mieg <56156797+DanielMieg@users.noreply.github.com>
This commit is contained in:
Dominik Lendle
2021-12-13 16:39:32 +01:00
committed by GitHub
parent ad0a3bda2b
commit f188979412
7 changed files with 299 additions and 11 deletions

View File

@@ -263,7 +263,7 @@ func abapEnvironmentRunATCCheckMetadata() config.StepData {
{
Name: "generateHTML",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},

View File

@@ -10,6 +10,7 @@ import (
"net/http/cookiejar"
"path/filepath"
"reflect"
"strings"
"time"
"github.com/SAP/jenkins-library/pkg/abaputils"
@@ -95,7 +96,7 @@ func runAbapEnvironmentRunAUnitTest(config *abapEnvironmentRunAUnitTestOptions,
resp, err = triggerAUnitrun(*config, details, client)
}
if err == nil {
err = handleAUnitResults(resp, details, client, config.AUnitResultsFileName)
err = handleAUnitResults(resp, details, client, config.AUnitResultsFileName, config.GenerateHTML)
}
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
@@ -156,7 +157,7 @@ func convertAUnitOptions(options *abapEnvironmentRunAUnitTestOptions) abaputils.
return subOptions
}
func handleAUnitResults(resp *http.Response, details abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, aunitResultFileName string) error {
func handleAUnitResults(resp *http.Response, details abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, aunitResultFileName string, generateHTML bool) error {
var err error
var abapEndpoint string
abapEndpoint = details.URL
@@ -174,7 +175,7 @@ func handleAUnitResults(resp *http.Response, details abaputils.ConnectionDetails
}
if err == nil {
defer resp.Body.Close()
err = parseAUnitResult(body, aunitResultFileName)
err = parseAUnitResult(body, aunitResultFileName, generateHTML)
}
if err != nil {
return fmt.Errorf("Handling AUnit result failed: %w", err)
@@ -362,7 +363,7 @@ func getResultAUnitRun(requestType string, details abaputils.ConnectionDetailsHT
return req, err
}
func parseAUnitResult(body []byte, aunitResultFileName string) (err error) {
func parseAUnitResult(body []byte, aunitResultFileName string, generateHTML bool) (err error) {
if len(body) == 0 {
return fmt.Errorf("Parsing AUnit result failed: %w", errors.New("Body is empty, can't parse empty body"))
}
@@ -380,6 +381,7 @@ func parseAUnitResult(body []byte, aunitResultFileName string) (err error) {
return fmt.Errorf("Writing results failed: %w", err)
}
log.Entry().Infof("Writing %s file was successful.", aunitResultFileName)
var reports []piperutils.Path
//Return before processing empty AUnit results --> XML can still be written with response body
if len(parsedXML.Testsuite.Testcase) == 0 {
log.Entry().Infof("There were no AUnit findings from this run. The response has been saved in the %s file", aunitResultFileName)
@@ -396,14 +398,55 @@ func parseAUnitResult(body []byte, aunitResultFileName string) (err error) {
log.Entry().Debugf("The following test has been skipped: %s: %s", skipped.Message, skipped.Text)
}
}
if generateHTML == true {
htmlString := generateHTMLDocumentAUnit(parsedXML)
htmlStringByte := []byte(htmlString)
aUnitResultHTMLFileName := strings.Trim(aunitResultFileName, ".xml") + ".html"
err = ioutil.WriteFile(aUnitResultHTMLFileName, htmlStringByte, 0644)
if err != nil {
return fmt.Errorf("Writing HTML document failed: %w", err)
}
log.Entry().Info("Writing " + aUnitResultHTMLFileName + " file was successful")
reports = append(reports, piperutils.Path{Target: aUnitResultHTMLFileName, Name: "ATC Results HTML file", Mandatory: true})
}
}
//Persist findings afterwards
var reports []piperutils.Path
reports = append(reports, piperutils.Path{Target: aunitResultFileName, Name: "AUnit Results", Mandatory: true})
piperutils.PersistReportsAndLinks("abapEnvironmentRunAUnitTest", "", reports, nil)
return nil
}
func generateHTMLDocumentAUnit(parsedXML *AUnitResult) (htmlDocumentString string) {
htmlDocumentString = `<!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"><head><title>AUnit Results</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><style>table,th,td {border-collapse:collapse;}th,td{padding: 5px;text-align:left;font-size:medium;}</style></head><body><h1 style="text-align:left;font-size:large">AUnit Results</h1><table><tr><th>Run title</th><td style="padding-right: 20px">` + parsedXML.Title + `</td><th>System</th><td style="padding-right: 20px">` + parsedXML.System + `</td><th>Client</th><td style="padding-right: 20px">` + parsedXML.Client + `</td><th>ExecutedBy</th><td style="padding-right: 20px">` + parsedXML.ExecutedBy + `</td><th>Duration</th><td style="padding-right: 20px">` + parsedXML.Time + `s</td><th>Timestamp</th><td style="padding-right: 20px">` + parsedXML.Timestamp + `</td></tr><tr><th>Failures</th><td style="padding-right: 20px">` + parsedXML.Failures + `</td><th>Errors</th><td style="padding-right: 20px">` + parsedXML.Errors + `</td><th>Skipped</th><td style="padding-right: 20px">` + parsedXML.Skipped + `</td><th>Asserts</th><td style="padding-right: 20px">` + parsedXML.Asserts + `</td><th>Tests</th><td style="padding-right: 20px">` + parsedXML.Tests + `</td></tr></table><br><table style="width:100%; border: 1px solid black""><tr style="border: 1px solid black"><th style="border: 1px solid black">Severity</th><th style="border: 1px solid black">File</th><th style="border: 1px solid black">Message</th><th style="border: 1px solid black">Type</th><th style="border: 1px solid black">Text</th></tr>`
var htmlDocumentStringError, htmlDocumentStringWarning, htmlDocumentStringInfo, htmlDocumentStringDefault string
for _, s := range parsedXML.Testsuite.Testcase {
//Add coloring of lines inside of the respective severities, e.g. failures in red
trBackgroundColorTestcase := "grey"
trBackgroundColorError := "rgba(227,85,0)"
trBackgroundColorFailure := "rgba(227,85,0)"
trBackgroundColorSkipped := "rgba(255,175,0, 0.2)"
if (len(s.Error) != 0) || (len(s.Failure) != 0) || (len(s.Skipped) != 0) {
htmlDocumentString += `<tr style="background-color: ` + trBackgroundColorTestcase + `"><td colspan="5"><b>Testcase: ` + s.Name + ` for class ` + s.Classname + `</b></td></tr>`
}
for _, t := range s.Error {
htmlDocumentString += `<tr style="background-color: ` + trBackgroundColorError + `"><td style="border: 1px solid black">Failure</td><td style="border: 1px solid black">` + s.Classname + `</td><td style="border: 1px solid black">` + t.Message + `</td><td style="border: 1px solid black">` + t.Type + `</td><td style="border: 1px solid black">` + t.Text + `</td></tr>`
}
for _, t := range s.Failure {
htmlDocumentString += `<tr style="background-color: ` + trBackgroundColorFailure + `"><td style="border: 1px solid black">Failure</td><td style="border: 1px solid black">` + s.Classname + `</td><td style="border: 1px solid black">` + t.Message + `</td><td style="border: 1px solid black">` + t.Type + `</td><td style="border: 1px solid black">` + t.Text + `</td></tr>`
}
for _, t := range s.Skipped {
htmlDocumentString += `<tr style="background-color: ` + trBackgroundColorSkipped + `"><td style="border: 1px solid black">Failure</td><td style="border: 1px solid black">` + s.Classname + `</td><td style="border: 1px solid black">` + t.Message + `</td><td style="border: 1px solid black">-</td><td style="border: 1px solid black">` + t.Text + `</td></tr>`
}
}
if len(parsedXML.Testsuite.Testcase) == 0 {
htmlDocumentString += `<tr><td colspan="5"><b>There are no AUnit findings to be displayed</b></td></tr>`
}
htmlDocumentString += htmlDocumentStringError + htmlDocumentStringWarning + htmlDocumentStringInfo + htmlDocumentStringDefault + `</table></body></html>`
return htmlDocumentString
}
//
// Object Set Structure
//

View File

@@ -26,6 +26,7 @@ type abapEnvironmentRunAUnitTestOptions struct {
Password string `json:"password,omitempty"`
Host string `json:"host,omitempty"`
AUnitResultsFileName string `json:"aUnitResultsFileName,omitempty"`
GenerateHTML bool `json:"generateHTML,omitempty"`
}
// AbapEnvironmentRunAUnitTestCommand Runs an AUnit Test
@@ -136,6 +137,7 @@ func addAbapEnvironmentRunAUnitTestFlags(cmd *cobra.Command, stepConfig *abapEnv
cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "Password for either the Cloud Foundry API or the Communication Arrangement for SAP_COM_0735")
cmd.Flags().StringVar(&stepConfig.Host, "host", os.Getenv("PIPER_host"), "Specifies the host address of the SAP BTP ABAP Environment system")
cmd.Flags().StringVar(&stepConfig.AUnitResultsFileName, "aUnitResultsFileName", `AUnitResults.xml`, "Specifies output file name for the results from the AUnit run.")
cmd.Flags().BoolVar(&stepConfig.GenerateHTML, "generateHTML", false, "Specifies whether the AUnit results should also be generated as an HTML document")
cmd.MarkFlagRequired("aUnitConfig")
cmd.MarkFlagRequired("username")
@@ -258,6 +260,15 @@ func abapEnvironmentRunAUnitTestMetadata() config.StepData {
Aliases: []config.Alias{},
Default: `AUnitResults.xml`,
},
{
Name: "generateHTML",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
Default: false,
},
},
},
Containers: []config.Container{

File diff suppressed because one or more lines are too long

View File

@@ -98,3 +98,39 @@ return this
You can simply use the JUnit Plugin for Jenkins in the AUnit stage within the `.pipeline/extensions/AUnit.groovy` file by using the `junit` command. You can set optional parameters like `skipPublishingChecks: true` in order to disable an integration to the GitHub Checks API. `allowEmptyResults: true` allows the build status of the Jenkins run to be `SUCCESS` even if there have been no results from the respective AUnit test run in the test results file. Vice versa, `allowEmptyResults: false` will set the build status to `FAILURE` if the test results file contains no results.
The `testResults` parameter specifies the path to the AUnit test results file which has been saved and pinned to the Jenkins job in the `abapEnvironmentRunAUnitTest` step. Please refer to the documentation of the ([JUnit Plugin](https://plugins.jenkins.io/junit/#documentation)) for more detailled information on the usage and configuration of the JUnit plugin parameters.
## 4. Extend the AUnit stage to send AUnit results via E-Mail
In general when executing the `AUnit` stage, the respective AUnit results will normally be pinned to the Jenkins Job in a JUnit XML format.
Additionally, you can set the `generateHTML` flag to `true` for the `abapEnvironmentRunAUnitTest` step. This includes the generation of an HTML document containing the AUnit results for the `abapEnvironmentRunAUnitTest` step that will also be pinned to the respective Jenkins Job.
The AUnit results can be attached to an E-Mail or being sent as the E-Mail body with the [Email Extension Plugin](https://www.jenkins.io/doc/pipeline/steps/email-ext/) ([GitHub Project](https://github.com/jenkinsci/email-ext-plugin)) using the `emailext()` method. Make sure that you have configured the Email Extension Plugin correctly before using it.
In the following example we only provide a sample configuration using the Jenkins [Email Extension Plugin](https://www.jenkins.io/doc/pipeline/steps/email-ext/). The E-Mail can be fully customized to your needs. Please refer to the Email Extension Plugin Documentation to see the full list of parameter that are supported.
If you haven't created it already, create/extend the file `.pipeline/extensions/AUnit.groovy` with the following content:
```groovy
void call(Map params) {
//access stage name
echo "Start - Extension for stage: ${params.stageName}"
//access config
echo "Current stage config: ${params.config}"
//execute original stage as defined in the template
params.originalStage()
emailext (
attachmentsPattern: 'AUnitResults.html', //This will attach the AUnit results to the E-Mail
to: 'user@example.com, admin@example.com',
subject: "AUnit results Mail from latest Run in System H01",
body: 'Dear User, here are the results from the latest AUnit test run ${env.BUILD_ID}.' + readFile('AUnitResults.html') //This will parse the AUnit results and send it as the E-Mail body
)
echo "End - Extension for stage: ${params.stageName}"
}
return this
```
Note that in above example the AUnit test run results, stored in the `AUnitResults.html` file that is pinned to the Jenkins job, will be sent as an attachment using the `attachmentsPattern` parameter as well as being parsed and attached to the E-Mail body using the `body` parameter. Both methods are possible. If you chose to include the AUnit test run results in the E-Mail body make sure to read the file content properly, e.g. using the `readFile()` method.
The `subject` parameter defines the subject of the E-Mail that will be sent. The `to` parameter specifies a list of recipients separated by a comma. You can also set a distribution list as a recipient.
For all parameters it is also possible to use Jenkins environment variables like `${env.BUILD_ID}` or `${env.JENKINS_URL}`.

View File

@@ -135,6 +135,7 @@ spec:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
mandatory: false
containers:
- name: cf

View File

@@ -128,6 +128,15 @@ spec:
- STEPS
mandatory: false
default: "AUnitResults.xml"
- name: generateHTML
type: bool
description: Specifies whether the AUnit results should also be generated as an HTML document
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
mandatory: false
containers:
- name: cf
image: ppiper/cf-cli:7