You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tea/env_test.go

377 lines
7.3 KiB
Go

package tea
import (
"testing"
)
func TestSave(t *testing.T) {
t.Run("empty begets nil", func(t *testing.T) {
e := mkenv(Pass)
if e != nil {
t.Errorf("saw unexpected env value looking for nil: %v", e)
}
})
t.Run("unexported fields are ignored", func(t *testing.T) {
type test struct {
Passing
foo int `tea:"save"`
}
if e := mkenv(test{foo: 5}); e != nil {
t.Errorf("saw unexpected env value looking for nil: %v", e)
}
})
4 years ago
t.Run("create an env from a test", func(t *testing.T) {
test := struct {
Passing
4 years ago
Foo int `tea:"save"`
}{
Foo: 5,
}
e := mkenv(&test)
if e == nil {
t.Fatalf("saw nil env when expecting a valid env")
}
4 years ago
foo, ok := e.data["Foo"]
if !ok {
t.Errorf("expected field Foo to be saved but was not saved")
}
4 years ago
if foo != 5 {
t.Errorf("expected value %v but saw %v instead", 5, foo)
}
})
4 years ago
t.Run("update an existing env", func(t *testing.T) {
test := struct {
Passing
4 years ago
Foo int `tea:"save"`
}{
Foo: 5,
}
e := mkenv(&test)
if e == nil {
t.Fatalf("saw nil env when expecting a valid env")
}
foo, ok := e.data["Foo"]
if !ok {
t.Errorf("expected field Foo to be saved but was not saved")
}
if foo != 5 {
t.Errorf("expected value %v but saw %v instead", 5, foo)
}
})
}
func TestLoad(t *testing.T) {
t.Run("load an int", func(t *testing.T) {
4 years ago
e := &env{
data: map[string]interface{}{"Foo": 5},
}
4 years ago
var test struct {
Passing
4 years ago
Foo int `tea:"load"`
}
if err := e.load(&test); err != nil {
t.Errorf("unexpected load error: %v", err)
}
if test.Foo != 5 {
t.Errorf("expected value %v but saw %v instead", 5, test.Foo)
}
})
t.Run("loads can fail", func(t *testing.T) {
4 years ago
e := &env{
data: map[string]interface{}{"NotFoo": 5},
}
var test struct {
Passing
4 years ago
Foo int `tea:"load"`
}
if err := e.load(&test); err == nil {
t.Errorf("expected a load error but did not see one")
}
})
t.Run("skip load if field is already set", func(t *testing.T) {
e := &env{
data: map[string]interface{}{"Foo": 3},
}
var test struct {
Passing
Foo int `tea:"load"`
}
test.Foo = 5
if err := e.load(&test); err != nil {
t.Errorf("unexpected load error: %v", err)
}
if test.Foo != 5 {
t.Errorf("load overwrote expected value of 5 with %d", test.Foo)
}
})
}
4 years ago
func TestMatch(t *testing.T) {
t.Run("required match field not present", func(t *testing.T) {
e := &env{
data: map[string]interface{}{"Foo": 5},
}
var test struct {
Passing
4 years ago
Name string `tea:"match"`
Foo int `tea:"load"`
}
if err := e.load(&test); err == nil {
t.Errorf("expected a load error but did not see one")
}
})
t.Run("required match field has wrong value", func(t *testing.T) {
e := &env{
data: map[string]interface{}{
"Foo": 5,
"Name": "alice",
},
}
var test struct {
Passing
4 years ago
Name string `tea:"match"`
Foo int `tea:"load"`
}
test.Name = "bob"
if err := e.load(&test); err == nil {
t.Errorf("expected a load error but did not see one")
}
})
t.Run("simple match", func(t *testing.T) {
e := &env{
data: map[string]interface{}{
"Foo": 5,
"Name": "alice",
},
}
var test struct {
Passing
4 years ago
Name string `tea:"match"`
Foo int `tea:"load"`
}
test.Name = "alice"
if err := e.load(&test); err != nil {
t.Errorf("unexpected load error: %v", err)
}
if test.Foo != 5 {
t.Errorf("expected Foo to load 5 but is %d instead", test.Foo)
}
})
t.Run("ancestor match", func(t *testing.T) {
e := &env{
data: map[string]interface{}{
"Foo": 3,
"Name": "bob",
},
parent: &env{
data: map[string]interface{}{
"Foo": 5,
"Name": "alice",
},
},
}
var test struct {
Passing
4 years ago
Name string `tea:"match"`
Foo int `tea:"load"`
}
test.Name = "alice"
if err := e.load(&test); err != nil {
t.Errorf("unexpected load error: %v", err)
}
if test.Foo != 5 {
t.Errorf("expected Foo to load 5 but is %d instead", test.Foo)
}
})
t.Run("complicated match", func(t *testing.T) {
type connect struct {
Passing
Role string `tea:"save"`
Name string `tea:"save"`
ID int `tea:"save"`
}
type request struct {
Passing
Role string `tea:"match"`
Name string `tea:"match"`
ID int `tea:"load"`
}
e := mkenv(connect{
Role: "host",
ID: 1,
})
e = e.save(connect{
Role: "player",
Name: "alice",
ID: 2,
})
e = e.save(connect{
Role: "player",
Name: "bob",
ID: 3,
})
bob := request{Role: "player", Name: "bob"}
if err := e.load(&bob); err != nil {
t.Errorf("failed to load bob: %s", err)
} else {
if bob.ID != 3 {
t.Errorf("expected bob to have ID 3, has %d instead", bob.ID)
}
}
alice := request{Role: "player", Name: "alice"}
if err := e.load(&alice); err != nil {
t.Errorf("failed to load alice: %s", err)
} else {
if alice.ID != 2 {
t.Errorf("expected alice to have ID 2, has %d instead", alice.ID)
}
}
host := request{Role: "host"}
if err := e.load(&host); err != nil {
t.Errorf("failed to load host: %s", err)
} else {
if host.ID != 1 {
t.Errorf("expected host to have ID 1, has %d instead", host.ID)
}
}
})
4 years ago
}
// Constructing a test node that has multiple parents:
// -----------------------------------------------------------------------------
//
// In this example, B is an optional test.
//
// Logical Execution
4 years ago
//
// A A
// /| / \
// / | / \
// B | ----> B C'
// \ | |
// \| |
// C C
//
// This logical graph of test dependencies would yield an execution plan
// consisting of two test chains:
//
// A -> B -> C
// A -> C
//
// We could write this as follows:
//
// root := New(A)
// b := root.Child(B)
// root.And(b).Child(C)
//
// Alternatively:
//
// root := New(A)
// root.Child(B).And(root).Child(C)
//
// If we permit a selection to append multiple children, we could write this as
// follows:
//
// root := New(A)
// root.Child(B, Pass).Child(C)
//
// This last form is not strictly the same, since it includes an additional
// node in the graph which is a passing test. However since Pass is a
// specific example, we can trivially remove nodes having a test value of
// Pass in the planning phase.
//
// Another simple example: a diamond-shaped test graph
//
// Logical Execution
4 years ago
//
// A A
// / \ / \
// / \ / \
// B C ----> B C
// \ / | |
// \ / | |
// D D D'
//
// Test Plan:
// - A -> B -> D
// - A -> C -> D
//
// Expressed in test code as follows:
//
// root := New(A)
// both := root.Child(B, C)
// both.Child(D)
//
// Alternatively:
//
// New(A).Child(B, C).Child(D)
//
//
//
// This API is fairly straightforward to use, but breaks down with even simple
// shapes:
//
// A
// / \
// / \
// B C
// / \ /
// / \ /
// E D
//
// Test Plan:
// - A -> B -> E
// - A -> B -> D
// - A -> C -> D
//
// Essentially what we're saying is:
// Run test A.
// If test A passes:
// Run test B.
// Run test C.
//
// Expressed as:
//
// root := New(A)
// b := root.Child(B)
// c := root.Child(C)
// b.Child(E)
// b.And(c).Child(D)
//