Skip to content

Commit e320a3d

Browse files
committed
feat: v1
Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com>
1 parent 6fe2990 commit e320a3d

File tree

5 files changed

+239
-13
lines changed

5 files changed

+239
-13
lines changed

.golangci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ linters:
2626
- dogsled
2727
- dupl
2828
- errcheck
29-
- funlen
29+
#- funlen
3030
- gochecknoinits
3131
#- gocognit
3232
- goconst

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: 8 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

main.go

Lines changed: 225 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,244 @@
11
package main
22

33
import (
4+
"context"
5+
"flag"
46
"fmt"
7+
"io/ioutil"
58
"log"
69
"os"
10+
"os/exec"
11+
"path/filepath"
12+
"strings"
13+
"time"
714

15+
"github.com/peterbourgon/ff/v3/ffcli"
816
"moul.io/motd"
9-
"moul.io/srand"
1017
)
1118

19+
var opts Opts
20+
1221
func main() {
1322
if err := run(os.Args); err != nil {
14-
log.Fatalf("error: %v", err)
23+
if err != flag.ErrHelp {
24+
log.Fatalf("error: %v", err)
25+
}
1526
os.Exit(1)
1627
}
1728
}
1829

19-
// nolint:unparam
2030
func run(args []string) error {
21-
srand.Fast()
22-
fmt.Print(motd.Default())
23-
fmt.Println("Hello World!", args)
31+
opts = Opts{
32+
verbose: false,
33+
}
34+
35+
testFlags := flag.NewFlagSet("testman test", flag.ExitOnError)
36+
//testFlags.BoolVar(&opts.continueOnFailure, "continue-on-failure", opts.continueOnFailure, "Continue on failure")
37+
testFlags.BoolVar(&opts.verbose, "v", opts.verbose, "verbose")
38+
listFlags := flag.NewFlagSet("testman list", flag.ExitOnError)
39+
listFlags.BoolVar(&opts.verbose, "v", opts.verbose, "verbose")
40+
41+
root := &ffcli.Command{
42+
ShortUsage: "testman <subcommand> [flags]",
43+
ShortHelp: "Advanced testing workflows for Go projects.",
44+
Exec: func(ctx context.Context, args []string) error {
45+
fmt.Println(motd.Default())
46+
return flag.ErrHelp
47+
},
48+
Subcommands: []*ffcli.Command{
49+
{
50+
Name: "test",
51+
FlagSet: testFlags,
52+
ShortHelp: "advanced go test workflows",
53+
ShortUsage: "testman test [flags] [packages]",
54+
LongHelp: "EXAMPLES\n testman test -v ./...",
55+
Exec: runTest,
56+
}, {
57+
Name: "list",
58+
FlagSet: listFlags,
59+
ShortHelp: "list available tests",
60+
ShortUsage: "testman list [packages]",
61+
LongHelp: "EXAMPLE\n testman list ./...",
62+
Exec: runList,
63+
},
64+
},
65+
}
66+
67+
return root.ParseAndRun(context.Background(), args[1:])
68+
}
69+
70+
func runList(ctx context.Context, args []string) error {
71+
if len(args) == 0 {
72+
return flag.ErrHelp
73+
}
74+
preRun()
75+
76+
// list packages
77+
pkgs, err := listPackagesWithTests(args)
78+
if err != nil {
79+
return err
80+
}
81+
82+
// list tests
83+
for _, pkg := range pkgs {
84+
tests, err := listDirTests(pkg.Dir)
85+
if err != nil {
86+
return err
87+
}
88+
89+
fmt.Println(pkg.ImportPath)
90+
for _, test := range tests {
91+
fmt.Printf(" %s\n", test)
92+
}
93+
}
94+
return nil
95+
}
96+
97+
func runTest(ctx context.Context, args []string) error {
98+
if len(args) == 0 {
99+
return flag.ErrHelp
100+
}
101+
preRun()
102+
103+
start := time.Now()
104+
105+
// list packages
106+
pkgs, err := listPackagesWithTests(args)
107+
if err != nil {
108+
return err
109+
}
110+
111+
// create temp dir
112+
tmpdir, err := ioutil.TempDir("", "testman")
113+
if err != nil {
114+
return err
115+
}
116+
defer os.RemoveAll(tmpdir)
117+
118+
// list tests
119+
for _, pkg := range pkgs {
120+
tests, err := listDirTests(pkg.Dir)
121+
if err != nil {
122+
return err
123+
}
124+
125+
pkgStart := time.Now()
126+
// compile test binary
127+
bin, err := compileTestBin(pkg, tmpdir)
128+
if err != nil {
129+
fmt.Printf("FAIL\t%s\t[compile error: %v]\n", pkg.ImportPath, err)
130+
return err
131+
}
132+
133+
everythingIsOK := true
134+
for _, test := range tests {
135+
// FIXME: check if matches run regex
136+
args := []string{
137+
"-test.count=1",
138+
"-test.timeout=300s",
139+
}
140+
if opts.verbose {
141+
args = append(args, "-test.v")
142+
}
143+
args = append(args, "-test.run", fmt.Sprintf("^%s$", test))
144+
cmd := exec.Command(bin, args...)
145+
log.Println(cmd.String())
146+
out, err := cmd.CombinedOutput()
147+
if err != nil {
148+
fmt.Printf("FAIL\t%s\t[compile error: %v]\n", pkg.ImportPath, err)
149+
if opts.verbose {
150+
fmt.Println(string(out))
151+
}
152+
everythingIsOK = false
153+
}
154+
}
155+
if everythingIsOK {
156+
fmt.Printf("ok\t%s\t%s\n", pkg.ImportPath, time.Since(pkgStart))
157+
}
158+
}
159+
160+
fmt.Printf("total: %s\n", time.Since(start))
24161
return nil
25162
}
163+
164+
func preRun() {
165+
if !opts.verbose {
166+
log.SetOutput(ioutil.Discard)
167+
}
168+
}
169+
170+
func compileTestBin(pkg Package, tempdir string) (string, error) {
171+
name := strings.Replace(pkg.ImportPath, "/", "~", -1)
172+
bin := filepath.Join(tempdir, name)
173+
cmd := exec.Command("go", "test", "-c", "-o", bin)
174+
cmd.Dir = pkg.Dir
175+
log.Println(cmd.String())
176+
out, err := cmd.CombinedOutput()
177+
if err != nil {
178+
fmt.Fprintln(os.Stderr, string(out))
179+
return "", err
180+
}
181+
182+
return bin, nil
183+
}
184+
185+
func listDirTests(dir string) ([]string, error) {
186+
cmd := exec.Command("go", "test", "-list", ".")
187+
cmd.Dir = dir
188+
log.Println(cmd.String())
189+
out, err := cmd.CombinedOutput()
190+
if err != nil {
191+
return nil, err
192+
}
193+
if strings.TrimSpace(string(out)) == "" {
194+
return nil, nil
195+
}
196+
tests := []string{}
197+
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
198+
for _, line := range lines {
199+
if strings.HasPrefix(line, "ok ") {
200+
continue
201+
}
202+
tests = append(tests, line)
203+
}
204+
return tests, nil
205+
}
206+
207+
func listPackagesWithTests(patterns []string) ([]Package, error) {
208+
cmdArgs := append([]string{"list", "-test", "-f", "{{.ImportPath}} {{.Dir}}"}, patterns...)
209+
cmd := exec.Command("go", cmdArgs...)
210+
log.Println(cmd.String())
211+
out, err := cmd.CombinedOutput()
212+
if err != nil {
213+
fmt.Fprintln(os.Stderr, string(out))
214+
return nil, err
215+
}
216+
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
217+
pkgs := []Package{}
218+
for _, line := range lines {
219+
parts := strings.SplitN(line, " ", 2)
220+
if !strings.HasSuffix(parts[0], ".test") {
221+
continue
222+
}
223+
pkgs = append(pkgs, Package{
224+
ImportPath: strings.TrimSuffix(parts[0], ".test"),
225+
Dir: parts[1],
226+
})
227+
}
228+
return pkgs, nil
229+
}
230+
231+
type Package struct {
232+
Dir string
233+
ImportPath string
234+
}
235+
236+
type Opts struct {
237+
verbose bool
238+
// run
239+
// timeout
240+
// c
241+
// debug
242+
// retries
243+
// continueOnFailure vs failFast
244+
}

main_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
package main
22

33
import (
4+
"flag"
45
"testing"
56

67
"go.uber.org/goleak"
78
)
89

910
func TestRun(t *testing.T) {
10-
err := run(nil)
11-
if err != nil {
12-
t.Fatalf("err should be nil: %v", err)
11+
err := run([]string{"testman"})
12+
if err != flag.ErrHelp {
13+
t.Fatalf("err should be flag.ErrHelp: %v", err)
1314
}
1415
}
1516

0 commit comments

Comments
 (0)