From a4174bf58d8b45100169c1becdd731b2294dccc3 Mon Sep 17 00:00:00 2001 From: qbart Date: Sat, 1 Oct 2022 22:35:33 +0200 Subject: [PATCH] Experiments with tests --- Makefile | 4 +- krab/cmd_test_run.go | 112 +++++++++++++++++++-- krab/type_set_runtime_parameters.go | 19 ++++ krab/type_test_example.go | 24 ++++- main.go | 4 +- test/fixtures/tests/versions.krab.hcl | 15 ++- test/fixtures/tests/versions_test.krab.hcl | 48 +++++---- 7 files changed, 190 insertions(+), 36 deletions(-) create mode 100644 krab/type_set_runtime_parameters.go diff --git a/Makefile b/Makefile index a658535..0fbbc7c 100644 --- a/Makefile +++ b/Makefile @@ -10,13 +10,13 @@ default: build: mkdir -p bin/ - go build -gcflags='-G=3' -o bin/krab main.go + go build -o bin/krab main.go install: cp bin/krab /usr/local/bin test: - DATABASE_URL="postgres://krab:secret@localhost:5432/krab?sslmode=disable&prefer_simple_protocol=true" go test -gcflags=-G=3 -v ./... && echo "☑️ " + DATABASE_URL="postgres://krab:secret@localhost:5432/krab?sslmode=disable&prefer_simple_protocol=true" go test -v ./... && echo "☑️ " docker_test: docker run --rm -e DATABASE_URL="postgres://krab:secret@localhost:5432/krab?sslmode=disable" \ diff --git a/krab/cmd_test_run.go b/krab/cmd_test_run.go index 138b433..e98a086 100644 --- a/krab/cmd_test_run.go +++ b/krab/cmd_test_run.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "strings" "github.com/ohkrab/krab/krabdb" "github.com/ohkrab/krab/krabhcl" @@ -65,19 +66,118 @@ func (c *CmdTestRun) Do(ctx context.Context, o CmdOpts) (interface{}, error) { } } - // err := c.Connection.Get(func(db krabdb.DB) error { - // resp, err := c.run(ctx, db, o.Inputs) - // result = resp - // return err - // }) + err := c.Connection.Get(func(db krabdb.DB) error { + resp, err := c.run(ctx, db, o.Inputs) + result = resp + return err + }) - return result, nil + return result, err } func (c *CmdTestRun) run(ctx context.Context, db krabdb.DB, inputs Inputs) (ResponseTestRun, error) { result := ResponseTestRun{} + for _, testCase := range c.Suite.Tests { + fmt.Println(ctc.ForegroundBlue, testCase.Name, ctc.Reset) + for _, it := range testCase.Its { + fmt.Println(" ", ctc.ForegroundBlue, it.Comment, ctc.Reset) + + // apply SET parameters + if testCase.Set != nil { + sb := &strings.Builder{} + testCase.Set.ToSQL(sb) + _, err := db.ExecContext(ctx, sb.String()) + if err != nil { + panic(fmt.Errorf("SET parameters not set: %w", err)) + } + } + + // execute `do` from `it` and collect results for further expectations + // fmt.Println(" ", it.Do.SQL) + queryResult := []map[string]interface{}{} + typeResult := map[string]string{} + rows, capturedErr := db.QueryContext(ctx, it.Do.SQL) + capturedErrConsumed := false + + if capturedErr == nil { + defer rows.Close() + + types, _ := rows.ColumnTypes() + for _, colType := range types { + typeResult[colType.Name()] = colType.DatabaseTypeName() + } + for rows.Next() { + row := map[string]interface{}{} + rows.MapScan(row) + queryResult = append(queryResult, row) + } + } else { + // if query errored we can populate result map for easier processing later + queryResult = append(queryResult, map[string]interface{}{ + "error": capturedErr.Error(), + }) + } + + for _, asserts := range it.RowsAsserts { + for _, expect := range asserts.Expectations { + if expect.Subject == "error" { + capturedErrConsumed = true + fmt.Println(ctc.ForegroundGreen, *expect.Contains, ctc.ForegroundYellow, capturedErr.Error(), ctc.Reset) + } + if expect.Equal == nil { + fmt.Println(" ", ctc.ForegroundGreen, expect.Subject, "= null", ctc.Reset) + } else { + fmt.Println(" ", ctc.ForegroundGreen, expect.Subject, "=", *expect.Equal, ctc.Reset) + } + } + } + + if !capturedErrConsumed && capturedErr != nil { + panic(fmt.Errorf("query `%s` failed to execute: %w", it.Do.SQL, capturedErr)) + } + + for _, rowAssert := range it.RowAsserts { + rowAssert.EachRow(func(i int64) { + query := &strings.Builder{} + query.WriteString("SELECT key, value FROM ( VALUES\n") + + rowData := queryResult[i] + for i, expect := range rowAssert.Expectations { + value, ok := rowData[expect.Subject] + if i != 0 { + query.WriteString("\n,") + } + query.WriteString(fmt.Sprintf("(%s, %s)", krabdb.QuoteIdent(expect.Subject), krabdb.Quote(value))) + fmt.Println(value) + if !ok { + panic("Expectation defined for missing column") + } + + color := ctc.ForegroundGreen + success := makeAssertion(expect, value) + if !success { + color = ctc.ForegroundRed + } + fmt.Println(" ", color, expect.Subject, value, *expect.Equal, ctc.Reset) + } + query.WriteString("\n) AS t(key, value)") + fmt.Println(" ", ctc.ForegroundGreen, query.String(), ctc.Reset) + }) + } + } + } + // _, err := db.ExecContext(ctx, sql) return result, nil } + +func makeAssertion(expect *Expect, value interface{}) bool { + if expect.Equal == nil { + return value == nil + } + // fmt.Printf("// expect: %T, value: %T\n", *expect.Equal, value) + + return *expect.Equal == value +} diff --git a/krab/type_set_runtime_parameters.go b/krab/type_set_runtime_parameters.go new file mode 100644 index 0000000..77a79e0 --- /dev/null +++ b/krab/type_set_runtime_parameters.go @@ -0,0 +1,19 @@ +package krab + +import ( + "io" +) + +// SetRuntimeParameters +// https://www.postgresql.org/docs/current/sql-set.html +type SetRuntimeParameters struct { + SearchPath *string `hcl:"search_path"` +} + +// ToSQL converts set parameters to SQL. +func (d *SetRuntimeParameters) ToSQL(w io.StringWriter) { + if d.SearchPath != nil { + w.WriteString("SET search_path TO ") + w.WriteString(*d.SearchPath) + } +} diff --git a/krab/type_test_example.go b/krab/type_test_example.go index 5c2b2dd..d13ad21 100644 --- a/krab/type_test_example.go +++ b/krab/type_test_example.go @@ -1,15 +1,19 @@ package krab import ( + "fmt" + "strconv" + "github.com/ohkrab/krab/krabhcl" ) // TestExample represents test runner configuration. // type TestExample struct { - TestSuiteRefName string `hcl:"test_suite,label"` - Name string `hcl:"name,label"` - Its []*TestExampleIt `hcl:"it,block"` + TestSuiteRefName string `hcl:"test_suite,label"` + Name string `hcl:"name,label"` + Set *SetRuntimeParameters `hcl:"set,block"` + Its []*TestExampleIt `hcl:"it,block"` } func (t *TestExample) Addr() krabhcl.Addr { @@ -41,8 +45,18 @@ type AssertRow struct { Expectations []*Expect `hcl:"expect,block"` } +// EachRow yields each function for every row defined in the scope. +func (a *AssertRow) EachRow(each func(i int64)) { + i, err := strconv.ParseInt(a.Scope, 10, 64) + if err != nil { + panic(fmt.Sprintf("Failed to parse scope `%s` to rows: %v", a.Scope, err)) + } + each(i) +} + // Expect type Expect struct { - Subject string `hcl:"subject,label"` - Equal *string `hcl:"equal,optional"` + Subject string `hcl:"subject,label"` + Equal *string `hcl:"eq,optional"` + Contains *string `hcl:"contains,optional"` } diff --git a/main.go b/main.go index f291407..1311546 100644 --- a/main.go +++ b/main.go @@ -18,14 +18,14 @@ func main() { dir, err := krabenv.ConfigDir() if err != nil { - ui.Error(fmt.Errorf("Can't read config dir: %w", err).Error()) + ui.Error(fmt.Errorf("can't read config dir: %w", err).Error()) os.Exit(1) } parser := krab.NewParser() config, err := parser.LoadConfigDir(dir) if err != nil { - ui.Error(fmt.Errorf("Parsing error: %w", err).Error()) + ui.Error(fmt.Errorf("parsing error: %w", err).Error()) os.Exit(1) } diff --git a/test/fixtures/tests/versions.krab.hcl b/test/fixtures/tests/versions.krab.hcl index 41797ef..dd6631c 100644 --- a/test/fixtures/tests/versions.krab.hcl +++ b/test/fixtures/tests/versions.krab.hcl @@ -7,7 +7,7 @@ migration "create_version_type" { major SMALLINT, minor SMALLINT, patch SMALLINT - ) + ); SQL } @@ -32,10 +32,19 @@ migration "create_version_function" { _v.major = _v.major + 1; _v.minor = 0; _v.patch = 0; - RETURN _v; + + WHEN _type = 'minor' THEN + _v.minor = _v.minor + 1; + _v.patch = 0; + + WHEN _type = 'patch' THEN + _v.patch = _v.patch + 1; + + ELSE + RAISE EXCEPTION 'Failed to increase version using type = % for version %.%.%', _type, _ver.major, _ver.minor, _ver.patch; END CASE; - RAISE EXCEPTION 'Failed to increase version using type = %s for version %L.%L.%L', _type, _ver.major, _ver.minor, _ver.patch; + RETURN _v; END; $$ RETURNS NULL ON NULL INPUT diff --git a/test/fixtures/tests/versions_test.krab.hcl b/test/fixtures/tests/versions_test.krab.hcl index 0893df8..1fc2963 100644 --- a/test/fixtures/tests/versions_test.krab.hcl +++ b/test/fixtures/tests/versions_test.krab.hcl @@ -16,53 +16,65 @@ test_suite "versions" { } test "versions" "version_inc()" { + set { + search_path = "testing" + } + it "increases `major` component by default when no type specified" { - do { sql = "SELECT version_inc('1.1.1') AS ver" } + do { + sql = "SELECT version_inc(row(1,1,1)::sem_version) AS ver" + } row "0" { - expect "ver" { equal = "2.0.0" } + column "ver" { assert = "(2,0,0)" } } } it "increases `major` component and resets `minor` and `patch`" { - do { sql = "SELECT version_inc('1.1.1', 'major') AS ver" } + do { sql = "SELECT version_inc(row(1,1,1)::sem_version, 'major') AS ver" } row "0" { - expect "ver" { equal = "2.0.0" } + column "ver" { assert = "ver = row(2,0,0)::sem_version" } } } - it "increases `minor` component and resets `patch` leaving `major` untouched" { - do { sql = "SELECT version_inc('1.1.1', 'minor') AS ver" } + describe "increases `minor` component and resets `patch` leaving `major` untouched" { + do { sql = "SELECT version_inc(row(1,1,1)::sem_version, 'minor') AS ver" } + # v1 - set scope row "0" { - expect "ver" { equal = "1.2.0" } + it "ver" { expect = "ver = row(2,0,0)::sem_version" } + its { expect = "ver = row(2,0,0)::sem_version" } } + + # v2 + it "0" "ver" { expect = "ver = row(2,0,0)::sem_version" } + its "0" { expect = "ver = row(2,0,0)::sem_version" } } it "increases `patch` component and leaves `major` and `minor` untouched" { - do { sql = "SELECT version_inc('1.1.1', 'patch') AS ver" } + do { sql = "SELECT version_inc(row(1,1,1)::sem_version, 'patch') AS ver" } row "0" { - expect "ver" { equal = "1.1.2" } + column "ver" { assert = "(1,1,2)" } } } - it "raises error when increasing invalid component" { - do { sql = "SELECT version_inc('1.1.1', 'invalid') AS ver" } + /* it "raises error when increasing invalid component" { */ + /* do { sql = "SELECT version_inc(row(1,1,1)::sem_version, 'invalid') AS ver" } */ - rows { - expect "error" { - equal = "Failed to increase version using type = 'invalid' for version 1.1.1" - } - } - } + /* rows { */ + /* raises_error { */ + /* message = "Failed to increase version using type = 'invalid' for version 1.1.1" */ + /* } */ + /* } */ + /* } */ it "returns null on null input" { do { sql = "SELECT version_inc(null) AS ver" } row "0" { - expect "ver" { equal = null } + column "ver" { assert = null } } } }