github.com/jaylevin/jenkins-library@v1.230.4/cmd/abapEnvironmentRunAUnitTest.go (about)

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