From 0e68126a83c57c42b6c48377ab38c3928cbf1bb3 Mon Sep 17 00:00:00 2001 From: Leo Logeart Date: Tue, 12 Sep 2023 12:11:30 +0200 Subject: [PATCH 1/3] Add coverage flag and first coverage file download --- README.md | 1 + constants.go | 2 ++ main.go | 10 +++++++ services.go | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++ step.yml | 15 +++++++++++ structs.go | 13 ++++++++++ util_fns.go | 2 ++ 7 files changed, 116 insertions(+) diff --git a/README.md b/README.md index d16c2f0..2b9966a 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ To add and configure the "BrowserStack App Automate - Espresso" step in Bitrise, | --- | --- | | `$BROWSERSTACK_BUILD_URL` |BrowserStack Dashboard url for the executed build| | `$BROWSERSTACK_BUILD_STATUS`| Status of the executed build. Check out the [test results guide](https://www.browserstack.com/docs/app-automate/espresso/view-test-results) to learn about available status | +| `$BROWSERSTACK_COVERAGE_REPORT`| Path to the coverage report downloaded from Browserstack when coverage is enabled | diff --git a/constants.go b/constants.go index 2478dbf..cb16ab6 100644 --- a/constants.go +++ b/constants.go @@ -8,6 +8,8 @@ const ( APP_AUTOMATE_BUILD_ENDPOINT = "/app-automate/espresso/v2/build" APP_AUTOMATE_BUILD_STATUS_ENDPOINT = "/app-automate/espresso/v2/builds/" APP_AUTOMATE_BUILD_DASHBOARD_URL = "https://app-automate.browserstack.com/dashboard/v2/builds/" + APP_AUTOMATE_SESSIONS_PATH = "/sessions/" + COVERAGE_FILE = "coverage.ec" SAMPLE_APP = "bs://b91841adbf33515fef7a1cca869a9526a86f9a0e" SAMPLE_TEST_SUITE = "bs://535a0932c8a785384b8470ec6166e093cd3b2c5f" diff --git a/main.go b/main.go index 24ab17d..6b195a4 100644 --- a/main.go +++ b/main.go @@ -87,6 +87,16 @@ func main() { if err != nil { failf(err.Error()) } + + use_coverage, _ := strconv.ParseBool(os.Getenv("use_coverage")) + if use_coverage { + var coverage_report, err = getCoverageReport(build_id, username, access_key) + cmd_log_coverage_report, err_coverage_report := exec.Command("bitrise", "envman", "add", "--key", "BROWSERSTACK_COVERAGE_REPORT", "--value", coverage_report).CombinedOutput() + + if err_coverage_report != nil { + fmt.Printf("Failed to expose coverage report with envman, error: %#v | output: %s", err, cmd_log_coverage_report) + } + } cmd_log_build_id, err_build_id := exec.Command("bitrise", "envman", "add", "--key", "BROWSERSTACK_BUILD_URL", "--value", APP_AUTOMATE_BUILD_DASHBOARD_URL+build_parsed_response["build_id"].(string)).CombinedOutput() cmd_log_build_status, err_build_status := exec.Command("bitrise", "envman", "add", "--key", "BROWSERSTACK_BUILD_STATUS", "--value", build_status).CombinedOutput() diff --git a/services.go b/services.go index ebcb064..8337cc5 100644 --- a/services.go +++ b/services.go @@ -212,3 +212,76 @@ func checkBuildStatus(build_id string, username string, access_key string, waitF } } } + +func getCoverageReport(build_id string, username string, access_key string) (string, error) { + + var sessionId, err = getFirstSessionId(build_id, username, access_key) + if err != nil { + return "", err + } + + client := &http.Client{} + req, _ := http.NewRequest("GET", BROWSERSTACK_DOMAIN+APP_AUTOMATE_BUILD_STATUS_ENDPOINT+build_id+APP_AUTOMATE_SESSIONS_PATH+sessionId, nil) + + req.SetBasicAuth(username, access_key) + + req.Header.Set("Content-Type", "application/json") + + res, err := client.Do(req) + + if err != nil { + return "", fmt.Errorf(HTTP_ERROR, err) + } + + defer res.Body.Close() + + // Create the file + out, err := os.Create(COVERAGE_FILE) + if err != nil { + return "",fmt.Errorf(HTTP_ERROR, err) + } + defer out.Close() + + _, err = io.Copy(out, res.Body) + + if err != nil { + return "", fmt.Errorf(HTTP_ERROR, err) + } + return COVERAGE_FILE, nil +} + +func getFirstSessionId(build_id string, username string, access_key string) (string, error) { + + client := &http.Client{} + req, _ := http.NewRequest("GET", BROWSERSTACK_DOMAIN+APP_AUTOMATE_BUILD_STATUS_ENDPOINT+build_id, nil) + + req.SetBasicAuth(username, access_key) + + req.Header.Set("Content-Type", "application/json") + + res, err := client.Do(req) + + if err != nil { + return "", fmt.Errorf(HTTP_ERROR, err) + } + + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + + if err != nil { + return "", fmt.Errorf(HTTP_ERROR, err) + } + + var buildResult Build + unmarshal_err := json.Unmarshal([]byte(body), &buildResult) + + if unmarshal_err != nil { + return "", fmt.Errorf(HTTP_ERROR, err) + } + + var id = buildResult.Devices[0].Sessions[0].Id + + return id, nil + +} \ No newline at end of file diff --git a/step.yml b/step.yml index 65241a2..e2bf85d 100644 --- a/step.yml +++ b/step.yml @@ -260,6 +260,15 @@ inputs: - "true" - "false" category: "Test Configuration" + - use_coverage: "false" + opts: + title: "Coverage" + description: "Generate the code coverage report" + summary: "Generate the code coverage report for projects that have jacoco and test coverage enabled" + value_options: + - "true" + - "false" + category: "Test Configuration" outputs: @@ -274,6 +283,12 @@ outputs: summary: Bs build status description: | Status of the executed build. Check values [here:] (https://www.browserstack.com/docs/app-automate/espresso/view-test-results) + - BROWSERSTACK_COVERAGE_REPORT: + opts: + title: "BrowserStack Coverage Report" + summary: Bs coverage report + description: | + Path to the coverage report downloaded from Browserstack when coverage is enabled diff --git a/structs.go b/structs.go index 9584efc..0e858c5 100644 --- a/structs.go +++ b/structs.go @@ -32,6 +32,7 @@ type BrowserStackPayload struct { Size []string `json:"size,omitempty"` UseMockServer bool `json:"allowDeviceMockServer,omitempty"` UseTestSharding interface{} `json:"shards,omitempty"` + UseCoverage bool `json:"coverage,omitempty"` // Apart from the inputs from UI, these are some more fields which we support. // We've mentioned the type and the json key for these field. @@ -54,3 +55,15 @@ type BrowserStackPayload struct { // LocalIdentifier string `json:"localIdentifier,omitempty"` // IdleTimeout string `json:"idleTimeout,omitempty"` } + +type Build struct { + Devices []Device `json:"devices"` +} + +type Device struct { + Sessions []Session `json:"sessions"` +} + +type Session struct { + Id string `json:"id"` +} \ No newline at end of file diff --git a/util_fns.go b/util_fns.go index f9b4bd0..5321b21 100644 --- a/util_fns.go +++ b/util_fns.go @@ -114,6 +114,7 @@ func createBuildPayload() BrowserStackPayload { clear_app_data, _ := strconv.ParseBool(os.Getenv("clear_app_data")) use_single_runner_invocation, _ := strconv.ParseBool(os.Getenv("use_single_runner_invocation")) use_mock_server, _ := strconv.ParseBool(os.Getenv("use_mock_server")) + use_coverage, _ := strconv.ParseBool(os.Getenv("use_coverage")) sharding_data := TestSharding{} if os.Getenv("use_test_sharding") != "" { @@ -136,6 +137,7 @@ func createBuildPayload() BrowserStackPayload { UseLocal: use_local, ClearAppData: clear_app_data, UseMockServer: use_mock_server, + UseCoverage: use_coverage, } getTestFilters(&payload) From 7e1270ceda21adef2303b8b9a3d03833ea70af7a Mon Sep 17 00:00:00 2001 From: Leo Logeart Date: Tue, 12 Sep 2023 14:57:03 +0200 Subject: [PATCH 2/3] Download all coverage files --- README.md | 2 +- constants.go | 2 +- main.go | 7 +++++-- services.go | 50 +++++++++++++++++++++++++++++++++++--------------- step.yml | 8 ++++---- 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 2b9966a..fe9c969 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ To add and configure the "BrowserStack App Automate - Espresso" step in Bitrise, | --- | --- | | `$BROWSERSTACK_BUILD_URL` |BrowserStack Dashboard url for the executed build| | `$BROWSERSTACK_BUILD_STATUS`| Status of the executed build. Check out the [test results guide](https://www.browserstack.com/docs/app-automate/espresso/view-test-results) to learn about available status | -| `$BROWSERSTACK_COVERAGE_REPORT`| Path to the coverage report downloaded from Browserstack when coverage is enabled | +| `$BROWSERSTACK_COVERAGE_REPORTS`| Path to the coverage report downloaded from Browserstack when coverage is enabled | diff --git a/constants.go b/constants.go index cb16ab6..9c1cd2a 100644 --- a/constants.go +++ b/constants.go @@ -9,7 +9,7 @@ const ( APP_AUTOMATE_BUILD_STATUS_ENDPOINT = "/app-automate/espresso/v2/builds/" APP_AUTOMATE_BUILD_DASHBOARD_URL = "https://app-automate.browserstack.com/dashboard/v2/builds/" APP_AUTOMATE_SESSIONS_PATH = "/sessions/" - COVERAGE_FILE = "coverage.ec" + COVERAGE_FOLDER = "browserstack_coverage" SAMPLE_APP = "bs://b91841adbf33515fef7a1cca869a9526a86f9a0e" SAMPLE_TEST_SUITE = "bs://535a0932c8a785384b8470ec6166e093cd3b2c5f" diff --git a/main.go b/main.go index 6b195a4..0f92dd7 100644 --- a/main.go +++ b/main.go @@ -90,8 +90,11 @@ func main() { use_coverage, _ := strconv.ParseBool(os.Getenv("use_coverage")) if use_coverage { - var coverage_report, err = getCoverageReport(build_id, username, access_key) - cmd_log_coverage_report, err_coverage_report := exec.Command("bitrise", "envman", "add", "--key", "BROWSERSTACK_COVERAGE_REPORT", "--value", coverage_report).CombinedOutput() + err = getCoverageReports(build_id, username, access_key) + if err != nil { + fmt.Printf("Failed to get coverage reports, error: %#v", err) + } + cmd_log_coverage_report, err_coverage_report := exec.Command("bitrise", "envman", "add", "--key", "BROWSERSTACK_COVERAGE_REPORTS", "--value", COVERAGE_FOLDER).CombinedOutput() if err_coverage_report != nil { fmt.Printf("Failed to expose coverage report with envman, error: %#v | output: %s", err, cmd_log_coverage_report) diff --git a/services.go b/services.go index 8337cc5..b6f1e36 100644 --- a/services.go +++ b/services.go @@ -213,15 +213,27 @@ func checkBuildStatus(build_id string, username string, access_key string, waitF } } -func getCoverageReport(build_id string, username string, access_key string) (string, error) { +func getCoverageReports(build_id string, username string, access_key string) (error) { - var sessionId, err = getFirstSessionId(build_id, username, access_key) + var sessionIds, err = getSessionIds(build_id, username, access_key) if err != nil { - return "", err + return err } + if err := os.Mkdir(COVERAGE_FOLDER, os.ModePerm); err != nil { + return err + } + for _, session := range sessionIds { + err = getCoverageReport(build_id, session, username, access_key) + if err != nil { + return err + } + } + return nil +} +func getCoverageReport(build_id string, sessionId string, username string, access_key string) (error) { client := &http.Client{} - req, _ := http.NewRequest("GET", BROWSERSTACK_DOMAIN+APP_AUTOMATE_BUILD_STATUS_ENDPOINT+build_id+APP_AUTOMATE_SESSIONS_PATH+sessionId, nil) + req, _ := http.NewRequest("GET", BROWSERSTACK_DOMAIN+APP_AUTOMATE_BUILD_STATUS_ENDPOINT+build_id+APP_AUTOMATE_SESSIONS_PATH+sessionId+"/coverage", nil) req.SetBasicAuth(username, access_key) @@ -230,28 +242,32 @@ func getCoverageReport(build_id string, username string, access_key string) (str res, err := client.Do(req) if err != nil { - return "", fmt.Errorf(HTTP_ERROR, err) + return fmt.Errorf(HTTP_ERROR, err) } defer res.Body.Close() // Create the file - out, err := os.Create(COVERAGE_FILE) + var filePath = COVERAGE_FOLDER+"/"+sessionId+"-coverage.ec" + out, err := os.Create(filePath) if err != nil { - return "",fmt.Errorf(HTTP_ERROR, err) + return fmt.Errorf(HTTP_ERROR, err) } defer out.Close() _, err = io.Copy(out, res.Body) if err != nil { - return "", fmt.Errorf(HTTP_ERROR, err) + return fmt.Errorf(HTTP_ERROR, err) } - return COVERAGE_FILE, nil + // Download all coverage files (may not be android), document the output variable + log.Println("Coverage file saved : ", filePath) + return nil } -func getFirstSessionId(build_id string, username string, access_key string) (string, error) { +func getSessionIds(build_id string, username string, access_key string) ([]string, error) { + var ids = make([]string, 0) client := &http.Client{} req, _ := http.NewRequest("GET", BROWSERSTACK_DOMAIN+APP_AUTOMATE_BUILD_STATUS_ENDPOINT+build_id, nil) @@ -262,7 +278,7 @@ func getFirstSessionId(build_id string, username string, access_key string) (str res, err := client.Do(req) if err != nil { - return "", fmt.Errorf(HTTP_ERROR, err) + return ids, fmt.Errorf(HTTP_ERROR, err) } defer res.Body.Close() @@ -270,18 +286,22 @@ func getFirstSessionId(build_id string, username string, access_key string) (str body, err := ioutil.ReadAll(res.Body) if err != nil { - return "", fmt.Errorf(HTTP_ERROR, err) + return ids, fmt.Errorf(HTTP_ERROR, err) } var buildResult Build unmarshal_err := json.Unmarshal([]byte(body), &buildResult) if unmarshal_err != nil { - return "", fmt.Errorf(HTTP_ERROR, err) + return ids, fmt.Errorf(HTTP_ERROR, err) } - var id = buildResult.Devices[0].Sessions[0].Id + for _, device := range buildResult.Devices { + for _, session := range device.Sessions { + ids = append(ids, session.Id) + } + } - return id, nil + return ids, nil } \ No newline at end of file diff --git a/step.yml b/step.yml index e2bf85d..14275c8 100644 --- a/step.yml +++ b/step.yml @@ -283,12 +283,12 @@ outputs: summary: Bs build status description: | Status of the executed build. Check values [here:] (https://www.browserstack.com/docs/app-automate/espresso/view-test-results) - - BROWSERSTACK_COVERAGE_REPORT: + - BROWSERSTACK_COVERAGE_REPORTS: opts: - title: "BrowserStack Coverage Report" - summary: Bs coverage report + title: "BrowserStack Coverage Report Folder" + summary: Bs coverage report folder description: | - Path to the coverage report downloaded from Browserstack when coverage is enabled + Path to the coverage report folder downloaded from Browserstack when coverage is enabled From b97bcb1c286fbf6553c2283c33f65921ffadcaaf Mon Sep 17 00:00:00 2001 From: Leo Logeart Date: Mon, 6 Nov 2023 09:54:14 +0100 Subject: [PATCH 3/3] Fix formatting --- main.go | 2 +- services.go | 16 ++++++++-------- structs.go | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index 0f92dd7..8d59293 100644 --- a/main.go +++ b/main.go @@ -87,7 +87,7 @@ func main() { if err != nil { failf(err.Error()) } - + use_coverage, _ := strconv.ParseBool(os.Getenv("use_coverage")) if use_coverage { err = getCoverageReports(build_id, username, access_key) diff --git a/services.go b/services.go index b6f1e36..1872c98 100644 --- a/services.go +++ b/services.go @@ -213,25 +213,25 @@ func checkBuildStatus(build_id string, username string, access_key string, waitF } } -func getCoverageReports(build_id string, username string, access_key string) (error) { +func getCoverageReports(build_id string, username string, access_key string) error { var sessionIds, err = getSessionIds(build_id, username, access_key) if err != nil { return err } - if err := os.Mkdir(COVERAGE_FOLDER, os.ModePerm); err != nil { + if err := os.Mkdir(COVERAGE_FOLDER, os.ModePerm); err != nil { return err - } + } for _, session := range sessionIds { - err = getCoverageReport(build_id, session, username, access_key) + err = getCoverageReport(build_id, session, username, access_key) if err != nil { return err } - } + } return nil } -func getCoverageReport(build_id string, sessionId string, username string, access_key string) (error) { +func getCoverageReport(build_id string, sessionId string, username string, access_key string) error { client := &http.Client{} req, _ := http.NewRequest("GET", BROWSERSTACK_DOMAIN+APP_AUTOMATE_BUILD_STATUS_ENDPOINT+build_id+APP_AUTOMATE_SESSIONS_PATH+sessionId+"/coverage", nil) @@ -248,7 +248,7 @@ func getCoverageReport(build_id string, sessionId string, username string, acces defer res.Body.Close() // Create the file - var filePath = COVERAGE_FOLDER+"/"+sessionId+"-coverage.ec" + var filePath = COVERAGE_FOLDER + "/" + sessionId + "-coverage.ec" out, err := os.Create(filePath) if err != nil { return fmt.Errorf(HTTP_ERROR, err) @@ -304,4 +304,4 @@ func getSessionIds(build_id string, username string, access_key string) ([]strin return ids, nil -} \ No newline at end of file +} diff --git a/structs.go b/structs.go index 0e858c5..8975685 100644 --- a/structs.go +++ b/structs.go @@ -66,4 +66,4 @@ type Device struct { type Session struct { Id string `json:"id"` -} \ No newline at end of file +}