Skip to content

Commit 1ba3c46

Browse files
authored
Merge pull request #6 from moul/dev/moul/retry
2 parents 72bf7b8 + ca7ef5b commit 1ba3c46

File tree

7 files changed

+101
-54
lines changed

7 files changed

+101
-54
lines changed

.github/workflows/go.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,6 @@ jobs:
7373
strategy:
7474
matrix:
7575
golang:
76-
#- 1.11.13
77-
#- 1.12.17
78-
#- 1.13.15
7976
#- 1.14.7
8077
- 1.15.1
8178
steps:
@@ -133,9 +130,6 @@ jobs:
133130
strategy:
134131
matrix:
135132
golang:
136-
- 1.11.13
137-
- 1.12.17
138-
- 1.13.15
139133
- 1.14.7
140134
- 1.15.1
141135
env:

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
GOPKG ?= moul.io/testman
22
DOCKER_IMAGE ?= moul/testman
3+
GOMOD_DIRS ?= .
34
GOBINS ?= .
45
NPM_PACKAGES ?= .
56

examples/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
integration:
22
# test with testman
3-
testman test -run ^TestStable ./...
4-
@# FIXME: test unstable tests
3+
testman test -timeout=10s -run ^TestStable ./... # should always work
4+
testman test -timeout=60s -run ^TestUnstable -retry=50 ./... # should work at least 1/50
55
@# FIXME: test broken tests
66

77
# test with default tools

go.mod

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go.sum

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

main.go

Lines changed: 73 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import (
1414
"time"
1515

1616
"github.com/peterbourgon/ff/v3/ffcli"
17-
"moul.io/godev"
1817
"moul.io/motd"
18+
"moul.io/u"
1919
)
2020

2121
var opts Opts
@@ -34,8 +34,8 @@ func run(args []string) error {
3434
testFlags := flag.NewFlagSet("testman test", flag.ExitOnError)
3535
testFlags.BoolVar(&opts.Verbose, "v", false, "verbose")
3636
testFlags.StringVar(&opts.Run, "run", "^(Test|Example)", "regex to filter out tests and examples")
37-
//testFlags.IntVar(&opts.Retry, "retry", 0, "fail after N retries")
38-
//testFlags.DurationVar(&opts.Timeout, "timeout", opts.Timeout, "max duration allowed to run the whole suite")
37+
testFlags.IntVar(&opts.Retry, "retry", 0, "fail after N retries")
38+
testFlags.DurationVar(&opts.Timeout, "timeout", opts.Timeout, "max duration allowed to run the whole suite")
3939
listFlags := flag.NewFlagSet("testman list", flag.ExitOnError)
4040
listFlags.BoolVar(&opts.Verbose, "v", false, "verbose")
4141
listFlags.StringVar(&opts.Run, "run", "^(Test|Example)", "regex to filter out tests and examples")
@@ -53,14 +53,14 @@ func run(args []string) error {
5353
FlagSet: testFlags,
5454
ShortHelp: "advanced go test workflows",
5555
ShortUsage: "testman test [flags] [packages]",
56-
LongHelp: "EXAMPLES\n testman test -v ./...",
56+
LongHelp: testLongHelp,
5757
Exec: runTest,
5858
}, {
5959
Name: "list",
6060
FlagSet: listFlags,
6161
ShortHelp: "list available tests",
6262
ShortUsage: "testman list [packages]",
63-
LongHelp: "EXAMPLE\n testman list ./...",
63+
LongHelp: listLongHelp,
6464
Exec: runList,
6565
},
6666
},
@@ -69,11 +69,26 @@ func run(args []string) error {
6969
return root.ParseAndRun(context.Background(), args[1:])
7070
}
7171

