Skip to content

Commit

Permalink
feat: add testing APIs (#184)
Browse files Browse the repository at this point in the history
Signed-off-by: peefy <[email protected]>
  • Loading branch information
Peefy authored Nov 24, 2023
1 parent a15c657 commit 17b297a
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 0 deletions.
9 changes: 9 additions & 0 deletions kclvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"kcl-lang.io/kcl-go/pkg/tools/lint"
"kcl-lang.io/kcl-go/pkg/tools/list"
"kcl-lang.io/kcl-go/pkg/tools/override"
"kcl-lang.io/kcl-go/pkg/tools/testing"
"kcl-lang.io/kcl-go/pkg/tools/validate"
)

Expand All @@ -45,6 +46,9 @@ type (
ListDepsOptions = list.DepOptions
ListDepFilesOption = list.Option
ValidateOptions = validate.ValidateOptions
TestOptions = testing.TestOptions
TestCaseInfo = testing.TestCaseInfo
TestResult = testing.TestResult
KCLResult = kcl.KCLResult
KCLResultList = kcl.KCLResultList

Expand Down Expand Up @@ -172,6 +176,11 @@ func ValidateCode(data, code string, opt *ValidateOptions) (ok bool, err error)
return validate.ValidateCode(data, code, opt)
}

// Test calls the test tool to run uni tests in packages.
func Test(testOpts *TestOptions, opts ...Option) (TestResult, error) {
return testing.Test(testOpts, opts...)
}

// GetSchemaType returns schema types from a kcl file or code.
//
// file: string
Expand Down
12 changes: 12 additions & 0 deletions kclvm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,18 @@ func TestListDepFiles(t *testing.T) {
}
}

func TestTestAPI(t *testing.T) {
result, err := kcl.Test(&kcl.TestOptions{
PkgList: []string{"./testdata/test_module/..."},
})
if err != nil {
t.Fatal(err)
}
assert2.Equal(t, len(result.Info), 2)
assert2.Equal(t, result.Info[0].ErrMessage, "")
assert2.Equal(t, strings.Contains(result.Info[1].ErrMessage, "Error"), true, result.Info[1].ErrMessage)
}

