From 9d200ab86d61c554b4d26f571b4ae66870c6f169 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 13:46:12 +0100 Subject: [PATCH 01/43] Initial commit for `databrickslabs/sandbox/acceptance` This PR adds integration for github actions --- .github/workflows/acceptance-go-libs.yml | 25 ++++++++++++++++++++++++ acceptance/action.yml | 21 ++++++++++++++++++++ acceptance/go.mod | 5 +++++ acceptance/go.sum | 2 ++ acceptance/main.go | 21 ++++++++++++++++++++ go.work | 1 + 6 files changed, 75 insertions(+) create mode 100644 .github/workflows/acceptance-go-libs.yml create mode 100644 acceptance/action.yml create mode 100644 acceptance/go.mod create mode 100644 acceptance/go.sum create mode 100644 acceptance/main.go diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml new file mode 100644 index 00000000..f7aeb741 --- /dev/null +++ b/.github/workflows/acceptance-go-libs.yml @@ -0,0 +1,25 @@ +name: acceptance + +on: + pull_request: + types: [opened, synchronize] + +permissions: + id-token: write + contents: read + pull-requests: write + +jobs: + acceptance: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.5.0 + + # - uses: azure/login@v1 + # with: + # client-id: ${{ var.ARM_CLIENT_ID }} + # tenant-id: ${{ secrets.ARM_TENANT_ID }} + # subscription-id: ${{ secrets.ARM_SUBSCRIPTION_ID }} + + - uses: databrickslabs/sandbox/acceptance@gh-action diff --git a/acceptance/action.yml b/acceptance/action.yml new file mode 100644 index 00000000..6195ff22 --- /dev/null +++ b/acceptance/action.yml @@ -0,0 +1,21 @@ +--- +name: 'Databricks Labs Acceptance Suite' +description: 'Run relevant acceptance suite' +outputs: + owner: + description: 'Sample output' + value: ${{ steps.invoke.outputs.sample }} + +runs: + using: 'composite' + steps: + - name: Download Go modules + run: go mod download + working-directory: ${{ github.action_path }} + shell: bash + + - name: Run action + id: invoke + run: go run main.go + working-directory: ${{ github.action_path }} + shell: bash \ No newline at end of file diff --git a/acceptance/go.mod b/acceptance/go.mod new file mode 100644 index 00000000..8997c63c --- /dev/null +++ b/acceptance/go.mod @@ -0,0 +1,5 @@ +module github.com/databrickslabs/sandbox/acceptance + +go 1.21.0 + +require github.com/sethvargo/go-githubactions v1.2.0 diff --git a/acceptance/go.sum b/acceptance/go.sum new file mode 100644 index 00000000..242e7883 --- /dev/null +++ b/acceptance/go.sum @@ -0,0 +1,2 @@ +github.com/sethvargo/go-githubactions v1.2.0 h1:Gbr36trCAj6uq7Rx1DolY1NTIg0wnzw3/N5WHdKIjME= +github.com/sethvargo/go-githubactions v1.2.0/go.mod h1:7/4WeHgYfSz9U5vwuToCK9KPnELVHAhGtRwLREOQV80= diff --git a/acceptance/main.go b/acceptance/main.go new file mode 100644 index 00000000..9185edf4 --- /dev/null +++ b/acceptance/main.go @@ -0,0 +1,21 @@ +package main + +import "github.com/sethvargo/go-githubactions" + +func main() { + a := githubactions.New() + + a.Debugf("this is debug") + a.Infof("This is info") + a.Noticef("This is notice") + a.Warningf("this is warning") + a.Errorf("this is error") + + m := map[string]string{ + "file": "app.go", + "line": "100", + } + a.WithFieldsMap(m).Errorf("an error message") + + a.SetOutput("sample", "foo") +} diff --git a/go.work b/go.work index 6d56a9be..0561bdca 100644 --- a/go.work +++ b/go.work @@ -4,4 +4,5 @@ use ( ./go-libs ./metascan ./runtime-packages + ./acceptance ) From 38c13d87a3a5250a9e0eec8717df87f717cc70dd Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 13:54:49 +0100 Subject: [PATCH 02/43] .. --- acceptance/action.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/acceptance/action.yml b/acceptance/action.yml index 6195ff22..54b42107 100644 --- a/acceptance/action.yml +++ b/acceptance/action.yml @@ -1,6 +1,12 @@ --- name: 'Databricks Labs Acceptance Suite' description: 'Run relevant acceptance suite' +author: Serge Smertin +inputs: + go-version: + description: 'Go version' + required: false + default: 1.21.0 outputs: owner: description: 'Sample output' @@ -9,6 +15,11 @@ outputs: runs: using: 'composite' steps: + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: ${{ inputs.go-version }} + - name: Download Go modules run: go mod download working-directory: ${{ github.action_path }} From b399f2565d3ca18358c3b59d148fb021f1fe0270 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 14:11:18 +0100 Subject: [PATCH 03/43] .. --- acceptance/action.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/acceptance/action.yml b/acceptance/action.yml index 54b42107..ff01f156 100644 --- a/acceptance/action.yml +++ b/acceptance/action.yml @@ -8,22 +8,30 @@ inputs: required: false default: 1.21.0 outputs: - owner: + sample: description: 'Sample output' value: ${{ steps.invoke.outputs.sample }} runs: using: 'composite' steps: + - name: log grounds + run: echo "::group::Preparing..." + shell: bash - name: Setup Go uses: actions/setup-go@v4 with: go-version: ${{ inputs.go-version }} + go-version-file: ${{ github.action_path }}/go.mod - name: Download Go modules run: go mod download working-directory: ${{ github.action_path }} shell: bash + + - name: log grounds + run: echo "::endgroup::" + shell: bash - name: Run action id: invoke From d7c8e1b97172964a22c0a6be2908e24f29f8e591 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 14:17:30 +0100 Subject: [PATCH 04/43] .. --- acceptance/action.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/acceptance/action.yml b/acceptance/action.yml index ff01f156..b35aec57 100644 --- a/acceptance/action.yml +++ b/acceptance/action.yml @@ -15,23 +15,15 @@ outputs: runs: using: 'composite' steps: - - name: log grounds - run: echo "::group::Preparing..." - shell: bash - name: Setup Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: ${{ inputs.go-version }} go-version-file: ${{ github.action_path }}/go.mod - name: Download Go modules run: go mod download working-directory: ${{ github.action_path }} shell: bash - - - name: log grounds - run: echo "::endgroup::" - shell: bash - name: Run action id: invoke From ed828ac57b76c50ab56b394533cc83709184f596 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 14:34:57 +0100 Subject: [PATCH 05/43] .. --- acceptance/action.yml | 1 + acceptance/main.go | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/acceptance/action.yml b/acceptance/action.yml index b35aec57..f54f3af3 100644 --- a/acceptance/action.yml +++ b/acceptance/action.yml @@ -15,6 +15,7 @@ outputs: runs: using: 'composite' steps: + # TODO: publish as docker image, eventually - name: Setup Go uses: actions/setup-go@v5 with: diff --git a/acceptance/main.go b/acceptance/main.go index 9185edf4..60becbac 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -5,8 +5,14 @@ import "github.com/sethvargo/go-githubactions" func main() { a := githubactions.New() + ghc, err := githubactions.Context() + if err != nil { + a.Errorf(err.Error()) + } + + org, repo := ghc.Repo() a.Debugf("this is debug") - a.Infof("This is info") + a.Infof("Org: %s, Repo: %s, Actor: %s, Workflow: %s, Ref: %s, ref name: %s, event: %v", org, repo, ghc.Actor, ghc.Workflow, ghc.Ref, ghc.RefName, ghc.Event) a.Noticef("This is notice") a.Warningf("this is warning") a.Errorf("this is error") @@ -18,4 +24,22 @@ func main() { a.WithFieldsMap(m).Errorf("an error message") a.SetOutput("sample", "foo") + + a.AddStepSummary(` +## Heading + +- :rocket: +- :moon: +`) + + if err := a.AddStepSummaryTemplate(` +## Heading + +- {{.Input}} +- :moon: +`, map[string]string{ + "Input": ":rocket:", + }); err != nil { + a.Errorf(err.Error()) + } } From 6b0d153245b83be8741b3484495a98234a5989fc Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 14:39:32 +0100 Subject: [PATCH 06/43] .. --- .github/workflows/acceptance-go-libs.yml | 6 ++++-- acceptance/main.go | 13 +++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index f7aeb741..6a0e071a 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -14,7 +14,8 @@ jobs: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.5.0 + - name: Checkout + uses: actions/checkout@v4 # - uses: azure/login@v1 # with: @@ -22,4 +23,5 @@ jobs: # tenant-id: ${{ secrets.ARM_TENANT_ID }} # subscription-id: ${{ secrets.ARM_SUBSCRIPTION_ID }} - - uses: databrickslabs/sandbox/acceptance@gh-action + - name: Acceptance + uses: databrickslabs/sandbox/acceptance@gh-action diff --git a/acceptance/main.go b/acceptance/main.go index 60becbac..498dcf1e 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -1,6 +1,10 @@ package main -import "github.com/sethvargo/go-githubactions" +import ( + "encoding/json" + + "github.com/sethvargo/go-githubactions" +) func main() { a := githubactions.New() @@ -12,7 +16,12 @@ func main() { org, repo := ghc.Repo() a.Debugf("this is debug") - a.Infof("Org: %s, Repo: %s, Actor: %s, Workflow: %s, Ref: %s, ref name: %s, event: %v", org, repo, ghc.Actor, ghc.Workflow, ghc.Ref, ghc.RefName, ghc.Event) + a.Infof("Org: %s, Repo: %s, Actor: %s, Workflow: %s, Ref: %s, ref name: %s", org, repo, ghc.Actor, ghc.Workflow, ghc.Ref, ghc.RefName) + raw, err := json.MarshalIndent(ghc.Event, "", " ") + if err != nil { + a.Errorf(err.Error()) + } + a.Infof("event: %s", string(raw)) a.Noticef("This is notice") a.Warningf("this is warning") a.Errorf("this is error") From 067382838fc11a74fe6a38fa2eddfa0e874c4451 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 16:21:54 +0100 Subject: [PATCH 07/43] .. --- acceptance/main.go | 18 ++++++++++++++++++ go-libs/github/github.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/acceptance/main.go b/acceptance/main.go index 498dcf1e..68ed43fb 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -1,8 +1,10 @@ package main import ( + "context" "encoding/json" + "github.com/databrickslabs/sandbox/go-libs/github" "github.com/sethvargo/go-githubactions" ) @@ -14,6 +16,8 @@ func main() { a.Errorf(err.Error()) } + gh := github.NewClient(&github.GitHubConfig{}) + org, repo := ghc.Repo() a.Debugf("this is debug") a.Infof("Org: %s, Repo: %s, Actor: %s, Workflow: %s, Ref: %s, ref name: %s", org, repo, ghc.Actor, ghc.Workflow, ghc.Ref, ghc.RefName) @@ -22,6 +26,20 @@ func main() { a.Errorf(err.Error()) } a.Infof("event: %s", string(raw)) + + var event struct { + PullRequest *github.PullRequest `json:"pull_request"` + } + err = json.Unmarshal(raw, &event) + if err != nil { + a.Errorf(err.Error()) + } + ctx := context.Background() + _, err = gh.CreateIssueComment(ctx, org, repo, event.PullRequest.Number, "Test from acceptance action") + if err != nil { + a.Errorf(err.Error()) + } + a.Noticef("This is notice") a.Warningf("this is warning") a.Errorf("this is error") diff --git a/go-libs/github/github.go b/go-libs/github/github.go index c20723e8..ee76b110 100644 --- a/go-libs/github/github.go +++ b/go-libs/github/github.go @@ -221,6 +221,7 @@ func (c *GitHubClient) GetPullRequestCommits(ctx context.Context, org, repo stri } // GetIssueComments returns comments for a number, which can be issue # or pr # +// Every pull request is an issue, but not every issue is a pull request. func (c *GitHubClient) GetIssueComments(ctx context.Context, org, repo string, number int) listing.Iterator[IssueComment] { path := fmt.Sprintf("%s/repos/%s/%s/issues/%d/comments", gitHubAPI, org, repo, number) return paginator[IssueComment, int64](ctx, c, path, &PageOptions{}, func(ic IssueComment) int64 { @@ -228,6 +229,39 @@ func (c *GitHubClient) GetIssueComments(ctx context.Context, org, repo string, n }) } +func (c *GitHubClient) CreateIssueComment(ctx context.Context, org, repo string, number int, body string) (*IssueComment, error) { + path := fmt.Sprintf("%s/repos/%s/%s/issues/%d/comments", gitHubAPI, org, repo, number) + var res IssueComment + err := c.api.Do(ctx, "POST", path, + httpclient.WithRequestData(map[string]string{ + "body": body, + }), + httpclient.WithResponseUnmarshal(&res)) + if err != nil { + return nil, err + } + return &res, nil +} + +func (c *GitHubClient) UpdateIssueComment(ctx context.Context, org, repo string, commentID int, body string) (*IssueComment, error) { + path := fmt.Sprintf("%s/repos/%s/%s/issues/comments/%d", gitHubAPI, org, repo, commentID) + var res IssueComment + err := c.api.Do(ctx, "PATCH", path, + httpclient.WithRequestData(map[string]string{ + "body": body, + }), + httpclient.WithResponseUnmarshal(&res)) + if err != nil { + return nil, err + } + return &res, nil +} + +func (c *GitHubClient) DeleteIssueComment(ctx context.Context, org, repo string, commentID int) error { + path := fmt.Sprintf("%s/repos/%s/%s/issues/comments/%d", gitHubAPI, org, repo, commentID) + return c.api.Do(ctx, "DELETE", path) +} + func (c *GitHubClient) GetRepoTrafficClones(ctx context.Context, org, repo string) ([]ClonesStat, error) { path := fmt.Sprintf("%s/repos/%s/%s/traffic/clones", gitHubAPI, org, repo) var res struct { From 844cdc5bc498670776da86556ae1a7dd20443e99 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 16:32:41 +0100 Subject: [PATCH 08/43] .. --- acceptance/main.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/acceptance/main.go b/acceptance/main.go index 68ed43fb..89e7badd 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -16,7 +16,13 @@ func main() { a.Errorf(err.Error()) } - gh := github.NewClient(&github.GitHubConfig{}) + gh := github.NewClient(&github.GitHubConfig{ + GitHubTokenSource: github.GitHubTokenSource{ + Pat: a.Getenv("GITHUB_TOKEN"), + }, + }) + // also - there's OIDC integration: + // a.GetIDToken(ctx, "api://AzureADTokenExchange") org, repo := ghc.Repo() a.Debugf("this is debug") From 98f07859f111f3576da3cf46257e25fc02652d1e Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 16:45:42 +0100 Subject: [PATCH 09/43] .. --- .github/workflows/acceptance-go-libs.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index 6a0e071a..edf4a26c 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -22,6 +22,14 @@ jobs: # client-id: ${{ var.ARM_CLIENT_ID }} # tenant-id: ${{ secrets.ARM_TENANT_ID }} # subscription-id: ${{ secrets.ARM_SUBSCRIPTION_ID }} + + - name: Dump github context + run: echo "$GITHUB_CONTEXT" + env: + GITHUB_CONTEXT: ${{ toJSON(github) }} + + - name: Dump env + run: env | sort - name: Acceptance uses: databrickslabs/sandbox/acceptance@gh-action From 0c7ba83395e31feb5093da02102aae065e5a079a Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 16:49:28 +0100 Subject: [PATCH 10/43] .. --- acceptance/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/acceptance/main.go b/acceptance/main.go index 89e7badd..eb80d5db 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -18,7 +18,8 @@ func main() { gh := github.NewClient(&github.GitHubConfig{ GitHubTokenSource: github.GitHubTokenSource{ - Pat: a.Getenv("GITHUB_TOKEN"), + // Pat: a.Getenv("GITHUB_TOKEN"), + Pat: ghc.Event["token"].(string), }, }) // also - there's OIDC integration: From 290f23b2d4663d08c3a974f0023610de71bd47ac Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 17:12:33 +0100 Subject: [PATCH 11/43] .. --- .github/workflows/acceptance-go-libs.yml | 2 + acceptance/main.go | 102 ++++++++++++----------- 2 files changed, 54 insertions(+), 50 deletions(-) diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index edf4a26c..c61ceb51 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -33,3 +33,5 @@ jobs: - name: Acceptance uses: databrickslabs/sandbox/acceptance@gh-action + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/acceptance/main.go b/acceptance/main.go index eb80d5db..98b6d948 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -2,78 +2,80 @@ package main import ( "context" + "encoding/base64" "encoding/json" + "fmt" "github.com/databrickslabs/sandbox/go-libs/github" "github.com/sethvargo/go-githubactions" ) -func main() { - a := githubactions.New() - - ghc, err := githubactions.Context() +func New(opts ...githubactions.Option) (*acceptance, error) { + a := githubactions.New(opts...) + context, err := a.Context() if err != nil { - a.Errorf(err.Error()) + return nil, err } + return &acceptance{ + action: a, + context: context, + gh: github.NewClient(&github.GitHubConfig{ + GitHubTokenSource: github.GitHubTokenSource{ + // TODO: autodetect + Pat: a.Getenv("GITHUB_TOKEN"), + }, + }), + }, nil +} - gh := github.NewClient(&github.GitHubConfig{ - GitHubTokenSource: github.GitHubTokenSource{ - // Pat: a.Getenv("GITHUB_TOKEN"), - Pat: ghc.Event["token"].(string), - }, - }) - // also - there's OIDC integration: - // a.GetIDToken(ctx, "api://AzureADTokenExchange") +type acceptance struct { + action *githubactions.Action + context *githubactions.GitHubContext + gh *github.GitHubClient +} - org, repo := ghc.Repo() - a.Debugf("this is debug") - a.Infof("Org: %s, Repo: %s, Actor: %s, Workflow: %s, Ref: %s, ref name: %s", org, repo, ghc.Actor, ghc.Workflow, ghc.Ref, ghc.RefName) - raw, err := json.MarshalIndent(ghc.Event, "", " ") +func (a *acceptance) currentPullRequest(ctx context.Context) (*github.PullRequest, error) { + raw, err := json.MarshalIndent(a.context.Event, "", " ") if err != nil { - a.Errorf(err.Error()) + return nil, fmt.Errorf("marshall: %w", err) } - a.Infof("event: %s", string(raw)) - + a.action.Infof("b64: %s", base64.StdEncoding.EncodeToString(raw)) var event struct { PullRequest *github.PullRequest `json:"pull_request"` } err = json.Unmarshal(raw, &event) if err != nil { - a.Errorf(err.Error()) + return nil, fmt.Errorf("unmarshall: %w", err) } - ctx := context.Background() - _, err = gh.CreateIssueComment(ctx, org, repo, event.PullRequest.Number, "Test from acceptance action") + return event.PullRequest, nil +} + +func (a *acceptance) comment(ctx context.Context) error { + pr, err := a.currentPullRequest(ctx) if err != nil { - a.Errorf(err.Error()) + return fmt.Errorf("pull request: %w", err) } - - a.Noticef("This is notice") - a.Warningf("this is warning") - a.Errorf("this is error") - - m := map[string]string{ - "file": "app.go", - "line": "100", + org, repo := a.context.Repo() + _, err = a.gh.CreateIssueComment(ctx, org, repo, pr.Number, "Test from acceptance action") + if err != nil { + return fmt.Errorf("new comment: %w", err) } - a.WithFieldsMap(m).Errorf("an error message") - - a.SetOutput("sample", "foo") - - a.AddStepSummary(` -## Heading - -- :rocket: -- :moon: -`) + return nil +} - if err := a.AddStepSummaryTemplate(` -## Heading +func mainE(ctx context.Context) error { + a, err := New() + if err != nil { + return err + } + return a.comment(ctx) + // also - there's OIDC integration: + // a.GetIDToken(ctx, "api://AzureADTokenExchange") +} -- {{.Input}} -- :moon: -`, map[string]string{ - "Input": ":rocket:", - }); err != nil { - a.Errorf(err.Error()) +func main() { + err := mainE(context.Background()) + if err != nil { + githubactions.Fatalf("failed: %s", err) } } From fb83cfcf9643062eb5e5082f206696af117a41d1 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 17:28:52 +0100 Subject: [PATCH 12/43] .. --- .github/workflows/acceptance-go-libs.yml | 12 ++++++------ acceptance/action.yml | 7 ++++++- acceptance/main.go | 3 ++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index c61ceb51..b2625109 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -23,13 +23,13 @@ jobs: # tenant-id: ${{ secrets.ARM_TENANT_ID }} # subscription-id: ${{ secrets.ARM_SUBSCRIPTION_ID }} - - name: Dump github context - run: echo "$GITHUB_CONTEXT" - env: - GITHUB_CONTEXT: ${{ toJSON(github) }} + # - name: Dump github context + # run: echo "$GITHUB_CONTEXT" + # env: + # GITHUB_CONTEXT: ${{ toJSON(github) }} - - name: Dump env - run: env | sort + # - name: Dump env + # run: env | sort - name: Acceptance uses: databrickslabs/sandbox/acceptance@gh-action diff --git a/acceptance/action.yml b/acceptance/action.yml index f54f3af3..8d190035 100644 --- a/acceptance/action.yml +++ b/acceptance/action.yml @@ -3,6 +3,9 @@ name: 'Databricks Labs Acceptance Suite' description: 'Run relevant acceptance suite' author: Serge Smertin inputs: + # token: + # description: GitHub token to use for commenting on PR + # required: true go-version: description: 'Go version' required: false @@ -30,4 +33,6 @@ runs: id: invoke run: go run main.go working-directory: ${{ github.action_path }} - shell: bash \ No newline at end of file + shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/acceptance/main.go b/acceptance/main.go index 98b6d948..24c4c278 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "os" "github.com/databrickslabs/sandbox/go-libs/github" "github.com/sethvargo/go-githubactions" @@ -39,7 +40,7 @@ func (a *acceptance) currentPullRequest(ctx context.Context) (*github.PullReques if err != nil { return nil, fmt.Errorf("marshall: %w", err) } - a.action.Infof("b64: %s", base64.StdEncoding.EncodeToString(raw)) + fmt.Fprintf(os.Stdout, "b64: %s", base64.StdEncoding.EncodeToString(raw)) var event struct { PullRequest *github.PullRequest `json:"pull_request"` } From 3d7eb0211fe02359ce9bab0b5dcc6aa99acdb7ec Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 17:30:24 +0100 Subject: [PATCH 13/43] .. --- .github/workflows/acceptance-go-libs.yml | 4 ++-- acceptance/action.yml | 8 +++----- acceptance/main.go | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index b2625109..cf648a36 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -33,5 +33,5 @@ jobs: - name: Acceptance uses: databrickslabs/sandbox/acceptance@gh-action - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/acceptance/action.yml b/acceptance/action.yml index 8d190035..39f12c43 100644 --- a/acceptance/action.yml +++ b/acceptance/action.yml @@ -3,9 +3,9 @@ name: 'Databricks Labs Acceptance Suite' description: 'Run relevant acceptance suite' author: Serge Smertin inputs: - # token: - # description: GitHub token to use for commenting on PR - # required: true + token: + description: GitHub token to use for commenting on PR + required: true go-version: description: 'Go version' required: false @@ -34,5 +34,3 @@ runs: run: go run main.go working-directory: ${{ github.action_path }} shell: bash - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/acceptance/main.go b/acceptance/main.go index 24c4c278..5e6cc0bb 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -23,7 +23,7 @@ func New(opts ...githubactions.Option) (*acceptance, error) { gh: github.NewClient(&github.GitHubConfig{ GitHubTokenSource: github.GitHubTokenSource{ // TODO: autodetect - Pat: a.Getenv("GITHUB_TOKEN"), + Pat: a.GetInput("token"), }, }), }, nil From 1652849303691e023e5cd71eee19bc81be55e867 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 17:37:47 +0100 Subject: [PATCH 14/43] .. --- .github/workflows/acceptance-go-libs.yml | 2 +- acceptance/action.yml | 2 +- acceptance/main.go | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index cf648a36..3090038b 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -34,4 +34,4 @@ jobs: - name: Acceptance uses: databrickslabs/sandbox/acceptance@gh-action with: - token: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/acceptance/action.yml b/acceptance/action.yml index 39f12c43..ea9d2ca0 100644 --- a/acceptance/action.yml +++ b/acceptance/action.yml @@ -3,7 +3,7 @@ name: 'Databricks Labs Acceptance Suite' description: 'Run relevant acceptance suite' author: Serge Smertin inputs: - token: + github_token: description: GitHub token to use for commenting on PR required: true go-version: diff --git a/acceptance/main.go b/acceptance/main.go index 5e6cc0bb..b421b632 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -2,10 +2,8 @@ package main import ( "context" - "encoding/base64" "encoding/json" "fmt" - "os" "github.com/databrickslabs/sandbox/go-libs/github" "github.com/sethvargo/go-githubactions" @@ -23,7 +21,7 @@ func New(opts ...githubactions.Option) (*acceptance, error) { gh: github.NewClient(&github.GitHubConfig{ GitHubTokenSource: github.GitHubTokenSource{ // TODO: autodetect - Pat: a.GetInput("token"), + Pat: a.GetInput("github_token"), }, }), }, nil @@ -40,7 +38,7 @@ func (a *acceptance) currentPullRequest(ctx context.Context) (*github.PullReques if err != nil { return nil, fmt.Errorf("marshall: %w", err) } - fmt.Fprintf(os.Stdout, "b64: %s", base64.StdEncoding.EncodeToString(raw)) + // fmt.Fprintf(os.Stdout, "b64: %s", base64.StdEncoding.EncodeToString(raw)) var event struct { PullRequest *github.PullRequest `json:"pull_request"` } @@ -52,6 +50,7 @@ func (a *acceptance) currentPullRequest(ctx context.Context) (*github.PullReques } func (a *acceptance) comment(ctx context.Context) error { + // todo: try a.context.Event["number"].(int) pr, err := a.currentPullRequest(ctx) if err != nil { return fmt.Errorf("pull request: %w", err) From 1a713545a651b12d67eca6f941229de503258920 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 17:40:51 +0100 Subject: [PATCH 15/43] .. --- .github/workflows/acceptance-go-libs.yml | 2 +- acceptance/action.yml | 2 +- acceptance/main.go | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index 3090038b..911acaa0 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -34,4 +34,4 @@ jobs: - name: Acceptance uses: databrickslabs/sandbox/acceptance@gh-action with: - github_token: ${{ secrets.GITHUB_TOKEN }} + xxx: ${{ secrets.GITHUB_TOKEN }} diff --git a/acceptance/action.yml b/acceptance/action.yml index ea9d2ca0..21e28c7e 100644 --- a/acceptance/action.yml +++ b/acceptance/action.yml @@ -3,7 +3,7 @@ name: 'Databricks Labs Acceptance Suite' description: 'Run relevant acceptance suite' author: Serge Smertin inputs: - github_token: + xxx: description: GitHub token to use for commenting on PR required: true go-version: diff --git a/acceptance/main.go b/acceptance/main.go index b421b632..2515128f 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -2,8 +2,10 @@ package main import ( "context" + "encoding/base64" "encoding/json" "fmt" + "os" "github.com/databrickslabs/sandbox/go-libs/github" "github.com/sethvargo/go-githubactions" @@ -15,13 +17,14 @@ func New(opts ...githubactions.Option) (*acceptance, error) { if err != nil { return nil, err } + fmt.Fprintf(os.Stdout, "b64: %s", base64.StdEncoding.EncodeToString([]byte(a.GetInput("xxx"))[:10])) return &acceptance{ action: a, context: context, gh: github.NewClient(&github.GitHubConfig{ GitHubTokenSource: github.GitHubTokenSource{ // TODO: autodetect - Pat: a.GetInput("github_token"), + Pat: a.GetInput("xxx"), }, }), }, nil From 39736b1eb5de51d2886ce40c4a2cbe3c5cc194ec Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 17:44:09 +0100 Subject: [PATCH 16/43] .. --- .github/workflows/acceptance-go-libs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index 911acaa0..c85d043a 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -35,3 +35,5 @@ jobs: uses: databrickslabs/sandbox/acceptance@gh-action with: xxx: ${{ secrets.GITHUB_TOKEN }} + # Workflows that call reusable workflows in the same organization or enterprise can use the inherit keyword to implicitly pass the secrets. + secrets: inherit From 2ab4f8331df3c26d0eccd1734f346cece8ad8a94 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 13 Jan 2024 17:46:30 +0100 Subject: [PATCH 17/43] .. --- .github/workflows/acceptance-go-libs.yml | 5 +---- acceptance/action.yml | 3 --- acceptance/main.go | 10 ++++++---- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index c85d043a..7d48f10c 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -33,7 +33,4 @@ jobs: - name: Acceptance uses: databrickslabs/sandbox/acceptance@gh-action - with: - xxx: ${{ secrets.GITHUB_TOKEN }} - # Workflows that call reusable workflows in the same organization or enterprise can use the inherit keyword to implicitly pass the secrets. - secrets: inherit + \ No newline at end of file diff --git a/acceptance/action.yml b/acceptance/action.yml index 21e28c7e..0fab6012 100644 --- a/acceptance/action.yml +++ b/acceptance/action.yml @@ -3,9 +3,6 @@ name: 'Databricks Labs Acceptance Suite' description: 'Run relevant acceptance suite' author: Serge Smertin inputs: - xxx: - description: GitHub token to use for commenting on PR - required: true go-version: description: 'Go version' required: false diff --git a/acceptance/main.go b/acceptance/main.go index 2515128f..797aebf5 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -2,10 +2,8 @@ package main import ( "context" - "encoding/base64" "encoding/json" "fmt" - "os" "github.com/databrickslabs/sandbox/go-libs/github" "github.com/sethvargo/go-githubactions" @@ -13,18 +11,22 @@ import ( func New(opts ...githubactions.Option) (*acceptance, error) { a := githubactions.New(opts...) + token, err := a.GetIDToken(context.Background(), "") + if err != nil { + return nil, fmt.Errorf("oidc: %w", err) + } context, err := a.Context() if err != nil { return nil, err } - fmt.Fprintf(os.Stdout, "b64: %s", base64.StdEncoding.EncodeToString([]byte(a.GetInput("xxx"))[:10])) return &acceptance{ action: a, context: context, gh: github.NewClient(&github.GitHubConfig{ GitHubTokenSource: github.GitHubTokenSource{ // TODO: autodetect - Pat: a.GetInput("xxx"), + // Pat: a.GetInput("xxx"), + Pat: token, }, }), }, nil From d6258e116462328457e7ab2df5a01156804a0574 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 12:05:38 +0100 Subject: [PATCH 18/43] .. --- .github/workflows/acceptance-go-libs.yml | 5 +++++ acceptance/action.yml | 20 ++------------------ acceptance/main.go | 4 ++-- acceptance/shim.js | 11 +++++++++++ 4 files changed, 20 insertions(+), 20 deletions(-) create mode 100644 acceptance/shim.js diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index 7d48f10c..d5231fa0 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -31,6 +31,11 @@ jobs: # - name: Dump env # run: env | sort + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: 1.21 + - name: Acceptance uses: databrickslabs/sandbox/acceptance@gh-action \ No newline at end of file diff --git a/acceptance/action.yml b/acceptance/action.yml index 0fab6012..fe934d5b 100644 --- a/acceptance/action.yml +++ b/acceptance/action.yml @@ -13,21 +13,5 @@ outputs: value: ${{ steps.invoke.outputs.sample }} runs: - using: 'composite' - steps: - # TODO: publish as docker image, eventually - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version-file: ${{ github.action_path }}/go.mod - - - name: Download Go modules - run: go mod download - working-directory: ${{ github.action_path }} - shell: bash - - - name: Run action - id: invoke - run: go run main.go - working-directory: ${{ github.action_path }} - shell: bash + using: node20 + main: shim.js \ No newline at end of file diff --git a/acceptance/main.go b/acceptance/main.go index 797aebf5..6d81490a 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -68,7 +68,7 @@ func (a *acceptance) comment(ctx context.Context) error { return nil } -func mainE(ctx context.Context) error { +func run(ctx context.Context) error { a, err := New() if err != nil { return err @@ -79,7 +79,7 @@ func mainE(ctx context.Context) error { } func main() { - err := mainE(context.Background()) + err := run(context.Background()) if err != nil { githubactions.Fatalf("failed: %s", err) } diff --git a/acceptance/shim.js b/acceptance/shim.js new file mode 100644 index 00000000..aa98baef --- /dev/null +++ b/acceptance/shim.js @@ -0,0 +1,11 @@ +// function chooseBinary() { +// // ... +// if (platform === 'linux' && arch === 'x64') { +// return `main-linux-amd64-${VERSION}` +// } +// // ... +// } + +// const binary = chooseBinary() +// const mainScript = `${__dirname}/${binary}` +const spawnSyncReturns = child_process.spawnSync('go', ['main.go'], { stdio: 'inherit' }) \ No newline at end of file From ff75dad8a0655725dfa16df72ed2334009358952 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 12:10:18 +0100 Subject: [PATCH 19/43] .. --- acceptance/shim.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/acceptance/shim.js b/acceptance/shim.js index aa98baef..42d552ae 100644 --- a/acceptance/shim.js +++ b/acceptance/shim.js @@ -8,4 +8,7 @@ // const binary = chooseBinary() // const mainScript = `${__dirname}/${binary}` -const spawnSyncReturns = child_process.spawnSync('go', ['main.go'], { stdio: 'inherit' }) \ No newline at end of file + +const { spawnSync } = require('child_process'); + +spawnSync('go', ['main.go'], { stdio: 'inherit' }); From 26f124732439c72310c5423cc7374a3a3f61fcf0 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 12:14:20 +0100 Subject: [PATCH 20/43] .. --- acceptance/shim.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/acceptance/shim.js b/acceptance/shim.js index 42d552ae..fdccd7a1 100644 --- a/acceptance/shim.js +++ b/acceptance/shim.js @@ -11,4 +11,6 @@ const { spawnSync } = require('child_process'); -spawnSync('go', ['main.go'], { stdio: 'inherit' }); +// TODO: try calling the result of GOOS=js GOARCH=wasm go build -o acceptance.wasm + +spawnSync('go', ['run', 'main.go'], { stdio: 'inherit' }); From 8160e4eb2d1371f0befee75ec39c0c9e38b50ff1 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 12:16:31 +0100 Subject: [PATCH 21/43] .. --- acceptance/shim.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/shim.js b/acceptance/shim.js index fdccd7a1..39398360 100644 --- a/acceptance/shim.js +++ b/acceptance/shim.js @@ -13,4 +13,4 @@ const { spawnSync } = require('child_process'); // TODO: try calling the result of GOOS=js GOARCH=wasm go build -o acceptance.wasm -spawnSync('go', ['run', 'main.go'], { stdio: 'inherit' }); +spawnSync('go', ['run', `${__dirname}/main.go`], { stdio: 'inherit' }); From 9ca53aed448fca1e65ac12b1f35d7c60523a5d7a Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 12:23:37 +0100 Subject: [PATCH 22/43] .. --- .github/workflows/acceptance-go-libs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index d5231fa0..f097cf1b 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -38,4 +38,6 @@ jobs: - name: Acceptance uses: databrickslabs/sandbox/acceptance@gh-action + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From 0323730cf06d7137c9df92efc4c48b3b4e780512 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 12:24:31 +0100 Subject: [PATCH 23/43] .. --- acceptance/main.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/acceptance/main.go b/acceptance/main.go index 6d81490a..9cedc002 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -11,10 +11,10 @@ import ( func New(opts ...githubactions.Option) (*acceptance, error) { a := githubactions.New(opts...) - token, err := a.GetIDToken(context.Background(), "") - if err != nil { - return nil, fmt.Errorf("oidc: %w", err) - } + // token, err := a.GetIDToken(context.Background(), "") + // if err != nil { + // return nil, fmt.Errorf("oidc: %w", err) + // } context, err := a.Context() if err != nil { return nil, err @@ -26,7 +26,7 @@ func New(opts ...githubactions.Option) (*acceptance, error) { GitHubTokenSource: github.GitHubTokenSource{ // TODO: autodetect // Pat: a.GetInput("xxx"), - Pat: token, + // Pat: token, }, }), }, nil From b1d1b8f68b99eeca6a7d16fe79afb7840e67ac07 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 12:49:44 +0100 Subject: [PATCH 24/43] .. --- acceptance/main.go | 35 +++++++++++++++++++++++++---------- go-libs/github/github.go | 12 ++++++++++-- go-libs/github/util.go | 5 +++++ 3 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 go-libs/github/util.go diff --git a/acceptance/main.go b/acceptance/main.go index 9cedc002..c38490cd 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -23,11 +23,7 @@ func New(opts ...githubactions.Option) (*acceptance, error) { action: a, context: context, gh: github.NewClient(&github.GitHubConfig{ - GitHubTokenSource: github.GitHubTokenSource{ - // TODO: autodetect - // Pat: a.GetInput("xxx"), - // Pat: token, - }, + GitHubTokenSource: github.GitHubTokenSource{}, }), }, nil } @@ -55,13 +51,32 @@ func (a *acceptance) currentPullRequest(ctx context.Context) (*github.PullReques } func (a *acceptance) comment(ctx context.Context) error { - // todo: try a.context.Event["number"].(int) - pr, err := a.currentPullRequest(ctx) - if err != nil { - return fmt.Errorf("pull request: %w", err) + numberAny, ok := a.context.Event["number"] + if !ok { + return fmt.Errorf("no pr number in the context") + } + number, ok := numberAny.(int) + if !ok { + return fmt.Errorf("pr number is not valid") + } + me, err := a.gh.CurrentUser(ctx) + if !ok { + return fmt.Errorf("current user: %w", err) } org, repo := a.context.Repo() - _, err = a.gh.CreateIssueComment(ctx, org, repo, pr.Number, "Test from acceptance action") + it := a.gh.GetIssueComments(ctx, org, repo, number) + for it.HasNext(ctx) { + comment, err := it.Next(ctx) + if !ok { + return fmt.Errorf("comment: %w", err) + } + if comment.User.Login != me.Login { + continue + } + _, err = a.gh.UpdateIssueComment(ctx, org, repo, comment.ID, "updated comment") + return err + } + _, err = a.gh.CreateIssueComment(ctx, org, repo, number, "Test from acceptance action") if err != nil { return fmt.Errorf("new comment: %w", err) } diff --git a/go-libs/github/github.go b/go-libs/github/github.go index ee76b110..e6ec4ae1 100644 --- a/go-libs/github/github.go +++ b/go-libs/github/github.go @@ -243,7 +243,7 @@ func (c *GitHubClient) CreateIssueComment(ctx context.Context, org, repo string, return &res, nil } -func (c *GitHubClient) UpdateIssueComment(ctx context.Context, org, repo string, commentID int, body string) (*IssueComment, error) { +func (c *GitHubClient) UpdateIssueComment(ctx context.Context, org, repo string, commentID int64, body string) (*IssueComment, error) { path := fmt.Sprintf("%s/repos/%s/%s/issues/comments/%d", gitHubAPI, org, repo, commentID) var res IssueComment err := c.api.Do(ctx, "PATCH", path, @@ -257,7 +257,7 @@ func (c *GitHubClient) UpdateIssueComment(ctx context.Context, org, repo string, return &res, nil } -func (c *GitHubClient) DeleteIssueComment(ctx context.Context, org, repo string, commentID int) error { +func (c *GitHubClient) DeleteIssueComment(ctx context.Context, org, repo string, commentID int64) error { path := fmt.Sprintf("%s/repos/%s/%s/issues/comments/%d", gitHubAPI, org, repo, commentID) return c.api.Do(ctx, "DELETE", path) } @@ -311,6 +311,14 @@ func (c *GitHubClient) GetUser(ctx context.Context, login string) (*User, error) return &user, err } +func (c *GitHubClient) CurrentUser(ctx context.Context) (*User, error) { + path := fmt.Sprintf("%s/users", gitHubAPI) + var user User + err := c.api.Do(ctx, "GET", path, + httpclient.WithResponseUnmarshal(&user)) + return &user, err +} + func paginator[T any, ID comparable]( ctx context.Context, c *GitHubClient, diff --git a/go-libs/github/util.go b/go-libs/github/util.go new file mode 100644 index 00000000..b562a20a --- /dev/null +++ b/go-libs/github/util.go @@ -0,0 +1,5 @@ +package github + +import "context" + +func Upsert(ctx context.Context) \ No newline at end of file From 8ca204bde99046ed36f706289cf4cb271e1bbecf Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 12:54:09 +0100 Subject: [PATCH 25/43] .. --- acceptance/shim.js | 10 ++++------ go-libs/github/util.go | 5 ----- 2 files changed, 4 insertions(+), 11 deletions(-) delete mode 100644 go-libs/github/util.go diff --git a/acceptance/shim.js b/acceptance/shim.js index 39398360..c0f2096e 100644 --- a/acceptance/shim.js +++ b/acceptance/shim.js @@ -5,12 +5,10 @@ // } // // ... // } - // const binary = chooseBinary() // const mainScript = `${__dirname}/${binary}` - -const { spawnSync } = require('child_process'); - // TODO: try calling the result of GOOS=js GOARCH=wasm go build -o acceptance.wasm - -spawnSync('go', ['run', `${__dirname}/main.go`], { stdio: 'inherit' }); +const { spawnSync } = require('child_process'); +const { exit } = require('node:process'); +const { status } = spawnSync('go', ['run', `${__dirname}/main.go`], { stdio: 'inherit' }); +exit(status); diff --git a/go-libs/github/util.go b/go-libs/github/util.go deleted file mode 100644 index b562a20a..00000000 --- a/go-libs/github/util.go +++ /dev/null @@ -1,5 +0,0 @@ -package github - -import "context" - -func Upsert(ctx context.Context) \ No newline at end of file From 09b7070f8c81a8c538e64c94307bd4263d5f3779 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 12:56:39 +0100 Subject: [PATCH 26/43] .. --- acceptance/main.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/acceptance/main.go b/acceptance/main.go index c38490cd..b3de5830 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -2,8 +2,10 @@ package main import ( "context" + "encoding/base64" "encoding/json" "fmt" + "os" "github.com/databrickslabs/sandbox/go-libs/github" "github.com/sethvargo/go-githubactions" @@ -51,6 +53,11 @@ func (a *acceptance) currentPullRequest(ctx context.Context) (*github.PullReques } func (a *acceptance) comment(ctx context.Context) error { + raw, err := json.MarshalIndent(a.context.Event, "", " ") + if err != nil { + return fmt.Errorf("marshall: %w", err) + } + fmt.Fprintf(os.Stdout, "b64: %s", base64.StdEncoding.EncodeToString(raw)) numberAny, ok := a.context.Event["number"] if !ok { return fmt.Errorf("no pr number in the context") From b4d71b245f894f64337d795e1c1c45a8e3c2395f Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 13:01:13 +0100 Subject: [PATCH 27/43] .. --- acceptance/main.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/acceptance/main.go b/acceptance/main.go index b3de5830..cc7773a7 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -62,10 +62,7 @@ func (a *acceptance) comment(ctx context.Context) error { if !ok { return fmt.Errorf("no pr number in the context") } - number, ok := numberAny.(int) - if !ok { - return fmt.Errorf("pr number is not valid") - } + number := numberAny.(int) me, err := a.gh.CurrentUser(ctx) if !ok { return fmt.Errorf("current user: %w", err) From ccab30ac349a4fc4e44b64c3f521704903e5339b Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 13:03:59 +0100 Subject: [PATCH 28/43] .. --- acceptance/main.go | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/acceptance/main.go b/acceptance/main.go index cc7773a7..23a73d4f 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -2,10 +2,8 @@ package main import ( "context" - "encoding/base64" "encoding/json" "fmt" - "os" "github.com/databrickslabs/sandbox/go-libs/github" "github.com/sethvargo/go-githubactions" @@ -53,25 +51,19 @@ func (a *acceptance) currentPullRequest(ctx context.Context) (*github.PullReques } func (a *acceptance) comment(ctx context.Context) error { - raw, err := json.MarshalIndent(a.context.Event, "", " ") + pr, err := a.currentPullRequest(ctx) if err != nil { - return fmt.Errorf("marshall: %w", err) - } - fmt.Fprintf(os.Stdout, "b64: %s", base64.StdEncoding.EncodeToString(raw)) - numberAny, ok := a.context.Event["number"] - if !ok { - return fmt.Errorf("no pr number in the context") + return fmt.Errorf("pr: %w", err) } - number := numberAny.(int) me, err := a.gh.CurrentUser(ctx) - if !ok { + if err != nil { return fmt.Errorf("current user: %w", err) } org, repo := a.context.Repo() - it := a.gh.GetIssueComments(ctx, org, repo, number) + it := a.gh.GetIssueComments(ctx, org, repo, pr.Number) for it.HasNext(ctx) { comment, err := it.Next(ctx) - if !ok { + if err != nil { return fmt.Errorf("comment: %w", err) } if comment.User.Login != me.Login { @@ -80,7 +72,7 @@ func (a *acceptance) comment(ctx context.Context) error { _, err = a.gh.UpdateIssueComment(ctx, org, repo, comment.ID, "updated comment") return err } - _, err = a.gh.CreateIssueComment(ctx, org, repo, number, "Test from acceptance action") + _, err = a.gh.CreateIssueComment(ctx, org, repo, pr.Number, "Test from acceptance action") if err != nil { return fmt.Errorf("new comment: %w", err) } From bb6a64638946115259a3b5f2df98475b43adbf4d Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 13:06:15 +0100 Subject: [PATCH 29/43] .. --- go-libs/github/github.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-libs/github/github.go b/go-libs/github/github.go index e6ec4ae1..8abb4474 100644 --- a/go-libs/github/github.go +++ b/go-libs/github/github.go @@ -312,7 +312,7 @@ func (c *GitHubClient) GetUser(ctx context.Context, login string) (*User, error) } func (c *GitHubClient) CurrentUser(ctx context.Context) (*User, error) { - path := fmt.Sprintf("%s/users", gitHubAPI) + path := fmt.Sprintf("%s/user", gitHubAPI) var user User err := c.api.Do(ctx, "GET", path, httpclient.WithResponseUnmarshal(&user)) From d9416a5db176480c1ac51bb840450f2414f60ed2 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 13:28:54 +0100 Subject: [PATCH 30/43] .. --- acceptance/main.go | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/acceptance/main.go b/acceptance/main.go index 23a73d4f..1ccd9a3b 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" "fmt" + "os" + "strings" "github.com/databrickslabs/sandbox/go-libs/github" "github.com/sethvargo/go-githubactions" @@ -25,6 +27,7 @@ func New(opts ...githubactions.Option) (*acceptance, error) { gh: github.NewClient(&github.GitHubConfig{ GitHubTokenSource: github.GitHubTokenSource{}, }), + getenv: os.Getenv, }, nil } @@ -32,6 +35,24 @@ type acceptance struct { action *githubactions.Action context *githubactions.GitHubContext gh *github.GitHubClient + getenv func(key string) string +} + +func (a *acceptance) runURL() string { + return fmt.Sprintf("%s/%s/actions/runs/%d/job/%s", // ?pr=56 + a.context.ServerURL, a.context.Repository, a.context.RunID, a.context.Job) +} + +func (a *acceptance) tag() string { + // The ref path to the workflow. For example, + // octocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch. + return fmt.Sprintf("\n", a.getenv("GITHUB_WORKFLOW_REF")) +} + +func (a *acceptance) taggedComment(body string) string { + // GITHUB_WORKFLOW_REF + return fmt.Sprintf("%s\n---\nRunning from [%s #%d](%s)%s", + body, a.context.Workflow, a.context.RunNumber, a.runURL(), a.tag()) } func (a *acceptance) currentPullRequest(ctx context.Context) (*github.PullRequest, error) { @@ -55,10 +76,7 @@ func (a *acceptance) comment(ctx context.Context) error { if err != nil { return fmt.Errorf("pr: %w", err) } - me, err := a.gh.CurrentUser(ctx) - if err != nil { - return fmt.Errorf("current user: %w", err) - } + tag := a.tag() org, repo := a.context.Repo() it := a.gh.GetIssueComments(ctx, org, repo, pr.Number) for it.HasNext(ctx) { @@ -66,13 +84,13 @@ func (a *acceptance) comment(ctx context.Context) error { if err != nil { return fmt.Errorf("comment: %w", err) } - if comment.User.Login != me.Login { + if !strings.Contains(comment.Body, tag) { continue } - _, err = a.gh.UpdateIssueComment(ctx, org, repo, comment.ID, "updated comment") + _, err = a.gh.UpdateIssueComment(ctx, org, repo, comment.ID, a.taggedComment("Updated comment")) return err } - _, err = a.gh.CreateIssueComment(ctx, org, repo, pr.Number, "Test from acceptance action") + _, err = a.gh.CreateIssueComment(ctx, org, repo, pr.Number, a.taggedComment("New comment")) if err != nil { return fmt.Errorf("new comment: %w", err) } From 10a2f04d45e8e7a70b5b2ca7c255ac5c6e4e6776 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 13:36:29 +0100 Subject: [PATCH 31/43] .. --- .github/workflows/acceptance-go-libs.yml | 12 ++++++------ acceptance/main.go | 4 +--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index f097cf1b..0555c262 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -23,13 +23,13 @@ jobs: # tenant-id: ${{ secrets.ARM_TENANT_ID }} # subscription-id: ${{ secrets.ARM_SUBSCRIPTION_ID }} - # - name: Dump github context - # run: echo "$GITHUB_CONTEXT" - # env: - # GITHUB_CONTEXT: ${{ toJSON(github) }} + - name: Dump github context + run: echo "$GITHUB_CONTEXT" || base64 + env: + GITHUB_CONTEXT: ${{ toJSON(github) }} - # - name: Dump env - # run: env | sort + - name: Dump env + run: env | sort | base64 | base64 - name: Setup Go uses: actions/setup-go@v5 diff --git a/acceptance/main.go b/acceptance/main.go index 1ccd9a3b..ebf62459 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -50,8 +50,7 @@ func (a *acceptance) tag() string { } func (a *acceptance) taggedComment(body string) string { - // GITHUB_WORKFLOW_REF - return fmt.Sprintf("%s\n---\nRunning from [%s #%d](%s)%s", + return fmt.Sprintf("%s\nRunning from [%s #%d](%s){:target=\"_blank\"}%s", body, a.context.Workflow, a.context.RunNumber, a.runURL(), a.tag()) } @@ -60,7 +59,6 @@ func (a *acceptance) currentPullRequest(ctx context.Context) (*github.PullReques if err != nil { return nil, fmt.Errorf("marshall: %w", err) } - // fmt.Fprintf(os.Stdout, "b64: %s", base64.StdEncoding.EncodeToString(raw)) var event struct { PullRequest *github.PullRequest `json:"pull_request"` } From 68e8cb5f6faed15c92d9d05ddebd47687ca9d59d Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 13:40:26 +0100 Subject: [PATCH 32/43] .. --- .github/workflows/acceptance-go-libs.yml | 9 ++++----- acceptance/main.go | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index 0555c262..07f4b3e0 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -23,13 +23,12 @@ jobs: # tenant-id: ${{ secrets.ARM_TENANT_ID }} # subscription-id: ${{ secrets.ARM_SUBSCRIPTION_ID }} - - name: Dump github context - run: echo "$GITHUB_CONTEXT" || base64 + - name: DEBUG + run: | + echo "$GITHUB_CONTEXT" + env | sort env: GITHUB_CONTEXT: ${{ toJSON(github) }} - - - name: Dump env - run: env | sort | base64 | base64 - name: Setup Go uses: actions/setup-go@v5 diff --git a/acceptance/main.go b/acceptance/main.go index ebf62459..fa766c68 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -50,7 +50,7 @@ func (a *acceptance) tag() string { } func (a *acceptance) taggedComment(body string) string { - return fmt.Sprintf("%s\nRunning from [%s #%d](%s){:target=\"_blank\"}%s", + return fmt.Sprintf("%s\nRunning from [%s #%d](%s)%s", body, a.context.Workflow, a.context.RunNumber, a.runURL(), a.tag()) } From 0ad1da109009d239483f358773c11999b79c2670 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Mon, 15 Jan 2024 13:50:01 +0100 Subject: [PATCH 33/43] .. --- .github/workflows/acceptance-go-libs.yml | 14 ++++++++++++++ acceptance/main.go | 5 +---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index 07f4b3e0..8a2cf933 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -35,6 +35,20 @@ jobs: with: go-version: 1.21 + - name: Get Job ID from GH API + id: get-job-id + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + jobs=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id}}/attempts/${{ github.run_attempt }}/jobs) + job_id=$(echo $jobs | jq -r '.jobs[] | select(.runner_name=="${{ runner.name }}") | .id') + echo "job_id=$job_id" >> $GITHUB_OUTPUT + + - name: Display Job ID + run: | + echo Job ID: ${{ steps.get-job-id.outputs.job_id }} + echo My full job URL is ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/job/${{ steps.get-job-id.outputs.job_id }} + - name: Acceptance uses: databrickslabs/sandbox/acceptance@gh-action env: diff --git a/acceptance/main.go b/acceptance/main.go index fa766c68..8752828a 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "os" "strings" "github.com/databrickslabs/sandbox/go-libs/github" @@ -27,7 +26,6 @@ func New(opts ...githubactions.Option) (*acceptance, error) { gh: github.NewClient(&github.GitHubConfig{ GitHubTokenSource: github.GitHubTokenSource{}, }), - getenv: os.Getenv, }, nil } @@ -35,7 +33,6 @@ type acceptance struct { action *githubactions.Action context *githubactions.GitHubContext gh *github.GitHubClient - getenv func(key string) string } func (a *acceptance) runURL() string { @@ -46,7 +43,7 @@ func (a *acceptance) runURL() string { func (a *acceptance) tag() string { // The ref path to the workflow. For example, // octocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch. - return fmt.Sprintf("\n", a.getenv("GITHUB_WORKFLOW_REF")) + return fmt.Sprintf("\n", a.action.Getenv("GITHUB_WORKFLOW_REF")) } func (a *acceptance) taggedComment(body string) string { From 0325398e358119b6b31195dfd95bd4b174acd398 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Tue, 16 Jan 2024 16:25:32 +0100 Subject: [PATCH 34/43] .. --- go-libs/github/github.go | 94 ++++++++++++++++++++++++++ go-libs/github/github_actions.go | 111 ++++++++++++++++++++++++++++++- go-libs/github/pull_requests.go | 13 ++++ go-libs/github/teams.go | 46 +++++++++++++ 4 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 go-libs/github/teams.go diff --git a/go-libs/github/github.go b/go-libs/github/github.go index 8abb4474..2464548c 100644 --- a/go-libs/github/github.go +++ b/go-libs/github/github.go @@ -1,6 +1,7 @@ package github import ( + "bytes" "context" "encoding/json" "fmt" @@ -143,6 +144,36 @@ func (c *GitHubClient) ListRepositoryIssues(ctx context.Context, org, repo strin }) } +func (c *GitHubClient) ListWorkflows(ctx context.Context, org, repo string) listing.Iterator[Workflow] { + path := fmt.Sprintf("%s/repos/%s/%s/actions/workflows", gitHubAPI, org, repo) + return rawPaginator[Workflow, Workflows, int64](ctx, c, path, &PageOptions{}, func(w Workflows) []Workflow { + return w.Workflows + }, func(w Workflow) int64 { + return w.ID + }) +} + +func (c *GitHubClient) ListWorkflowRuns(ctx context.Context, org, repo string, opts *ListWorkflowRunsOptions) listing.Iterator[WorkflowRun] { + path := fmt.Sprintf("%s/repos/%s/%s/actions/runs", gitHubAPI, org, repo) + return rawPaginator[WorkflowRun, WorkflowRuns, int64](ctx, c, path, opts, func(wr WorkflowRuns) []WorkflowRun { + return wr.WorkflowRuns + }, func(wr WorkflowRun) int64 { + return wr.ID + }) +} + +func (c *GitHubClient) GetWorkflowRunLogs(ctx context.Context, org, repo string, runID int64) (*bytes.Buffer, error) { + var location string + path := fmt.Sprintf("%s/repos/%v/%v/actions/runs/%v/logs", gitHubAPI, org, repo, runID) + err := c.api.Do(ctx, "GET", path, httpclient.WithResponseHeader("Location", &location)) + if err != nil { + return nil, fmt.Errorf("redirect: %w", err) + } + var buf bytes.Buffer + err = c.api.Do(ctx, "GET", location, httpclient.WithResponseUnmarshal(&buf)) + return &buf, err +} + func (c *GitHubClient) ListRuns(ctx context.Context, org, repo, workflow string) listing.Iterator[workflowRun] { path := fmt.Sprintf("%s/repos/%s/%s/actions/workflows/%v.yml/runs", gitHubAPI, org, repo, workflow) type response struct { @@ -155,6 +186,15 @@ func (c *GitHubClient) ListRuns(ctx context.Context, org, repo, workflow string) }) } +func (c *GitHubClient) ListWorkflowJobs(ctx context.Context, org, repo string, runID int64) listing.Iterator[WorkflowJob] { + path := fmt.Sprintf("%s/repos/%s/%s/actions/runs/%v/jobs", gitHubAPI, org, repo, runID) + return rawPaginator[WorkflowJob, Jobs, int64](ctx, c, path, &PageOptions{}, func(j Jobs) []WorkflowJob { + return j.Jobs + }, func(wj WorkflowJob) int64 { + return wj.ID + }) +} + func (c *GitHubClient) ListCommits(ctx context.Context, org, repo string, req *ListCommits) listing.Iterator[RepositoryCommit] { path := fmt.Sprintf("%s/repos/%s/%s/commits", gitHubAPI, org, repo) return paginator[RepositoryCommit, string](ctx, c, path, req, func(rc RepositoryCommit) string { @@ -213,6 +253,13 @@ func (c *GitHubClient) GetPullRequestComments(ctx context.Context, org, repo str }) } +func (c *GitHubClient) GetPullRequestReviews(ctx context.Context, org, repo string, number int) listing.Iterator[PullRequestReview] { + path := fmt.Sprintf("%s/repos/%s/%s/pulls/%d/reviews", gitHubAPI, org, repo, number) + return paginator[PullRequestReview, int64](ctx, c, path, &PageOptions{}, func(prr PullRequestReview) int64 { + return prr.ID + }) +} + func (c *GitHubClient) GetPullRequestCommits(ctx context.Context, org, repo string, number int) listing.Iterator[RepositoryCommit] { path := fmt.Sprintf("%s/repos/%s/%s/pulls/%d/commits", gitHubAPI, org, repo, number) return paginator[RepositoryCommit, string](ctx, c, path, &PageOptions{}, func(rc RepositoryCommit) string { @@ -319,6 +366,53 @@ func (c *GitHubClient) CurrentUser(ctx context.Context) (*User, error) { return &user, err } +func (c *GitHubClient) ListTeams(ctx context.Context, org string) listing.Iterator[Team] { + path := fmt.Sprintf("%s/orgs/%s/teams", gitHubAPI, org) + return paginator[Team, string](ctx, c, path, &PageOptions{}, func(t Team) string { + return t.Slug + }) +} + +func (c *GitHubClient) CreateTeam(ctx context.Context, org string, req NewTeam) (*Team, error) { + var res Team + path := fmt.Sprintf("%s/orgs/%s/teams", gitHubAPI, org) + err := c.api.Do(ctx, "POST", path, + httpclient.WithRequestData(req), + httpclient.WithResponseUnmarshal(&res)) + return &res, err +} + +func (c *GitHubClient) DeleteTeam(ctx context.Context, org, slug string) error { + path := fmt.Sprintf("%s/orgs/%s/teams/%s", gitHubAPI, org, slug) + return c.api.Do(ctx, "DELETE", path) +} + +func (c *GitHubClient) ListTeamMembers(ctx context.Context, org, slug string) listing.Iterator[User] { + path := fmt.Sprintf("%s/orgs/%s/teams/%s/members", gitHubAPI, org, slug) + return paginator[User, string](ctx, c, path, &PageOptions{}, func(u User) string { + return u.Login + }) +} + +func (c *GitHubClient) AddTeamMember(ctx context.Context, org, teamSlug, username string) error { + path := fmt.Sprintf("%s/orgs/%s/teams/%s/members/%s", gitHubAPI, org, teamSlug, username) + return c.api.Do(ctx, "POST", path) +} + +func (c *GitHubClient) RemoveTeamMember(ctx context.Context, org, teamSlug, username string) error { + path := fmt.Sprintf("%s/orgs/%s/teams/%s/members/%s", gitHubAPI, org, teamSlug, username) + return c.api.Do(ctx, "DELETE", path) +} + +func (c *GitHubClient) GraphQL(ctx context.Context, query string, vars map[string]any, data any) error { + return c.api.Do(ctx, "POST", "https://api.github.com/graphql", + httpclient.WithRequestData(map[string]any{ + "query": query, + "variables": vars, + }), + httpclient.WithResponseUnmarshal(data)) +} + func paginator[T any, ID comparable]( ctx context.Context, c *GitHubClient, diff --git a/go-libs/github/github_actions.go b/go-libs/github/github_actions.go index e901072c..6ae7aff7 100644 --- a/go-libs/github/github_actions.go +++ b/go-libs/github/github_actions.go @@ -13,6 +13,115 @@ import ( "golang.org/x/oauth2" ) +type ListWorkflowRunsOptions struct { + Actor string `url:"actor,omitempty"` + Branch string `url:"branch,omitempty"` + + // Returns workflow run triggered by the event you specify. For example, push, pull_request or issue. + Event string `url:"event,omitempty"` + + // Can be one of: completed, action_required, cancelled, failure, neutral, skipped, stale, + // success, timed_out, in_progress, queued, requested, waiting, pending + Status string `url:"status,omitempty"` + + // Returns workflow runs created within the given date-time range. + // See https://docs.github.com/search-github/getting-started-with-searching-on-github/understanding-the-search-syntax#query-for-dates + Created string `url:"created,omitempty"` + PageOptions +} + +type WorkflowRuns struct { + TotalCount int `json:"total_count,omitempty"` + WorkflowRuns []WorkflowRun `json:"workflow_runs,omitempty"` +} + +type WorkflowRun struct { + ID int64 `json:"id,omitempty"` + Name string `json:"name,omitempty"` + NodeID string `json:"node_id,omitempty"` + HeadBranch string `json:"head_branch,omitempty"` + HeadSHA string `json:"head_sha,omitempty"` + RunNumber int `json:"run_number,omitempty"` + RunAttempt int `json:"run_attempt,omitempty"` + Event string `json:"event,omitempty"` + Status string `json:"status,omitempty"` + Conclusion string `json:"conclusion,omitempty"` + WorkflowID int64 `json:"workflow_id,omitempty"` + CheckSuiteID int64 `json:"check_suite_id,omitempty"` + CheckSuiteNodeID string `json:"check_suite_node_id,omitempty"` + URL string `json:"url,omitempty"` + HTMLURL string `json:"html_url,omitempty"` + PullRequests []PullRequest `json:"pull_requests,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + RunStartedAt *time.Time `json:"run_started_at,omitempty"` + JobsURL string `json:"jobs_url,omitempty"` + LogsURL string `json:"logs_url,omitempty"` + CheckSuiteURL string `json:"check_suite_url,omitempty"` + ArtifactsURL string `json:"artifacts_url,omitempty"` + CancelURL string `json:"cancel_url,omitempty"` + RerunURL string `json:"rerun_url,omitempty"` + PreviousAttemptURL string `json:"previous_attempt_url,omitempty"` + WorkflowURL string `json:"workflow_url,omitempty"` + Repository Repo `json:"repository,omitempty"` + HeadRepository Repo `json:"head_repository,omitempty"` + Actor User `json:"actor,omitempty"` +} + +type Workflow struct { + ID int64 `json:"id,omitempty"` + NodeID string `json:"node_id,omitempty"` + Name string `json:"name,omitempty"` + Path string `json:"path,omitempty"` + State string `json:"state,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + URL string `json:"url,omitempty"` + HTMLURL string `json:"html_url,omitempty"` + BadgeURL string `json:"badge_url,omitempty"` +} + +type Workflows struct { + TotalCount int `json:"total_count,omitempty"` + Workflows []Workflow `json:"workflows,omitempty"` +} + +type TaskStep struct { + Name string `json:"name,omitempty"` + Status string `json:"status,omitempty"` + Conclusion string `json:"conclusion,omitempty"` + Number int64 `json:"number,omitempty"` + StartedAt time.Time `json:"started_at,omitempty"` + CompletedAt time.Time `json:"completed_at,omitempty"` +} + +type WorkflowJob struct { + ID int64 `json:"id,omitempty"` + RunID int64 `json:"run_id,omitempty"` + RunURL string `json:"run_url,omitempty"` + NodeID string `json:"node_id,omitempty"` + HeadSHA string `json:"head_sha,omitempty"` + URL string `json:"url,omitempty"` + HTMLURL string `json:"html_url,omitempty"` + Status string `json:"status,omitempty"` + Conclusion string `json:"conclusion,omitempty"` + StartedAt time.Time `json:"started_at,omitempty"` + CompletedAt time.Time `json:"completed_at,omitempty"` + Name string `json:"name,omitempty"` + Steps []TaskStep `json:"steps,omitempty"` + CheckRunURL string `json:"check_run_url,omitempty"` + Labels []string `json:"labels,omitempty"` + RunnerID int64 `json:"runner_id,omitempty"` + RunnerName string `json:"runner_name,omitempty"` + RunnerGroupID int64 `json:"runner_group_id,omitempty"` + RunnerGroupName string `json:"runner_group_name,omitempty"` +} + +type Jobs struct { + TotalCount int `json:"total_count,omitempty"` + Jobs []WorkflowJob `json:"jobs,omitempty"` +} + const ghApi = "https://api.github.com" type GitHubActionsWorkflow struct { @@ -61,7 +170,7 @@ func (g *GitHubActionsWorkflow) listRuns(client *http.Client, ref string) ([]wor split := strings.SplitN(ref, "/", 3) path := fmt.Sprintf("/repos/%s/%s/actions/workflows/%v.yml/runs", split[0], split[1], split[2]) var response struct { - TotalCount *int `json:"total_count,omitempty"` + TotalCount int `json:"total_count,omitempty"` WorkflowRuns []workflowRun `json:"workflow_runs,omitempty"` } err := g.get(client, path, &response) diff --git a/go-libs/github/pull_requests.go b/go-libs/github/pull_requests.go index 2eab0a87..178d52c0 100644 --- a/go-libs/github/pull_requests.go +++ b/go-libs/github/pull_requests.go @@ -138,3 +138,16 @@ type PullRequestComment struct { HTMLURL string `json:"html_url,omitempty"` PullRequestURL string `json:"pull_request_url,omitempty"` } + +type PullRequestReview struct { + ID int64 `json:"id,omitempty"` + NodeID string `json:"node_id,omitempty"` + User User `json:"user,omitempty"` + Body string `json:"body,omitempty"` + SubmittedAt time.Time `json:"submitted_at,omitempty"` + CommitID string `json:"commit_id,omitempty"` + HTMLURL string `json:"html_url,omitempty"` + PullRequestURL string `json:"pull_request_url,omitempty"` + State string `json:"state,omitempty"` + AuthorAssociation string `json:"author_association,omitempty"` +} diff --git a/go-libs/github/teams.go b/go-libs/github/teams.go new file mode 100644 index 00000000..e9d5d356 --- /dev/null +++ b/go-libs/github/teams.go @@ -0,0 +1,46 @@ +package github + +type Team struct { + ID int64 `json:"id,omitempty"` + NodeID string `json:"node_id,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + URL string `json:"url,omitempty"` + Slug string `json:"slug,omitempty"` + + // Permission specifies the default permission for repositories owned by the team. + Permission string `json:"permission,omitempty"` + + // Permissions identifies the permissions that a team has on a given + // repository. This is only populated when calling Repositories.ListTeams. + Permissions map[string]bool `json:"permissions,omitempty"` + + // Privacy identifies the level of privacy this team should have. + // Possible values are: + // secret - only visible to organization owners and members of this team + // closed - visible to all members of this organization + // Default is "secret". + Privacy string `json:"privacy,omitempty"` + + MembersCount *int `json:"members_count,omitempty"` + ReposCount *int `json:"repos_count,omitempty"` + HTMLURL string `json:"html_url,omitempty"` + MembersURL string `json:"members_url,omitempty"` + RepositoriesURL string `json:"repositories_url,omitempty"` + Parent *Team `json:"parent,omitempty"` +} + +type NewTeam struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Maintainers []string `json:"maintainers,omitempty"` + RepoNames []string `json:"repo_names,omitempty"` + ParentTeamID int64 `json:"parent_team_id,omitempty"` + + // Privacy identifies the level of privacy this team should have. + // Possible values are: + // secret - only visible to organization owners and members of this team + // closed - visible to all members of this organization + // Default is "secret". + Privacy string `json:"privacy,omitempty"` +} From bc73164ef8774c91d68365e576f5adba5b1381da Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Fri, 2 Feb 2024 19:05:05 +0100 Subject: [PATCH 35/43] .. --- acceptance/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/acceptance/main.go b/acceptance/main.go index 8752828a..b054ccdf 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -98,6 +98,7 @@ func run(ctx context.Context) error { return err } return a.comment(ctx) + // also - there's OIDC integration: // a.GetIDToken(ctx, "api://AzureADTokenExchange") } From dd71fc943ef24ab92c7394b82f4167284eead3a8 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Fri, 2 Feb 2024 19:29:01 +0100 Subject: [PATCH 36/43] .. --- acceptance/main.go | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/acceptance/main.go b/acceptance/main.go index b054ccdf..7141dbf2 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -35,9 +35,21 @@ type acceptance struct { gh *github.GitHubClient } -func (a *acceptance) runURL() string { - return fmt.Sprintf("%s/%s/actions/runs/%d/job/%s", // ?pr=56 - a.context.ServerURL, a.context.Repository, a.context.RunID, a.context.Job) +func (a *acceptance) runURL(ctx context.Context) (string, error) { + org, repo := a.context.Repo() + workflowJobs := a.gh.ListWorkflowJobs(ctx, org, repo, a.context.RunID) + for workflowJobs.HasNext(ctx) { + job, err := workflowJobs.Next(ctx) + if err != nil { + return "", err + } + if job.RunnerName == a.action.Getenv("RUNNER_NAME") { + url := fmt.Sprintf("%s/%s/actions/runs/%d/job/%d", // ?pr=56 + a.context.ServerURL, a.context.Repository, a.context.RunID, job.ID) + return url, nil + } + } + return "", fmt.Errorf("id not found for current run: %s", a.context.Job) } func (a *acceptance) tag() string { @@ -46,9 +58,13 @@ func (a *acceptance) tag() string { return fmt.Sprintf("\n", a.action.Getenv("GITHUB_WORKFLOW_REF")) } -func (a *acceptance) taggedComment(body string) string { +func (a *acceptance) taggedComment(ctx context.Context, body string) (string, error) { + runUrl, err := a.runURL(ctx) + if err != nil { + return "", fmt.Errorf("run url: %w", err) + } return fmt.Sprintf("%s\nRunning from [%s #%d](%s)%s", - body, a.context.Workflow, a.context.RunNumber, a.runURL(), a.tag()) + body, a.context.Workflow, a.context.RunNumber, runUrl, a.tag()), nil } func (a *acceptance) currentPullRequest(ctx context.Context) (*github.PullRequest, error) { @@ -82,10 +98,18 @@ func (a *acceptance) comment(ctx context.Context) error { if !strings.Contains(comment.Body, tag) { continue } - _, err = a.gh.UpdateIssueComment(ctx, org, repo, comment.ID, a.taggedComment("Updated comment")) + text, err := a.taggedComment(ctx, "Updated comment") + if err != nil { + return fmt.Errorf("text: %w", err) + } + _, err = a.gh.UpdateIssueComment(ctx, org, repo, comment.ID, text) return err } - _, err = a.gh.CreateIssueComment(ctx, org, repo, pr.Number, a.taggedComment("New comment")) + text, err := a.taggedComment(ctx, "New comment") + if err != nil { + return fmt.Errorf("text: %w", err) + } + _, err = a.gh.CreateIssueComment(ctx, org, repo, pr.Number, text) if err != nil { return fmt.Errorf("new comment: %w", err) } From 5b3168a6a5db13c5cc8bd65cd683af760af0931f Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Fri, 2 Feb 2024 20:35:43 +0100 Subject: [PATCH 37/43] ,, --- .github/workflows/acceptance-go-libs.yml | 16 +- acceptance/action.yml | 4 + acceptance/ecosystem/golang.go | 306 +++++++++++++++++++++++ acceptance/ecosystem/report.go | 97 +++++++ acceptance/ecosystem/runner.go | 36 +++ acceptance/go.mod | 11 +- acceptance/go.sum | 8 + acceptance/main.go | 14 +- go.work.sum | 4 +- 9 files changed, 474 insertions(+), 22 deletions(-) create mode 100644 acceptance/ecosystem/golang.go create mode 100644 acceptance/ecosystem/report.go create mode 100644 acceptance/ecosystem/runner.go diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index 8a2cf933..0dbdf94e 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -35,22 +35,10 @@ jobs: with: go-version: 1.21 - - name: Get Job ID from GH API - id: get-job-id - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - jobs=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id}}/attempts/${{ github.run_attempt }}/jobs) - job_id=$(echo $jobs | jq -r '.jobs[] | select(.runner_name=="${{ runner.name }}") | .id') - echo "job_id=$job_id" >> $GITHUB_OUTPUT - - - name: Display Job ID - run: | - echo Job ID: ${{ steps.get-job-id.outputs.job_id }} - echo My full job URL is ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/job/${{ steps.get-job-id.outputs.job_id }} - - name: Acceptance uses: databrickslabs/sandbox/acceptance@gh-action + with: + directory: go-libs env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/acceptance/action.yml b/acceptance/action.yml index fe934d5b..1e5abfcf 100644 --- a/acceptance/action.yml +++ b/acceptance/action.yml @@ -7,6 +7,10 @@ inputs: description: 'Go version' required: false default: 1.21.0 + directory: + description: 'Working directory' + required: false + default: . outputs: sample: description: 'Sample output' diff --git a/acceptance/ecosystem/golang.go b/acceptance/ecosystem/golang.go new file mode 100644 index 00000000..aa121e35 --- /dev/null +++ b/acceptance/ecosystem/golang.go @@ -0,0 +1,306 @@ +package ecosystem + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "io" + "log" + "os" + "regexp" + "strings" + "sync" + "time" + + "github.com/databricks/databricks-sdk-go/logger" + "github.com/databrickslabs/sandbox/go-libs/env" + "github.com/databrickslabs/sandbox/go-libs/fileset" + "github.com/databrickslabs/sandbox/go-libs/process" + "github.com/nxadm/tail" +) + +type GoTestRunner struct{} + +func (r GoTestRunner) Detect(files fileset.FileSet) bool { + return files.Exists(`go.mod`, `module .*\n`) +} + +func (r GoTestRunner) ListAll(files fileset.FileSet) (all []string) { + found, _ := files.FindAll(`_test.go`, `func (TestAcc\w+)\(t`) + for _, v := range found { + all = append(all, v...) + } + return all +} + +// func (r GoTestRunner) setCmdEnv(cmd *exec.Cmd, vars map[string]string) error { +// // run test with current environment +// cmd.Env = os.Environ() +// // and variables from test environment +// for k, v := range vars { +// cmd.Env = append(cmd.Env, fmt.Sprintf(`%s=%s`, k, v)) +// if strings.HasSuffix(k, "_TOKEN") || +// strings.HasSuffix(k, "_CREDENTIALS") || +// strings.HasSuffix(k, "_PASSWORD") || +// strings.HasSuffix(k, "_SAS") || +// strings.HasSuffix(k, "_KEY") || +// strings.HasSuffix(k, "_SECRET") { +// log.Printf("[DEBUG][ENV] %s=***", k) +// continue +// } +// log.Printf("[DEBUG][ENV] %s=%s", k, v) +// } +// return nil +// } + +func (r GoTestRunner) RunOne(ctx context.Context, files fileset.FileSet, one string) error { + found := files.FirstMatch(`_test.go`, fmt.Sprintf(`func %s\(`, one)) + if found == nil { + return fmt.Errorf("not found: %s", one) + } + logger.Infof(ctx, "found test in %s", found.Dir()) + + // make sure to sync on writing to stdout + reader, writer := io.Pipe() + defer reader.Close() + defer writer.Close() + + // create temp file to forward logs produced by subprocess of subprocess + debug, err := os.CreateTemp("/tmp", fmt.Sprintf("debug-%s-*.log", one)) + if err != nil { + return err + } + defer debug.Close() + defer os.Remove(debug.Name()) + tailer, err := tail.TailFile(debug.Name(), tail.Config{Follow: true}) + if err != nil { + return err + } + go io.CopyBuffer(os.Stdout, reader, make([]byte, 128)) + go func() { + for line := range tailer.Lines { + writer.Write([]byte(line.Text + "\n")) + } + }() + + return process.Forwarded(ctx, []string{"go", "test", ".", "-v", + "-coverpkg=./...", + "-coverprofile=coverage.txt", + "-timeout=30m", + "-run", fmt.Sprintf("^%s$", one)}, + reader, writer, os.Stderr, + process.WithDir(found.Dir()), + + // Terraform debug logging is a bit involved. + // See https://www.terraform.io/plugin/log/managing + process.WithEnv("TF_LOG", "DEBUG"), + process.WithEnv("TF_LOG_SDK", "INFO"), + process.WithEnv("TF_LOG_PATH", debug.Name()), + ) +} + +func (r GoTestRunner) RunAll(ctx context.Context, files fileset.FileSet) (results TestReport, err error) { + goMod := files.FirstMatch(`go.mod`, `module .*\n`) + root := files.Root() + if goMod == nil { + return nil, fmt.Errorf("%s has no module file", root) + } + raw, err := goMod.Raw() + if err != nil { + return nil, err + } + lines := strings.Split(string(raw), "\n") + module := strings.Split(lines[0], " ")[1] + + // make sure to sync on writing to stdout + pipeReader, pipeWriter := io.Pipe() + + // certain environments need to further filter down the set of tests to run, + // hence the `TEST_FILTER` environment variable (for now) with `TestAcc` as + // the default prefix. + testFilter, ok := env.Lookup(ctx, "TEST_FILTER") + if !ok { + // testFilter = "TestAcc" + testFilter = "Test" + } + // Tee into file so we can debug issues with logic below. + teeFile, err := os.OpenFile("test.log", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if err != nil { + logger.Errorf(ctx, "unable to open log file: %s", err) + return nil, err + } + defer teeFile.Close() + reader := io.TeeReader(pipeReader, teeFile) + + // We have to wait for the output to be fully processed before returning. + var wg sync.WaitGroup + wg.Add(1) + + go func() { + defer wg.Done() + ch := ReadTestEvents(ctx, reader) + results = CollectTestReport(ch) + + // Strip root module from package name for brevity. + for i := range results { + results[i].Package = strings.TrimPrefix(results[i].Package, module+"/") + } + }() + + // we may later need to add something like `"-parallel", "20"``, preferably configurable. + // on GitHub actions we have only a single core available, hence no test parallelism. + // on the other hand, current way of logging pollutes test log output with another test log output, + // that may lead to confusion. Hence, on CI it's still better to have no parallelism. + err = process.Forwarded(ctx, []string{ + "go", "test", "./...", "-json", + "-timeout", "1h", + "-coverpkg=./...", + "-coverprofile=coverage.txt", + "-run", fmt.Sprintf("^%s", testFilter), + }, pipeReader, pipeWriter, os.Stderr, + process.WithDir(root)) + + // The process has terminated; close the writer it had been writing into. + pipeWriter.Close() + + // Wait for the goroutine above to finish collecting the test report. + wg.Wait() + + return results, err +} + +// TestEvent is defined at https://pkg.go.dev/cmd/test2json#hdr-Output_Format. +type TestEvent struct { + Time time.Time // encodes as an RFC3339-format string + Action string + Package string + Test string + Elapsed float64 // seconds + Output string +} + +func ReadTestEvents(ctx context.Context, reader io.Reader) <-chan TestEvent { + ch := make(chan TestEvent) + + go func() { + defer close(ch) + + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + line := scanner.Bytes() + + var testEvent TestEvent + err := json.Unmarshal(line, &testEvent) + if err != nil { + logger.Errorf(ctx, "cannot parse JSON line: %s - %s", err, string(line)) + return + } + + ch <- testEvent + } + + err := scanner.Err() + if err != nil && err != io.ErrClosedPipe { + logger.Errorf(ctx, "cannot scan json lines: %s", err) + return + } + }() + + return ch +} + +func collectOutput(testEvents []TestEvent) string { + var b strings.Builder + for _, testEvent := range testEvents { + if testEvent.Action == "output" { + b.WriteString(testEvent.Output) + } + } + return b.String() +} + +func summarize(output string) string { + var re = regexp.MustCompile(`(?mUs)Error:\s+(.*)Test:\s+`) + concise := re.FindAllString(output, -1) + return strings.Join(concise, "\n") +} + +func CollectTestReport(ch <-chan TestEvent) (report TestReport) { + testEventsByKey := map[string][]TestEvent{} + + for testEvent := range ch { + if testEvent.Test == "" { + continue + } + + // Keep track of all events per test. + key := fmt.Sprintf("%s/%s", testEvent.Package, testEvent.Test) + testEventsByKey[key] = append(testEventsByKey[key], testEvent) + + // Only take action on pass/skip/fail + switch testEvent.Action { + case "pass": + testLog := collectOutput(testEventsByKey[key]) + delete(testEventsByKey, key) + report = append(report, TestResult{ + Time: testEvent.Time, + Package: testEvent.Package, + Name: testEvent.Test, + Pass: true, + Skip: false, + Output: testLog, + Elapsed: testEvent.Elapsed, + }) + log.Printf("[INFO] ✅ %s (%0.3fs)", testEvent.Test, testEvent.Elapsed) + case "skip": + testLog := collectOutput(testEventsByKey[key]) + delete(testEventsByKey, key) + report = append(report, TestResult{ + Time: testEvent.Time, + Package: testEvent.Package, + Name: testEvent.Test, + Pass: false, + Skip: true, + Output: testLog, + Elapsed: testEvent.Elapsed, + }) + log.Printf("[INFO] 🦥 %s: %s", testEvent.Test, testLog) + case "fail": + testLog := collectOutput(testEventsByKey[key]) + delete(testEventsByKey, key) + report = append(report, TestResult{ + Time: testEvent.Time, + Package: testEvent.Package, + Name: testEvent.Test, + Pass: false, + Skip: false, + Output: testLog, + Elapsed: testEvent.Elapsed, + }) + log.Printf("[INFO] ❌ %s (%0.3fs)\n%s", testEvent.Test, testEvent.Elapsed, summarize(testLog)) + // We print the full error logs in case the test fails for easier debugging on github actions + log.Printf("[INFO][%s] %s", testEvent.Test, testLog) + default: + continue + } + } + + // Mark remaining tests as failed (timed out?) + for _, testEvents := range testEventsByKey { + testEvent := testEvents[len(testEvents)-1] + testLog := collectOutput(testEvents) + report = append(report, TestResult{ + Time: testEvent.Time, + Package: testEvent.Package, + Name: testEvent.Test, + Pass: false, + Skip: false, + Output: testLog, + Elapsed: testEvent.Elapsed, + }) + log.Printf("[INFO] ❌ %s (%0.3fs)\n%s", testEvent.Test, testEvent.Elapsed, summarize(testLog)) + } + + return +} diff --git a/acceptance/ecosystem/report.go b/acceptance/ecosystem/report.go new file mode 100644 index 00000000..275a3c1e --- /dev/null +++ b/acceptance/ecosystem/report.go @@ -0,0 +1,97 @@ +package ecosystem + +import ( + "encoding/json" + "fmt" + "os" + "path" + "strings" + "time" +) + +type TestResult struct { + Time time.Time `json:"ts"` // encodes as an RFC3339-format string + Project string `json:"project"` + Package string `json:"package"` + Name string `json:"name"` + Pass bool `json:"pass"` + Skip bool `json:"skip"` + Output string `json:"output"` + Elapsed float64 `json:"elapsed"` +} + +type TestReport []TestResult + +func (r TestReport) Pass() bool { + for _, v := range r { + if !v.Pass { + return false + } + } + return true +} + +func (r TestReport) String() string { + var passed, failed, run, skipped int + for _, v := range r { + if v.Skip { + skipped++ + continue + } + run++ + if v.Pass { + passed++ + continue + } + failed++ + } + result := "❌" + if passed == run { + result = "✅" + } + return fmt.Sprintf("%s %d/%d passed, %d failed, %d skipped", + result, passed, run, failed, skipped) +} + +func (r TestReport) StepSummary() string { + if r.Pass() { + return "# " + r.String() + } + res := []string{"# " + r.String()} + res = append(res, "") + for _, v := range r { + if v.Pass || v.Skip { + continue + } + res = append(res, "") + } + res = append(res, "
") + res = append(res, fmt.Sprintf("❌ %s.%s (%0.3fs)
", v.Package, v.Name, v.Elapsed)) + res = append(res, fmt.Sprintf("
%s
", v.Output)) + res = append(res, "
") + return strings.Join(res, "\n") +} + +func (r TestReport) WriteReport(project, dst string) error { + parent := path.Dir(dst) + if _, err := os.Stat(parent); os.IsNotExist(err) { + err = os.MkdirAll(parent, 0755) + if err != nil { + return err + } + } + f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0755) + if err != nil { + return err + } + for _, v := range r { + v.Project = project + raw, err := json.Marshal(v) + if err != nil { + return err + } + f.Write(raw) + f.WriteString("\n") + } + return f.Close() +} diff --git a/acceptance/ecosystem/runner.go b/acceptance/ecosystem/runner.go new file mode 100644 index 00000000..bdc69ca8 --- /dev/null +++ b/acceptance/ecosystem/runner.go @@ -0,0 +1,36 @@ +package ecosystem + +import ( + "context" + "fmt" + + "github.com/databrickslabs/sandbox/go-libs/fileset" +) + +type TestRunner interface { + Detect(files fileset.FileSet) bool + ListAll(files fileset.FileSet) []string + RunOne(ctx context.Context, files fileset.FileSet, one string) error + RunAll(ctx context.Context, files fileset.FileSet) (TestReport, error) +} + +var runners = []TestRunner{ + GoTestRunner{}, +} + +func RunAll(ctx context.Context, folder string) (TestReport, error) { + files, err := fileset.RecursiveChildren(folder) + if err != nil { + return nil, fmt.Errorf("fileset: %w", err) + } + var runner TestRunner + for _, v := range runners { + if v.Detect(files) { + runner = v + } + } + if runner == nil { + return nil, fmt.Errorf("no supported ecosystem detected") + } + return runner.RunAll(ctx, files) +} diff --git a/acceptance/go.mod b/acceptance/go.mod index 8997c63c..5cbe8475 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -2,4 +2,13 @@ module github.com/databrickslabs/sandbox/acceptance go 1.21.0 -require github.com/sethvargo/go-githubactions v1.2.0 +require ( + github.com/nxadm/tail v1.4.11 + github.com/sethvargo/go-githubactions v1.2.0 +) + +require ( + github.com/fsnotify/fsnotify v1.6.0 // indirect + golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect +) diff --git a/acceptance/go.sum b/acceptance/go.sum index 242e7883..4d1044f6 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -1,2 +1,10 @@ +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/sethvargo/go-githubactions v1.2.0 h1:Gbr36trCAj6uq7Rx1DolY1NTIg0wnzw3/N5WHdKIjME= github.com/sethvargo/go-githubactions v1.2.0/go.mod h1:7/4WeHgYfSz9U5vwuToCK9KPnELVHAhGtRwLREOQV80= +golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/acceptance/main.go b/acceptance/main.go index 7141dbf2..87b020de 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -6,6 +6,7 @@ import ( "fmt" "strings" + "github.com/databrickslabs/sandbox/acceptance/ecosystem" "github.com/databrickslabs/sandbox/go-libs/github" "github.com/sethvargo/go-githubactions" ) @@ -82,7 +83,7 @@ func (a *acceptance) currentPullRequest(ctx context.Context) (*github.PullReques return event.PullRequest, nil } -func (a *acceptance) comment(ctx context.Context) error { +func (a *acceptance) comment(ctx context.Context, commentText string) error { pr, err := a.currentPullRequest(ctx) if err != nil { return fmt.Errorf("pr: %w", err) @@ -98,14 +99,14 @@ func (a *acceptance) comment(ctx context.Context) error { if !strings.Contains(comment.Body, tag) { continue } - text, err := a.taggedComment(ctx, "Updated comment") + text, err := a.taggedComment(ctx, commentText) if err != nil { return fmt.Errorf("text: %w", err) } _, err = a.gh.UpdateIssueComment(ctx, org, repo, comment.ID, text) return err } - text, err := a.taggedComment(ctx, "New comment") + text, err := a.taggedComment(ctx, commentText) if err != nil { return fmt.Errorf("text: %w", err) } @@ -121,7 +122,12 @@ func run(ctx context.Context) error { if err != nil { return err } - return a.comment(ctx) + directory := a.action.GetInput("directory") + report, err := ecosystem.RunAll(ctx, directory) + if err != nil { + return err + } + return a.comment(ctx, report.StepSummary()) // also - there's OIDC integration: // a.GetIDToken(ctx, "api://AzureADTokenExchange") diff --git a/go.work.sum b/go.work.sum index 7e22d412..f9f8811d 100644 --- a/go.work.sum +++ b/go.work.sum @@ -163,8 +163,6 @@ github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -358,7 +356,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From c421dfdf9a0e6a402c9033afe7cc3fe8e6a7e691 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Fri, 2 Feb 2024 22:31:25 +0100 Subject: [PATCH 38/43] got something working --- acceptance/boilerplate/actions.go | 124 ++++++++++ acceptance/ecosystem/golang.go | 24 +- acceptance/main.go | 119 +--------- acceptance/main_test.go | 16 ++ go-libs/coverage.txt | 374 ++++++++++++++++++++++++++++++ go-libs/lite/logger.go | 2 +- 6 files changed, 536 insertions(+), 123 deletions(-) create mode 100644 acceptance/boilerplate/actions.go create mode 100644 acceptance/main_test.go create mode 100644 go-libs/coverage.txt diff --git a/acceptance/boilerplate/actions.go b/acceptance/boilerplate/actions.go new file mode 100644 index 00000000..37a0b20a --- /dev/null +++ b/acceptance/boilerplate/actions.go @@ -0,0 +1,124 @@ +package boilerplate + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/databrickslabs/sandbox/go-libs/env" + "github.com/databrickslabs/sandbox/go-libs/github" + "github.com/sethvargo/go-githubactions" +) + +func New(ctx context.Context, opts ...githubactions.Option) (*boilerplate, error) { + opts = append(opts, githubactions.WithGetenv(func(key string) string { + return env.Get(ctx, key) + })) + a := githubactions.New(opts...) + // token, err := a.GetIDToken(context.Background(), "") + // if err != nil { + // return nil, fmt.Errorf("oidc: %w", err) + // } + context, err := a.Context() + if err != nil { + return nil, err + } + return &boilerplate{ + Action: a, + context: context, + GitHub: github.NewClient(&github.GitHubConfig{ + GitHubTokenSource: github.GitHubTokenSource{}, + }), + }, nil +} + +type boilerplate struct { + Action *githubactions.Action + context *githubactions.GitHubContext + GitHub *github.GitHubClient +} + +func (a *boilerplate) RunURL(ctx context.Context) (string, error) { + org, repo := a.context.Repo() + workflowJobs := a.GitHub.ListWorkflowJobs(ctx, org, repo, a.context.RunID) + for workflowJobs.HasNext(ctx) { + job, err := workflowJobs.Next(ctx) + if err != nil { + return "", err + } + if job.RunnerName == a.Action.Getenv("RUNNER_NAME") { + url := fmt.Sprintf("%s/%s/actions/runs/%d/job/%d", // ?pr=56 + a.context.ServerURL, a.context.Repository, a.context.RunID, job.ID) + return url, nil + } + } + return "", fmt.Errorf("id not found for current run: %s", a.context.Job) +} + +func (a *boilerplate) tag() string { + // The ref path to the workflow. For example, + // octocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch. + return fmt.Sprintf("\n", a.Action.Getenv("GITHUB_WORKFLOW_REF")) +} + +func (a *boilerplate) taggedComment(ctx context.Context, body string) (string, error) { + runUrl, err := a.RunURL(ctx) + if err != nil { + return "", fmt.Errorf("run url: %w", err) + } + return fmt.Sprintf("%s\nRunning from [%s #%d](%s)%s", + body, a.context.Workflow, a.context.RunNumber, runUrl, a.tag()), nil +} + +func (a *boilerplate) currentPullRequest(ctx context.Context) (*github.PullRequest, error) { + if a.context.Event == nil { + return nil, fmt.Errorf("missing actions event") + } + raw, err := json.MarshalIndent(a.context.Event, "", " ") + if err != nil { + return nil, fmt.Errorf("marshall: %w", err) + } + var event struct { + PullRequest *github.PullRequest `json:"pull_request"` + } + err = json.Unmarshal(raw, &event) + if err != nil { + return nil, fmt.Errorf("unmarshall: %w", err) + } + return event.PullRequest, nil +} + +func (a *boilerplate) Comment(ctx context.Context, commentText string) error { + pr, err := a.currentPullRequest(ctx) + if err != nil { + return fmt.Errorf("pr: %w", err) + } + tag := a.tag() + org, repo := a.context.Repo() + it := a.GitHub.GetIssueComments(ctx, org, repo, pr.Number) + for it.HasNext(ctx) { + comment, err := it.Next(ctx) + if err != nil { + return fmt.Errorf("comment: %w", err) + } + if !strings.Contains(comment.Body, tag) { + continue + } + text, err := a.taggedComment(ctx, commentText) + if err != nil { + return fmt.Errorf("text: %w", err) + } + _, err = a.GitHub.UpdateIssueComment(ctx, org, repo, comment.ID, text) + return err + } + text, err := a.taggedComment(ctx, commentText) + if err != nil { + return fmt.Errorf("text: %w", err) + } + _, err = a.GitHub.CreateIssueComment(ctx, org, repo, pr.Number, text) + if err != nil { + return fmt.Errorf("new comment: %w", err) + } + return nil +} diff --git a/acceptance/ecosystem/golang.go b/acceptance/ecosystem/golang.go index aa121e35..99c81fe2 100644 --- a/acceptance/ecosystem/golang.go +++ b/acceptance/ecosystem/golang.go @@ -114,7 +114,11 @@ func (r GoTestRunner) RunAll(ctx context.Context, files fileset.FileSet) (result module := strings.Split(lines[0], " ")[1] // make sure to sync on writing to stdout - pipeReader, pipeWriter := io.Pipe() + // See https://github.com/golang/go/issues/10338 + pipeReader, pipeWriter, err := os.Pipe() + if err != nil { + return nil, err + } // certain environments need to further filter down the set of tests to run, // hence the `TEST_FILTER` environment variable (for now) with `TestAcc` as @@ -130,7 +134,6 @@ func (r GoTestRunner) RunAll(ctx context.Context, files fileset.FileSet) (result logger.Errorf(ctx, "unable to open log file: %s", err) return nil, err } - defer teeFile.Close() reader := io.TeeReader(pipeReader, teeFile) // We have to wait for the output to be fully processed before returning. @@ -139,8 +142,8 @@ func (r GoTestRunner) RunAll(ctx context.Context, files fileset.FileSet) (result go func() { defer wg.Done() - ch := ReadTestEvents(ctx, reader) - results = CollectTestReport(ch) + ch := readGoTestEvents(ctx, reader) + results = collectTestReport(ch) // Strip root module from package name for brevity. for i := range results { @@ -154,17 +157,22 @@ func (r GoTestRunner) RunAll(ctx context.Context, files fileset.FileSet) (result // that may lead to confusion. Hence, on CI it's still better to have no parallelism. err = process.Forwarded(ctx, []string{ "go", "test", "./...", "-json", - "-timeout", "1h", + // "-timeout", "1h", + "-timeout", "1s", "-coverpkg=./...", "-coverprofile=coverage.txt", "-run", fmt.Sprintf("^%s", testFilter), - }, pipeReader, pipeWriter, os.Stderr, + }, nil, pipeWriter, pipeWriter, process.WithDir(root)) // The process has terminated; close the writer it had been writing into. pipeWriter.Close() // Wait for the goroutine above to finish collecting the test report. + // + // If c.Stdin is not an *os.File, Wait also waits for the I/O loop + // copying from c.Stdin into the process's standard input + // to complete. wg.Wait() return results, err @@ -180,7 +188,7 @@ type TestEvent struct { Output string } -func ReadTestEvents(ctx context.Context, reader io.Reader) <-chan TestEvent { +func readGoTestEvents(ctx context.Context, reader io.Reader) <-chan TestEvent { ch := make(chan TestEvent) go func() { @@ -226,7 +234,7 @@ func summarize(output string) string { return strings.Join(concise, "\n") } -func CollectTestReport(ch <-chan TestEvent) (report TestReport) { +func collectTestReport(ch <-chan TestEvent) (report TestReport) { testEventsByKey := map[string][]TestEvent{} for testEvent := range ch { diff --git a/acceptance/main.go b/acceptance/main.go index 87b020de..f851ea03 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -2,132 +2,23 @@ package main import ( "context" - "encoding/json" - "fmt" - "strings" + "github.com/databrickslabs/sandbox/acceptance/boilerplate" "github.com/databrickslabs/sandbox/acceptance/ecosystem" - "github.com/databrickslabs/sandbox/go-libs/github" "github.com/sethvargo/go-githubactions" ) -func New(opts ...githubactions.Option) (*acceptance, error) { - a := githubactions.New(opts...) - // token, err := a.GetIDToken(context.Background(), "") - // if err != nil { - // return nil, fmt.Errorf("oidc: %w", err) - // } - context, err := a.Context() - if err != nil { - return nil, err - } - return &acceptance{ - action: a, - context: context, - gh: github.NewClient(&github.GitHubConfig{ - GitHubTokenSource: github.GitHubTokenSource{}, - }), - }, nil -} - -type acceptance struct { - action *githubactions.Action - context *githubactions.GitHubContext - gh *github.GitHubClient -} - -func (a *acceptance) runURL(ctx context.Context) (string, error) { - org, repo := a.context.Repo() - workflowJobs := a.gh.ListWorkflowJobs(ctx, org, repo, a.context.RunID) - for workflowJobs.HasNext(ctx) { - job, err := workflowJobs.Next(ctx) - if err != nil { - return "", err - } - if job.RunnerName == a.action.Getenv("RUNNER_NAME") { - url := fmt.Sprintf("%s/%s/actions/runs/%d/job/%d", // ?pr=56 - a.context.ServerURL, a.context.Repository, a.context.RunID, job.ID) - return url, nil - } - } - return "", fmt.Errorf("id not found for current run: %s", a.context.Job) -} - -func (a *acceptance) tag() string { - // The ref path to the workflow. For example, - // octocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch. - return fmt.Sprintf("\n", a.action.Getenv("GITHUB_WORKFLOW_REF")) -} - -func (a *acceptance) taggedComment(ctx context.Context, body string) (string, error) { - runUrl, err := a.runURL(ctx) - if err != nil { - return "", fmt.Errorf("run url: %w", err) - } - return fmt.Sprintf("%s\nRunning from [%s #%d](%s)%s", - body, a.context.Workflow, a.context.RunNumber, runUrl, a.tag()), nil -} - -func (a *acceptance) currentPullRequest(ctx context.Context) (*github.PullRequest, error) { - raw, err := json.MarshalIndent(a.context.Event, "", " ") - if err != nil { - return nil, fmt.Errorf("marshall: %w", err) - } - var event struct { - PullRequest *github.PullRequest `json:"pull_request"` - } - err = json.Unmarshal(raw, &event) - if err != nil { - return nil, fmt.Errorf("unmarshall: %w", err) - } - return event.PullRequest, nil -} - -func (a *acceptance) comment(ctx context.Context, commentText string) error { - pr, err := a.currentPullRequest(ctx) - if err != nil { - return fmt.Errorf("pr: %w", err) - } - tag := a.tag() - org, repo := a.context.Repo() - it := a.gh.GetIssueComments(ctx, org, repo, pr.Number) - for it.HasNext(ctx) { - comment, err := it.Next(ctx) - if err != nil { - return fmt.Errorf("comment: %w", err) - } - if !strings.Contains(comment.Body, tag) { - continue - } - text, err := a.taggedComment(ctx, commentText) - if err != nil { - return fmt.Errorf("text: %w", err) - } - _, err = a.gh.UpdateIssueComment(ctx, org, repo, comment.ID, text) - return err - } - text, err := a.taggedComment(ctx, commentText) - if err != nil { - return fmt.Errorf("text: %w", err) - } - _, err = a.gh.CreateIssueComment(ctx, org, repo, pr.Number, text) - if err != nil { - return fmt.Errorf("new comment: %w", err) - } - return nil -} - -func run(ctx context.Context) error { - a, err := New() +func run(ctx context.Context, opts ...githubactions.Option) error { + b, err := boilerplate.New(ctx) if err != nil { return err } - directory := a.action.GetInput("directory") + directory := b.Action.GetInput("directory") report, err := ecosystem.RunAll(ctx, directory) if err != nil { return err } - return a.comment(ctx, report.StepSummary()) + return b.Comment(ctx, report.StepSummary()) // also - there's OIDC integration: // a.GetIDToken(ctx, "api://AzureADTokenExchange") diff --git a/acceptance/main_test.go b/acceptance/main_test.go new file mode 100644 index 00000000..4fc78824 --- /dev/null +++ b/acceptance/main_test.go @@ -0,0 +1,16 @@ +package main + +import ( + "context" + "testing" + + "github.com/databrickslabs/sandbox/go-libs/env" + "github.com/stretchr/testify/assert" +) + +func TestXxx(t *testing.T) { + ctx := context.Background() + ctx = env.Set(ctx, "INPUT_DIRECTORY", "../go-libs") + err := run(ctx) + assert.NoError(t, err) +} diff --git a/go-libs/coverage.txt b/go-libs/coverage.txt new file mode 100644 index 00000000..890eabeb --- /dev/null +++ b/go-libs/coverage.txt @@ -0,0 +1,374 @@ +mode: set +github.com/databrickslabs/sandbox/go-libs/env/context.go:13.53,15.22 2 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:15.22,17.3 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:18.2,18.12 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:21.52,22.16 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:22.16,24.3 1 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:25.2,26.9 2 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:26.9,28.3 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:29.2,29.10 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:32.71,34.2 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:38.61,43.8 3 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:43.8,45.3 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:48.2,48.26 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:53.50,56.2 2 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:62.66,66.2 3 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:68.26,69.31 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:69.31,71.3 1 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:72.2,72.15 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:75.73,77.2 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:82.55,84.16 2 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:84.16,86.3 1 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:87.2,87.18 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:92.49,94.36 2 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:94.36,96.22 2 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:96.22,97.12 1 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:99.3,99.25 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:102.2,102.32 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:102.32,104.3 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:105.2,105.10 1 1 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:22.57,26.2 1 1 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:32.39,34.2 1 1 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:36.61,37.44 1 1 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:37.44,38.21 1 1 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:38.21,39.12 1 1 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:41.3,41.31 1 1 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:41.31,43.15 2 1 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:43.15,44.13 1 1 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:46.4,46.17 1 1 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:49.2,49.12 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:18.83,24.2 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:33.89,35.36 2 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:35.36,37.3 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:37.8,37.23 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:37.23,39.3 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:39.8,39.54 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:39.54,41.3 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:42.2,42.25 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:53.111,56.31 3 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:56.31,58.3 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:59.2,59.16 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:59.16,61.3 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:62.2,62.32 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:65.76,68.16 3 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:68.16,70.3 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:71.2,73.36 3 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:73.36,76.17 3 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:76.17,78.4 1 0 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:79.3,80.17 2 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:80.17,82.4 1 0 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:83.3,83.19 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:84.8,84.23 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:84.23,86.3 1 0 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:87.2,87.18 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:90.43,92.2 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:94.57,97.16 3 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:97.16,99.3 1 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:100.2,102.16 3 1 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:102.16,104.3 1 0 +github.com/databrickslabs/sandbox/go-libs/localcache/jsonfile.go:105.2,105.16 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:13.53,15.22 2 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:15.22,17.3 1 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:18.2,18.12 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:21.52,22.16 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:22.16,24.3 1 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:25.2,26.9 2 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:26.9,28.3 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:29.2,29.10 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:32.71,34.2 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:38.61,43.8 3 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:43.8,45.3 1 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:48.2,48.26 1 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:53.50,56.2 2 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:62.66,66.2 3 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:68.26,69.31 1 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:69.31,71.3 1 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:72.2,72.15 1 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:75.73,77.2 1 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:82.55,84.16 2 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:84.16,86.3 1 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:87.2,87.18 1 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:92.49,94.36 2 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:94.36,96.22 2 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:96.22,97.12 1 0 +github.com/databrickslabs/sandbox/go-libs/env/context.go:99.3,99.25 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:102.2,102.32 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:102.32,104.3 1 1 +github.com/databrickslabs/sandbox/go-libs/env/context.go:105.2,105.10 1 1 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:22.57,26.2 1 0 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:32.39,34.2 1 0 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:36.61,37.44 1 0 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:37.44,38.21 1 0 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:38.21,39.12 1 0 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:41.3,41.31 1 0 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:41.31,43.15 2 0 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:43.15,44.13 1 0 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:46.4,46.17 1 0 +github.com/databrickslabs/sandbox/go-libs/env/loader.go:49.2,49.12 1 0 +github.com/databrickslabs/sandbox/go-libs/process/background.go:21.42,23.2 1 1 +github.com/databrickslabs/sandbox/go-libs/process/background.go:25.42,27.2 1 1 +github.com/databrickslabs/sandbox/go-libs/process/background.go:29.89,41.33 9 1 +github.com/databrickslabs/sandbox/go-libs/process/background.go:41.33,43.3 1 1 +github.com/databrickslabs/sandbox/go-libs/process/background.go:44.2,44.25 1 1 +github.com/databrickslabs/sandbox/go-libs/process/background.go:44.25,46.17 2 1 +github.com/databrickslabs/sandbox/go-libs/process/background.go:46.17,48.4 1 1 +github.com/databrickslabs/sandbox/go-libs/process/background.go:50.2,50.41 1 1 +github.com/databrickslabs/sandbox/go-libs/process/background.go:50.41,57.3 1 1 +github.com/databrickslabs/sandbox/go-libs/process/background.go:58.2,58.29 1 1 +github.com/databrickslabs/sandbox/go-libs/process/forwarded.go:14.125,25.33 7 1 +github.com/databrickslabs/sandbox/go-libs/process/forwarded.go:25.33,27.3 1 1 +github.com/databrickslabs/sandbox/go-libs/process/forwarded.go:30.2,30.25 1 1 +github.com/databrickslabs/sandbox/go-libs/process/forwarded.go:30.25,32.17 2 1 +github.com/databrickslabs/sandbox/go-libs/process/forwarded.go:32.17,34.4 1 0 +github.com/databrickslabs/sandbox/go-libs/process/forwarded.go:37.2,37.25 1 1 +github.com/databrickslabs/sandbox/go-libs/process/opts.go:13.44,14.54 1 0 +github.com/databrickslabs/sandbox/go-libs/process/opts.go:14.54,18.3 3 0 +github.com/databrickslabs/sandbox/go-libs/process/opts.go:21.50,22.54 1 0 +github.com/databrickslabs/sandbox/go-libs/process/opts.go:22.54,23.26 1 0 +github.com/databrickslabs/sandbox/go-libs/process/opts.go:23.26,25.18 2 0 +github.com/databrickslabs/sandbox/go-libs/process/opts.go:25.18,27.5 1 0 +github.com/databrickslabs/sandbox/go-libs/process/opts.go:29.3,29.13 1 0 +github.com/databrickslabs/sandbox/go-libs/process/opts.go:33.37,34.52 1 1 +github.com/databrickslabs/sandbox/go-libs/process/opts.go:34.52,37.3 2 1 +github.com/databrickslabs/sandbox/go-libs/process/opts.go:40.52,41.52 1 0 +github.com/databrickslabs/sandbox/go-libs/process/opts.go:41.52,43.17 2 0 +github.com/databrickslabs/sandbox/go-libs/process/opts.go:43.17,45.4 1 0 +github.com/databrickslabs/sandbox/go-libs/process/opts.go:46.3,47.13 2 0 +github.com/databrickslabs/sandbox/go-libs/process/opts.go:51.55,52.52 1 1 +github.com/databrickslabs/sandbox/go-libs/process/opts.go:52.52,56.3 3 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:14.68,18.2 3 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:20.55,22.8 2 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:22.8,24.3 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:25.2,25.18 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:41.62,44.2 2 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:46.59,49.2 2 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:51.79,54.2 2 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:61.71,68.2 2 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:71.71,78.2 2 0 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:81.78,88.2 2 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:90.39,92.2 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:94.33,96.2 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:98.52,99.28 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:99.28,101.3 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:102.2,102.8 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:106.63,108.30 2 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:108.30,109.32 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:109.32,111.11 2 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:111.11,112.13 1 0 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:114.4,114.22 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:117.2,117.20 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:121.52,124.2 2 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:126.51,133.2 3 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:135.48,138.8 3 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:138.8,139.24 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:139.24,141.4 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:142.3,142.24 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:142.24,144.4 1 0 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:145.3,145.18 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:147.2,147.23 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:147.23,149.3 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:150.2,150.32 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:150.32,152.3 1 1 +github.com/databrickslabs/sandbox/go-libs/process/stub.go:153.2,153.26 1 1 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:23.13,29.2 2 1 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:32.81,38.2 5 1 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:41.76,43.46 2 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:43.46,45.3 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:46.2,49.62 4 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:53.77,59.16 4 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:59.16,61.3 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:62.2,62.28 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:62.28,64.3 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:65.2,69.30 4 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:73.76,79.16 4 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:79.16,81.3 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:82.2,82.28 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:82.28,84.3 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:85.2,89.30 4 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:93.57,95.17 2 1 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:95.17,97.3 1 1 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:98.2,98.14 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:101.37,103.16 2 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:103.16,104.54 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:106.2,106.10 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:110.43,113.2 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:116.42,119.19 3 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:119.19,121.3 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:122.2,122.21 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:122.21,124.3 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:125.2,125.18 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:128.51,130.19 2 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:130.19,132.3 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:133.2,133.21 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:133.21,135.3 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:136.2,136.18 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:139.59,140.17 1 1 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:140.17,144.3 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:145.2,145.16 1 1 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:149.23,152.2 2 1 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:155.58,156.18 1 1 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:156.18,158.3 1 1 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:159.2,160.16 2 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:160.16,162.3 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:163.2,164.16 2 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:164.16,166.3 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:167.2,169.16 3 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:169.16,171.3 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:172.2,173.9 2 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:173.9,175.3 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:176.2,176.25 1 0 +github.com/databrickslabs/sandbox/go-libs/fixtures/init.go:176.25,178.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:30.65,33.37 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:33.37,35.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:36.2,36.34 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:36.34,38.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:39.2,40.18 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:40.18,42.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:43.2,44.13 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:52.71,55.18 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:55.18,57.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:58.2,60.17 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:69.46,70.19 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:70.19,73.3 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:74.2,74.32 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:74.32,76.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:77.2,78.16 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:78.16,81.3 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:82.2,82.32 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:82.32,85.17 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:85.17,90.4 4 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:92.2,92.13 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:121.84,123.9 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:123.9,125.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:126.2,126.15 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:129.88,130.15 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:130.15,132.15 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:132.15,134.4 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:136.2,136.27 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:137.15,139.13 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:140.62,141.42 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:142.18,143.49 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:143.49,146.18 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:146.18,148.5 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:149.4,150.14 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:152.3,153.13 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:154.15,157.13 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:158.10,159.23 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:160.36,161.43 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:162.34,163.41 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:164.37,166.43 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:169.2,169.12 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:173.60,174.24 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:174.24,177.17 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:177.17,179.4 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:180.3,180.28 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:182.2,182.12 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:186.60,187.24 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:187.24,190.17 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:190.17,192.4 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:193.3,193.19 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:195.2,195.12 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:198.87,201.16 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:201.16,203.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:204.2,205.23 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:205.23,207.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:208.2,210.16 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:210.16,212.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:213.2,217.27 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:218.14,219.63 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:220.17,221.66 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:222.18,223.67 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:224.14,225.63 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:226.10,227.59 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:231.85,234.16 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:234.16,236.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:237.2,238.23 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:238.23,240.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:241.2,243.16 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:243.16,245.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:246.2,250.27 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:251.26,253.13 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:254.23,255.61 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:256.27,257.65 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:258.23,259.61 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:260.10,261.59 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:271.49,273.2 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:275.39,277.2 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:279.61,281.2 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/iteration.go:283.71,285.2 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:25.73,31.2 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:42.82,43.136 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:43.136,45.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:46.2,47.24 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:47.24,49.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:50.2,51.24 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:51.24,53.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:54.2,54.56 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:54.56,56.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:57.2,57.63 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:57.63,59.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:60.2,60.54 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:60.54,62.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:63.2,63.54 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:63.54,65.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:66.2,79.8 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:79.8,81.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:82.2,82.33 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:94.96,97.25 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:97.25,99.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:100.2,101.16 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:101.16,103.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:104.2,105.31 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:105.31,106.18 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:107.44,108.12 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:109.25,112.29 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:113.11,114.27 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:117.2,117.41 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:117.41,121.3 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:122.2,122.48 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:126.130,127.27 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:127.27,129.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:130.2,130.27 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:130.27,132.17 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:132.17,134.4 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:135.3,135.32 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:137.2,137.22 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:137.22,139.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:140.2,141.96 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:141.96,143.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:144.2,154.16 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:154.16,156.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:157.2,158.19 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:158.19,160.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:161.2,161.49 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:161.49,163.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:164.2,164.48 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:164.48,166.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:167.2,171.34 5 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:171.34,173.17 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:173.17,175.4 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:176.3,177.26 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:177.26,179.4 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:180.3,181.18 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:181.18,183.4 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:184.3,184.43 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:184.43,191.4 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:192.3,193.55 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:193.55,195.4 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:196.3,197.33 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:197.33,200.4 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:201.3,204.12 3 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:206.2,209.16 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:209.16,211.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:212.2,212.78 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:216.109,218.16 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:218.16,220.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:221.2,222.21 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:222.21,224.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:225.2,226.27 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:226.27,228.3 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:229.2,241.8 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:245.99,249.2 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:252.121,254.2 1 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:257.79,262.2 2 0 +github.com/databrickslabs/sandbox/go-libs/sqlexec/sqlexec.go:265.101,267.2 1 0 diff --git a/go-libs/lite/logger.go b/go-libs/lite/logger.go index 7aa43cee..4233c12f 100644 --- a/go-libs/lite/logger.go +++ b/go-libs/lite/logger.go @@ -100,5 +100,5 @@ func (s *slogAdapter) Warnf(ctx context.Context, format string, v ...any) { } func (s *slogAdapter) Errorf(ctx context.Context, format string, v ...any) { - s.ErrorContext(ctx, fmt.Sprintf(format, v...), nil) + s.ErrorContext(ctx, fmt.Sprintf(format, v...)) } From 908c4925ca585a64a40c957572402173225a1b57 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Fri, 2 Feb 2024 22:36:49 +0100 Subject: [PATCH 39/43] ... --- acceptance/ecosystem/golang.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/acceptance/ecosystem/golang.go b/acceptance/ecosystem/golang.go index 99c81fe2..676ae997 100644 --- a/acceptance/ecosystem/golang.go +++ b/acceptance/ecosystem/golang.go @@ -115,7 +115,14 @@ func (r GoTestRunner) RunAll(ctx context.Context, files fileset.FileSet) (result // make sure to sync on writing to stdout // See https://github.com/golang/go/issues/10338 - pipeReader, pipeWriter, err := os.Pipe() + outReader, outWriter, err := os.Pipe() + if err != nil { + return nil, err + } + // otherwise [ERROR] cannot parse JSON line: + // invalid character 'g' looking for beginning of value - + // go: downloading github.com/stretchr/testify v1.8.4 + errReader, errWriter, err := os.Pipe() if err != nil { return nil, err } @@ -134,7 +141,8 @@ func (r GoTestRunner) RunAll(ctx context.Context, files fileset.FileSet) (result logger.Errorf(ctx, "unable to open log file: %s", err) return nil, err } - reader := io.TeeReader(pipeReader, teeFile) + go io.Copy(teeFile, errReader) + reader := io.TeeReader(outReader, teeFile) // We have to wait for the output to be fully processed before returning. var wg sync.WaitGroup @@ -162,11 +170,11 @@ func (r GoTestRunner) RunAll(ctx context.Context, files fileset.FileSet) (result "-coverpkg=./...", "-coverprofile=coverage.txt", "-run", fmt.Sprintf("^%s", testFilter), - }, nil, pipeWriter, pipeWriter, + }, nil, outWriter, errWriter, process.WithDir(root)) // The process has terminated; close the writer it had been writing into. - pipeWriter.Close() + outWriter.Close() // Wait for the goroutine above to finish collecting the test report. // From 38f0768e75a62ac32a1bd7c4decc0766c79568de Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Fri, 2 Feb 2024 22:40:18 +0100 Subject: [PATCH 40/43] ... --- acceptance/boilerplate/actions.go | 2 +- acceptance/ecosystem/golang.go | 20 -------------------- go-libs/process/background_test.go | 2 +- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/acceptance/boilerplate/actions.go b/acceptance/boilerplate/actions.go index 37a0b20a..35598519 100644 --- a/acceptance/boilerplate/actions.go +++ b/acceptance/boilerplate/actions.go @@ -67,7 +67,7 @@ func (a *boilerplate) taggedComment(ctx context.Context, body string) (string, e if err != nil { return "", fmt.Errorf("run url: %w", err) } - return fmt.Sprintf("%s\nRunning from [%s #%d](%s)%s", + return fmt.Sprintf("%s\n\nRunning from [%s #%d](%s)%s", body, a.context.Workflow, a.context.RunNumber, runUrl, a.tag()), nil } diff --git a/acceptance/ecosystem/golang.go b/acceptance/ecosystem/golang.go index 676ae997..20e943d0 100644 --- a/acceptance/ecosystem/golang.go +++ b/acceptance/ecosystem/golang.go @@ -34,26 +34,6 @@ func (r GoTestRunner) ListAll(files fileset.FileSet) (all []string) { return all } -// func (r GoTestRunner) setCmdEnv(cmd *exec.Cmd, vars map[string]string) error { -// // run test with current environment -// cmd.Env = os.Environ() -// // and variables from test environment -// for k, v := range vars { -// cmd.Env = append(cmd.Env, fmt.Sprintf(`%s=%s`, k, v)) -// if strings.HasSuffix(k, "_TOKEN") || -// strings.HasSuffix(k, "_CREDENTIALS") || -// strings.HasSuffix(k, "_PASSWORD") || -// strings.HasSuffix(k, "_SAS") || -// strings.HasSuffix(k, "_KEY") || -// strings.HasSuffix(k, "_SECRET") { -// log.Printf("[DEBUG][ENV] %s=***", k) -// continue -// } -// log.Printf("[DEBUG][ENV] %s=%s", k, v) -// } -// return nil -// } - func (r GoTestRunner) RunOne(ctx context.Context, files fileset.FileSet, one string) error { found := files.FirstMatch(`_test.go`, fmt.Sprintf(`func %s\(`, one)) if found == nil { diff --git a/go-libs/process/background_test.go b/go-libs/process/background_test.go index 5bf2400b..c14b712b 100644 --- a/go-libs/process/background_test.go +++ b/go-libs/process/background_test.go @@ -22,7 +22,7 @@ func TestBackground(t *testing.T) { ctx := context.Background() res, err := Background(ctx, []string{"echo", "1"}, WithDir("/")) assert.NoError(t, err) - assert.Equal(t, "1", strings.TrimSpace(res)) + assert.Equal(t, "2", strings.TrimSpace(res)) } func TestBackgroundOnlyStdoutGetsoutOnSuccess(t *testing.T) { From eca1a12eff03fe91baf91773f6bb496ab22c23f4 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Fri, 2 Feb 2024 22:47:24 +0100 Subject: [PATCH 41/43] ... --- acceptance/ecosystem/golang.go | 1 + acceptance/main.go | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/acceptance/ecosystem/golang.go b/acceptance/ecosystem/golang.go index 20e943d0..3824257a 100644 --- a/acceptance/ecosystem/golang.go +++ b/acceptance/ecosystem/golang.go @@ -155,6 +155,7 @@ func (r GoTestRunner) RunAll(ctx context.Context, files fileset.FileSet) (result // The process has terminated; close the writer it had been writing into. outWriter.Close() + errWriter.Close() // Wait for the goroutine above to finish collecting the test report. // diff --git a/acceptance/main.go b/acceptance/main.go index f851ea03..8b22089a 100644 --- a/acceptance/main.go +++ b/acceptance/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "github.com/databrickslabs/sandbox/acceptance/boilerplate" "github.com/databrickslabs/sandbox/acceptance/ecosystem" @@ -14,12 +15,12 @@ func run(ctx context.Context, opts ...githubactions.Option) error { return err } directory := b.Action.GetInput("directory") - report, err := ecosystem.RunAll(ctx, directory) + report, testErr := ecosystem.RunAll(ctx, directory) + err = b.Comment(ctx, report.StepSummary()) if err != nil { - return err + return errors.Join(testErr, err) } - return b.Comment(ctx, report.StepSummary()) - + return testErr // also - there's OIDC integration: // a.GetIDToken(ctx, "api://AzureADTokenExchange") } From 9f6586de76cfe787093ad79d0fce590ec7f6e332 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Fri, 2 Feb 2024 22:52:29 +0100 Subject: [PATCH 42/43] .. --- go-libs/process/background_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-libs/process/background_test.go b/go-libs/process/background_test.go index c14b712b..5bf2400b 100644 --- a/go-libs/process/background_test.go +++ b/go-libs/process/background_test.go @@ -22,7 +22,7 @@ func TestBackground(t *testing.T) { ctx := context.Background() res, err := Background(ctx, []string{"echo", "1"}, WithDir("/")) assert.NoError(t, err) - assert.Equal(t, "2", strings.TrimSpace(res)) + assert.Equal(t, "1", strings.TrimSpace(res)) } func TestBackgroundOnlyStdoutGetsoutOnSuccess(t *testing.T) { From b4656f35865bca28ad68885de204c4a48effa69b Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Fri, 2 Feb 2024 22:57:55 +0100 Subject: [PATCH 43/43] .. --- .github/workflows/acceptance-go-libs.yml | 14 +------- acceptance/ecosystem/golang.go | 41 ++++++------------------ 2 files changed, 11 insertions(+), 44 deletions(-) diff --git a/.github/workflows/acceptance-go-libs.yml b/.github/workflows/acceptance-go-libs.yml index 0dbdf94e..90d843eb 100644 --- a/.github/workflows/acceptance-go-libs.yml +++ b/.github/workflows/acceptance-go-libs.yml @@ -3,6 +3,7 @@ name: acceptance on: pull_request: types: [opened, synchronize] + paths: ['go-libs/**'] permissions: id-token: write @@ -17,19 +18,6 @@ jobs: - name: Checkout uses: actions/checkout@v4 - # - uses: azure/login@v1 - # with: - # client-id: ${{ var.ARM_CLIENT_ID }} - # tenant-id: ${{ secrets.ARM_TENANT_ID }} - # subscription-id: ${{ secrets.ARM_SUBSCRIPTION_ID }} - - - name: DEBUG - run: | - echo "$GITHUB_CONTEXT" - env | sort - env: - GITHUB_CONTEXT: ${{ toJSON(github) }} - - name: Setup Go uses: actions/setup-go@v5 with: diff --git a/acceptance/ecosystem/golang.go b/acceptance/ecosystem/golang.go index 3824257a..5447f159 100644 --- a/acceptance/ecosystem/golang.go +++ b/acceptance/ecosystem/golang.go @@ -92,7 +92,6 @@ func (r GoTestRunner) RunAll(ctx context.Context, files fileset.FileSet) (result } lines := strings.Split(string(raw), "\n") module := strings.Split(lines[0], " ")[1] - // make sure to sync on writing to stdout // See https://github.com/golang/go/issues/10338 outReader, outWriter, err := os.Pipe() @@ -106,14 +105,12 @@ func (r GoTestRunner) RunAll(ctx context.Context, files fileset.FileSet) (result if err != nil { return nil, err } - // certain environments need to further filter down the set of tests to run, // hence the `TEST_FILTER` environment variable (for now) with `TestAcc` as // the default prefix. testFilter, ok := env.Lookup(ctx, "TEST_FILTER") if !ok { - // testFilter = "TestAcc" - testFilter = "Test" + testFilter = "TestAcc" } // Tee into file so we can debug issues with logic below. teeFile, err := os.OpenFile("test.log", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) @@ -123,52 +120,44 @@ func (r GoTestRunner) RunAll(ctx context.Context, files fileset.FileSet) (result } go io.Copy(teeFile, errReader) reader := io.TeeReader(outReader, teeFile) - // We have to wait for the output to be fully processed before returning. var wg sync.WaitGroup wg.Add(1) - go func() { defer wg.Done() ch := readGoTestEvents(ctx, reader) results = collectTestReport(ch) - // Strip root module from package name for brevity. for i := range results { results[i].Package = strings.TrimPrefix(results[i].Package, module+"/") } }() - // we may later need to add something like `"-parallel", "20"``, preferably configurable. // on GitHub actions we have only a single core available, hence no test parallelism. // on the other hand, current way of logging pollutes test log output with another test log output, // that may lead to confusion. Hence, on CI it's still better to have no parallelism. err = process.Forwarded(ctx, []string{ "go", "test", "./...", "-json", - // "-timeout", "1h", - "-timeout", "1s", + "-timeout", "1h", "-coverpkg=./...", "-coverprofile=coverage.txt", "-run", fmt.Sprintf("^%s", testFilter), }, nil, outWriter, errWriter, process.WithDir(root)) - // The process has terminated; close the writer it had been writing into. outWriter.Close() errWriter.Close() - // Wait for the goroutine above to finish collecting the test report. // // If c.Stdin is not an *os.File, Wait also waits for the I/O loop // copying from c.Stdin into the process's standard input // to complete. wg.Wait() - return results, err } -// TestEvent is defined at https://pkg.go.dev/cmd/test2json#hdr-Output_Format. -type TestEvent struct { +// goTestEvent is defined at https://pkg.go.dev/cmd/test2json#hdr-Output_Format. +type goTestEvent struct { Time time.Time // encodes as an RFC3339-format string Action string Package string @@ -177,37 +166,32 @@ type TestEvent struct { Output string } -func readGoTestEvents(ctx context.Context, reader io.Reader) <-chan TestEvent { - ch := make(chan TestEvent) - +func readGoTestEvents(ctx context.Context, reader io.Reader) <-chan goTestEvent { + ch := make(chan goTestEvent) go func() { defer close(ch) - scanner := bufio.NewScanner(reader) for scanner.Scan() { line := scanner.Bytes() - var testEvent TestEvent + var testEvent goTestEvent err := json.Unmarshal(line, &testEvent) if err != nil { logger.Errorf(ctx, "cannot parse JSON line: %s - %s", err, string(line)) return } - ch <- testEvent } - err := scanner.Err() if err != nil && err != io.ErrClosedPipe { logger.Errorf(ctx, "cannot scan json lines: %s", err) return } }() - return ch } -func collectOutput(testEvents []TestEvent) string { +func collectOutput(testEvents []goTestEvent) string { var b strings.Builder for _, testEvent := range testEvents { if testEvent.Action == "output" { @@ -223,18 +207,15 @@ func summarize(output string) string { return strings.Join(concise, "\n") } -func collectTestReport(ch <-chan TestEvent) (report TestReport) { - testEventsByKey := map[string][]TestEvent{} - +func collectTestReport(ch <-chan goTestEvent) (report TestReport) { + testEventsByKey := map[string][]goTestEvent{} for testEvent := range ch { if testEvent.Test == "" { continue } - // Keep track of all events per test. key := fmt.Sprintf("%s/%s", testEvent.Package, testEvent.Test) testEventsByKey[key] = append(testEventsByKey[key], testEvent) - // Only take action on pass/skip/fail switch testEvent.Action { case "pass": @@ -282,7 +263,6 @@ func collectTestReport(ch <-chan TestEvent) (report TestReport) { continue } } - // Mark remaining tests as failed (timed out?) for _, testEvents := range testEventsByKey { testEvent := testEvents[len(testEvents)-1] @@ -298,6 +278,5 @@ func collectTestReport(ch <-chan TestEvent) (report TestReport) { }) log.Printf("[INFO] ❌ %s (%0.3fs)\n%s", testEvent.Test, testEvent.Elapsed, summarize(testLog)) } - return }