-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
testutils: Add script-based test utilities
Add testutils.CommandBuilder for creating commands for testcript. This allows implementing tests as scripts, which becomes useful when tests perform multiple steps on tables and need to verify the output each step. Signed-off-by: Jussi Maki <[email protected]>
- Loading branch information
Showing
5 changed files
with
265 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright Authors of Cilium | ||
|
||
package testutils | ||
|
||
import ( | ||
"bytes" | ||
"flag" | ||
"fmt" | ||
"slices" | ||
"strings" | ||
"text/tabwriter" | ||
"time" | ||
|
||
"github.com/cilium/statedb" | ||
"github.com/rogpeppe/go-internal/testscript" | ||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
type Cmd = func(ts *testscript.TestScript, neg bool, args []string) | ||
|
||
func ShowTableCmd[Obj statedb.TableWritable](db *statedb.DB, tbl statedb.Table[Obj]) Cmd { | ||
var zero Obj | ||
return func(ts *testscript.TestScript, neg bool, args []string) { | ||
var buf bytes.Buffer | ||
w := tabwriter.NewWriter(&buf, 5, 4, 3, ' ', 0) | ||
fmt.Fprintf(w, "%s\n", strings.Join(zero.TableHeader(), "\t")) | ||
for obj := range tbl.All(db.ReadTxn()) { | ||
fmt.Fprintf(w, "%s\n", strings.Join(obj.TableRow(), "\t")) | ||
} | ||
w.Flush() | ||
ts.Logf("%s", buf.String()) | ||
} | ||
} | ||
|
||
func CompareTableCmd[Obj statedb.TableWritable](db *statedb.DB, tbl statedb.Table[Obj]) Cmd { | ||
var zero Obj | ||
return func(ts *testscript.TestScript, neg bool, args []string) { | ||
var flags flag.FlagSet | ||
wait := flags.Duration("wait", 0, "Wait for the table contents to match") | ||
err := flags.Parse(args) | ||
args = args[len(args)-flags.NArg():] | ||
if err != nil || len(args) != 1 { | ||
ts.Fatalf("usage: cmp [-wait=<duration>] file") | ||
} | ||
|
||
data := ts.ReadFile(args[0]) | ||
lines := strings.Split(data, "\n") | ||
if len(lines) < 1 { | ||
ts.Fatalf("input too short") | ||
} | ||
lines = slices.DeleteFunc(lines, func(line string) bool { | ||
return strings.TrimSpace(line) == "" | ||
}) | ||
|
||
header := zero.TableHeader() | ||
columnNames := strings.Split(lines[0], "\t") | ||
columns := make([]int, 0, len(header)) | ||
loop: | ||
for _, name := range columnNames { | ||
for i, name2 := range header { | ||
if strings.EqualFold(name, name2) { | ||
columns = append(columns, i) | ||
continue loop | ||
} | ||
} | ||
ts.Fatalf("column %q not part of %v", name, header) | ||
} | ||
lines = lines[1:] | ||
origLines := lines | ||
|
||
tryUntil := time.Now().Add(*wait) | ||
for { | ||
lines = origLines | ||
|
||
equal := true | ||
var diff bytes.Buffer | ||
w := tabwriter.NewWriter(&diff, 5, 4, 3, ' ', 0) | ||
|
||
fmt.Fprintf(w, " %s\n", strings.Join(zero.TableHeader(), "\t")) | ||
|
||
for obj := range tbl.All(db.ReadTxn()) { | ||
row := strings.Join(takeColumns(obj.TableRow(), columns), "\t") | ||
if len(lines) == 0 { | ||
equal = false | ||
fmt.Fprintf(w, "- %s\n", row) | ||
continue | ||
} | ||
line := lines[0] | ||
if row == line { | ||
fmt.Fprintf(w, " %s\n", row) | ||
} else { | ||
fmt.Fprintf(w, "- %s\n", row) | ||
fmt.Fprintf(w, "+ %s\n", line) | ||
equal = false | ||
} | ||
lines = lines[1:] | ||
} | ||
for _, line := range lines { | ||
fmt.Fprintf(w, "+ %s\n", line) | ||
equal = false | ||
} | ||
|
||
if equal { | ||
break | ||
} | ||
w.Flush() | ||
if time.Now().After(tryUntil) { | ||
ts.Fatalf("table mismatch (check tabs!):\n%s", diff.String()) | ||
} | ||
time.Sleep(10 * time.Millisecond) | ||
} | ||
} | ||
} | ||
|
||
func takeColumns[T any](xs []T, idxs []int) []T { | ||
// Assuming idxs is sorted. | ||
for i, idx := range idxs { | ||
xs[i] = xs[idx] | ||
} | ||
return xs[:len(idxs)] | ||
} | ||
|
||
func InsertCmd[Obj any](db *statedb.DB, tbl statedb.RWTable[Obj]) Cmd { | ||
return func(ts *testscript.TestScript, neg bool, args []string) { | ||
if len(args) == 0 { | ||
ts.Fatalf("usage: insert path...") | ||
} | ||
wtxn := db.WriteTxn(tbl) | ||
defer wtxn.Commit() | ||
for _, arg := range args { | ||
data := ts.ReadFile(arg) | ||
var obj Obj | ||
if err := yaml.Unmarshal([]byte(data), &obj); err != nil { | ||
ts.Fatalf("Unmarshal(%s): %s", arg, err) | ||
} | ||
_, _, err := tbl.Insert(wtxn, obj) | ||
if err != nil { | ||
ts.Fatalf("Insert(%s): %s", arg, err) | ||
} | ||
} | ||
} | ||
} | ||
|
||
func DeleteCmd[Obj any](db *statedb.DB, tbl statedb.RWTable[Obj]) Cmd { | ||
return func(ts *testscript.TestScript, neg bool, args []string) { | ||
if len(args) == 0 { | ||
ts.Fatalf("usage: delete path...") | ||
} | ||
wtxn := db.WriteTxn(tbl) | ||
defer wtxn.Commit() | ||
for _, arg := range args { | ||
data := ts.ReadFile(arg) | ||
var obj Obj | ||
if err := yaml.Unmarshal([]byte(data), &obj); err != nil { | ||
ts.Fatalf("Unmarshal(%s): %s", arg, err) | ||
} | ||
_, _, err := tbl.Delete(wtxn, obj) | ||
if err != nil { | ||
ts.Fatalf("Delete(%s): %s", arg, err) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright Authors of Cilium | ||
|
||
package testutils_test | ||
|
||
import ( | ||
"strconv" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/cilium/statedb" | ||
"github.com/cilium/statedb/index" | ||
"github.com/cilium/statedb/testutils" | ||
"github.com/rogpeppe/go-internal/testscript" | ||
) | ||
|
||
type object struct { | ||
ID uint64 | ||
Name string | ||
Tags []string | ||
} | ||
|
||
func (o object) TableHeader() []string { | ||
return []string{"ID", "Name", "Tags"} | ||
} | ||
|
||
func (o object) TableRow() []string { | ||
return []string{ | ||
strconv.FormatUint(o.ID, 10), | ||
o.Name, | ||
strings.Join(o.Tags, ", "), | ||
} | ||
} | ||
|
||
var idIdx = statedb.Index[object, uint64]{ | ||
Name: "id", | ||
FromObject: func(obj object) index.KeySet { | ||
return index.NewKeySet(index.Uint64(obj.ID)) | ||
}, | ||
FromKey: index.Uint64, | ||
Unique: true, | ||
} | ||
|
||
func TestScriptCommands(t *testing.T) { | ||
db := statedb.New() | ||
tbl, err := statedb.NewTable("test", idIdx) | ||
if err != nil { | ||
t.Fatalf("NewTable: %s", err) | ||
} | ||
if err := db.RegisterTable(tbl); err != nil { | ||
t.Fatalf("RegisterTable: %s", err) | ||
} | ||
|
||
testscript.Run(t, testscript.Params{ | ||
Dir: "testdata", | ||
Setup: func(e *testscript.Env) error { | ||
txn := db.WriteTxn(tbl) | ||
tbl.DeleteAll(txn) | ||
txn.Commit() | ||
return nil | ||
}, | ||
Cmds: map[string]testutils.Cmd{ | ||
"cmp_objects": testutils.CompareTableCmd(db, tbl), | ||
"show_objects": testutils.ShowTableCmd(db, tbl), | ||
"insert_object": testutils.InsertCmd(db, tbl), | ||
"delete_object": testutils.DeleteCmd(db, tbl), | ||
}, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
insert_object example.yaml | ||
show_objects | ||
cmp_objects example.table | ||
cmp_objects example_only_ids.table | ||
|
||
cmp_objects -wait=1s example.table | ||
|
||
delete_object example.yaml | ||
cmp_objects empty.table | ||
|
||
-- example.yaml -- | ||
id: 123 | ||
name: quux | ||
tags: | ||
- foo | ||
- bar | ||
|
||
-- example.table -- | ||
ID Name Tags | ||
123 quux foo, bar | ||
|
||
-- example_only_ids.table -- | ||
ID | ||
123 | ||
|
||
-- empty.table -- | ||
ID |