You've already forked sap-jenkins-library
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:
@@ -263,7 +263,7 @@ func abapEnvironmentRunATCCheckMetadata() config.StepData {
|
|||||||
{
|
{
|
||||||
Name: "generateHTML",
|
Name: "generateHTML",
|
||||||
ResourceRef: []config.ResourceReference{},
|
ResourceRef: []config.ResourceReference{},
|
||||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
|
||||||
Type: "bool",
|
Type: "bool",
|
||||||
Mandatory: false,
|
Mandatory: false,
|
||||||
Aliases: []config.Alias{},
|
Aliases: []config.Alias{},
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SAP/jenkins-library/pkg/abaputils"
|
"github.com/SAP/jenkins-library/pkg/abaputils"
|
||||||
@@ -95,7 +96,7 @@ func runAbapEnvironmentRunAUnitTest(config *abapEnvironmentRunAUnitTestOptions,
|
|||||||
resp, err = triggerAUnitrun(*config, details, client)
|
resp, err = triggerAUnitrun(*config, details, client)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = handleAUnitResults(resp, details, client, config.AUnitResultsFileName)
|
err = handleAUnitResults(resp, details, client, config.AUnitResultsFileName, config.GenerateHTML)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Entry().WithError(err).Fatal("step execution failed")
|
log.Entry().WithError(err).Fatal("step execution failed")
|
||||||
@@ -156,7 +157,7 @@ func convertAUnitOptions(options *abapEnvironmentRunAUnitTestOptions) abaputils.
|
|||||||
return subOptions
|
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 err error
|
||||||
var abapEndpoint string
|
var abapEndpoint string
|
||||||
abapEndpoint = details.URL
|
abapEndpoint = details.URL
|
||||||
@@ -174,7 +175,7 @@ func handleAUnitResults(resp *http.Response, details abaputils.ConnectionDetails
|
|||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
err = parseAUnitResult(body, aunitResultFileName)
|
err = parseAUnitResult(body, aunitResultFileName, generateHTML)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Handling AUnit result failed: %w", err)
|
return fmt.Errorf("Handling AUnit result failed: %w", err)
|
||||||
@@ -362,7 +363,7 @@ func getResultAUnitRun(requestType string, details abaputils.ConnectionDetailsHT
|
|||||||
return req, err
|
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 {
|
if len(body) == 0 {
|
||||||
return fmt.Errorf("Parsing AUnit result failed: %w", errors.New("Body is empty, can't parse empty body"))
|
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)
|
return fmt.Errorf("Writing results failed: %w", err)
|
||||||
}
|
}
|
||||||
log.Entry().Infof("Writing %s file was successful.", aunitResultFileName)
|
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
|
//Return before processing empty AUnit results --> XML can still be written with response body
|
||||||
if len(parsedXML.Testsuite.Testcase) == 0 {
|
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)
|
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)
|
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
|
//Persist findings afterwards
|
||||||
var reports []piperutils.Path
|
|
||||||
reports = append(reports, piperutils.Path{Target: aunitResultFileName, Name: "AUnit Results", Mandatory: true})
|
reports = append(reports, piperutils.Path{Target: aunitResultFileName, Name: "AUnit Results", Mandatory: true})
|
||||||
piperutils.PersistReportsAndLinks("abapEnvironmentRunAUnitTest", "", reports, nil)
|
piperutils.PersistReportsAndLinks("abapEnvironmentRunAUnitTest", "", reports, nil)
|
||||||
return 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
|
// Object Set Structure
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ type abapEnvironmentRunAUnitTestOptions struct {
|
|||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
Host string `json:"host,omitempty"`
|
Host string `json:"host,omitempty"`
|
||||||
AUnitResultsFileName string `json:"aUnitResultsFileName,omitempty"`
|
AUnitResultsFileName string `json:"aUnitResultsFileName,omitempty"`
|
||||||
|
GenerateHTML bool `json:"generateHTML,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AbapEnvironmentRunAUnitTestCommand Runs an AUnit Test
|
// 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.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.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().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("aUnitConfig")
|
||||||
cmd.MarkFlagRequired("username")
|
cmd.MarkFlagRequired("username")
|
||||||
@@ -258,6 +260,15 @@ func abapEnvironmentRunAUnitTestMetadata() config.StepData {
|
|||||||
Aliases: []config.Alias{},
|
Aliases: []config.Alias{},
|
||||||
Default: `AUnitResults.xml`,
|
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{
|
Containers: []config.Container{
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -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.
|
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.
|
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}`.
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ spec:
|
|||||||
- PARAMETERS
|
- PARAMETERS
|
||||||
- STAGES
|
- STAGES
|
||||||
- STEPS
|
- STEPS
|
||||||
|
- GENERAL
|
||||||
mandatory: false
|
mandatory: false
|
||||||
containers:
|
containers:
|
||||||
- name: cf
|
- name: cf
|
||||||
|
|||||||
@@ -128,6 +128,15 @@ spec:
|
|||||||
- STEPS
|
- STEPS
|
||||||
mandatory: false
|
mandatory: false
|
||||||
default: "AUnitResults.xml"
|
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:
|
containers:
|
||||||
- name: cf
|
- name: cf
|
||||||
image: ppiper/cf-cli:7
|
image: ppiper/cf-cli:7
|
||||||
|
|||||||
Reference in New Issue
Block a user