Skip to content

Commit 119315e

Browse files
committed
feat: support to run tests with multiple threads
1 parent 7c4f12c commit 119315e

File tree

10 files changed

+147
-33
lines changed

10 files changed

+147
-33
lines changed

cmd/run.go

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
11
package cmd
22

33
import (
4+
"context"
45
"fmt"
56
"path"
67
"path/filepath"
78
"strings"
9+
"sync"
10+
"time"
811

12+
"github.com/linuxsuren/api-testing/pkg/render"
913
"github.com/linuxsuren/api-testing/pkg/runner"
1014
"github.com/linuxsuren/api-testing/pkg/testing"
1115
"github.com/spf13/cobra"
16+
"golang.org/x/sync/semaphore"
1217
)
1318

1419
type runOption struct {
15-
pattern string
20+
pattern string
21+
duration time.Duration
22+
thread int64
23+
context context.Context
1624
}
1725

1826
// CreateRunCommand returns the run command
@@ -31,31 +39,84 @@ See also https://github.com/LinuxSuRen/api-testing/tree/master/sample`,
3139
flags := cmd.Flags()
3240
flags.StringVarP(&opt.pattern, "pattern", "p", "test-suite-*.yaml",
3341
"The file pattern which try to execute the test cases")
42+
flags.DurationVarP(&opt.duration, "duration", "", 0, "Running duration")
43+
flags.Int64VarP(&opt.thread, "thread", "", 1, "Threads of the execution")
3444
return
3545
}
3646

