From 25c45828a7e4d3988262421ff796da016bc74b9c Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Sun, 6 Oct 2019 14:24:33 -0300 Subject: [PATCH 1/2] fix: skip aware semerrgroup Signed-off-by: Carlos Alexandro Becker --- internal/pipe/docker/docker_test.go | 30 ++++++++++++++++ internal/semerrgroup/sem.go | 54 +++++++++++++++++++++++++++-- internal/semerrgroup/sem_test.go | 34 ++++++++++++++++++ 3 files changed, 115 insertions(+), 3 deletions(-) diff --git a/internal/pipe/docker/docker_test.go b/internal/pipe/docker/docker_test.go index 6475eed411b..7ac3f128d33 100644 --- a/internal/pipe/docker/docker_test.go +++ b/internal/pipe/docker/docker_test.go @@ -232,6 +232,35 @@ func TestRunPipe(t *testing.T) { assertImageLabels: noLabels, assertError: testlib.AssertSkipped, }, + "one_img_error_with_skip_push": { + dockers: []config.Docker{ + { + ImageTemplates: []string{ + registry + "goreleaser/one_img_error_with_skip_push:true", + }, + Goos: "linux", + Goarch: "amd64", + Dockerfile: "testdata/Dockerfile.true", + Binaries: []string{"mybin"}, + SkipPush: "true", + }, + { + ImageTemplates: []string{ + registry + "goreleaser/one_img_error_with_skip_push:false", + }, + Goos: "linux", + Goarch: "amd64", + Dockerfile: "testdata/Dockerfile.false", + Binaries: []string{"mybin"}, + SkipPush: "true", + }, + }, + expect: []string{ + registry + "goreleaser/one_img_error_with_skip_push:true", + }, + assertImageLabels: noLabels, + assertError: shouldErr("failed to build docker image"), + }, "valid_no_latest": { dockers: []config.Docker{ { @@ -531,6 +560,7 @@ func TestRunPipe(t *testing.T) { Dist: dist, Dockers: docker.dockers, }) + ctx.Parallelism = 1 ctx.Env = docker.env ctx.Version = "1.0.0" ctx.Git = context.GitInfo{ diff --git a/internal/semerrgroup/sem.go b/internal/semerrgroup/sem.go index fe7f4859c6a..806c529d82a 100644 --- a/internal/semerrgroup/sem.go +++ b/internal/semerrgroup/sem.go @@ -2,7 +2,12 @@ // size, so you can control the number of tasks being executed simultaneously. package semerrgroup -import "golang.org/x/sync/errgroup" +import ( + "sync" + + "github.com/goreleaser/goreleaser/internal/pipe" + "golang.org/x/sync/errgroup" +) // Group is the Semphore ErrorGroup itself type Group interface { @@ -47,7 +52,8 @@ func (s *parallelGroup) Wait() error { var _ Group = &serialGroup{} type serialGroup struct { - err error + err error + errOnce sync.Once } // Go execs runs `fn` and saves the result if no error has been encountered. @@ -55,10 +61,52 @@ func (s *serialGroup) Go(fn func() error) { if s.err != nil { return } - s.err = fn() + if err := fn(); err != nil { + s.errOnce.Do(func() { + s.err = err + }) + } } // Wait waits for Go to complete and returns the first error encountered. func (s *serialGroup) Wait() error { return s.err } + +var _ Group = &skipAwareGroup{} + +// NewSkipAware returns a new Group of a given size and aware of pipe skips. +func NewSkipAware(g Group) Group { + return &skipAwareGroup{g: g} +} + +type skipAwareGroup struct { + g Group + skipErr error + skipOnce sync.Once +} + +// Go execs runs `fn` and saves the result if no error has been encountered. +func (s *skipAwareGroup) Go(fn func() error) { + s.g.Go(func() error { + var err = fn() + // if the err is a skip, set it for later, but return nil for now so the + // the group proceeds. + if pipe.IsSkip(err) { + s.skipOnce.Do(func() { + s.skipErr = err + }) + return nil + } + return err + }) +} + +// Wait waits for Go to complete and returns the first error encountered. +func (s *skipAwareGroup) Wait() error { + // if we got a "real error", return it, otherwise return skipErr or nil. + if err := s.g.Wait(); err != nil { + return err + } + return s.skipErr +} diff --git a/internal/semerrgroup/sem_test.go b/internal/semerrgroup/sem_test.go index 1e06856eb31..e85b0da5b98 100644 --- a/internal/semerrgroup/sem_test.go +++ b/internal/semerrgroup/sem_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/goreleaser/goreleaser/internal/pipe" + "github.com/stretchr/testify/require" ) @@ -54,3 +56,35 @@ func TestSemaphoreOrderError(t *testing.T) { require.EqualError(t, g.Wait(), "fake err") require.Equal(t, []int{0}, output) } + +func TestSemaphoreSkipAware(t *testing.T) { + var g = NewSkipAware(New(1)) + var lock sync.Mutex + var counter int + for i := 0; i < 10; i++ { + g.Go(func() error { + time.Sleep(10 * time.Millisecond) + lock.Lock() + counter++ + lock.Unlock() + return pipe.Skip("fake skip") + }) + } + require.EqualError(t, g.Wait(), "fake skip") + require.Equal(t, counter, 10) +} + +func TestSemaphoreSkipAndRealError(t *testing.T) { + var g = NewSkipAware(New(10)) + for i := 0; i < 100; i++ { + g.Go(func() error { + time.Sleep(10 * time.Millisecond) + return pipe.Skip("fake skip") + }) + } + g.Go(func() error { + time.Sleep(10 * time.Millisecond) + return fmt.Errorf("errrrrr") + }) + require.EqualError(t, g.Wait(), "errrrrr") +} From 9c2b9c9ecd19a75c6e55f3d937dd3336125cacb9 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Sun, 6 Oct 2019 15:26:50 -0300 Subject: [PATCH 2/2] fix: docker: skip push skips other builds Signed-off-by: Carlos Alexandro Becker --- internal/pipe/docker/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pipe/docker/docker.go b/internal/pipe/docker/docker.go index fb4b10b7569..c77a95ee77b 100644 --- a/internal/pipe/docker/docker.go +++ b/internal/pipe/docker/docker.go @@ -80,7 +80,7 @@ func (Pipe) Publish(ctx *context.Context) error { } func doRun(ctx *context.Context) error { - var g = semerrgroup.New(ctx.Parallelism) + var g = semerrgroup.NewSkipAware(semerrgroup.New(ctx.Parallelism)) for _, docker := range ctx.Config.Dockers { docker := docker g.Go(func() error {