Skip to content

Commit 4878176

Browse files
authored
Merge pull request #58 from jaypipes/cli
add `gdt run` command
2 parents f444c37 + b7e3661 commit 4878176

File tree

8 files changed

+442
-34
lines changed

8 files changed

+442
-34
lines changed

cmd/gdt/cmd/lint.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88

99
"github.com/gdt-dev/core/parse"
1010
"github.com/gdt-dev/core/scenario"
11-
_ "github.com/gdt-dev/gdt"
11+
_ "github.com/gdt-dev/kube"
1212
"github.com/samber/lo"
1313
"github.com/spf13/cobra"
1414

@@ -66,20 +66,27 @@ func doLint(cmd *cobra.Command, args []string) error {
6666
return err
6767
}
6868
if fi.IsDir() {
69-
cli.Vf("checking directory %q ...", path)
69+
cli.Df("checking directory %q ...", path)
7070
dirResults, err := lintDir(path)
7171
if err != nil {
7272
return err
7373
}
7474
results = append(results, dirResults...)
7575
} else {
76-
cli.Vf("checking file %q ...", path)
76+
cli.Df("checking file %q ...", path)
7777
res := lintResult{path: path}
7878
f, err := os.Open(path)
7979
if err != nil {
8080
return err
8181
}
8282
defer f.Close()
83+
84+
// Need to chdir here so that test scenario may reference files in
85+
// relative directories
86+
if err := os.Chdir(filepath.Dir(path)); err != nil {
87+
return err
88+
}
89+
8390
sc, err := scenario.FromReader(f, scenario.WithPath(path))
8491
if err != nil {
8592
if ep, ok := err.(*parse.Error); ok {

cmd/gdt/cmd/run.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"strings"
7+
"time"
8+
9+
gdtcontext "github.com/gdt-dev/core/context"
10+
"github.com/gdt-dev/core/run"
11+
"github.com/gdt-dev/core/scenario"
12+
"github.com/gdt-dev/core/suite"
13+
"github.com/spf13/cobra"
14+
15+
"github.com/gdt-dev/gdt/cmd/gdt/pkg/cli"
16+
)
17+
18+
const (
19+
debugPrefix = "[gdt]"
20+
runUsage = `run <subject> [<subject> ...]`
21+
runDescLong = `Check test scenarios or test suites for parse errors.
22+
23+
The command will run gdt test scenarios or test suites pointed to by <subject>.
24+
25+
<subject> should be a path to a YAML file or a directory containing YAML files.
26+
27+
Returns 0 on if all subject test scenarios complete without failure, 1
28+
otherwise.
29+
`
30+
)
31+
32+
var RunCmd = &cobra.Command{
33+
Use: runUsage,
34+
Short: "run test scenario/suites.",
35+
Long: runDescLong,
36+
Aliases: []string{"exec"},
37+
RunE: doRun,
38+
}
39+
40+
func init() {
41+
RunCmd.Flags().BoolVarP(
42+
&optQuiet,
43+
"quiet",
44+
"q",
45+
false,
46+
optQuietUsage,
47+
)
48+
}
49+
50+
func doRun(cmd *cobra.Command, args []string) error {
51+
if len(args) == 0 {
52+
return fmt.Errorf("supply <subject> containing filepath to YAML file or directory.")
53+
}
54+
if cli.CommonOptions.Debug {
55+
cli.CommonOptions.Verbose = true
56+
}
57+
ctx := gdtcontext.New(gdtcontext.WithDebugPrefix(debugPrefix))
58+
run := run.New()
59+
for _, path := range args {
60+
fi, err := os.Stat(path)
61+
if err != nil {
62+
if os.IsNotExist(err) {
63+
return fmt.Errorf("%q not found.", path)
64+
}
65+
return err
66+
}
67+
if fi.IsDir() {
68+
cli.Df("loading suite from directory %q ...", path)
69+
su, err := suite.FromDir(path)
70+
if err != nil {
71+
return err
72+
}
73+
err = su.Run(ctx, run)
74+
if err != nil {
75+
// Run() only returns RuntimeErrors. The `run` object will
76+
// contain assertion failures, which are not considered
77+
// RuntimeErrors.
78+
return err
79+
}
80+
} else {
81+
cli.Df("loading scenario from file %q ...", path)
82+
f, err := os.Open(path)
83+
if err != nil {
84+
return err
85+
}
86+
defer f.Close()
87+
88+
sc, err := scenario.FromReader(f, scenario.WithPath(path))
89+
if err != nil {
90+
return err
91+
}
92+
err = sc.Run(ctx, run)
93+
if err != nil {
94+
// Run() only returns RuntimeErrors. The `run` object will
95+
// contain assertion failures, which are not considered
96+
// RuntimeErrors.
97+
return err
98+
}
99+
}
100+
}
101+
102+
if !optQuiet {
103+
paths := run.ScenarioPaths()
104+
for _, path := range paths {
105+
if cli.CommonOptions.Verbose {
106+
fmt.Printf("=== RUN: %s\n", path)
107+
}
108+
var scenElapsed time.Duration
109+
110+
results := run.ScenarioResults(path)
111+
scenOK := true
112+
for _, res := range results {
113+
scenElapsed += res.Elapsed()
114+
scenOK = scenOK && res.OK()
115+
printTestUnitResult(res)
116+
}
117+
118+
if !optQuiet {
119+
if scenOK {
120+
if cli.CommonOptions.Verbose {
121+
fmt.Printf("PASS (%s)\n", scenElapsed)
122+
} else {
123+
fmt.Printf("ok\t%s\t%s\n", path, scenElapsed)
124+
}
125+
} else {
126+
fmt.Printf("FAIL\t%s\t%s\n", path, scenElapsed)
127+
}
128+
}
129+
}
130+
}
131+
if !run.OK() {
132+
if cli.CommonOptions.Verbose {
133+
fmt.Println("FAIL")
134+
}
135+
os.Exit(1)
136+
} else {
137+
if cli.CommonOptions.Verbose {
138+
fmt.Println("PASS")
139+
}
140+
}
141+
return nil
142+
}
143+
144+
func printTestUnitResult(r run.TestUnitResult) {
145+
if r.Skipped() {
146+
if cli.CommonOptions.Verbose {
147+
fmt.Printf("--- SKIP: %s (%s)\n", r.Name(), r.Elapsed())
148+
}
149+
} else if r.OK() {
150+
if cli.CommonOptions.Verbose {
151+
fmt.Printf("--- PASS: %s (%s)\n", r.Name(), r.Elapsed())
152+
}
153+
} else {
154+
for _, fail := range r.Failures() {
155+
indentFail := indent(fail.Error(), 1)
156+
if !optQuiet {
157+
fmt.Printf(
158+
"--- FAIL: %s (%s)\n%s\n",
159+
r.Name(), r.Elapsed(), indentFail,
160+
)
161+
}
162+
}
163+
}
164+
165+
if cli.CommonOptions.Debug || !r.OK() {
166+
detail := r.Detail()
167+
if len(detail) > 0 {
168+
cli.HorizontalSectionHeader("detail")
169+
fmt.Printf("%s", r.Detail())
170+
cli.HorizontalBar()
171+
}
172+
}
173+
}
174+
175+
func indent(subject string, level int) string {
176+
indentStr := strings.Repeat(" ", level*4)
177+
b := strings.Builder{}
178+
lines := strings.Split(subject, "\n")
179+
for _, line := range lines {
180+
b.WriteString(fmt.Sprintf("%s%s", indentStr, line))
181+
}
182+
return b.String()
183+
}

cmd/gdt/go.mod

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,58 @@ module github.com/gdt-dev/gdt/cmd/gdt
33
go 1.24.3
44

55
require (
6-
github.com/gdt-dev/core v1.9.11
7-
github.com/gdt-dev/gdt v1.9.9
6+
github.com/gdt-dev/core v1.10.0
7+
github.com/gdt-dev/kube v1.10.1
88
github.com/samber/lo v1.51.0
99
github.com/spf13/cobra v1.10.1
1010
github.com/spf13/pflag v1.0.10
1111
golang.org/x/term v0.35.0
1212
)
1313

1414
require (
15-
github.com/PaesslerAG/gval v1.0.0 // indirect
16-
github.com/PaesslerAG/jsonpath v0.1.1 // indirect
1715
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
18-
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
16+
github.com/davecgh/go-spew v1.1.1 // indirect
17+
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
18+
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
19+
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
20+
github.com/go-logr/logr v1.4.2 // indirect
21+
github.com/go-openapi/jsonpointer v0.21.0 // indirect
22+
github.com/go-openapi/jsonreference v0.20.2 // indirect
23+
github.com/go-openapi/swag v0.23.0 // indirect
24+
github.com/gogo/protobuf v1.3.2 // indirect
25+
github.com/google/gnostic-models v0.7.0 // indirect
26+
github.com/google/uuid v1.6.0 // indirect
1927
github.com/inconshreveable/mousetrap v1.1.0 // indirect
28+
github.com/josharian/intern v1.0.0 // indirect
29+
github.com/json-iterator/go v1.1.12 // indirect
30+
github.com/mailru/easyjson v0.7.7 // indirect
31+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
32+
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
33+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
34+
github.com/theory/jsonpath v0.10.1 // indirect
35+
github.com/x448/float16 v0.8.4 // indirect
36+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
37+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
38+
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
39+
go.yaml.in/yaml/v2 v2.4.2 // indirect
40+
go.yaml.in/yaml/v3 v3.0.4 // indirect
41+
golang.org/x/net v0.38.0 // indirect
42+
golang.org/x/oauth2 v0.27.0 // indirect
2043
golang.org/x/sys v0.36.0 // indirect
21-
golang.org/x/text v0.22.0 // indirect
44+
golang.org/x/text v0.23.0 // indirect
45+
golang.org/x/time v0.9.0 // indirect
46+
google.golang.org/protobuf v1.36.5 // indirect
47+
gopkg.in/inf.v0 v0.9.1 // indirect
2248
gopkg.in/yaml.v3 v3.0.1 // indirect
49+
k8s.io/api v0.34.1 // indirect
50+
k8s.io/apimachinery v0.34.1 // indirect
51+
k8s.io/client-go v0.34.1 // indirect
52+
k8s.io/klog/v2 v2.130.1 // indirect
53+
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
54+
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
55+
sigs.k8s.io/controller-runtime v0.22.1 // indirect
56+
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
57+
sigs.k8s.io/randfill v1.0.0 // indirect
58+
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
59+
sigs.k8s.io/yaml v1.6.0 // indirect
2360
)

0 commit comments

Comments
 (0)