-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
180 lines (155 loc) · 4.77 KB
/
Copy pathmain.go
File metadata and controls
180 lines (155 loc) · 4.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// Command httpsuite runs .http files as API tests, for local development and CI.
package main
import (
"context"
"flag"
"fmt"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"github.com/uradical/httpsuite/internal/output"
"github.com/uradical/httpsuite/internal/runner"
"github.com/uradical/httpsuite/internal/suite"
)
// suiteFileName is the suite definition looked up during directory discovery.
const suiteFileName = "httpsuite.yaml"
// Build information, injected via -ldflags at release time by GoReleaser.
var (
version = "dev"
commit = "none"
date = "unknown"
)
// varFlags collects repeatable --var key=value flags.
type varFlags map[string]string
func (v varFlags) String() string { return "" }
func (v varFlags) Set(s string) error {
i := strings.Index(s, "=")
if i <= 0 {
return fmt.Errorf("invalid --var %q, want key=value", s)
}
v[s[:i]] = s[i+1:]
return nil
}
func main() {
os.Exit(run(os.Args[1:]))
}
// run parses args, executes the discovered requests, and returns a process exit
// code: 0 all passed, 1 any failed, 2 on a usage or setup error.
func run(args []string) int {
cliVars := varFlags{}
var ui bool
var showVersion bool
var reportPath string
var envName string
fs := flag.NewFlagSet("httpsuite", flag.ContinueOnError)
fs.Var(cliVars, "var", "override a {{placeholder}} as key=value (repeatable)")
fs.StringVar(&envName, "env", "", "select an environment from http-client.env.json")
fs.BoolVar(&ui, "ui", false, "open the results UI (not yet implemented)")
fs.BoolVar(&showVersion, "version", false, "print version information and exit")
fs.StringVar(&reportPath, "report", "", "write a JUnit XML report to the given file")
fs.Usage = func() {
fmt.Fprintln(os.Stderr, "usage: httpsuite [--var key=value]... [--env name] [--report file] [--ui] [--version] [path]")
fs.PrintDefaults()
}
if err := fs.Parse(args); err != nil {
return 2
}
if showVersion {
fmt.Printf("httpsuite %s (commit %s, built %s)\n", version, commit, date)
return 0
}
if ui {
fmt.Println("--ui: not yet implemented")
return 0
}
path := "."
if fs.NArg() > 0 {
path = fs.Arg(0)
}
results, err := execute(path, buildVars(cliVars), envName, &http.Client{})
if err != nil {
fmt.Fprintln(os.Stderr, "httpsuite:", err)
return 2
}
output.Report(os.Stdout, results, output.IsTerminal())
if reportPath != "" {
if err := writeReport(reportPath, results, path); err != nil {
fmt.Fprintln(os.Stderr, "httpsuite: writing report:", err)
return 2
}
}
for _, r := range results {
if !r.Passed() {
return 1
}
}
return 0
}
// writeReport writes a JUnit XML report of results to path.
func writeReport(path string, results []output.Result, suiteName string) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
return output.JUnit(f, results, suiteName)
}
// buildVars layers CLI --var overrides on top of the OS environment.
func buildVars(cli map[string]string) map[string]string {
vars := map[string]string{}
for _, e := range os.Environ() {
if i := strings.Index(e, "="); i > 0 {
vars[e[:i]] = e[i+1:]
}
}
for k, v := range cli {
vars[k] = v
}
return vars
}
// execute resolves path into a set of requests and runs them. A file path runs
// that single file; a directory is discovered via suiteFileName, falling back
// to a sorted *.http glob.
func execute(path string, vars map[string]string, envName string, client *http.Client) ([]output.Result, error) {
info, err := os.Stat(path)
if err != nil {
return nil, err
}
ctx := context.Background()
if !info.IsDir() {
return runner.RunFileEnv(ctx, client, path, vars, envName, suite.DefaultTimeout)
}
suitePath := filepath.Join(path, suiteFileName)
if _, err := os.Stat(suitePath); err == nil {
s, err := suite.ParseFile(suitePath)
if err != nil {
return nil, err
}
runGroup := func(group suite.Group) []output.Result {
return runner.RunGroup(ctx, client, path, group, vars, envName)
}
return runner.RunSuite(s, runGroup), nil
}
matches, err := filepath.Glob(filepath.Join(path, "*.http"))
if err != nil {
return nil, err
}
sort.Strings(matches)
var all []output.Result
for _, file := range matches {
opts := runner.Options{Vars: vars, EnvName: envName, Timeout: suite.DefaultTimeout}
all = append(all, fileResults(ctx, client, file, file, opts)...)
}
return all, nil
}
// fileResults runs a single .http file, turning a parse/read error into a single
// failed result (labelled with display) so one bad file does not abort the run.
func fileResults(ctx context.Context, client *http.Client, fullPath, display string, opts runner.Options) []output.Result {
res, err := runner.RunFileOpts(ctx, client, fullPath, opts)
if err != nil {
return []output.Result{{Method: "-", URL: display, Err: err, Reason: err.Error()}}
}
return res
}