github.com/xgoffin/jenkins-library@v1.154.0/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 type abapEnvironmentRunAUnitTestUtils interface { 25 command.ExecRunner 26 27 FileExists(filename string) (bool, error) 28 29 // Add more methods here, or embed additional interfaces, or remove/replace as required. 30 // The abapEnvironmentRunAUnitTestUtils interface should be descriptive of your runtime dependencies, 31 // i.e. include everything you need to be able to mock in tests. 32 // Unit tests shall be executable in parallel (not depend on global state), and don't (re-)test dependencies. 33 } 34 35 type abapEnvironmentRunAUnitTestUtilsBundle struct { 36 *command.Command 37 *piperutils.Files 38 39 // Embed more structs as necessary to implement methods or interfaces you add to abapEnvironmentRunAUnitTestUtils. 40 // Structs embedded in this way must each have a unique set of methods attached. 41 // If there is no struct which implements the method you need, attach the method to 42 // abapEnvironmentRunAUnitTestUtilsBundle and forward to the implementation of the dependency. 43 } 44 45 func newAbapEnvironmentRunAUnitTestUtils() abapEnvironmentRunAUnitTestUtils { 46 utils := abapEnvironmentRunAUnitTestUtilsBundle{ 47 Command: &command.Command{}, 48 Files: &piperutils.Files{}, 49 } 50 // Reroute command output to logging framework 51 utils.Stdout(log.Writer()) 52 utils.Stderr(log.Writer()) 53 return &utils 54 } 55 56 func abapEnvironmentRunAUnitTest(config abapEnvironmentRunAUnitTestOptions, telemetryData *telemetry.CustomData) { 57 58 // for command execution use Command 59 c := command.Command{} 60 // reroute command output to logging framework 61 c.Stdout(log.Writer()) 62 c.Stderr(log.Writer()) 63 64 var autils = abaputils.AbapUtils{ 65 Exec: &c, 66 } 67 68 client := piperhttp.Client{} 69 70 // error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end 71 err := runAbapEnvironmentRunAUnitTest(&config, telemetryData, &autils, &client) 72 if err != nil { 73 log.Entry().WithError(err).Fatal("step execution failed") 74 } 75 } 76 77 func runAbapEnvironmentRunAUnitTest(config *abapEnvironmentRunAUnitTestOptions, telemetryData *telemetry.CustomData, com abaputils.Communication, client piperhttp.Sender) error { 78 var details abaputils.ConnectionDetailsHTTP 79 subOptions := convertAUnitOptions(config) 80 details, err := com.GetAbapCommunicationArrangementInfo(subOptions, "") 81 var resp *http.Response 82 cookieJar, _ := cookiejar.New(nil) 83 //Fetch Xcrsf-Token 84 if err == nil { 85 credentialsOptions := piperhttp.ClientOptions{ 86 Username: details.User, 87 Password: details.Password, 88 CookieJar: cookieJar, 89 } 90 client.SetOptions(credentialsOptions) 91 details.XCsrfToken, err = fetchAUnitXcsrfToken("GET", details, nil, client) 92 } 93 if err == nil { 94 resp, err = triggerAUnitrun(*config, details, client) 95 } 96 if err == nil { 97 err = fetchAndPersistAUnitResults(resp, details, client, config.AUnitResultsFileName, config.GenerateHTML) 98 } 99 if err != nil { 100 log.Entry().WithError(err).Fatal("step execution failed") 101 } 102 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") 103 return nil 104 } 105 106 func triggerAUnitrun(config abapEnvironmentRunAUnitTestOptions, details abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) (*http.Response, error) { 107 108 abapEndpoint := details.URL 109 bodyString, err := buildAUnitRequestBody(config) 110 if err != nil { 111 return nil, err 112 } 113 114 //Trigger AUnit run 115 var resp *http.Response 116 117 var body = []byte(bodyString) 118 log.Entry().Debugf("Request Body: %s", bodyString) 119 details.URL = abapEndpoint + "/sap/bc/adt/api/abapunit/runs" 120 resp, err = runAUnit("POST", details, body, client) 121 return resp, err 122 } 123 124 func resolveAUnitConfiguration(config abapEnvironmentRunAUnitTestOptions) (aUnitConfig AUnitConfig, err error) { 125 126 if config.AUnitConfig != "" { 127 // Configuration defaults to AUnitConfig 128 log.Entry().Infof("AUnit Configuration: %s", config.AUnitConfig) 129 result, err := abaputils.ReadConfigFile(config.AUnitConfig) 130 if err != nil { 131 return aUnitConfig, err 132 } 133 err = json.Unmarshal(result, &aUnitConfig) 134 return aUnitConfig, err 135 136 } else if config.Repositories != "" { 137 // Fallback / EasyMode is the Repositories configuration 138 log.Entry().Infof("AUnit Configuration derived from: %s", config.Repositories) 139 repos, err := abaputils.GetRepositories((&abaputils.RepositoriesConfig{Repositories: config.Repositories})) 140 if err != nil { 141 return aUnitConfig, err 142 } 143 for _, repo := range repos { 144 aUnitConfig.ObjectSet.SoftwareComponents = append(aUnitConfig.ObjectSet.SoftwareComponents, SoftwareComponents{Name: repo.Name}) 145 } 146 aUnitConfig.Title = "AUnit Test Run" 147 return aUnitConfig, nil 148 } else { 149 // Fail if no configuration is provided 150 return aUnitConfig, errors.New("No configuration provided - please provide either an AUnit configuration file or a repository configuration file") 151 } 152 } 153 154 func convertAUnitOptions(options *abapEnvironmentRunAUnitTestOptions) abaputils.AbapEnvironmentOptions { 155 subOptions := abaputils.AbapEnvironmentOptions{} 156 157 subOptions.CfAPIEndpoint = options.CfAPIEndpoint 158 subOptions.CfServiceInstance = options.CfServiceInstance 159 subOptions.CfServiceKeyName = options.CfServiceKeyName 160 subOptions.CfOrg = options.CfOrg 161 subOptions.CfSpace = options.CfSpace 162 subOptions.Host = options.Host 163 subOptions.Password = options.Password 164 subOptions.Username = options.Username 165 166 return subOptions 167 } 168 169 func fetchAndPersistAUnitResults(resp *http.Response, details abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, aunitResultFileName string, generateHTML bool) error { 170 var err error 171 var abapEndpoint string 172 abapEndpoint = details.URL 173 location := resp.Header.Get("Location") 174 details.URL = abapEndpoint + location 175 location, err = pollAUnitRun(details, nil, client) 176 if err == nil { 177 details.URL = abapEndpoint + location 178 resp, err = getAUnitResults("GET", details, nil, client) 179 } 180 //Parse response 181 var body []byte 182 if err == nil { 183 body, err = ioutil.ReadAll(resp.Body) 184 } 185 if err == nil { 186 defer resp.Body.Close() 187 err = persistAUnitResult(body, aunitResultFileName, generateHTML) 188 } 189 if err != nil { 190 return fmt.Errorf("Handling AUnit result failed: %w", err) 191 } 192 return nil 193 } 194 195 func buildAUnitRequestBody(config abapEnvironmentRunAUnitTestOptions) (bodyString string, err error) { 196 197 bodyString = "" 198 AUnitConfig, err := resolveAUnitConfiguration(config) 199 if err != nil { 200 return bodyString, err 201 } 202 203 //Checks before building the XML body 204 if AUnitConfig.Title == "" { 205 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") 206 } 207 if AUnitConfig.Context == "" { 208 AUnitConfig.Context = "ABAP Environment Pipeline" 209 } 210 if reflect.DeepEqual(ObjectSet{}, AUnitConfig.ObjectSet) { 211 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") 212 } 213 214 //Build Options 215 optionsString := buildAUnitOptionsString(AUnitConfig) 216 //Build metadata string 217 metadataString := `<aunit:run title="` + AUnitConfig.Title + `" context="` + AUnitConfig.Context + `" xmlns:aunit="http://www.sap.com/adt/api/aunit">` 218 //Build Object Set 219 objectSetString := buildAUnitObjectSetString(AUnitConfig) 220 221 bodyString += `<?xml version="1.0" encoding="UTF-8"?>` + metadataString + optionsString + objectSetString + `</aunit:run>` 222 223 return bodyString, nil 224 } 225 226 func runAUnit(requestType string, details abaputils.ConnectionDetailsHTTP, body []byte, client piperhttp.Sender) (*http.Response, error) { 227 228 log.Entry().WithField("ABAP endpoint: ", details.URL).Info("Triggering AUnit run") 229 230 header := make(map[string][]string) 231 header["X-Csrf-Token"] = []string{details.XCsrfToken} 232 header["Content-Type"] = []string{"application/vnd.sap.adt.api.abapunit.run.v1+xml; charset=utf-8;"} 233 234 req, err := client.SendRequest(requestType, details.URL, bytes.NewBuffer(body), header, nil) 235 if err != nil { 236 return req, fmt.Errorf("Triggering AUnit run failed: %w", err) 237 } 238 defer req.Body.Close() 239 return req, err 240 } 241 242 func buildAUnitOptionsString(AUnitConfig AUnitConfig) (optionsString string) { 243 244 optionsString += `<aunit:options>` 245 if AUnitConfig.Options.Measurements != "" { 246 optionsString += `<aunit:measurements type="` + AUnitConfig.Options.Measurements + `"/>` 247 } else { 248 optionsString += `<aunit:measurements type="none"/>` 249 } 250 //We assume there must be one scope configured 251 optionsString += `<aunit:scope` 252 if AUnitConfig.Options.Scope.OwnTests != nil { 253 optionsString += ` ownTests="` + fmt.Sprintf("%v", *AUnitConfig.Options.Scope.OwnTests) + `"` 254 } else { 255 optionsString += ` ownTests="true"` 256 } 257 if AUnitConfig.Options.Scope.ForeignTests != nil { 258 optionsString += ` foreignTests="` + fmt.Sprintf("%v", *AUnitConfig.Options.Scope.ForeignTests) + `"` 259 } else { 260 optionsString += ` foreignTests="true"` 261 } 262 //We assume there must be one riskLevel configured 263 optionsString += `/><aunit:riskLevel` 264 if AUnitConfig.Options.RiskLevel.Harmless != nil { 265 optionsString += ` harmless="` + fmt.Sprintf("%v", *AUnitConfig.Options.RiskLevel.Harmless) + `"` 266 } else { 267 optionsString += ` harmless="true"` 268 } 269 if AUnitConfig.Options.RiskLevel.Dangerous != nil { 270 optionsString += ` dangerous="` + fmt.Sprintf("%v", *AUnitConfig.Options.RiskLevel.Dangerous) + `"` 271 } else { 272 optionsString += ` dangerous="true"` 273 } 274 if AUnitConfig.Options.RiskLevel.Critical != nil { 275 optionsString += ` critical="` + fmt.Sprintf("%v", *AUnitConfig.Options.RiskLevel.Critical) + `"` 276 } else { 277 optionsString += ` critical="true"` 278 } 279 //We assume there must be one duration time configured 280 optionsString += `/><aunit:duration` 281 if AUnitConfig.Options.Duration.Short != nil { 282 optionsString += ` short="` + fmt.Sprintf("%v", *AUnitConfig.Options.Duration.Short) + `"` 283 } else { 284 optionsString += ` short="true"` 285 } 286 if AUnitConfig.Options.Duration.Medium != nil { 287 optionsString += ` medium="` + fmt.Sprintf("%v", *AUnitConfig.Options.Duration.Medium) + `"` 288 } else { 289 optionsString += ` medium="true"` 290 } 291 if AUnitConfig.Options.Duration.Long != nil { 292 optionsString += ` long="` + fmt.Sprintf("%v", *AUnitConfig.Options.Duration.Long) + `"` 293 } else { 294 optionsString += ` long="true"` 295 } 296 optionsString += `/></aunit:options>` 297 return optionsString 298 } 299 300 func writeObjectSetProperties(set MultiPropertySet) (objectSetString string) { 301 for _, packages := range set.PackageNames { 302 objectSetString += `<osl:package name="` + packages.Name + `"/>` 303 } 304 for _, objectTypeGroup := range set.ObjectTypeGroups { 305 objectSetString += `<osl:objectTypeGroup name="` + objectTypeGroup.Name + `"/>` 306 } 307 for _, objectType := range set.ObjectTypes { 308 objectSetString += `<osl:objectType name="` + objectType.Name + `"/>` 309 } 310 for _, owner := range set.Owners { 311 objectSetString += `<osl:owner name="` + owner.Name + `"/>` 312 } 313 for _, releaseState := range set.ReleaseStates { 314 objectSetString += `<osl:releaseState value="` + releaseState.Value + `"/>` 315 } 316 for _, version := range set.Versions { 317 objectSetString += `<osl:version value="` + version.Value + `"/>` 318 } 319 for _, applicationComponent := range set.ApplicationComponents { 320 objectSetString += `<osl:applicationComponent name="` + applicationComponent.Name + `"/>` 321 } 322 for _, component := range set.SoftwareComponents { 323 objectSetString += `<osl:softwareComponent name="` + component.Name + `"/>` 324 } 325 for _, transportLayer := range set.TransportLayers { 326 objectSetString += `<osl:transportLayer name="` + transportLayer.Name + `"/>` 327 } 328 for _, language := range set.Languages { 329 objectSetString += `<osl:language value="` + language.Value + `"/>` 330 } 331 for _, sourceSystem := range set.SourceSystems { 332 objectSetString += `<osl:sourceSystem name="` + sourceSystem.Name + `"/>` 333 } 334 return objectSetString 335 } 336 337 func buildAUnitObjectSetString(AUnitConfig AUnitConfig) (objectSetString string) { 338 339 //Build ObjectSets 340 s := AUnitConfig.ObjectSet 341 if s.Type == "" { 342 s.Type = "multiPropertySet" 343 } 344 if s.Type != "multiPropertySet" { 345 log.Entry().Infof("Wrong configuration has been detected: %s has been used. This is currently not supported and this set will not be included in this run. Please check the step documentation for more information", s.Type) 346 } else { 347 objectSetString += `<osl:objectSet xsi:type="` + s.Type + `" xmlns:osl="http://www.sap.com/api/osl" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">` 348 349 if !(reflect.DeepEqual(s.PackageNames, AUnitPackage{})) || !(reflect.DeepEqual(s.SoftwareComponents, SoftwareComponents{})) { 350 //To ensure Scomps and packages can be assigned on this level 351 mps := MultiPropertySet{ 352 PackageNames: s.PackageNames, 353 SoftwareComponents: s.SoftwareComponents, 354 } 355 objectSetString += writeObjectSetProperties(mps) 356 } 357 358 objectSetString += writeObjectSetProperties(s.MultiPropertySet) 359 360 if !(reflect.DeepEqual(s.MultiPropertySet, MultiPropertySet{})) { 361 log.Entry().Info("Wrong configuration has been detected: MultiPropertySet has been used. Please note that there is no official documentation for this usage. Please check the step documentation for more information") 362 } 363 364 for _, t := range s.Set { 365 log.Entry().Infof("Wrong configuration has been detected: %s has been used. This is currently not supported and this set will not be included in this run. Please check the step documentation for more information", t.Type) 366 } 367 objectSetString += `</osl:objectSet>` 368 } 369 return objectSetString 370 } 371 372 func fetchAUnitXcsrfToken(requestType string, details abaputils.ConnectionDetailsHTTP, body []byte, client piperhttp.Sender) (string, error) { 373 374 log.Entry().WithField("ABAP Endpoint: ", details.URL).Debug("Fetching Xcrsf-Token") 375 376 details.URL += "/sap/bc/adt/api/abapunit/runs/00000000000000000000000000000000" 377 details.XCsrfToken = "fetch" 378 header := make(map[string][]string) 379 header["X-Csrf-Token"] = []string{details.XCsrfToken} 380 header["Accept"] = []string{"application/vnd.sap.adt.api.abapunit.run-status.v1+xml"} 381 req, err := client.SendRequest(requestType, details.URL, bytes.NewBuffer(body), header, nil) 382 if err != nil { 383 return "", fmt.Errorf("Fetching Xcsrf-Token failed: %w", err) 384 } 385 defer req.Body.Close() 386 387 token := req.Header.Get("X-Csrf-Token") 388 return token, err 389 } 390 391 func pollAUnitRun(details abaputils.ConnectionDetailsHTTP, body []byte, client piperhttp.Sender) (string, error) { 392 393 log.Entry().WithField("ABAP endpoint", details.URL).Info("Polling AUnit run status") 394 395 for { 396 resp, err := getHTTPResponseAUnitRun("GET", details, nil, client) 397 if err != nil { 398 return "", fmt.Errorf("Getting HTTP response failed: %w", err) 399 } 400 bodyText, err := ioutil.ReadAll(resp.Body) 401 if err != nil { 402 return "", fmt.Errorf("Reading response body failed: %w", err) 403 } 404 x := new(AUnitRun) 405 xml.Unmarshal(bodyText, &x) 406 407 log.Entry().Infof("Current polling status: %s", x.Progress.Status) 408 if x.Progress.Status == "Not Created" { 409 return "", err 410 } 411 if x.Progress.Status == "Completed" || x.Progress.Status == "FINISHED" { 412 return x.Link.Href, err 413 } 414 if x.Progress.Status == "" { 415 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")) 416 } 417 time.Sleep(10 * time.Second) 418 } 419 } 420 421 func getHTTPResponseAUnitRun(requestType string, details abaputils.ConnectionDetailsHTTP, body []byte, client piperhttp.Sender) (*http.Response, error) { 422 423 log.Entry().WithField("ABAP Endpoint: ", details.URL).Info("Polling AUnit run status") 424 425 header := make(map[string][]string) 426 header["Accept"] = []string{"application/vnd.sap.adt.api.abapunit.run-status.v1+xml"} 427 428 req, err := client.SendRequest(requestType, details.URL, bytes.NewBuffer(body), header, nil) 429 if err != nil { 430 return req, fmt.Errorf("Getting AUnit run status failed: %w", err) 431 } 432 return req, err 433 } 434 435 func getAUnitResults(requestType string, details abaputils.ConnectionDetailsHTTP, body []byte, client piperhttp.Sender) (*http.Response, error) { 436 437 log.Entry().WithField("ABAP Endpoint: ", details.URL).Info("Getting AUnit results") 438 439 header := make(map[string][]string) 440 header["x-csrf-token"] = []string{details.XCsrfToken} 441 header["Accept"] = []string{"application/vnd.sap.adt.api.junit.run-result.v1+xml"} 442 443 req, err := client.SendRequest(requestType, details.URL, bytes.NewBuffer(body), header, nil) 444 if err != nil { 445 return req, fmt.Errorf("Getting AUnit run results failed: %w", err) 446 } 447 return req, err 448 } 449 450 func persistAUnitResult(body []byte, aunitResultFileName string, generateHTML bool) (err error) { 451 if len(body) == 0 { 452 return fmt.Errorf("Parsing AUnit result failed: %w", errors.New("Body is empty, can't parse empty body")) 453 } 454 455 responseBody := string(body) 456 log.Entry().Debugf("Response body: %s", responseBody) 457 458 //Optional checks before writing the Results 459 parsedXML := new(AUnitResult) 460 xml.Unmarshal([]byte(body), &parsedXML) 461 462 //Write Results 463 err = ioutil.WriteFile(aunitResultFileName, body, 0644) 464 if err != nil { 465 return fmt.Errorf("Writing results failed: %w", err) 466 } 467 log.Entry().Infof("Writing %s file was successful.", aunitResultFileName) 468 var reports []piperutils.Path 469 //Return before processing empty AUnit results --> XML can still be written with response body 470 if len(parsedXML.Testsuite.Testcase) == 0 { 471 log.Entry().Infof("There were no AUnit findings from this run. The response has been saved in the %s file", aunitResultFileName) 472 } else { 473 log.Entry().Infof("Please find the results from the respective AUnit run in the %s file or in below logs", aunitResultFileName) 474 //Logging of AUnit findings 475 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) 476 for _, s := range parsedXML.Testsuite.Testcase { 477 //Log Infos for testcase 478 //HTML Procesing can be done here 479 for _, failure := range s.Failure { 480 log.Entry().Debugf("%s, %s: %s found by %s", failure.Type, failure.Message, failure.Message, s.Classname) 481 } 482 for _, skipped := range s.Skipped { 483 log.Entry().Debugf("The following test has been skipped: %s: %s", skipped.Message, skipped.Text) 484 } 485 } 486 if generateHTML == true { 487 htmlString := generateHTMLDocumentAUnit(parsedXML) 488 htmlStringByte := []byte(htmlString) 489 aUnitResultHTMLFileName := strings.Trim(aunitResultFileName, ".xml") + ".html" 490 err = ioutil.WriteFile(aUnitResultHTMLFileName, htmlStringByte, 0644) 491 if err != nil { 492 return fmt.Errorf("Writing HTML document failed: %w", err) 493 } 494 log.Entry().Info("Writing " + aUnitResultHTMLFileName + " file was successful") 495 reports = append(reports, piperutils.Path{Target: aUnitResultHTMLFileName, Name: "ATC Results HTML file", Mandatory: true}) 496 } 497 } 498 //Persist findings afterwards 499 reports = append(reports, piperutils.Path{Target: aunitResultFileName, Name: "AUnit Results", Mandatory: true}) 500 piperutils.PersistReportsAndLinks("abapEnvironmentRunAUnitTest", "", reports, nil) 501 return nil 502 } 503 504 func generateHTMLDocumentAUnit(parsedXML *AUnitResult) (htmlDocumentString string) { 505 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>` 506 507 var htmlDocumentStringError, htmlDocumentStringWarning, htmlDocumentStringInfo, htmlDocumentStringDefault string 508 for _, s := range parsedXML.Testsuite.Testcase { 509 //Add coloring of lines inside of the respective severities, e.g. failures in red 510 trBackgroundColorTestcase := "grey" 511 trBackgroundColorError := "rgba(227,85,0)" 512 trBackgroundColorFailure := "rgba(227,85,0)" 513 trBackgroundColorSkipped := "rgba(255,175,0, 0.2)" 514 if (len(s.Error) != 0) || (len(s.Failure) != 0) || (len(s.Skipped) != 0) { 515 htmlDocumentString += `<tr style="background-color: ` + trBackgroundColorTestcase + `"><td colspan="5"><b>Testcase: ` + s.Name + ` for class ` + s.Classname + `</b></td></tr>` 516 } 517 for _, t := range s.Error { 518 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>` 519 } 520 for _, t := range s.Failure { 521 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>` 522 } 523 for _, t := range s.Skipped { 524 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>` 525 } 526 } 527 if len(parsedXML.Testsuite.Testcase) == 0 { 528 htmlDocumentString += `<tr><td colspan="5"><b>There are no AUnit findings to be displayed</b></td></tr>` 529 } 530 htmlDocumentString += htmlDocumentStringError + htmlDocumentStringWarning + htmlDocumentStringInfo + htmlDocumentStringDefault + `</table></body></html>` 531 532 return htmlDocumentString 533 } 534 535 // 536 // Object Set Structure 537 // 538 539 //AUnitConfig object for parsing yaml config of software components and packages 540 type AUnitConfig struct { 541 Title string `json:"title,omitempty"` 542 Context string `json:"context,omitempty"` 543 Options AUnitOptions `json:"options,omitempty"` 544 ObjectSet ObjectSet `json:"objectset,omitempty"` 545 } 546 547 //AUnitOptions in form of packages and software components to be checked 548 type AUnitOptions struct { 549 Measurements string `json:"measurements,omitempty"` 550 Scope Scope `json:"scope,omitempty"` 551 RiskLevel RiskLevel `json:"risklevel,omitempty"` 552 Duration Duration `json:"duration,omitempty"` 553 } 554 555 //Scope in form of packages and software components to be checked 556 type Scope struct { 557 OwnTests *bool `json:"owntests,omitempty"` 558 ForeignTests *bool `json:"foreigntests,omitempty"` 559 } 560 561 //RiskLevel in form of packages and software components to be checked 562 type RiskLevel struct { 563 Harmless *bool `json:"harmless,omitempty"` 564 Dangerous *bool `json:"dangerous,omitempty"` 565 Critical *bool `json:"critical,omitempty"` 566 } 567 568 //Duration in form of packages and software components to be checked 569 type Duration struct { 570 Short *bool `json:"short,omitempty"` 571 Medium *bool `json:"medium,omitempty"` 572 Long *bool `json:"long,omitempty"` 573 } 574 575 //ObjectSet in form of packages and software components to be checked 576 type ObjectSet struct { 577 PackageNames []AUnitPackage `json:"packages,omitempty"` 578 SoftwareComponents []SoftwareComponents `json:"softwarecomponents,omitempty"` 579 Type string `json:"type,omitempty"` 580 MultiPropertySet MultiPropertySet `json:"multipropertyset,omitempty"` 581 Set []Set `json:"set,omitempty"` 582 } 583 584 //MultiPropertySet that can possibly contain any subsets/object of the OSL 585 type MultiPropertySet struct { 586 Type string `json:"type,omitempty"` 587 PackageNames []AUnitPackage `json:"packages,omitempty"` 588 ObjectTypeGroups []ObjectTypeGroup `json:"objecttypegroups,omitempty"` 589 ObjectTypes []ObjectType `json:"objecttypes,omitempty"` 590 Owners []Owner `json:"owners,omitempty"` 591 ReleaseStates []ReleaseState `json:"releasestates,omitempty"` 592 Versions []Version `json:"versions,omitempty"` 593 ApplicationComponents []ApplicationComponent `json:"applicationcomponents,omitempty"` 594 SoftwareComponents []SoftwareComponents `json:"softwarecomponents,omitempty"` 595 TransportLayers []TransportLayer `json:"transportlayers,omitempty"` 596 Languages []Language `json:"languages,omitempty"` 597 SourceSystems []SourceSystem `json:"sourcesystems,omitempty"` 598 } 599 600 //Set 601 type Set struct { 602 Type string `json:"type,omitempty"` 603 Set []Set `json:"set,omitempty"` 604 PackageSet []AUnitPackageSet `json:"package,omitempty"` 605 FlatObjectSet []AUnitFlatObjectSet `json:"object,omitempty"` 606 ComponentSet []AUnitComponentSet `json:"component,omitempty"` 607 TransportSet []AUnitTransportSet `json:"transport,omitempty"` 608 ObjectTypeSet []AUnitObjectTypeSet `json:"objecttype,omitempty"` 609 } 610 611 //AUnitPackageSet in form of packages to be checked 612 type AUnitPackageSet struct { 613 Name string `json:"name,omitempty"` 614 IncludeSubpackages *bool `json:"includesubpackages,omitempty"` 615 } 616 617 //AUnitFlatObjectSet 618 type AUnitFlatObjectSet struct { 619 Name string `json:"name,omitempty"` 620 Type string `json:"type,omitempty"` 621 } 622 623 //AUnitComponentSet in form of software components to be checked 624 type AUnitComponentSet struct { 625 Name string `json:"name,omitempty"` 626 } 627 628 //AUnitTransportSet in form of transports to be checked 629 type AUnitTransportSet struct { 630 Number string `json:"number,omitempty"` 631 } 632 633 //AUnitObjectTypeSet 634 type AUnitObjectTypeSet struct { 635 Name string `json:"name,omitempty"` 636 } 637 638 //AUnitPackage for MPS 639 type AUnitPackage struct { 640 Name string `json:"name,omitempty"` 641 } 642 643 //ObjectTypeGroup 644 type ObjectTypeGroup struct { 645 Name string `json:"name,omitempty"` 646 } 647 648 //ObjectType 649 type ObjectType struct { 650 Name string `json:"name,omitempty"` 651 } 652 653 //Owner 654 type Owner struct { 655 Name string `json:"name,omitempty"` 656 } 657 658 //ReleaseState 659 type ReleaseState struct { 660 Value string `json:"value,omitempty"` 661 } 662 663 //Version 664 type Version struct { 665 Value string `json:"value,omitempty"` 666 } 667 668 //ApplicationComponent 669 type ApplicationComponent struct { 670 Name string `json:"name,omitempty"` 671 } 672 673 //SoftwareComponents 674 type SoftwareComponents struct { 675 Name string `json:"name,omitempty"` 676 } 677 678 //TransportLayer 679 type TransportLayer struct { 680 Name string `json:"name,omitempty"` 681 } 682 683 //Language 684 type Language struct { 685 Value string `json:"value,omitempty"` 686 } 687 688 //SourceSystem 689 type SourceSystem struct { 690 Name string `json:"name,omitempty"` 691 } 692 693 // 694 // AUnit Run Structure 695 // 696 697 //AUnitRun Object for parsing XML 698 type AUnitRun struct { 699 XMLName xml.Name `xml:"run"` 700 Title string `xml:"title,attr"` 701 Context string `xml:"context,attr"` 702 Progress Progress `xml:"progress"` 703 ExecutedBy ExecutedBy `xml:"executedBy"` 704 Time Time `xml:"time"` 705 Link AUnitLink `xml:"link"` 706 } 707 708 //Progress of AUnit run 709 type Progress struct { 710 Status string `xml:"status,attr"` 711 Percentage string `xml:"percentage,attr"` 712 } 713 714 //ExecutedBy User 715 type ExecutedBy struct { 716 User string `xml:"user,attr"` 717 } 718 719 //Time run was started and finished 720 type Time struct { 721 Started string `xml:"started,attr"` 722 Ended string `xml:"ended,attr"` 723 } 724 725 //AUnitLink containing result locations 726 type AUnitLink struct { 727 Href string `xml:"href,attr"` 728 Rel string `xml:"rel,attr"` 729 Type string `xml:"type,attr"` 730 } 731 732 // 733 // AUnit Result Structure 734 // 735 736 type AUnitResult struct { 737 XMLName xml.Name `xml:"testsuites"` 738 Title string `xml:"title,attr"` 739 System string `xml:"system,attr"` 740 Client string `xml:"client,attr"` 741 ExecutedBy string `xml:"executedBy,attr"` 742 Time string `xml:"time,attr"` 743 Timestamp string `xml:"timestamp,attr"` 744 Failures string `xml:"failures,attr"` 745 Errors string `xml:"errors,attr"` 746 Skipped string `xml:"skipped,attr"` 747 Asserts string `xml:"asserts,attr"` 748 Tests string `xml:"tests,attr"` 749 Testsuite struct { 750 Tests string `xml:"tests,attr"` 751 Asserts string `xml:"asserts,attr"` 752 Skipped string `xml:"skipped,attr"` 753 Errors string `xml:"errors,attr"` 754 Failures string `xml:"failures,attr"` 755 Timestamp string `xml:"timestamp,attr"` 756 Time string `xml:"time,attr"` 757 Hostname string `xml:"hostname,attr"` 758 Package string `xml:"package,attr"` 759 Name string `xml:"name,attr"` 760 Testcase []struct { 761 Asserts string `xml:"asserts,attr"` 762 Time string `xml:"time,attr"` 763 Name string `xml:"name,attr"` 764 Classname string `xml:"classname,attr"` 765 Error []struct { 766 Text string `xml:",chardata"` 767 Type string `xml:"type,attr"` 768 Message string `xml:"message,attr"` 769 } `xml:"error"` 770 Failure []struct { 771 Text string `xml:",chardata"` 772 Type string `xml:"type,attr"` 773 Message string `xml:"message,attr"` 774 } `xml:"failure"` 775 Skipped []struct { 776 Text string `xml:",chardata"` 777 Message string `xml:"message,attr"` 778 } `xml:"skipped"` 779 } `xml:"testcase"` 780 } `xml:"testsuite"` 781 }