From 41d93e23ee9d756bf8a86d7201931c8b3b4a3d06 Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Fri, 31 Jul 2020 18:02:33 +0000 Subject: [PATCH] still not hitting every env condition ugh --- env.go | 58 +++++++++++++++++++++----- env_test.go | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++- error.go | 8 ++++ 3 files changed, 169 insertions(+), 11 deletions(-) create mode 100644 error.go diff --git a/env.go b/env.go index 0827069..0c11572 100644 --- a/env.go +++ b/env.go @@ -53,9 +53,9 @@ func (e *env) load(dest Test) error { destV := reflect.ValueOf(dest).Elem() destT := destV.Type() - e = e.match(dest) - if e == nil { - return fmt.Errorf("failed to find a matching environment") + e, err := e.match(dest) + if err != nil { + return fmt.Errorf("match failed: %w", err) } for i := 0; i < destT.NumField(); i++ { @@ -86,23 +86,29 @@ func (e *env) load(dest Test) error { } 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 } -func (e *env) match(dest Test) *env { +func (e *env) match(dest Test) (*env, error) { destV := reflect.ValueOf(dest).Elem() destT := destV.Type() required := getMatchFields(destT) if len(required) == 0 { - return e + return e, nil } - var last *env - var leaf *env + var ( + 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 { present := make([]reflect.StructField, 0, len(required)) @@ -113,7 +119,10 @@ func (e *env) match(dest Test) *env { break } if reflect.TypeOf(ev).AssignableTo(f.Type) { + foundWithMatchingType[f.Name] = true present = append(present, f) + } else { + foundWithWrongType[f.Name] = true } } @@ -125,7 +134,10 @@ func (e *env) match(dest Test) *env { for _, f := range required { fv := destV.FieldByName(f.Name) if fv.Interface() == e.data[f.Name] { + foundWithCorrectValue[f.Name] = true 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 } diff --git a/env_test.go b/env_test.go index 8b4a733..9973f51 100644 --- a/env_test.go +++ b/env_test.go @@ -1,9 +1,18 @@ package tea import ( + "errors" "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) { t.Run("empty begets nil", func(t *testing.T) { e := mkenv(Pass) @@ -100,7 +109,9 @@ func TestLoad(t *testing.T) { } 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 { 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 { 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 { Passing 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.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) + } + } }) } diff --git a/error.go b/error.go new file mode 100644 index 0000000..27644e5 --- /dev/null +++ b/error.go @@ -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")