From 17b297a9a1772d139c4623218f84b0df1e058ca7 Mon Sep 17 00:00:00 2001 From: Peefy Date: Fri, 24 Nov 2023 12:08:35 +0800 Subject: [PATCH] feat: add testing APIs (#184) Signed-off-by: peefy --- kclvm.go | 9 +++ kclvm_test.go | 12 ++++ pkg/tools/testing/indent.go | 34 ++++++++++ pkg/tools/testing/reporter.go | 76 +++++++++++++++++++++++ pkg/tools/testing/reporter_test.go | 43 +++++++++++++ pkg/tools/testing/testing.go | 92 ++++++++++++++++++++++++++++ testdata/test_module/kcl.mod | 3 + testdata/test_module/pkg/func.k | 3 + testdata/test_module/pkg/func_test.k | 7 +++ 9 files changed, 279 insertions(+) create mode 100644 pkg/tools/testing/indent.go create mode 100644 pkg/tools/testing/reporter.go create mode 100644 pkg/tools/testing/reporter_test.go create mode 100644 pkg/tools/testing/testing.go create mode 100644 testdata/test_module/kcl.mod create mode 100644 testdata/test_module/pkg/func.k create mode 100644 testdata/test_module/pkg/func_test.k diff --git a/kclvm.go b/kclvm.go index a6cf9cbb..37814e00 100644 --- a/kclvm.go +++ b/kclvm.go @@ -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" ) @@ -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 @@ -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 diff --git a/kclvm_test.go b/kclvm_test.go index 442d4cbe..50c15a79 100644 --- a/kclvm_test.go +++ b/kclvm_test.go @@ -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) diff --git a/pkg/tools/testing/indent.go b/pkg/tools/testing/indent.go new file mode 100644 index 00000000..282efd3c --- /dev/null +++ b/pkg/tools/testing/indent.go @@ -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 +} diff --git a/pkg/tools/testing/reporter.go b/pkg/tools/testing/reporter.go new file mode 100644 index 00000000..43aae52d --- /dev/null +++ b/pkg/tools/testing/reporter.go @@ -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)) +} diff --git a/pkg/tools/testing/reporter_test.go b/pkg/tools/testing/reporter_test.go new file mode 100644 index 00000000..5e9d645f --- /dev/null +++ b/pkg/tools/testing/reporter_test.go @@ -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()) + } +} diff --git a/pkg/tools/testing/testing.go b/pkg/tools/testing/testing.go new file mode 100644 index 00000000..50af7af5 --- /dev/null +++ b/pkg/tools/testing/testing.go @@ -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 +} diff --git a/testdata/test_module/kcl.mod b/testdata/test_module/kcl.mod new file mode 100644 index 00000000..35d888aa --- /dev/null +++ b/testdata/test_module/kcl.mod @@ -0,0 +1,3 @@ +[package] +name = "test_data" + diff --git a/testdata/test_module/pkg/func.k b/testdata/test_module/pkg/func.k new file mode 100644 index 00000000..26df9cf5 --- /dev/null +++ b/testdata/test_module/pkg/func.k @@ -0,0 +1,3 @@ +func = lambda x { + x +} diff --git a/testdata/test_module/pkg/func_test.k b/testdata/test_module/pkg/func_test.k new file mode 100644 index 00000000..2aadb5a3 --- /dev/null +++ b/testdata/test_module/pkg/func_test.k @@ -0,0 +1,7 @@ +test_func_0 = lambda { + assert func("a") == "a" +} + +test_func_1 = lambda { + assert func("a") == "d" +}