Skip to content

Commit

Permalink
feat: add writeFile function to expr (#277)
Browse files Browse the repository at this point in the history
  • Loading branch information
LinuxSuRen authored Nov 17, 2023
1 parent 7124dc4 commit 649b0d8
Show file tree
Hide file tree
Showing 25 changed files with 686 additions and 97 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fmt:
build:
mkdir -p bin
rm -rf bin/atest
go build ${TOOLEXEC} -a ${BUILD_FLAG} -o bin/${BINARY} main.go
CGO_ENABLED=0 go build ${TOOLEXEC} -a ${BUILD_FLAG} -o bin/${BINARY} main.go
build-ext: build-ext-git build-ext-orm build-ext-s3 build-ext-etcd
build-ext-git:
CGO_ENABLED=0 go build -ldflags "-w -s" -o bin/atest-store-git extensions/store-git/main.go
Expand Down
2 changes: 2 additions & 0 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ func (o *runOption) runSuite(loader testing.Loader, dataContext map[string]inter
runner := runner.GetTestSuiteRunner(testSuite)
runner.WithTestReporter(o.reporter)
runner.WithSecure(testSuite.Spec.Secure)
runner.WithOutputWriter(os.Stdout)
runner.WithWriteLevel(o.level)
if output, err = runner.RunTestCase(&testCase, dataContext, ctxWithTimeout); err != nil && !o.requestIgnoreError {
err = fmt.Errorf("failed to run '%s', %v", testCase.Name, err)
return
Expand Down
105 changes: 84 additions & 21 deletions pkg/runner/expr_function.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,32 +26,21 @@ SOFTWARE.
package runner

import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"time"

"github.com/antonmedv/expr"
"github.com/antonmedv/expr/ast"
"github.com/antonmedv/expr/builtin"
"github.com/antonmedv/expr/vm"
)

var extensionFuncs = []*ast.Function{
{
Name: "sleep",
Func: ExprFuncSleep,
},
{
Name: "httpReady",
Func: ExprFuncHTTPReady,
},
{
Name: "exec",
Func: func(params ...interface{}) (res any, err error) {
exec.Command("sh", "-c", params[0].(string)).Run()
return
},
},
}

// ExprFuncSleep is an expr function for sleeping
func ExprFuncSleep(params ...interface{}) (res interface{}, err error) {
if len(params) < 1 {
Expand Down Expand Up @@ -90,14 +79,88 @@ func ExprFuncHTTPReady(params ...interface{}) (res interface{}, err error) {
return
}

var resp *http.Response
for i := 0; i < retry; i++ {
var resp *http.Response
if resp, err = http.Get(api); err == nil && resp != nil && resp.StatusCode == http.StatusOK {
resp, err = http.Get(api)
alive := err == nil && resp != nil && resp.StatusCode == http.StatusOK

if alive && len(params) >= 3 {
log.Println("checking the response")
exprText := params[2].(string)

// check the response
var data []byte
if data, err = io.ReadAll(resp.Body); err == nil {
unstruct := make(map[string]interface{})

if err = json.Unmarshal(data, &unstruct); err != nil {
log.Printf("failed to unmarshal the response data: %v\n", err)
return
}

unstruct["data"] = unstruct
var program *vm.Program
if program, err = expr.Compile(exprText, expr.Env(unstruct)); err != nil {
log.Printf("failed to compile: %s, %v\n", exprText, err)
return
}

var result interface{}
if result, err = expr.Run(program, unstruct); err != nil {
log.Printf("failed to Run: %s, %v\n", exprText, err)
return
}

if val, ok := result.(bool); ok {
if val {
return
}
} else {
err = fmt.Errorf("the result of %s should be a bool", exprText)
return
}
}
} else if alive {
return
}
fmt.Println("waiting for", api)

log.Println("waiting for", api)
time.Sleep(1 * time.Second)
}
err = fmt.Errorf("failed to wait for the API ready in %d times", retry)
return
}

func init() {
builtin.Builtins = append(builtin.Builtins, []*ast.Function{
{
Name: "sleep",
Func: ExprFuncSleep,
},
{
Name: "httpReady",
Func: ExprFuncHTTPReady,
},
{
Name: "command",
Func: func(params ...interface{}) (res any, err error) {
var output []byte
output, err = exec.Command("sh", "-c", params[0].(string)).CombinedOutput()
if output != nil {
res = string(output)
}
return
},
},
{
Name: "writeFile",
Func: func(params ...interface{}) (res any, err error) {
filename := params[0]
content := params[1]

err = os.WriteFile(filename.(string), []byte(content.(string)), 0644)
return
},
},
}...)
}
100 changes: 98 additions & 2 deletions pkg/runner/expr_function_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@ SOFTWARE.
package runner_test

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

"github.com/antonmedv/expr"
"github.com/antonmedv/expr/vm"
"github.com/h2non/gock"
"github.com/linuxsuren/api-testing/pkg/runner"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -60,15 +65,15 @@ func TestExprFuncSleep(t *testing.T) {

func TestExprFuncHTTPReady(t *testing.T) {
t.Run("normal", func(t *testing.T) {
defer gock.Clean()
defer gock.Off()
gock.New(urlFoo).Reply(http.StatusOK)

_, err := runner.ExprFuncHTTPReady(urlFoo, 1)
assert.NoError(t, err)
})

t.Run("failed", func(t *testing.T) {
defer gock.Clean()
defer gock.Off()
gock.New(urlFoo).Reply(http.StatusNotFound)

_, err := runner.ExprFuncHTTPReady(urlFoo, 1)
Expand All @@ -89,4 +94,95 @@ func TestExprFuncHTTPReady(t *testing.T) {
_, err := runner.ExprFuncHTTPReady(urlFoo, "two")
assert.Error(t, err)
})

t.Run("check the response", func(t *testing.T) {
defer gock.Off()
gock.New(urlFoo).Reply(http.StatusOK).BodyString(`{"name": "test"}`)
_, err := runner.ExprFuncHTTPReady(urlFoo, 1, `data.name == "test"`)
assert.NoError(t, err)
})

t.Run("response is not JSON", func(t *testing.T) {
defer gock.Off()
gock.New(urlFoo).Reply(http.StatusOK).BodyString(`name: test`)
_, err := runner.ExprFuncHTTPReady(urlFoo, 1, `data.name == "test"`)
assert.Error(t, err)
})

t.Run("response checking result is failed", func(t *testing.T) {
defer gock.Off()
gock.New(urlFoo).Reply(http.StatusOK).BodyString(`{"name": "test"}`)
_, err := runner.ExprFuncHTTPReady(urlFoo, 1, `data.name == "test"`)
assert.NoError(t, err)
})

t.Run("not a bool expr", func(t *testing.T) {
defer gock.Off()
gock.New(urlFoo).Reply(http.StatusOK).BodyString(`{"name": "test"}`)
_, err := runner.ExprFuncHTTPReady(urlFoo, 1, `name + "test"`)
assert.Error(t, err)
})

t.Run("failed to compile", func(t *testing.T) {
defer gock.Off()
gock.New(urlFoo).Reply(http.StatusOK).BodyString(`{"name": "test"}`)
_, err := runner.ExprFuncHTTPReady(urlFoo, 1, `1~!@`)
assert.Error(t, err)
})
}

func TestFunctions(t *testing.T) {
tmpFile, err := os.CreateTemp(os.TempDir(), "test")
if err != nil {
t.Fatal("failed to create temp file")
}
defer os.Remove(tmpFile.Name())

tests := []struct {
name string
expr string
syntaxErr bool
verify func(t *testing.T, result any, resultErr error)
}{{
name: "invalid syntax",
expr: "sleep 1",
syntaxErr: true,
}, {
name: "command",
expr: `command("echo 1")`,
verify: func(t *testing.T, result any, resultErr error) {
assert.NoError(t, resultErr)
assert.Equal(t, "1\n", result)
},
}, {
name: "writeFile",
expr: fmt.Sprintf(`writeFile("%s", "hello")`, tmpFile.Name()),
verify: func(t *testing.T, result any, resultErr error) {
assert.NoError(t, resultErr)

data, err := io.ReadAll(tmpFile)
assert.NoError(t, err)
assert.Equal(t, "hello", string(data))
},
}}

for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var program *vm.Program
program, err = expr.Compile(tt.expr, expr.Env(tt))
if tt.syntaxErr {
assert.Error(t, err, "%q %d", tt.name, i)
return
}
if !assert.NotNil(t, program, "%q %d", tt.name, i) {
return
}

var result any
result, err = expr.Run(program, tt)
if tt.verify != nil {
tt.verify(t, result, err)
}
})
}
}
4 changes: 2 additions & 2 deletions pkg/runner/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (r *gRPCTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataContext

defer func() {
if err == nil {
err = runJob(testcase.After, dataContext)
err = runJob(testcase.After, dataContext, output)
}
}()

Expand All @@ -102,7 +102,7 @@ func (r *gRPCTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataContext
return
}

if err = runJob(testcase.Before, dataContext); err != nil {
if err = runJob(testcase.Before, dataContext, nil); err != nil {
return
}

Expand Down
Loading

0 comments on commit 649b0d8

Please sign in to comment.