3747
func (o *runOption) runE(cmd *cobra.Command, args []string) (err error) {
3848
var files []string
39-
ctx := getDefaultContext()
49+
o.context = cmd.Context()
4050

4151
if files, err = filepath.Glob(o.pattern); err == nil {
4252
for i := range files {
4353
item := files[i]
44-
if err = runSuite(item, ctx); err != nil {
54+
if err = o.runSuiteWithDuration(item); err != nil {
4555
return
4656
}
4757
}
4858
}
4959
return
5060
}
5161

62+
func (o *runOption) runSuiteWithDuration(suite string) (err error) {
63+
sem := semaphore.NewWeighted(o.thread)
64+
stop := false
65+
var timeout *time.Ticker
66+
if o.duration > 0 {
67+
timeout = time.NewTicker(o.duration)
68+
} else {
69+
// make sure having a valid timer
70+
timeout = time.NewTicker(time.Second)
71+
}
72+
errChannel := make(chan error, 10)
73+
var wait sync.WaitGroup
74+
75+
for !stop {
76+
select {
77+
case <-timeout.C:
78+
stop = true
79+
case err = <-errChannel:
80+
if err != nil {
81+
stop = true
82+
}
83+
default:
84+
if err := sem.Acquire(o.context, 1); err != nil {
85+
continue
86+
}
87+
wait.Add(1)
88+
if o.duration <= 0 {
89+
stop = true
90+
}
91+
92+
go func(ch chan error) {
93+
defer sem.Release(1)
94+
defer wait.Done()
95+
96+
ctx := getDefaultContext()
97+
ch <- runSuite(suite, ctx)
98+
}(errChannel)
99+
}
100+
}
101+
err = <-errChannel
102+
wait.Wait()
103+
return
104+
}
105+
52106
func runSuite(suite string, ctx map[string]interface{}) (err error) {
53107
var testSuite *testing.TestSuite
54108
if testSuite, err = testing.Parse(suite); err != nil {
55109
return
56110
}
57111

58-
testSuite.API = strings.TrimSuffix(testSuite.API, "/")
112+
var result string
113+
if result, err = render.Render("base api", testSuite.API, ctx); err == nil {
114+
testSuite.API = result
115+
testSuite.API = strings.TrimSuffix(testSuite.API, "/")
116+
} else {
117+
return
118+
}
119+
59120
for _, testCase := range testSuite.Items {
60121
// reuse the API prefix
61122
if strings.HasPrefix(testCase.Request.API, "/") {

cmd/run_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package cmd
22

33
import (
4-
"fmt"
54
"net/http"
65
"testing"
76

@@ -89,7 +88,6 @@ func TestRunCommand(t *testing.T) {
8988

9089
root.SetArgs(append([]string{"run"}, tt.args...))
9190

92-
fmt.Println(tt.args)
9391
err := root.Execute()
9492
assert.Equal(t, tt.hasErr, err != nil, err)
9593
})

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,6 @@ require (
3030
github.com/spf13/cast v1.3.1 // indirect
3131
github.com/spf13/pflag v1.0.5 // indirect
3232
golang.org/x/crypto v0.3.0 // indirect
33+
golang.org/x/sync v0.1.0 // indirect
3334
gopkg.in/yaml.v3 v3.0.1 // indirect
3435
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
7272
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
7373
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
7474
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
75+
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
76+
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
7577
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
7678
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
7779
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import (
99

1010
func main() {
1111
cmd := &cobra.Command{
12-
Use: "atest",
12+
Use: "atest",
13+
Short: "API testing tool",
1314
}
1415
cmd.AddCommand(c.CreateInitCommand(), c.CreateRunCommand())
1516

pkg/render/doc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Package render provides a simple way to render as template
2+
package render

pkg/render/template.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package render
2+
3+
import (
4+
"bytes"
5+
"html/template"
6+
"strings"
7+
8+
"github.com/Masterminds/sprig/v3"
9+
)
10+
11+
// Render render then return the result
12+
func Render(name, text string, ctx interface{}) (result string, err error) {
13+
var tpl *template.Template
14+
if tpl, err = template.New(name).Funcs(sprig.FuncMap()).Parse(text); err == nil {
15+
buf := new(bytes.Buffer)
16+
if err = tpl.Execute(buf, ctx); err == nil {
17+
result = strings.TrimSpace(buf.String())
18+
}
19+
}
20+
return
21+
}

pkg/render/template_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package render
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestRender(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
text string
13+
ctx interface{}
14+
expect string
15+
}{{
16+
name: "default",
17+
text: `{{default "hello" .Bar}}`,
18+
ctx: nil,
19+
expect: "hello",
20+
}, {
21+
name: "trim",
22+
text: `{{trim " hello "}}`,
23+
ctx: "",
24+
expect: "hello",
25+
}}
26+
for _, tt := range tests {
27+
t.Run(tt.name, func(t *testing.T) {
28+
result, err := Render(tt.name, tt.text, tt.ctx)
29+
assert.Nil(t, err)
30+
assert.Equal(t, tt.expect, result)
31+
})
32+
}
33+
}

pkg/runner/simple.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"os"
1212
"reflect"
1313
"strings"
14+
"time"
1415

1516
"github.com/andreyvit/diff"
1617
"github.com/antonmedv/expr"
@@ -36,7 +37,9 @@ func RunTestCase(testcase *testing.TestCase, ctx interface{}) (output interface{
3637
}
3738
}()
3839

39-
client := http.Client{}
40+
client := http.Client{
41+
Timeout: time.Second * 30,
42+
}
4043
var requestBody io.Reader
4144
if testcase.Request.Body != "" {
4245
requestBody = bytes.NewBufferString(testcase.Request.Body)

pkg/testing/parser.go

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package testing
22

33
import (
4-
"bytes"
5-
"html/template"
64
"net/http"
75
"os"
86
"strings"
97

10-
"github.com/Masterminds/sprig/v3"
8+
"github.com/linuxsuren/api-testing/pkg/render"
119
"gopkg.in/yaml.v2"
1210
)
1311

@@ -24,15 +22,12 @@ func Parse(configFile string) (testSuite *TestSuite, err error) {
2422
// Render injects the template based context
2523
func (r *Request) Render(ctx interface{}) (err error) {
2624
// template the API
27-
var tpl *template.Template
28-
if tpl, err = template.New("api").Funcs(sprig.FuncMap()).Parse(r.API); err != nil {
25+
var result string
26+
if result, err = render.Render("api", r.API, ctx); err == nil {
27+
r.API = result
28+
} else {
2929
return
3030
}
31-
buf := new(bytes.Buffer)
32-
if err = tpl.Execute(buf, ctx); err != nil {
33-
return
34-
}
35-
r.API = buf.String()
3631

3732
// read body from file
3833
if r.BodyFromFile != "" {
@@ -45,29 +40,26 @@ func (r *Request) Render(ctx interface{}) (err error) {
4540

4641
// template the header
4742
for key, val := range r.Header {
48-
if tpl, err = template.New("header").Funcs(sprig.FuncMap()).Parse(val); err == nil {
49-
buf = new(bytes.Buffer)
50-
if err = tpl.Execute(buf, ctx); err == nil {
51-
r.Header[key] = buf.String()
52-
}
43+
if result, err = render.Render("header", val, ctx); err == nil {
44+
r.Header[key] = result
45+
} else {
46+
return
5347
}
5448
}
5549

5650
// template the body
57-
if tpl, err = template.New("body").Funcs(sprig.FuncMap()).Parse(r.Body); err == nil {
58-
buf = new(bytes.Buffer)
59-
if err = tpl.Execute(buf, ctx); err == nil {
60-
r.Body = buf.String()
61-
}
51+
if result, err = render.Render("body", r.Body, ctx); err == nil {
52+
r.Body = result
53+
} else {
54+
return
6255
}
6356

6457
// template the form
6558
for key, val := range r.Form {
66-
if tpl, err = template.New("form").Funcs(sprig.FuncMap()).Parse(val); err == nil {
67-
buf = new(bytes.Buffer)
68-
if err = tpl.Execute(buf, ctx); err == nil {
69-
r.Form[key] = buf.String()
70-
}
59+
if result, err = render.Render("form", val, ctx); err == nil {
60+
r.Form[key] = result
61+
} else {
62+
return
7163
}
7264
}
7365

0 commit comments

Comments
 (0)