-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #151 from hpidcock/multichecker
#151 MultiChecker allows you to perform a DeepEquals but have bespoke checkers based on path matching. For example, this allows the value at "b" to be ignored. ``` a1 := map[string]string{"a": "a", "b": "b", "c": "c"} a2 := map[string]string{"a": "a", "b": "bbbb", "c": "c"} checker := jc.NewMultiChecker().Add(`["b"]`, jc.Ignore) c.Check(a1, checker, a2) ``` This allows the second element to have the SameContents check applied, ignoring order of elements: ``` a1 := [][]string{{"a", "b", "c"}, {"c", "d", "e"}} a2 := [][]string{{"a", "b", "c"}, {"e", "c", "d"}} checker := jc.NewMultiChecker().Add("[1]", jc.SameContents, jc.ExpectedValue) c.Check(a1, checker, a2) ```
- Loading branch information
Showing
4 changed files
with
256 additions
and
8 deletions.
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,113 @@ | ||
// Copyright 2020 Canonical Ltd. | ||
// Licensed under the LGPLv3, see LICENCE file for details. | ||
|
||
package checkers | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
|
||
gc "gopkg.in/check.v1" | ||
) | ||
|
||
// MultiChecker is a deep checker that by default matches for equality. | ||
// But checks can be overriden based on path (either explicit match or regexp) | ||
type MultiChecker struct { | ||
*gc.CheckerInfo | ||
checks map[string]multiCheck | ||
regexChecks []regexCheck | ||
} | ||
|
||
type multiCheck struct { | ||
checker gc.Checker | ||
args []interface{} | ||
} | ||
|
||
type regexCheck struct { | ||
multiCheck | ||
regex *regexp.Regexp | ||
} | ||
|
||
// NewMultiChecker creates a MultiChecker which is a deep checker that by default matches for equality. | ||
// But checks can be overriden based on path (either explicit match or regexp) | ||
func NewMultiChecker() *MultiChecker { | ||
return &MultiChecker{ | ||
CheckerInfo: &gc.CheckerInfo{Name: "MultiChecker", Params: []string{"obtained", "expected"}}, | ||
checks: make(map[string]multiCheck), | ||
} | ||
} | ||
|
||
// Add an explict checker by path. | ||
func (checker *MultiChecker) Add(path string, c gc.Checker, args ...interface{}) *MultiChecker { | ||
checker.checks[path] = multiCheck{ | ||
checker: c, | ||
args: args, | ||
} | ||
return checker | ||
} | ||
|
||
// AddRegex exception which matches path with regex. | ||
func (checker *MultiChecker) AddRegex(pathRegex string, c gc.Checker, args ...interface{}) *MultiChecker { | ||
checker.regexChecks = append(checker.regexChecks, regexCheck{ | ||
multiCheck: multiCheck{ | ||
checker: c, | ||
args: args, | ||
}, | ||
regex: regexp.MustCompile("^" + pathRegex + "$"), | ||
}) | ||
return checker | ||
} | ||
|
||
// Check for go check Checker interface. | ||
func (checker *MultiChecker) Check(params []interface{}, names []string) (result bool, errStr string) { | ||
customCheckFunc := func(path string, a1 interface{}, a2 interface{}) (useDefault bool, equal bool, err error) { | ||
var mc *multiCheck | ||
if c, ok := checker.checks[path]; ok { | ||
mc = &c | ||
} else { | ||
for _, v := range checker.regexChecks { | ||
if v.regex.MatchString(path) { | ||
mc = &v.multiCheck | ||
break | ||
} | ||
} | ||
} | ||
if mc == nil { | ||
return true, false, nil | ||
} | ||
|
||
params := append([]interface{}{a1}, mc.args...) | ||
info := mc.checker.Info() | ||
if len(params) < len(info.Params) { | ||
return false, false, fmt.Errorf("Wrong number of parameters for %s: want %d, got %d", info.Name, len(info.Params), len(params)+1) | ||
} | ||
// Copy since it may be mutated by Check. | ||
names := append([]string{}, info.Params...) | ||
|
||
// Trim to the expected params len. | ||
params = params[:len(info.Params)] | ||
|
||
// Perform substitution | ||
for i, v := range params { | ||
if v == ExpectedValue { | ||
params[i] = a2 | ||
} | ||
} | ||
|
||
result, errStr := mc.checker.Check(params, names) | ||
if result { | ||
return false, true, nil | ||
} | ||
if path == "" { | ||
path = "top level" | ||
} | ||
return false, false, fmt.Errorf("mismatch at %s: %s", path, errStr) | ||
} | ||
if ok, err := DeepEqualWithCustomCheck(params[0], params[1], customCheckFunc); !ok { | ||
return false, err.Error() | ||
} | ||
return true, "" | ||
} | ||
|
||
// ExpectedValue if passed to MultiChecker.Add or MultiChecker.AddRegex, will be substituded with the expected value. | ||
var ExpectedValue = &struct{}{} |
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,71 @@ | ||
package checkers_test | ||
|
||
import ( | ||
jc "github.com/juju/testing/checkers" | ||
gc "gopkg.in/check.v1" | ||
) | ||
|
||
type MultiCheckerSuite struct{} | ||
|
||
var _ = gc.Suite(&MultiCheckerSuite{}) | ||
|
||
func (s *MultiCheckerSuite) TestDeepEquals(c *gc.C) { | ||
for i, test := range deepEqualTests { | ||
c.Logf("test %d. %v == %v is %v", i, test.a, test.b, test.eq) | ||
result, msg := jc.NewMultiChecker().Check([]interface{}{test.a, test.b}, nil) | ||
c.Check(result, gc.Equals, test.eq) | ||
if test.eq { | ||
c.Check(msg, gc.Equals, "") | ||
} else { | ||
c.Check(msg, gc.Not(gc.Equals), "") | ||
} | ||
} | ||
} | ||
|
||
func (s *MultiCheckerSuite) TestArray(c *gc.C) { | ||
a1 := []string{"a", "b", "c"} | ||
a2 := []string{"a", "bbb", "c"} | ||
|
||
checker := jc.NewMultiChecker().Add("[1]", jc.Ignore) | ||
c.Check(a1, checker, a2) | ||
} | ||
|
||
func (s *MultiCheckerSuite) TestMap(c *gc.C) { | ||
a1 := map[string]string{"a": "a", "b": "b", "c": "c"} | ||
a2 := map[string]string{"a": "a", "b": "bbbb", "c": "c"} | ||
|
||
checker := jc.NewMultiChecker().Add(`["b"]`, jc.Ignore) | ||
c.Check(a1, checker, a2) | ||
} | ||
|
||
func (s *MultiCheckerSuite) TestRegexArray(c *gc.C) { | ||
a1 := []string{"a", "b", "c"} | ||
a2 := []string{"a", "bbb", "ccc"} | ||
|
||
checker := jc.NewMultiChecker().AddRegex("\\[[1-2]\\]", jc.Ignore) | ||
c.Check(a1, checker, a2) | ||
} | ||
|
||
func (s *MultiCheckerSuite) TestRegexMap(c *gc.C) { | ||
a1 := map[string]string{"a": "a", "b": "b", "c": "c"} | ||
a2 := map[string]string{"a": "aaaa", "b": "bbbb", "c": "cccc"} | ||
|
||
checker := jc.NewMultiChecker().AddRegex(`\[".*"\]`, jc.Ignore) | ||
c.Check(a1, checker, a2) | ||
} | ||
|
||
func (s *MultiCheckerSuite) TestArrayArraysUnordered(c *gc.C) { | ||
a1 := [][]string{{"a", "b", "c"}, {"c", "d", "e"}} | ||
a2 := [][]string{{"a", "b", "c"}, {}} | ||
|
||
checker := jc.NewMultiChecker().Add("[1]", jc.SameContents, []string{"e", "c", "d"}) | ||
c.Check(a1, checker, a2) | ||
} | ||
|
||
func (s *MultiCheckerSuite) TestArrayArraysUnorderedWithExpected(c *gc.C) { | ||
a1 := [][]string{{"a", "b", "c"}, {"c", "d", "e"}} | ||
a2 := [][]string{{"a", "b", "c"}, {"e", "c", "d"}} | ||
|
||
checker := jc.NewMultiChecker().Add("[1]", jc.SameContents, jc.ExpectedValue) | ||
c.Check(a1, checker, a2) | ||
} |