72+
const (
73+
testLongHelp = `EXAMPLES
74+
testman test ./...
75+
testman test -v ./...
76+
testman test -run ^TestUnstable -timeout=300s -retry=50 ./...`
77+
listLongHelp = `EXAMPLES
78+
testman list ./...
79+
testman list -v ./...
80+
testman list -run ^TestStable ./...`
81+
)
82+
7283
func runList(ctx context.Context, args []string) error {
7384
if len(args) == 0 {
7485
return flag.ErrHelp
7586
}
76-
preRun()
87+
cleanup, err := preRun()
88+
if err != nil {
89+
return err
90+
}
91+
defer cleanup()
7792

7893
// list packages
7994
pkgs, err := listPackagesWithTests(args)
@@ -103,22 +118,28 @@ func runTest(ctx context.Context, args []string) error {
103118
if len(args) == 0 {
104119
return flag.ErrHelp
105120
}
106-
preRun()
107-
log.Printf("runTest opts=%s args=%s", godev.JSON(opts), godev.JSON(args))
108-
start := time.Now()
109-
110-
// list packages
111-
pkgs, err := listPackagesWithTests(args)
121+
cleanup, err := preRun()
112122
if err != nil {
113123
return err
114124
}
125+
defer cleanup()
115126

116-
// create temp dir
117-
tmpdir, err := ioutil.TempDir("", "testman")
127+
log.Printf("runTest opts=%s args=%s", u.JSON(opts), u.JSON(args))
128+
start := time.Now()
129+
130+
if opts.Timeout > 0 {
131+
go func() {
132+
<-time.After(opts.Timeout)
133+
fmt.Printf("FAIL: timed out after %s\n", time.Since(start))
134+
os.Exit(1)
135+
}()
136+
}
137+
138+
// list packages
139+
pkgs, err := listPackagesWithTests(args)
118140
if err != nil {
119141
return err
120142
}
121-
defer os.RemoveAll(tmpdir)
122143

123144
atLeastOneFailure := false
124145
// list tests
@@ -133,7 +154,7 @@ func runTest(ctx context.Context, args []string) error {
133154

134155
pkgStart := time.Now()
135156
// compile test binary
136-
bin, err := compileTestBin(pkg, tmpdir)
157+
bin, err := compileTestBin(pkg, opts.TmpDir)
137158
if err != nil {
138159
fmt.Printf("FAIL\t%s\t[compile error: %v]\n", pkg.ImportPath, err)
139160
return err
@@ -144,40 +165,61 @@ func runTest(ctx context.Context, args []string) error {
144165
// FIXME: check if matches run regex
145166
args := []string{
146167
"-test.count=1",
147-
"-test.timeout=300s",
168+
fmt.Sprintf("-test.timeout=%s", opts.Timeout),
148169
}
149170
if opts.Verbose {
150171
args = append(args, "-test.v")
151172
}
152173
args = append(args, "-test.run", fmt.Sprintf("^%s$", test))
153-
cmd := exec.Command(bin, args...)
154-
log.Println(cmd.String())
155-
out, err := cmd.CombinedOutput()
156-
if err != nil {
157-
fmt.Printf("FAIL\t%s.%s\t[compile error: %v]\n", pkg.ImportPath, test, err)
158-
if opts.Verbose {
159-
fmt.Println(string(out))
174+
for i := opts.Retry; i >= 0; i-- {
175+
cmd := exec.Command(bin, args...)
176+
log.Println(cmd.String())
177+
out, err := cmd.CombinedOutput()
178+
if err != nil {
179+
if i == 0 {
180+
fmt.Printf("FAIL\t%s.%s\t[test error: %v]\n", pkg.ImportPath, test, err)
181+
isPackageOK = false
182+
atLeastOneFailure = true
183+
} else if opts.Verbose {
184+
fmt.Printf("RETRY\t%s.%s\t[test error: %v]\n", pkg.ImportPath, test, err)
185+
}
186+
if opts.Verbose {
187+
fmt.Println(string(out))
188+
}
189+
} else {
190+
fmt.Printf("ok\t%s.%s\n", pkg.ImportPath, test)
191+
break
160192
}
161-
isPackageOK = false
162-
atLeastOneFailure = true
163193
}
164194
}
165195
if isPackageOK {
166196
fmt.Printf("ok\t%s\t%s\n", pkg.ImportPath, time.Since(pkgStart))
167197
}
168198
}
169199

170-
fmt.Printf("total: %s\n", time.Since(start))
200+
log.Printf("total: %s\n", time.Since(start))
171201
if atLeastOneFailure {
172202
os.Exit(1)
173203
}
174204
return nil
175205
}
176206

177-
func preRun() {
207+
func preRun() (func(), error) {
178208
if !opts.Verbose {
179209
log.SetOutput(ioutil.Discard)
180210
}
211+
212+
// create temp dir
213+
var err error
214+
opts.TmpDir, err = ioutil.TempDir("", "testman")
215+
if err != nil {
216+
return nil, err
217+
}
218+
219+
cleanup := func() {
220+
os.RemoveAll(opts.TmpDir)
221+
}
222+
return cleanup, nil
181223
}
182224

183225
func compileTestBin(pkg Package, tempdir string) (string, error) {
@@ -258,8 +300,9 @@ type Package struct {
258300
type Opts struct {
259301
Verbose bool
260302
Run string
261-
// Timeout time.Duration
262-
// Retry int
303+
Timeout time.Duration
304+
Retry int
305+
TmpDir string
263306
// c
264307
// debug
265308
// continueOnFailure vs failFast

rules.mk

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ all: help
3030
##
3131

3232
rwildcard = $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d))
33+
check-program = $(foreach exec,$(1),$(if $(shell PATH="$(PATH)" which $(exec)),,$(error "No $(exec) in PATH")))
34+
my-filter-out = $(foreach v,$(2),$(if $(findstring $(1),$(v)),,$(v)))
35+
novendor = $(call my-filter-out,vendor/,$(1))
3336

3437
##
3538
## rules.mk
@@ -70,7 +73,7 @@ GO ?= go
7073
GOPATH ?= $(HOME)/go
7174
GO_INSTALL_OPTS ?=
7275
GO_TEST_OPTS ?= -test.timeout=30s
73-
GOMOD_DIR ?= .
76+
GOMOD_DIRS ?= $(sort $(call novendor,$(dir $(call rwildcard,.,*/go.mod go.mod))))
7477
GOCOVERAGE_FILE ?= ./coverage.txt
7578
GOTESTJSON_FILE ?= ./go-test.json
7679
GOBUILDLOG_FILE ?= ./go-build.log
@@ -96,6 +99,7 @@ INSTALL_STEPS += go.install
9699

97100
.PHONY: go.release
98101
go.release:
102+
$(call check-program, goreleaser)
99103
goreleaser --snapshot --skip-publish --rm-dist
100104
@echo -n "Do you want to release? [y/N] " && read ans && \
101105
if [ $${ans:-N} = y ]; then set -xe; goreleaser --rm-dist; fi
@@ -107,18 +111,19 @@ go.unittest:
107111
ifeq ($(CI),true)
108112
@echo "mode: atomic" > /tmp/gocoverage
109113
@rm -f $(GOTESTJSON_FILE)
110-
@set -e; for dir in `find $(GOMOD_DIR) -type f -name "go.mod" | grep -v /vendor/ | sed 's@/[^/]*$$@@' | sort | uniq`; do (set -e; (set -euf pipefail; \
114+
@set -e; for dir in $(GOMOD_DIRS); do (set -e; (set -euf pipefail; \
111115
cd $$dir; \
112-
($(GO) test ./... $(GO_TEST_OPTS) -cover -coverprofile=/tmp/profile.out -covermode=atomic -race -json | tee -a $(GOTESTJSON_FILE) 3>&1 1>&2 2>&3 | tee -a $(GOBUILDLOG_FILE); \
116+
(($(GO) test ./... $(GO_TEST_OPTS) -cover -coverprofile=/tmp/profile.out -covermode=atomic -race -json && touch $@.ok) | tee -a $(GOTESTJSON_FILE) 3>&1 1>&2 2>&3 | tee -a $(GOBUILDLOG_FILE); \
113117
); \
118+
rm $@.ok 2>/dev/null || exit 1; \
114119
if [ -f /tmp/profile.out ]; then \
115120
cat /tmp/profile.out | sed "/mode: atomic/d" >> /tmp/gocoverage; \
116121
rm -f /tmp/profile.out; \
117122
fi)); done
118123
@mv /tmp/gocoverage $(GOCOVERAGE_FILE)
119124
else
120125
@echo "mode: atomic" > /tmp/gocoverage
121-
@set -e; for dir in `find $(GOMOD_DIR) -type f -name "go.mod" | grep -v /vendor/ | sed 's@/[^/]*$$@@' | sort | uniq`; do (set -e; (set -xe; \
126+
@set -e; for dir in $(GOMOD_DIRS); do (set -e; (set -xe; \
122127
cd $$dir; \
123128
$(GO) test ./... $(GO_TEST_OPTS) -cover -coverprofile=/tmp/profile.out -covermode=atomic -race); \
124129
if [ -f /tmp/profile.out ]; then \
@@ -130,44 +135,44 @@ endif
130135

131136
.PHONY: go.checkdoc
132137
go.checkdoc:
133-
go doc $(GOMOD_DIR)
138+
go doc $(first $(GOMOD_DIRS))
134139

135140
.PHONY: go.coverfunc
136141
go.coverfunc: go.unittest
137142
go tool cover -func=$(GOCOVERAGE_FILE) | grep -v .pb.go: | grep -v .pb.gw.go:
138143

139144
.PHONY: go.lint
140145
go.lint:
141-
@set -e; for dir in `find $(GOMOD_DIR) -type f -name "go.mod" | grep -v /vendor/ | sed 's@/[^/]*$$@@' | sort | uniq`; do ( set -xe; \
146+
@set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
142147
cd $$dir; \
143148
golangci-lint run --verbose ./...; \
144149
); done
145150

146151
.PHONY: go.tidy
147152
go.tidy:
148-
@set -e; for dir in `find $(GOMOD_DIR) -type f -name "go.mod" | grep -v /vendor/ | sed 's@/[^/]*$$@@' | sort | uniq`; do ( set -xe; \
153+
@set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
149154
cd $$dir; \
150155
$(GO) mod tidy; \
151156
); done
152157

153158
.PHONY: go.build
154159
go.build:
155-
@set -e; for dir in `find $(GOMOD_DIR) -type f -name "go.mod" | grep -v /vendor/ | sed 's@/[^/]*$$@@' | sort | uniq`; do ( set -xe; \
160+
@set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
156161
cd $$dir; \
157162
$(GO) build ./...; \
158163
); done
159164

160165
.PHONY: go.bump-deps
161166
go.bumpdeps:
162-
@set -e; for dir in `find $(GOMOD_DIR) -type f -name "go.mod" | grep -v /vendor/ | sed 's@/[^/]*$$@@' | sort | uniq`; do ( set -xe; \
167+
@set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
163168
cd $$dir; \
164169
$(GO) get -u ./...; \
165170
); done
166171

167172
.PHONY: go.bump-deps
168173
go.fmt:
169174
if ! command -v goimports &>/dev/null; then GO111MODULE=off go get golang.org/x/tools/cmd/goimports; fi
170-
@set -e; for dir in `find $(GOMOD_DIR) -type f -name "go.mod" | grep -v /vendor/ | sed 's@/[^/]*$$@@' | sort | uniq`; do ( set -xe; \
175+
@set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \
171176
cd $$dir; \
172177
goimports -w `go list -f '{{.Dir}}' ./...)` \
173178
); done
@@ -238,6 +243,7 @@ ifdef DOCKER_IMAGE
238243
ifneq ($(DOCKER_IMAGE),none)
239244
.PHONY: docker.build
240245
docker.build:
246+
$(call check-program, docker)
241247
$(call docker_build,$(DOCKERFILE_PATH),$(DOCKER_IMAGE))
242248

243249
BUILD_STEPS += docker.build
@@ -316,3 +322,5 @@ help::
316322
@[ "$(TIDY_STEPS)" != "" ] && echo " tidy" || true
317323
@[ "$(UNITTEST_STEPS)" != "" ] && echo " unittest" || true
318324
@# FIXME: list other commands
325+
326+
print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) @true

0 commit comments

Comments
 (0)