func TestWithExternalpkg(t *testing.T) {
absPath1, err := filepath.Abs("./testdata_external/external_1/")
assert2.Equal(t, nil, err)
Expand Down
34 changes: 34 additions & 0 deletions pkg/tools/testing/indent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package testing

import "io"

type indentingWriter struct {
w io.Writer
}

func NewIndentingWriter(w io.Writer) indentingWriter {
return indentingWriter{
w: w,
}
}

func (w indentingWriter) Write(bs []byte) (int, error) {
var written int
indent := true
for _, b := range bs {
if indent {
wrote, err := w.w.Write([]byte(" "))
if err != nil {
return written, err
}
written += wrote
}
wrote, err := w.w.Write([]byte{b})
if err != nil {
return written, err
}
written += wrote
indent = b == '\n'
}
return written, nil
}
76 changes: 76 additions & 0 deletions pkg/tools/testing/reporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package testing

import (
"fmt"
"io"
"strings"
)

func DefaultReporter(output io.Writer) Reporter {
return &PrettyReporter{
Output: output,
}
}

// Reporter defines the interface for reporting test results.
type Reporter interface {
// Report is called with a channel that will contain test results.
Report(result *TestResult) error
}

// PrettyReporter reports test results in a simple human readable format.
type PrettyReporter struct {
Output io.Writer
Verbose bool
}

// Report prints the test report to the reporter's output.
func (r PrettyReporter) Report(result *TestResult) error {
if result == nil {
return nil
}
dirty := false
var pass, fail, skip int

var failures []*TestCaseInfo
for _, info := range result.Info {
if info.Pass() {
pass++
} else if info.Skip() {
skip++
} else if info.ErrMessage != "" {
fail++
failures = append(failures, &info)
}
}

for _, info := range result.Info {
fmt.Fprintln(r.Output, info.Format())
}

if dirty {
r.hl()
}

total := pass + fail + skip

r.hl()

if pass != 0 {
fmt.Fprintln(r.Output, "PASS:", fmt.Sprintf("%d/%d", pass, total))
}

if fail != 0 {
fmt.Fprintln(r.Output, "FAIL:", fmt.Sprintf("%d/%d", fail, total))
}

if skip != 0 {
fmt.Fprintln(r.Output, "SKIPPED:", fmt.Sprintf("%d/%d", skip, total))
}

return nil
}

func (r PrettyReporter) hl() {
fmt.Fprintln(r.Output, strings.Repeat("-", 80))
}
43 changes: 43 additions & 0 deletions pkg/tools/testing/reporter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package testing

import (
"bytes"
"testing"
)

func TestPrettyReporter(t *testing.T) {
var buf bytes.Buffer
result := TestResult{
Info: []TestCaseInfo{
{
Name: "test_foo",
Duration: 1024,
},
{
Name: "test_bar",
Duration: 2048,
ErrMessage: "Error: assert failed",
},
},
}

r := PrettyReporter{
Output: &buf,
Verbose: false,
}
if err := r.Report(&result); err != nil {
t.Fatal(err)
}

exp := `test_foo: PASS (1ms)
test_bar: FAIL (2ms)
Error: assert failed
--------------------------------------------------------------------------------
PASS: 1/2
FAIL: 1/2
`

if exp != buf.String() {
t.Fatalf("Expected:\n\n%v\n\nGot:\n\n%v", exp, buf.String())
}
}
92 changes: 92 additions & 0 deletions pkg/tools/testing/testing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package testing

import (
"fmt"

"kcl-lang.io/kcl-go/pkg/kcl"
"kcl-lang.io/kcl-go/pkg/service"
"kcl-lang.io/kcl-go/pkg/spec/gpyrpc"
)

type TestOptions struct {
PkgList []string
RunRegRxp string
FailFast bool
// NoRun bool
}

type TestResult struct {
Info []TestCaseInfo
}

type TestCaseInfo struct {
Name string
Duration uint64
LogMessage string
ErrMessage string
}

func (t *TestCaseInfo) Pass() bool {
return t.ErrMessage == ""
}

func (t *TestCaseInfo) Fail() bool {
return t.ErrMessage != ""
}

// TODO
func (t *TestCaseInfo) Skip() bool {
return false
}

func (t *TestCaseInfo) Format() string {
status := fmt.Sprintf("%s: %s (%dms)", t.Name, t.StatusString(), t.Duration/1000)
if t.Fail() {
return fmt.Sprintf("%s\n%s", status, t.ErrMessage)
}
return status
}

func (t *TestCaseInfo) StatusString() string {
if t.Pass() {
return "PASS"
} else if t.Fail() {
return "FAIL"
} else if t.Skip() {
return "SKIPPED"
}
return "ERROR"
}

func Test(testOpts *TestOptions, opts ...kcl.Option) (TestResult, error) {
if testOpts == nil {
testOpts = &TestOptions{}
}
args := kcl.NewOption().Merge(opts...)
if err := args.Err; err != nil {
return TestResult{}, err
}

client := service.NewKclvmServiceClient()
resp, err := client.Test(&gpyrpc.Test_Args{
ExecArgs: args.ExecProgram_Args,
PkgList: testOpts.PkgList,
RunRegexp: testOpts.RunRegRxp,
FailFast: testOpts.FailFast,
})
if err != nil {
return TestResult{}, err
}
var info []TestCaseInfo
for _, i := range resp.GetInfo() {
info = append(info, TestCaseInfo{
Name: i.Name,
Duration: i.Duration,
LogMessage: i.LogMessage,
ErrMessage: i.Error,
})
}
return TestResult{
Info: info,
}, nil
}
3 changes: 3 additions & 0 deletions testdata/test_module/kcl.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[package]
name = "test_data"

3 changes: 3 additions & 0 deletions testdata/test_module/pkg/func.k
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
func = lambda x {
x
}
7 changes: 7 additions & 0 deletions testdata/test_module/pkg/func_test.k
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
test_func_0 = lambda {
assert func("a") == "a"
}

test_func_1 = lambda {
assert func("a") == "d"
}

0 comments on commit 17b297a

Please sign in to comment.