Skip to content

Commit

Permalink
feat: support to run tests with multiple threads (#13)
Browse files Browse the repository at this point in the history
Co-authored-by: rick <LinuxSuRen@users.noreply.github.com>
  • Loading branch information
LinuxSuRen and LinuxSuRen authored Mar 22, 2023
1 parent 7c4f12c commit 8564fc1
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 33 deletions.
69 changes: 65 additions & 4 deletions cmd/run.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
package cmd

import (
"context"
"fmt"
"path"
"path/filepath"
"strings"
"sync"
"time"

"github.com/linuxsuren/api-testing/pkg/render"
"github.com/linuxsuren/api-testing/pkg/runner"
"github.com/linuxsuren/api-testing/pkg/testing"
"github.com/spf13/cobra"
"golang.org/x/sync/semaphore"
)

type runOption struct {
pattern string
pattern string
duration time.Duration
thread int64
context context.Context
}

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

func (o *runOption) runE(cmd *cobra.Command, args []string) (err error) {
var files []string
ctx := getDefaultContext()
o.context = cmd.Context()

if files, err = filepath.Glob(o.pattern); err == nil {
for i := range files {
item := files[i]
if err = runSuite(item, ctx); err != nil {
if err = o.runSuiteWithDuration(item); err != nil {
return
}
}
}
return
}

func (o *runOption) runSuiteWithDuration(suite string) (err error) {
sem := semaphore.NewWeighted(o.thread)
stop := false
var timeout *time.Ticker
if o.duration > 0 {
timeout = time.NewTicker(o.duration)
} else {
// make sure having a valid timer
timeout = time.NewTicker(time.Second)
}
errChannel := make(chan error, 10)
var wait sync.WaitGroup

for !stop {
select {
case <-timeout.C:
stop = true
case err = <-errChannel:
if err != nil {
stop = true
}
default:
if err := sem.Acquire(o.context, 1); err != nil {
continue
}
wait.Add(1)
if o.duration <= 0 {
stop = true
}

go func(ch chan error) {
defer sem.Release(1)
defer wait.Done()

ctx := getDefaultContext()
ch <- runSuite(suite, ctx)
}(errChannel)
}
}
err = <-errChannel
wait.Wait()
return
}

func runSuite(suite string, ctx map[string]interface{}) (err error) {
var testSuite *testing.TestSuite
if testSuite, err = testing.Parse(suite); err != nil {
return
}

testSuite.API = strings.TrimSuffix(testSuite.API, "/")
var result string
if result, err = render.Render("base api", testSuite.API, ctx); err == nil {
testSuite.API = result
testSuite.API = strings.TrimSuffix(testSuite.API, "/")
} else {
return
}

for _, testCase := range testSuite.Items {
// reuse the API prefix
if strings.HasPrefix(testCase.Request.API, "/") {
Expand Down
2 changes: 0 additions & 2 deletions cmd/run_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cmd

import (
"fmt"
"net/http"
"testing"

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

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

fmt.Println(tt.args)
err := root.Execute()
assert.Equal(t, tt.hasErr, err != nil, err)
})
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ require (
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.3.0 // indirect
golang.org/x/sync v0.1.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import (

func main() {
cmd := &cobra.Command{
Use: "atest",
Use: "atest",
Short: "API testing tool",
}
cmd.AddCommand(c.CreateInitCommand(), c.CreateRunCommand())

Expand Down
2 changes: 2 additions & 0 deletions pkg/render/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package render provides a simple way to render as template
package render
21 changes: 21 additions & 0 deletions pkg/render/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package render

import (
"bytes"
"html/template"
"strings"

"github.com/Masterminds/sprig/v3"
)

// Render render then return the result
func Render(name, text string, ctx interface{}) (result string, err error) {
var tpl *template.Template
if tpl, err = template.New(name).Funcs(sprig.FuncMap()).Parse(text); err == nil {
buf := new(bytes.Buffer)
if err = tpl.Execute(buf, ctx); err == nil {
result = strings.TrimSpace(buf.String())
}
}
return
}
33 changes: 33 additions & 0 deletions pkg/render/template_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package render

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestRender(t *testing.T) {
tests := []struct {
name string
text string
ctx interface{}
expect string
}{{
name: "default",
text: `{{default "hello" .Bar}}`,
ctx: nil,
expect: "hello",
}, {
name: "trim",
text: `{{trim " hello "}}`,
ctx: "",
expect: "hello",
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Render(tt.name, tt.text, tt.ctx)
assert.Nil(t, err)
assert.Equal(t, tt.expect, result)
})
}
}
5 changes: 4 additions & 1 deletion pkg/runner/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"reflect"
"strings"
"time"

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

client := http.Client{}
client := http.Client{
Timeout: time.Second * 30,
}
var requestBody io.Reader
if testcase.Request.Body != "" {
requestBody = bytes.NewBufferString(testcase.Request.Body)
Expand Down
42 changes: 17 additions & 25 deletions pkg/testing/parser.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package testing

import (
"bytes"
"html/template"
"net/http"
"os"
"strings"

"github.com/Masterminds/sprig/v3"
"github.com/linuxsuren/api-testing/pkg/render"
"gopkg.in/yaml.v2"
)

Expand All @@ -24,15 +22,12 @@ func Parse(configFile string) (testSuite *TestSuite, err error) {
// Render injects the template based context
func (r *Request) Render(ctx interface{}) (err error) {
// template the API
var tpl *template.Template
if tpl, err = template.New("api").Funcs(sprig.FuncMap()).Parse(r.API); err != nil {
var result string
if result, err = render.Render("api", r.API, ctx); err == nil {
r.API = result
} else {
return
}
buf := new(bytes.Buffer)
if err = tpl.Execute(buf, ctx); err != nil {
return
}
r.API = buf.String()

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

// template the header
for key, val := range r.Header {
if tpl, err = template.New("header").Funcs(sprig.FuncMap()).Parse(val); err == nil {
buf = new(bytes.Buffer)
if err = tpl.Execute(buf, ctx); err == nil {
r.Header[key] = buf.String()
}
if result, err = render.Render("header", val, ctx); err == nil {
r.Header[key] = result
} else {
return
}
}

// template the body
if tpl, err = template.New("body").Funcs(sprig.FuncMap()).Parse(r.Body); err == nil {
buf = new(bytes.Buffer)
if err = tpl.Execute(buf, ctx); err == nil {
r.Body = buf.String()
}
if result, err = render.Render("body", r.Body, ctx); err == nil {
r.Body = result
} else {
return
}

// template the form
for key, val := range r.Form {
if tpl, err = template.New("form").Funcs(sprig.FuncMap()).Parse(val); err == nil {
buf = new(bytes.Buffer)
if err = tpl.Execute(buf, ctx); err == nil {
r.Form[key] = buf.String()
}
if result, err = render.Render("form", val, ctx); err == nil {
r.Form[key] = result
} else {
return
}
}

Expand Down

0 comments on commit 8564fc1

Please sign in to comment.