github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/abapEnvironmentRunAUnitTest.go (about)

     1  package cmd
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"encoding/xml"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/http/cookiejar"
    11  	"os"
    12  	"reflect"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/SAP/jenkins-library/pkg/abaputils"
    17  	"github.com/SAP/jenkins-library/pkg/command"
    18  	piperhttp "github.com/SAP/jenkins-library/pkg/http"
    19  	"github.com/SAP/jenkins-library/pkg/log"
    20  	"github.com/SAP/jenkins-library/pkg/piperutils"
    21  	"github.com/SAP/jenkins-library/pkg/telemetry"
    22  	"github.com/pkg/errors"
    23  )
    24  
    25  func abapEnvironmentRunAUnitTest(config abapEnvironmentRunAUnitTestOptions, telemetryData *telemetry.CustomData) {
    26  
    27  	// for command execution use Command
    28  	c := command.Command{}
    29  	// reroute command output to logging framework
    30  	c.Stdout(log.Writer())
    31  	c.Stderr(log.Writer())
    32  
    33  	var autils = abaputils.AbapUtils{
    34  		Exec: &c,
    35  	}
    36  
    37  	client := piperhttp.Client{}
    38  	utils := piperutils.Files{}
    39  
    40  	// error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end
    41  	err := runAbapEnvironmentRunAUnitTest(&config, telemetryData, &autils, &client, &utils)
    42  	if err != nil {
    43  		log.Entry().WithError(err).Fatal("step execution failed")
    44  	}
    45  }
    46  
    47  func runAbapEnvironmentRunAUnitTest(config *abapEnvironmentRunAUnitTestOptions, telemetryData *telemetry.CustomData, com abaputils.Communication, client piperhttp.Sender, utils piperutils.FileUtils) error {
    48  	var details abaputils.ConnectionDetailsHTTP
    49  	subOptions := convertAUnitOptions(config)
    50  	details, err := com.GetAbapCommunicationArrangementInfo(subOptions, "")
    51  	var resp *http.Response
    52  	cookieJar, _ := cookiejar.New(nil)
    53  	//Fetch Xcrsf-Token
    54  	if err == nil {
    55  		credentialsOptions := piperhttp.ClientOptions{
    56  			Username:  details.User,
    57  			Password:  details.Password,
    58  			CookieJar: cookieJar,
    59  		}
    60  		client.SetOptions(credentialsOptions)
    61  		details.XCsrfToken, err = fetchAUnitXcsrfToken("GET", details, nil, client)
    62  	}
    63  	if err == nil {
    64  		resp, err = triggerAUnitrun(*config, details, client)
    65  	}
    66  	if err == nil {
    67  		err = fetchAndPersistAUnitResults(resp, details, client, utils, config.AUnitResultsFileName, config.GenerateHTML)
    68  	}
    69  	if err != nil {
    70  		log.Entry().WithError(err).Fatal("step execution failed")
    71  	}
    72  	log.Entry().Info("AUnit test run completed successfully. If there are any results from the respective run they will be listed in the logs above as well as being saved in the output .xml file")
    73  	return nil
    74  }
    75  
    76  func triggerAUnitrun(config abapEnvironmentRunAUnitTestOptions, details abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) (*http.Response, error) {
    77  
    78  	abapEndpoint := details.URL
    79  	bodyString, err := buildAUnitRequestBody(config)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	//Trigger AUnit run
    85  	var resp *http.Response
    86  
    87  	var body = []byte(bodyString)
    88  	log.Entry().Debugf("Request Body: %s", bodyString)
    89  	details.URL = abapEndpoint + "/sap/bc/adt/api/abapunit/runs"
    90  	resp, err = runAUnit("POST", details, body, client)
    91  	return resp, err
    92  }
    93  
    94  func resolveAUnitConfiguration(config abapEnvironmentRunAUnitTestOptions) (aUnitConfig AUnitConfig, err error) {
    95  
    96  	if config.AUnitConfig != "" {
    97  		// Configuration defaults to AUnitConfig
    98  		log.Entry().Infof("AUnit Configuration: %s", config.AUnitConfig)
    99  		result, err := abaputils.ReadConfigFile(config.AUnitConfig)
   100  		if err != nil {
   101  			return aUnitConfig, err
   102  		}
   103  		err = json.Unmarshal(result, &aUnitConfig)
   104  		return aUnitConfig, err
   105  
   106  	} else if config.Repositories != "" {
   107  		// Fallback / EasyMode is the Repositories configuration
   108  		log.Entry().Infof("AUnit Configuration derived from: %s", config.Repositories)
   109  		repos, err := abaputils.GetRepositories((&abaputils.RepositoriesConfig{Repositories: config.Repositories}), false)
   110  		if err != nil {
   111  			return aUnitConfig, err
   112  		}
   113  		for _, repo := range repos {
   114  			aUnitConfig.ObjectSet.SoftwareComponents = append(aUnitConfig.ObjectSet.SoftwareComponents, abaputils.SoftwareComponents{Name: repo.Name})
   115  		}
   116  		aUnitConfig.Title = "AUnit Test Run"
   117  		return aUnitConfig, nil
   118  	} else {
   119  		// Fail if no configuration is provided
   120  		return aUnitConfig, errors.New("No configuration provided - please provide either an AUnit configuration file or a repository configuration file")
   121  	}
   122  }
   123  
   124  func convertAUnitOptions(options *abapEnvironmentRunAUnitTestOptions) abaputils.AbapEnvironmentOptions {
   125  	subOptions := abaputils.AbapEnvironmentOptions{}
   126  
   127  	subOptions.CfAPIEndpoint = options.CfAPIEndpoint
   128  	subOptions.CfServiceInstance = options.CfServiceInstance
   129  	subOptions.CfServiceKeyName = options.CfServiceKeyName
   130  	subOptions.CfOrg = options.CfOrg
   131  	subOptions.CfSpace = options.CfSpace
   132  	subOptions.Host = options.Host
   133  	subOptions.Password = options.Password
   134  	subOptions.Username = options.Username
   135  
   136  	return subOptions
   137  }
   138  
   139  func fetchAndPersistAUnitResults(resp *http.Response, details abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, utils piperutils.FileUtils, aunitResultFileName string, generateHTML bool) error {
   140  	var err error
   141  	abapEndpoint := details.URL
   142  	location := resp.Header.Get("Location")
   143  	details.URL = abapEndpoint + location
   144  	location, err = pollAUnitRun(details, nil, client)
   145  	if err == nil {
   146  		details.URL = abapEndpoint + location
   147  		resp, err = getAUnitResults("GET", details, nil, client)
   148  	}
   149  	//Parse response
   150  	var body []byte
   151  	if err == nil {
   152  		body, err = io.ReadAll(resp.Body)
   153  	}
   154  	if err == nil {
   155  		defer resp.Body.Close()
   156  		err = persistAUnitResult(utils, body, aunitResultFileName, generateHTML)
   157  	}
   158  	if err != nil {
   159  		return fmt.Errorf("Handling AUnit result failed: %w", err)
   160  	}
   161  	return nil
   162  }
   163  
   164  func buildAUnitRequestBody(config abapEnvironmentRunAUnitTestOptions) (bodyString string, err error) {
   165  
   166  	bodyString = ""
   167  	AUnitConfig, err := resolveAUnitConfiguration(config)
   168  	if err != nil {
   169  		return bodyString, err
   170  	}
   171  
   172  	//Checks before building the XML body
   173  	if AUnitConfig.Title == "" {
   174  		return bodyString, fmt.Errorf("Error while parsing AUnit test run config. No title for the AUnit run has been provided. Please configure an appropriate title for the respective test run")
   175  	}
   176  	if AUnitConfig.Context == "" {
   177  		AUnitConfig.Context = "ABAP Environment Pipeline"
   178  	}
   179  	if reflect.DeepEqual(abaputils.ObjectSet{}, AUnitConfig.ObjectSet) {
   180  		return bodyString, fmt.Errorf("Error while parsing AUnit test run object set config. No object set has been provided. Please configure the objects you want to be checked for the respective test run")
   181  	}
   182  
   183  	//Build Options
   184  	optionsString := buildAUnitOptionsString(AUnitConfig)
   185  	//Build metadata string
   186  	metadataString := `<aunit:run title="` + AUnitConfig.Title + `" context="` + AUnitConfig.Context + `" xmlns:aunit="http://www.sap.com/adt/api/aunit">`
   187  	//Build Object Set
   188  	objectSetString := abaputils.BuildOSLString(AUnitConfig.ObjectSet)
   189  
   190  	bodyString += `<?xml version="1.0" encoding="UTF-8"?>` + metadataString + optionsString + objectSetString + `</aunit:run>`
   191  
   192  	return bodyString, nil
   193  }
   194  
   195  func runAUnit(requestType string, details abaputils.ConnectionDetailsHTTP, body []byte, client piperhttp.Sender) (*http.Response, error) {
   196  
   197  	log.Entry().WithField("ABAP endpoint: ", details.URL).Info("Triggering AUnit run")
   198  
   199  	header := make(map[string][]string)
   200  	header["X-Csrf-Token"] = []string{details.XCsrfToken}
   201  	header["Content-Type"] = []string{"application/vnd.sap.adt.api.abapunit.run.v1+xml; charset=utf-8;"}
   202  
   203  	req, err := client.SendRequest(requestType, details.URL, bytes.NewBuffer(body), header, nil)
   204  	if err != nil {
   205  		return req, fmt.Errorf("Triggering AUnit run failed: %w", err)
   206  	}
   207  	defer req.Body.Close()
   208  	return req, err
   209  }
   210  
   211  func buildAUnitOptionsString(AUnitConfig AUnitConfig) (optionsString string) {
   212  
   213  	optionsString += `<aunit:options>`
   214  	if AUnitConfig.Options.Measurements != "" {
   215  		optionsString += `<aunit:measurements type="` + AUnitConfig.Options.Measurements + `"/>`
   216  	} else {
   217  		optionsString += `<aunit:measurements type="none"/>`
   218  	}
   219  	//We assume there must be one scope configured
   220  	optionsString += `<aunit:scope`
   221  	if AUnitConfig.Options.Scope.OwnTests != nil {
   222  		optionsString += ` ownTests="` + fmt.Sprintf("%v", *AUnitConfig.Options.Scope.OwnTests) + `"`
   223  	} else {
   224  		optionsString += ` ownTests="true"`
   225  	}
   226  	if AUnitConfig.Options.Scope.ForeignTests != nil {
   227  		optionsString += ` foreignTests="` + fmt.Sprintf("%v", *AUnitConfig.Options.Scope.ForeignTests) + `"`
   228  	} else {
   229  		optionsString += ` foreignTests="true"`
   230  	}
   231  	//We assume there must be one riskLevel configured
   232  	optionsString += `/><aunit:riskLevel`
   233  	if AUnitConfig.Options.RiskLevel.Harmless != nil {
   234  		optionsString += ` harmless="` + fmt.Sprintf("%v", *AUnitConfig.Options.RiskLevel.Harmless) + `"`
   235  	} else {
   236  		optionsString += ` harmless="true"`
   237  	}
   238  	if AUnitConfig.Options.RiskLevel.Dangerous != nil {
   239  		optionsString += ` dangerous="` + fmt.Sprintf("%v", *AUnitConfig.Options.RiskLevel.Dangerous) + `"`
   240  	} else {
   241  		optionsString += ` dangerous="true"`
   242  	}
   243  	if AUnitConfig.Options.RiskLevel.Critical != nil {
   244  		optionsString += ` critical="` + fmt.Sprintf("%v", *AUnitConfig.Options.RiskLevel.Critical) + `"`
   245  	} else {
   246  		optionsString += ` critical="true"`
   247  	}
   248  	//We assume there must be one duration time configured
   249  	optionsString += `/><aunit:duration`
   250  	if AUnitConfig.Options.Duration.Short != nil {
   251  		optionsString += ` short="` + fmt.Sprintf("%v", *AUnitConfig.Options.Duration.Short) + `"`
   252  	} else {
   253  		optionsString += ` short="true"`
   254  	}
   255  	if AUnitConfig.Options.Duration.Medium != nil {
   256  		optionsString += ` medium="` + fmt.Sprintf("%v", *AUnitConfig.Options.Duration.Medium) + `"`
   257  	} else {
   258  		optionsString += ` medium="true"`
   259  	}
   260  	if AUnitConfig.Options.Duration.Long != nil {
   261  		optionsString += ` long="` + fmt.Sprintf("%v", *AUnitConfig.Options.Duration.Long) + `"`
   262  	} else {
   263  		optionsString += ` long="true"`
   264  	}
   265  	optionsString += `/></aunit:options>`
   266  	return optionsString
   267  }
   268  
   269  func fetchAUnitXcsrfToken(requestType string, details abaputils.ConnectionDetailsHTTP, body []byte, client piperhttp.Sender) (string, error) {
   270  
   271  	log.Entry().WithField("ABAP Endpoint: ", details.URL).Debug("Fetching Xcrsf-Token")
   272  
   273  	details.URL += "/sap/bc/adt/api/abapunit/runs/00000000000000000000000000000000"
   274  	details.XCsrfToken = "fetch"
   275  	header := make(map[string][]string)
   276  	header["X-Csrf-Token"] = []string{details.XCsrfToken}
   277  	header["Accept"] = []string{"application/vnd.sap.adt.api.abapunit.run-status.v1+xml"}
   278  	req, err := client.SendRequest(requestType, details.URL, bytes.NewBuffer(body), header, nil)
   279  	if err != nil {
   280  		return "", fmt.Errorf("Fetching Xcsrf-Token failed: %w", err)
   281  	}
   282  	defer req.Body.Close()
   283  
   284  	token := req.Header.Get("X-Csrf-Token")
   285  	return token, err
   286  }
   287  
   288  func pollAUnitRun(details abaputils.ConnectionDetailsHTTP, body []byte, client piperhttp.Sender) (string, error) {
   289  
   290  	log.Entry().WithField("ABAP endpoint", details.URL).Info("Polling AUnit run status")
   291  
   292  	for {
   293  		resp, err := getHTTPResponseAUnitRun("GET", details, nil, client)
   294  		if err != nil {
   295  			return "", fmt.Errorf("Getting HTTP response failed: %w", err)
   296  		}
   297  		bodyText, err := io.ReadAll(resp.Body)
   298  		if err != nil {
   299  			return "", fmt.Errorf("Reading response body failed: %w", err)
   300  		}
   301  		x := new(AUnitRun)
   302  		if err := xml.Unmarshal(bodyText, &x); err != nil {
   303  			return "", err
   304  		}
   305  
   306  		log.Entry().Infof("Current polling status: %s", x.Progress.Status)
   307  		if x.Progress.Status == "Not Created" {
   308  			return "", err
   309  		}
   310  		if x.Progress.Status == "Completed" || x.Progress.Status == "FINISHED" {
   311  			return x.Link.Href, err
   312  		}
   313  		if x.Progress.Status == "" {
   314  			return "", fmt.Errorf("Could not get any response from AUnit poll: %w", errors.New("Status from AUnit run is empty. Either it's not an ABAP system or AUnit run hasn't started"))
   315  		}
   316  		time.Sleep(10 * time.Second)
   317  	}
   318  }
   319  
   320  func getHTTPResponseAUnitRun(requestType string, details abaputils.ConnectionDetailsHTTP, body []byte, client piperhttp.Sender) (*http.Response, error) {
   321  
   322  	log.Entry().WithField("ABAP Endpoint: ", details.URL).Info("Polling AUnit run status")
   323  
   324  	header := make(map[string][]string)
   325  	header["Accept"] = []string{"application/vnd.sap.adt.api.abapunit.run-status.v1+xml"}
   326  
   327  	req, err := client.SendRequest(requestType, details.URL, bytes.NewBuffer(body), header, nil)
   328  	if err != nil {
   329  		return req, fmt.Errorf("Getting AUnit run status failed: %w", err)
   330  	}
   331  	return req, err
   332  }
   333  
   334  func getAUnitResults(requestType string, details abaputils.ConnectionDetailsHTTP, body []byte, client piperhttp.Sender) (*http.Response, error) {
   335  
   336  	log.Entry().WithField("ABAP Endpoint: ", details.URL).Info("Getting AUnit results")
   337  
   338  	header := make(map[string][]string)
   339  	header["x-csrf-token"] = []string{details.XCsrfToken}
   340  	header["Accept"] = []string{"application/vnd.sap.adt.api.junit.run-result.v1+xml"}
   341  
   342  	req, err := client.SendRequest(requestType, details.URL, bytes.NewBuffer(body), header, nil)
   343  	if err != nil {
   344  		return req, fmt.Errorf("Getting AUnit run results failed: %w", err)
   345  	}
   346  	return req, err
   347  }
   348  
   349  func persistAUnitResult(utils piperutils.FileUtils, body []byte, aunitResultFileName string, generateHTML bool) (err error) {
   350  	if len(body) == 0 {
   351  		return fmt.Errorf("Parsing AUnit result failed: %w", errors.New("Body is empty, can't parse empty body"))
   352  	}
   353  
   354  	responseBody := string(body)
   355  	log.Entry().Debugf("Response body: %s", responseBody)
   356  
   357  	//Optional checks before writing the Results
   358  	parsedXML := new(AUnitResult)
   359  	if err := xml.Unmarshal([]byte(body), &parsedXML); err != nil {
   360  		log.Entry().WithError(err).Warning("failed to unmarshal xml response")
   361  	}
   362  
   363  	//Write Results
   364  	err = os.WriteFile(aunitResultFileName, body, 0644)
   365  	if err != nil {
   366  		return fmt.Errorf("Writing results failed: %w", err)
   367  	}
   368  	log.Entry().Infof("Writing %s file was successful.", aunitResultFileName)
   369  	var reports []piperutils.Path
   370  	//Return before processing empty AUnit results --> XML can still be written with response body
   371  	if len(parsedXML.Testsuite.Testcase) == 0 {
   372  		log.Entry().Infof("There were no AUnit findings from this run. The response has been saved in the %s file", aunitResultFileName)
   373  	} else {
   374  		log.Entry().Infof("Please find the results from the respective AUnit run in the %s file or in below logs", aunitResultFileName)
   375  		//Logging of AUnit findings
   376  		log.Entry().Infof(`Here are the results for the AUnit test run '%s' executed by User %s on System %s in Client %s at %s. The AUnit run took %s seconds and contains %s tests with %s failures, %s errors, %s skipped and %s assert findings`, parsedXML.Title, parsedXML.System, parsedXML.ExecutedBy, parsedXML.Client, parsedXML.Timestamp, parsedXML.Time, parsedXML.Tests, parsedXML.Failures, parsedXML.Errors, parsedXML.Skipped, parsedXML.Asserts)
   377  		for _, s := range parsedXML.Testsuite.Testcase {
   378  			//Log Infos for testcase
   379  			//HTML Procesing can be done here
   380  			for _, failure := range s.Failure {
   381  				log.Entry().Debugf("%s, %s: %s found by %s", failure.Type, failure.Message, failure.Message, s.Classname)
   382  			}
   383  			for _, skipped := range s.Skipped {
   384  				log.Entry().Debugf("The following test has been skipped: %s: %s", skipped.Message, skipped.Text)
   385  			}
   386  		}
   387  		if generateHTML {
   388  			htmlString := generateHTMLDocumentAUnit(parsedXML)
   389  			htmlStringByte := []byte(htmlString)
   390  			aUnitResultHTMLFileName := strings.Trim(aunitResultFileName, ".xml") + ".html"
   391  			err = os.WriteFile(aUnitResultHTMLFileName, htmlStringByte, 0644)
   392  			if err != nil {
   393  				return fmt.Errorf("Writing HTML document failed: %w", err)
   394  			}
   395  			log.Entry().Info("Writing " + aUnitResultHTMLFileName + " file was successful")
   396  			reports = append(reports, piperutils.Path{Target: aUnitResultHTMLFileName, Name: "ATC Results HTML file", Mandatory: true})
   397  		}
   398  	}
   399  	//Persist findings afterwards
   400  	reports = append(reports, piperutils.Path{Target: aunitResultFileName, Name: "AUnit Results", Mandatory: true})
   401  	piperutils.PersistReportsAndLinks("abapEnvironmentRunAUnitTest", "", utils, reports, nil)
   402  	return nil
   403  }
   404  
   405  func generateHTMLDocumentAUnit(parsedXML *AUnitResult) (htmlDocumentString string) {
   406  	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>`
   407  
   408  	var htmlDocumentStringError, htmlDocumentStringWarning, htmlDocumentStringInfo, htmlDocumentStringDefault string
   409  	for _, s := range parsedXML.Testsuite.Testcase {
   410  		//Add coloring of lines inside of the respective severities, e.g. failures in red
   411  		trBackgroundColorTestcase := "grey"
   412  		trBackgroundColorError := "rgba(227,85,0)"
   413  		trBackgroundColorFailure := "rgba(227,85,0)"
   414  		trBackgroundColorSkipped := "rgba(255,175,0, 0.2)"
   415  		if (len(s.Error) != 0) || (len(s.Failure) != 0) || (len(s.Skipped) != 0) {
   416  			htmlDocumentString += `<tr style="background-color: ` + trBackgroundColorTestcase + `"><td colspan="5"><b>Testcase: ` + s.Name + ` for class ` + s.Classname + `</b></td></tr>`
   417  		}
   418  		for _, t := range s.Error {
   419  			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>`
   420  		}
   421  		for _, t := range s.Failure {
   422  			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>`
   423  		}
   424  		for _, t := range s.Skipped {
   425  			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>`
   426  		}
   427  	}
   428  	if len(parsedXML.Testsuite.Testcase) == 0 {
   429  		htmlDocumentString += `<tr><td colspan="5"><b>There are no AUnit findings to be displayed</b></td></tr>`
   430  	}
   431  	htmlDocumentString += htmlDocumentStringError + htmlDocumentStringWarning + htmlDocumentStringInfo + htmlDocumentStringDefault + `</table></body></html>`
   432  
   433  	return htmlDocumentString
   434  }
   435  
   436  //
   437  //	Object Set Structure
   438  //
   439  
   440  // AUnitConfig object for parsing yaml config of software components and packages
   441  type AUnitConfig struct {
   442  	Title     string              `json:"title,omitempty"`
   443  	Context   string              `json:"context,omitempty"`
   444  	Options   AUnitOptions        `json:"options,omitempty"`
   445  	ObjectSet abaputils.ObjectSet `json:"objectset,omitempty"`
   446  }
   447  
   448  // AUnitOptions in form of packages and software components to be checked
   449  type AUnitOptions struct {
   450  	Measurements string    `json:"measurements,omitempty"`
   451  	Scope        Scope     `json:"scope,omitempty"`
   452  	RiskLevel    RiskLevel `json:"risklevel,omitempty"`
   453  	Duration     Duration  `json:"duration,omitempty"`
   454  }
   455  
   456  // Scope in form of packages and software components to be checked
   457  type Scope struct {
   458  	OwnTests     *bool `json:"owntests,omitempty"`
   459  	ForeignTests *bool `json:"foreigntests,omitempty"`
   460  }
   461  
   462  // RiskLevel in form of packages and software components to be checked
   463  type RiskLevel struct {
   464  	Harmless  *bool `json:"harmless,omitempty"`
   465  	Dangerous *bool `json:"dangerous,omitempty"`
   466  	Critical  *bool `json:"critical,omitempty"`
   467  }
   468  
   469  // Duration in form of packages and software components to be checked
   470  type Duration struct {
   471  	Short  *bool `json:"short,omitempty"`
   472  	Medium *bool `json:"medium,omitempty"`
   473  	Long   *bool `json:"long,omitempty"`
   474  }
   475  
   476  //
   477  //	AUnit Run Structure
   478  //
   479  
   480  // AUnitRun Object for parsing XML
   481  type AUnitRun struct {
   482  	XMLName    xml.Name   `xml:"run"`
   483  	Title      string     `xml:"title,attr"`
   484  	Context    string     `xml:"context,attr"`
   485  	Progress   Progress   `xml:"progress"`
   486  	ExecutedBy ExecutedBy `xml:"executedBy"`
   487  	Time       Time       `xml:"time"`
   488  	Link       AUnitLink  `xml:"link"`
   489  }
   490  
   491  // Progress of AUnit run
   492  type Progress struct {
   493  	Status     string `xml:"status,attr"`
   494  	Percentage string `xml:"percentage,attr"`
   495  }
   496  
   497  // ExecutedBy User
   498  type ExecutedBy struct {
   499  	User string `xml:"user,attr"`
   500  }
   501  
   502  // Time run was started and finished
   503  type Time struct {
   504  	Started string `xml:"started,attr"`
   505  	Ended   string `xml:"ended,attr"`
   506  }
   507  
   508  // AUnitLink containing result locations
   509  type AUnitLink struct {
   510  	Href string `xml:"href,attr"`
   511  	Rel  string `xml:"rel,attr"`
   512  	Type string `xml:"type,attr"`
   513  }
   514  
   515  //
   516  //	AUnit Result Structure
   517  //
   518  
   519  type AUnitResult struct {
   520  	XMLName    xml.Name `xml:"testsuites"`
   521  	Title      string   `xml:"title,attr"`
   522  	System     string   `xml:"system,attr"`
   523  	Client     string   `xml:"client,attr"`
   524  	ExecutedBy string   `xml:"executedBy,attr"`
   525  	Time       string   `xml:"time,attr"`
   526  	Timestamp  string   `xml:"timestamp,attr"`
   527  	Failures   string   `xml:"failures,attr"`
   528  	Errors     string   `xml:"errors,attr"`
   529  	Skipped    string   `xml:"skipped,attr"`
   530  	Asserts    string   `xml:"asserts,attr"`
   531  	Tests      string   `xml:"tests,attr"`
   532  	Testsuite  struct {
   533  		Tests     string `xml:"tests,attr"`
   534  		Asserts   string `xml:"asserts,attr"`
   535  		Skipped   string `xml:"skipped,attr"`
   536  		Errors    string `xml:"errors,attr"`
   537  		Failures  string `xml:"failures,attr"`
   538  		Timestamp string `xml:"timestamp,attr"`
   539  		Time      string `xml:"time,attr"`
   540  		Hostname  string `xml:"hostname,attr"`
   541  		Package   string `xml:"package,attr"`
   542  		Name      string `xml:"name,attr"`
   543  		Testcase  []struct {
   544  			Asserts   string `xml:"asserts,attr"`
   545  			Time      string `xml:"time,attr"`
   546  			Name      string `xml:"name,attr"`
   547  			Classname string `xml:"classname,attr"`
   548  			Error     []struct {
   549  				Text    string `xml:",chardata"`
   550  				Type    string `xml:"type,attr"`
   551  				Message string `xml:"message,attr"`
   552  			} `xml:"error"`
   553  			Failure []struct {
   554  				Text    string `xml:",chardata"`
   555  				Type    string `xml:"type,attr"`
   556  				Message string `xml:"message,attr"`
   557  			} `xml:"failure"`
   558  			Skipped []struct {
   559  				Text    string `xml:",chardata"`
   560  				Message string `xml:"message,attr"`
   561  			} `xml:"skipped"`
   562  		} `xml:"testcase"`
   563  	} `xml:"testsuite"`
   564  }