still not hitting every env condition ugh

g-counter
Jordan Orelli 4 years ago
parent 6a1e87f67e
commit 41d93e23ee

@ -53,9 +53,9 @@ func (e *env) load(dest Test) error {
destV := reflect.ValueOf(dest).Elem() destV := reflect.ValueOf(dest).Elem()
destT := destV.Type() destT := destV.Type()
e = e.match(dest) e, err := e.match(dest)
if e == nil { if err != nil {
return fmt.Errorf("failed to find a matching environment") return fmt.Errorf("match failed: %w", err)
} }
for i := 0; i < destT.NumField(); i++ { for i := 0; i < destT.NumField(); i++ {
@ -86,23 +86,29 @@ func (e *env) load(dest Test) error {
} }
if !set { if !set {
return fmt.Errorf("failed to set required field: %q", f.Name) return fmt.Errorf("%w: failed to set required field: %q", PlanError, f.Name)
} }
} }
return nil return nil
} }
func (e *env) match(dest Test) *env { func (e *env) match(dest Test) (*env, error) {
destV := reflect.ValueOf(dest).Elem() destV := reflect.ValueOf(dest).Elem()
destT := destV.Type() destT := destV.Type()
required := getMatchFields(destT) required := getMatchFields(destT)
if len(required) == 0 { if len(required) == 0 {
return e return e, nil
} }
var last *env var (
var leaf *env last *env
leaf *env
foundWithWrongType = make(map[string]bool)
foundWithMatchingType = make(map[string]bool)
foundWithWrongValue = make(map[string]bool)
foundWithCorrectValue = make(map[string]bool)
)
for e := e; e != nil; e = e.parent { for e := e; e != nil; e = e.parent {
present := make([]reflect.StructField, 0, len(required)) present := make([]reflect.StructField, 0, len(required))
@ -113,7 +119,10 @@ func (e *env) match(dest Test) *env {
break break
} }
if reflect.TypeOf(ev).AssignableTo(f.Type) { if reflect.TypeOf(ev).AssignableTo(f.Type) {
foundWithMatchingType[f.Name] = true
present = append(present, f) present = append(present, f)
} else {
foundWithWrongType[f.Name] = true
} }
} }
@ -125,7 +134,10 @@ func (e *env) match(dest Test) *env {
for _, f := range required { for _, f := range required {
fv := destV.FieldByName(f.Name) fv := destV.FieldByName(f.Name)
if fv.Interface() == e.data[f.Name] { if fv.Interface() == e.data[f.Name] {
foundWithCorrectValue[f.Name] = true
matched[f.Name] = e.data[f.Name] matched[f.Name] = e.data[f.Name]
} else {
foundWithWrongValue[f.Name] = true
} }
} }
@ -156,5 +168,33 @@ func (e *env) match(dest Test) *env {
} }
} }
return leaf if leaf == nil {
var notFound []string
for _, f := range required {
if !foundWithMatchingType[f.Name] && !foundWithWrongType[f.Name] {
notFound = append(notFound, f.Name)
}
}
switch len(notFound) {
case 0:
break
case 1:
return nil, fmt.Errorf("%w: missing required field: %q", PlanError, notFound[0])
default:
return nil, fmt.Errorf("%w: missing %d required fields: %s", PlanError, len(notFound), notFound)
}
for f, _ := range foundWithWrongType {
if !foundWithMatchingType[f] {
return nil, fmt.Errorf("%w: field %s was only found with unmatching types", PlanError, f)
}
}
for f, _ := range foundWithWrongValue {
if !foundWithCorrectValue[f] {
return nil, fmt.Errorf("%w: field %s was only found with unmatching values", RunError, f)
}
}
return nil, fmt.Errorf("%w: required match fields not encountered on the same layer", PlanError)
}
return leaf, nil
} }

@ -1,9 +1,18 @@
package tea package tea
import ( import (
"errors"
"testing" "testing"
) )
func assertErrorType(t *testing.T, err error, target error) {
if !errors.Is(err, target) {
t.Errorf("expected %v, instead saw %v", target, err)
} else {
t.Logf("found expected %v: %v", target, err)
}
}
func TestSave(t *testing.T) { func TestSave(t *testing.T) {
t.Run("empty begets nil", func(t *testing.T) { t.Run("empty begets nil", func(t *testing.T) {
e := mkenv(Pass) e := mkenv(Pass)
@ -100,7 +109,9 @@ func TestLoad(t *testing.T) {
} }
if err := e.load(&test); err == nil { if err := e.load(&test); err == nil {
t.Errorf("expected a load error but did not see one") t.Fatalf("expected a load error but did not see one")
} else {
assertErrorType(t, err, PlanError)
} }
}) })
@ -138,6 +149,8 @@ func TestMatch(t *testing.T) {
if err := e.load(&test); err == nil { if err := e.load(&test); err == nil {
t.Errorf("expected a load error but did not see one") t.Errorf("expected a load error but did not see one")
} else {
assertErrorType(t, err, PlanError)
} }
}) })
@ -158,6 +171,30 @@ func TestMatch(t *testing.T) {
if err := e.load(&test); err == nil { if err := e.load(&test); err == nil {
t.Errorf("expected a load error but did not see one") t.Errorf("expected a load error but did not see one")
} else {
assertErrorType(t, err, RunError)
}
})
t.Run("required match field has wrong type", func(t *testing.T) {
e := &env{
data: map[string]interface{}{
"Foo": 5,
"Name": []byte("alice"),
},
}
var test struct {
Passing
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")
} else {
assertErrorType(t, err, PlanError)
} }
}) })
@ -213,7 +250,7 @@ func TestMatch(t *testing.T) {
} }
}) })
t.Run("complicated match", func(t *testing.T) { t.Run("layer-skipping matches", func(t *testing.T) {
type connect struct { type connect struct {
Passing Passing
Role string `tea:"save"` Role string `tea:"save"`
@ -269,7 +306,80 @@ func TestMatch(t *testing.T) {
t.Errorf("expected host to have ID 1, has %d instead", host.ID) t.Errorf("expected host to have ID 1, has %d instead", host.ID)
} }
} }
})
t.Run("layer-skipping matches", 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"`
body string
}
e := mkenv(connect{
Role: "host",
ID: 1,
})
e = e.save(request{
Role: "host",
body: "one",
})
e = e.save(connect{
Role: "player",
Name: "alice",
ID: 2000000,
})
e = e.save(Pass)
e = e.save(connect{
Role: "player",
Name: "alice",
ID: 2,
})
e = e.save(connect{
Role: "player",
Name: "bob",
ID: 3,
})
e = e.save(Pass)
e = e.save(request{
Role: "player",
body: "one",
})
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)
}
}
}) })
} }

@ -0,0 +1,8 @@
package tea
type testError string
func (e testError) Error() string { return string(e) }
const PlanError = testError("test plan error")
const RunError = testError("test run error")
Loading…
Cancel
Save