Skip to content

Commit ce6f162

Browse files
authored
refactor: Move functions into test.go (#4)
1 parent 9382caf commit ce6f162

7 files changed

+114
-157
lines changed

contains.go

-36
This file was deleted.

deep_equal.go

-15
This file was deleted.

deep_equal_example_test.go

-21
This file was deleted.

mockingt_test.go

-66
This file was deleted.

test.go

+39
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package test
22

33
import (
4+
"bytes"
45
"reflect"
6+
"strings"
57
"testing"
68
)
79

@@ -21,6 +23,15 @@ func NotEqual[T comparable](t testing.TB, bad, got T) {
2123
}
2224
}
2325

26+
// DeepEqual calls t.Fatalf if want and got are different according to reflect.DeepEqual.
27+
func DeepEqual[T any](t testing.TB, want, got T) {
28+
t.Helper()
29+
// Pass as pointers to get around the nil-interface problem
30+
if !reflect.DeepEqual(&want, &got) {
31+
t.Fatalf("reflect.DeepEqual(%#v, %#v) == false", want, got)
32+
}
33+
}
34+
2435
// AllEqual calls t.Fatalf if want != got.
2536
func AllEqual[T comparable](t testing.TB, want, got []T) {
2637
t.Helper()
@@ -97,3 +108,31 @@ func False(t testing.TB, value bool) {
97108
t.Fatalf("got: true")
98109
}
99110
}
111+
112+
// Contains calls t.Fatalf if needle is not contained in the string or []byte haystack.
113+
func Contains[byteseq ~string | ~[]byte](t testing.TB, needle string, haystack byteseq) {
114+
t.Helper()
115+
if !contains(haystack, needle) {
116+
t.Fatalf("%q not in %q", needle, haystack)
117+
}
118+
}
119+
120+
// NotContains calls t.Fatalf if needle is contained in the string or []byte haystack.
121+
func NotContains[byteseq ~string | ~[]byte](t testing.TB, needle string, haystack byteseq) {
122+
t.Helper()
123+
if contains(haystack, needle) {
124+
t.Fatalf("%q in %q", needle, haystack)
125+
}
126+
}
127+
128+
func contains[byteseq ~string | ~[]byte](haystack byteseq, needle string) bool {
129+
rv := reflect.ValueOf(haystack)
130+
switch rv.Kind() {
131+
case reflect.String:
132+
return strings.Contains(rv.String(), needle)
133+
case reflect.Slice:
134+
return bytes.Contains(rv.Bytes(), []byte(needle))
135+
default:
136+
panic("unreachable")
137+
}
138+
}

test_example_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ func Example() {
1616
test.NotEqual(t, "hello", "world") // good
1717
test.NotEqual(t, "goodbye", "goodbye") // bad
1818

19+
test.DeepEqual(t, map[int]bool{1: true, 2: false}, map[int]bool{1: true, 2: false}) // good
20+
test.DeepEqual(t, nil, []int{}) // bad
21+
1922
s := []int{1, 2, 3}
2023
test.AllEqual(t, []int{1, 2, 3}, s) // good
2124
test.AllEqual(t, []int{3, 2, 1}, s) // bad
@@ -37,6 +40,7 @@ func Example() {
3740
// Output:
3841
// want: hello; got: world
3942
// got: goodbye
43+
// reflect.DeepEqual([]int(nil), []int{}) == false
4044
// want: [3 2 1]; got: [1 2 3]
4145
// got: <nil>
4246
// got: (O_o)

test_test.go

+71-19
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,22 @@ import (
44
"errors"
55
"fmt"
66
"io"
7+
"runtime"
78
"strings"
9+
"sync"
810
"testing"
911
"time"
1012

1113
"github.com/raeperd/test"
1214
)
1315

14-
type testingTB struct {
15-
testing.TB
16-
failed bool
17-
w io.Writer
18-
}
19-
20-
func (t *testingTB) Helper() {}
21-
22-
func (t *testingTB) Fatalf(format string, args ...any) {
23-
t.failed = true
24-
fmt.Fprintf(t.w, format, args...)
25-
}
26-
2716
func Test(t *testing.T) {
2817
beOkay := func(callback func(tb testing.TB)) {
2918
t.Helper()
3019
var buf strings.Builder
31-
tb := &testingTB{w: &buf}
32-
callback(tb)
33-
if tb.failed {
20+
mt := &mockingT{w: &buf}
21+
callback(mt)
22+
if mt.Failed() {
3423
t.Fatal("failed too soon")
3524
}
3625
if buf.String() != "" {
@@ -47,9 +36,9 @@ func Test(t *testing.T) {
4736
beBad := func(callback func(tb testing.TB)) {
4837
t.Helper()
4938
var buf strings.Builder
50-
tb := &testingTB{w: &buf}
51-
callback(tb)
52-
if !tb.failed {
39+
mt := &mockingT{w: &buf}
40+
callback(mt)
41+
if !mt.Failed() {
5342
t.Fatal("did not fail")
5443
}
5544
if buf.String() == "" {
@@ -65,3 +54,66 @@ func Test(t *testing.T) {
6554
beBad(func(tb testing.TB) { test.True(tb, false) })
6655
beBad(func(tb testing.TB) { test.False(tb, true) })
6756
}
57+
58+
type mockingT struct {
59+
testing.T
60+
sync.RWMutex
61+
failed bool
62+
cleanups []func()
63+
w io.Writer
64+
}
65+
66+
func (m *mockingT) setFailed(b bool) {
67+
m.Lock()
68+
defer m.Unlock()
69+
m.failed = b
70+
}
71+
72+
func (m *mockingT) Failed() bool {
73+
m.RLock()
74+
defer m.RUnlock()
75+
return m.failed
76+
}
77+
78+
func (m *mockingT) Run(name string, f func(t *testing.T)) {
79+
m.cleanups = nil
80+
m.setFailed(false)
81+
ch := make(chan struct{})
82+
defer func() {
83+
for _, f := range m.cleanups {
84+
defer f()
85+
}
86+
}()
87+
// Use a goroutine so Fatalf can call Goexit
88+
go func() {
89+
defer close(ch)
90+
f(&m.T)
91+
}()
92+
<-ch
93+
}
94+
95+
func (m *mockingT) Cleanup(f func()) {
96+
m.cleanups = append(m.cleanups, f)
97+
}
98+
99+
func (*mockingT) Log(args ...any) {
100+
fmt.Println(args...)
101+
}
102+
103+
func (*mockingT) Helper() {}
104+
105+
func (m *mockingT) Fatalf(format string, args ...any) {
106+
m.setFailed(true)
107+
if m.w != nil {
108+
fmt.Fprintf(m.w, format, args...)
109+
// Do not call runtime.Goexit here, so that caller can read the output
110+
} else {
111+
m.Errorf(format, args...)
112+
runtime.Goexit()
113+
}
114+
}
115+
116+
func (m *mockingT) Errorf(format string, args ...any) {
117+
m.setFailed(true)
118+
fmt.Printf(format+"\n", args...)
119+
}

0 commit comments

Comments
 (0)