From b61accb8afb935132ead0b2acb43daf744692151 Mon Sep 17 00:00:00 2001 From: tkrop Date: Fri, 15 Dec 2023 18:46:59 +0100 Subject: [PATCH] feat: improve handling of repeated parallel test setup (#60) Signed-off-by: tkrop --- Makefile | 4 ++- internal/mock/common_test.go | 5 ++++ test/caller_test.go | 2 +- test/testing.go | 49 ++++++++++++++++++++++++++++++------ test/testing_test.go | 19 ++++++++++++++ 5 files changed, 70 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 1d245ff..2a7c29a 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,9 @@ endif # request targets, while the single target can be used to define the # precondition of custom target. .PHONY: $(TARGETS) $(addprefix target/,$(TARGETS)) -$(TARGETS):; $(GOBIN)/go-make $(MAKEFLAGS) $(MAKECMDGOALS); +$(eval $(lastwords $(MAKECMDGOALS)):;@:) +$(firstword $(MAKECMDGOALS)): + $(GOBIN)/go-make $(MAKEFLAGS) $(MAKECMDGOALS); $(addprefix target/,$(TARGETS)): target/%: $(GOBIN)/go-make $(MAKEFLAGS) $*; diff --git a/internal/mock/common_test.go b/internal/mock/common_test.go index 46c48c7..c77432b 100644 --- a/internal/mock/common_test.go +++ b/internal/mock/common_test.go @@ -200,6 +200,11 @@ var ( Params: []*Param{}, Results: []*Param{{Type: "string"}}, Variadic: false, + }, { + Name: "Parallel", + Params: []*Param{}, + Results: []*Param{}, + Variadic: false, }, { Name: "TempDir", Params: []*Param{}, diff --git a/test/caller_test.go b/test/caller_test.go index 3910eb9..31f3f88 100644 --- a/test/caller_test.go +++ b/test/caller_test.go @@ -94,7 +94,7 @@ var ( }() // CallerTestErrorf provides the file with the line number of the `Errorf` // call in testing. - CallerTestErrorf = path.Join(SourceDir, "testing.go:180") + CallerTestErrorf = path.Join(SourceDir, "testing.go:211") // CallerGomockErrorf provides the file with the line number of the // `Errorf` call in gomock. CallerGomockErrorf = path.Join(SourceDir, "gomock.go:61") diff --git a/test/testing.go b/test/testing.go index 21888fd..20290a3 100644 --- a/test/testing.go +++ b/test/testing.go @@ -40,6 +40,18 @@ const ( Parallel = true ) +// ensureParallel ensures that the test runs test parameter sets in parallel. +func ensureParallel(t *testing.T) { + t.Helper() + defer func() { + if v := recover(); v != nil && + v != "testing: t.Parallel called multiple times" { + panic(v) + } + }() + t.Parallel() +} + // TODO: consider following convenience methods: // // // Result is a convenience method that returns the first argument ans swollows @@ -87,12 +99,31 @@ type Reporter interface { // Test is a minimal interface for abstracting test methods that are needed to // setup an isolated test environment for GoMock and Testify. type Test interface { + // Helper declares a test helper function. The method delegates the request + // to the parent test context. Helper() + // Name provides the test name. The method delegates the request to the + // parent test context. Name() string + // TempDir creates a new temporary directory for the test. The method + // delegates the request to the parent test context. TempDir() string + // Errorf handles a failure messages when a test is supposed to continue. + // The method either delegates the request to the parent test context or + // to the test reporter. Errorf(format string, args ...any) + // Fatalf handles a fatal failure messge that immediate aborts of the test + // execution. The method either delegates the request to the parent test + // context or to the test reporter. Fatalf(format string, args ...any) + // FailNow handles fatal failure notifications without log output that + // aborts test execution immediately. The method either delegates the + // request to the parent test context or to the test reporter. FailNow() + // Parallel declares that the test is to be run in parallel with (and only + // with) other parallel tests. The method delegates the request to the + // parent test context and silently recovers when setting up twice. + Parallel() } // Cleanuper defines an interface to add a custom mehtod that is called after @@ -127,7 +158,7 @@ func NewTester(t Test, expect Expect) *Tester { // Parallel delegates request to `testing.T.Parallel()`. func (t *Tester) Parallel() { if t, ok := t.t.(*testing.T); ok { - t.Parallel() + ensureParallel(t) } } @@ -391,15 +422,21 @@ func (r *runner[P]) RunSeq(call func(t Test, param P)) Runner[P] { return r.run(call, false) } +// parallel ensures that the test runner runs the test parameter sets in +// parallel. +func (r *runner[P]) parallel(parallel bool) { + if parallel { + ensureParallel(r.t) + } +} + // Run runs the test parameter sets either parallel or in sequence. func (r *runner[P]) run( call func(t Test, param P), parallel bool, ) Runner[P] { switch params := r.params.(type) { case map[string]P: - if parallel { - r.t.Parallel() - } + r.parallel(parallel) r.wg.Add(len(params)) for name, param := range params { @@ -408,9 +445,7 @@ func (r *runner[P]) run( } case []P: - if parallel { - r.t.Parallel() - } + r.parallel(parallel) r.wg.Add(len(params)) for index, param := range params { diff --git a/test/testing_test.go b/test/testing_test.go index 2d6150e..7efa08d 100644 --- a/test/testing_test.go +++ b/test/testing_test.go @@ -280,3 +280,22 @@ func TestTypePanic(t *testing.T) { test.New[TestParam](t, ParamParam{expect: false}). Run(func(t test.Test, param TestParam) {}) } + +func TestParallel(t *testing.T) { + t.Parallel() + test.New[ParamParam](t, []ParamParam{{expect: false}}). + Run(func(t test.Test, param ParamParam) { + t.Parallel() + }) +} + +func TestParallelDenied(t *testing.T) { + t.Setenv("TESTING", "true") + defer func() { + assert.Equal(t, "testing: t.Parallel called after t.Setenv;"+ + " cannot set environment variables in parallel tests", recover()) + }() + + test.New[ParamParam](t, []ParamParam{{expect: false}}). + Run(func(t test.Test, param ParamParam) {}) +}