Skip to content

Commit 7057176

Browse files
committed
feat: add support for benchmarks (#144)
Signed-off-by: Tronje Krop <tronje.krop@jactors.de>
1 parent b827750 commit 7057176

8 files changed

Lines changed: 137 additions & 29 deletions

File tree

.github/workflows/build.yaml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ jobs:
77
linux:
88
runs-on: ubuntu-latest
99
steps:
10+
- name: Checkout code
11+
uses: actions/checkout@v5
12+
1013
- name: Set up Go
11-
uses: actions/setup-go@v5
14+
uses: actions/setup-go@v6
1215
with:
1316
go-version: 1.26
1417
cache: false
1518

16-
- name: Checkout code
17-
uses: actions/checkout@v4
18-
1919
- name: Build and tests
2020
env:
2121
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
@@ -31,15 +31,15 @@ jobs:
3131
# macos:
3232
# runs-on: macos-latest
3333
# steps:
34+
# - name: Checkout code
35+
# uses: actions/checkout@v5
36+
3437
# - name: Set up Go
35-
# uses: actions/setup-go@v5
38+
# uses: actions/setup-go@v6
3639
# with:
3740
# go-version: 1.26
3841
# cache: false
3942

40-
# - name: Checkout code
41-
# uses: actions/checkout@v4
42-
4343
# - name: Build and tests
4444
# env:
4545
# BASH_COMPAT: 3.2
@@ -53,14 +53,14 @@ jobs:
5353
permissions:
5454
contents: write
5555
steps:
56+
- name: Checkout code
57+
uses: actions/checkout@v5
58+
5659
- name: Set up Go
57-
uses: actions/setup-go@v5
60+
uses: actions/setup-go@v6
5861
with:
5962
go-version: 1.26
6063

61-
- name: Checkout code
62-
uses: actions/checkout@v4
63-
6464
- name: Release new version
6565
env:
6666
GH_TOKEN: ${{ github.token }}

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ TMPDIR ?= /tmp
1818
# Setup default go-make installation flags.
1919
INSTALL_FLAGS ?= -mod=readonly -buildvcs=auto
2020
# Setup go-make version to use desired build and config scripts.
21-
GOMAKE_DEP ?= github.com/tkrop/go-make@v0.2.6
21+
GOMAKE_DEP ?= github.com/tkrop/go-make@v0.3.0
2222
# Request targets from go-make show-targets target.
2323
TARGETS := $(shell command -v $(GOBIN)/go-make >/dev/null || \
2424
$(GO) install $(INSTALL_FLAGS) $(GOMAKE_DEP) >&2 && \

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,3 +281,11 @@ project has more than 25 Stars, I will introduce semantic versions `v1`.
281281
If you like to contribute, please create an issue and/or pull request with a
282282
proper description of your proposal or contribution. I will review it and
283283
provide feedback on it as fast as possible.
284+
285+
286+
## Disclaimer
287+
288+
This software is `100%` written by humans. AI is only used to review code,
289+
search for bugs, improve test coverage, and analyse performance issues. All
290+
actions executed with the help of AI are carefully reviewed, checked, and
291+
modified with the human standards and quality goals in mind.

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/tkrop/go-testing
22

3-
go 1.26.2
3+
go 1.26.3
44

55
require (
66
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
@@ -10,12 +10,12 @@ require (
1010
github.com/stretchr/testify v1.11.1
1111
go.uber.org/mock v0.6.0
1212
golang.org/x/text v0.30.0
13-
golang.org/x/tools v0.44.0
13+
golang.org/x/tools v0.45.0
1414
)
1515

1616
require (
1717
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
18-
golang.org/x/mod v0.35.0 // indirect
18+
golang.org/x/mod v0.36.0 // indirect
1919
golang.org/x/sync v0.20.0 // indirect
2020
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
2121
gopkg.in/yaml.v3 v3.0.1 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
2828
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
2929
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
3030
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
31-
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
32-
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
31+
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
32+
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
3333
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
3434
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
3535
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
3636
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
37-
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
38-
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
37+
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
38+
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
3939
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
4040
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
4141
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

reflect/reflect.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,3 +306,40 @@ func Name[P any](name string, param P) string {
306306
}
307307
return ""
308308
}
309+
310+
// Run executes a test with given name and test callback function on the given
311+
// target. The target is typically a test runner instance, such as `*testing.T`
312+
// or `*testing.B` and must implement `Run(name string, call func(T))` that is
313+
// called to execute the test.
314+
//
315+
// A panic is raised if the target has no `Run` method or if target type does
316+
// not implement T.
317+
func Run[T any](target any, name string, call func(T)) {
318+
value := reflect.ValueOf(target)
319+
if !value.IsValid() {
320+
panic(name + ": target must not be nil")
321+
}
322+
323+
method := value.MethodByName("Run")
324+
if !method.IsValid() {
325+
panic(name + ": target does not implement method [" +
326+
reflect.TypeOf(target).String() + " => Run]")
327+
}
328+
329+
// Build a typed func(*concreteT) that wraps the inner argument as T
330+
// before forwarding to the provided call function.
331+
wrapper := reflect.MakeFunc(method.Type().In(1),
332+
func(args []reflect.Value) []reflect.Value {
333+
iface := args[0].Interface()
334+
if typ, ok := iface.(T); ok {
335+
call(typ)
336+
return nil
337+
}
338+
339+
panic(name + ": target does not implement type [" +
340+
reflect.TypeOf(iface).String() + " => " +
341+
reflect.TypeOf((*T)(nil)).Elem().String() + "]")
342+
})
343+
344+
method.Call([]reflect.Value{reflect.ValueOf(name), wrapper})
345+
}

reflect/reflect_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,3 +1154,67 @@ func TestName(t *testing.T) {
11541154
assert.Equal(t, param.expect, result)
11551155
})
11561156
}
1157+
1158+
// runner is a test double whose Run method delivers itself to the callback.
1159+
type runner struct {
1160+
called bool
1161+
}
1162+
1163+
func (r *runner) Run(_ string, call func(*runner)) bool {
1164+
call(r)
1165+
1166+
return true
1167+
}
1168+
1169+
// failer is a test double that does not implement the expected type.
1170+
type failer struct{}
1171+
1172+
func (failer) Run(_ string, call func(failer)) bool {
1173+
call(failer{})
1174+
1175+
return true
1176+
}
1177+
1178+
type RunParams struct {
1179+
setup mock.SetupFunc
1180+
target any
1181+
expectCall bool
1182+
}
1183+
1184+
var runTestCases = map[string]RunParams{
1185+
"nil-target": {
1186+
setup: test.Panic("call: target must not be nil"),
1187+
},
1188+
"no-run-method": {
1189+
target: struct{}{},
1190+
setup: test.Panic("call: target does not implement method " +
1191+
"[struct {} => Run]"),
1192+
},
1193+
"not-type": {
1194+
target: failer{},
1195+
setup: test.Panic("call: target does not implement type " +
1196+
"[reflect_test.failer => *reflect_test.runner]"),
1197+
},
1198+
1199+
"run": {
1200+
target: &runner{},
1201+
expectCall: true,
1202+
},
1203+
}
1204+
1205+
func TestRun(t *testing.T) {
1206+
test.Map(t, runTestCases).
1207+
Run(func(t test.Test, param RunParams) {
1208+
// Given
1209+
mock.NewMocks(t).Expect(param.setup)
1210+
1211+
// When
1212+
reflect.Run(param.target, "call", func(inner *runner) {
1213+
inner.called = true
1214+
})
1215+
1216+
// Then
1217+
assert.Equal(t, param.expectCall,
1218+
param.target != nil && param.target.(*runner).called)
1219+
})
1220+
}

test/runner.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"runtime"
88
"strconv"
99
"strings"
10-
"testing"
1110
"time"
1211

1312
"github.com/tkrop/go-testing/internal/maps"
@@ -130,7 +129,7 @@ type Factory[P any] interface {
130129
// factory is a generic parameterized test factory struct.
131130
type factory[P any] struct {
132131
// The testing context to run the tests in.
133-
t *testing.T
132+
t Test
134133
// A wait group to synchronize the test execution.
135134
wg sync.WaitGroup
136135
// The test parameter sets to run.
@@ -148,7 +147,7 @@ type factory[P any] struct {
148147
// can be a single test parameter set, a slice of test parameter sets, or a map
149148
// of named test parameter sets. The test runner is looking into the parameter
150149
// set to determine a suitable test case name, e.g. by using a `name` parameter.
151-
func Any[P any](t *testing.T, params any) Factory[P] {
150+
func Any[P any](t Test, params any) Factory[P] {
152151
t.Helper()
153152

154153
return &factory[P]{
@@ -161,7 +160,7 @@ func Any[P any](t *testing.T, params any) Factory[P] {
161160
// Param creates a new parallel test runner with given test parameter sets
162161
// provided as variadic arguments. The test runner is looking into the
163162
// parameter set to find a suitable test case name.
164-
func Param[P any](t *testing.T, params ...P) Factory[P] {
163+
func Param[P any](t Test, params ...P) Factory[P] {
165164
t.Helper()
166165

167166
if len(params) == 1 {
@@ -172,7 +171,7 @@ func Param[P any](t *testing.T, params ...P) Factory[P] {
172171

173172
// Map creates a new parallel test runner with given test parameter sets
174173
// provided as a test case name to parameter sets mapping.
175-
func Map[P any](t *testing.T, params ...map[string]P) Factory[P] {
174+
func Map[P any](t Test, params ...map[string]P) Factory[P] {
176175
t.Helper()
177176

178177
return Any[P](t, maps.Add(maps.Copy(params[0]), params[1:]...))
@@ -181,7 +180,7 @@ func Map[P any](t *testing.T, params ...map[string]P) Factory[P] {
181180
// Slice creates a new parallel test runner with given test parameter sets
182181
// provided as a slice. The test runner is looking into the parameter set to
183182
// find a suitable test case name.
184-
func Slice[P any](t *testing.T, params ...[]P) Factory[P] {
183+
func Slice[P any](t Test, params ...[]P) Factory[P] {
185184
t.Helper()
186185

187186
return Any[P](t, slices.Add(params...))
@@ -297,16 +296,16 @@ func (r *factory[P]) exec(
297296
return
298297
}
299298

300-
r.t.Run(name, r.wrap(param, call, parallel))
299+
reflect.Run(r.t, name, r.wrap(param, call, parallel))
301300
}
302301

303302
// wrap creates the wrapper method eventually executing the test.
304303
func (r *factory[P]) wrap(
305304
param P, call ParamFunc[P], parallel bool,
306-
) func(*testing.T) {
305+
) func(Test) {
307306
r.wg.Add(1)
308307

309-
return func(t *testing.T) {
308+
return func(t Test) {
310309
t.Helper()
311310

312311
New(t, parallel).

0 commit comments

Comments
 (0)