From 5426b7b3c86a9afc3fab855e2f3043d0efe658e4 Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Sun, 31 Oct 2021 18:39:11 +0100 Subject: [PATCH 01/19] feat(generics): create viable POC (#63) * Use go1.18 - Declare go 1.18 in go.mod - Update make commands * Declare generic checker types * Type checker providers using Checker[T] interface * Update checker providers interfaces - Disable code generation for providers interfaces - Update providers interfaces manually * Update checker providers unit tests * Update package check examples * Update package checkconv - Type checkconv.Assert using check.Checker[T] interface - Type checkconv.Cast using check.Checker[T] interface * Update testx runners - Add type parameters to ValueRunner - Use new type check.Checker[T] - Update tests and examples * Update temporary CI config - Disable jobs: gen, lint - Disable test coverage - Add setup step: Install gotip * Replace all occurrences of interface{} -> any --- .circleci/config.yml | 169 ++++++++++---------- .golangci.yml | 6 +- Makefile | 8 +- check/README.md | 8 +- check/check.go | 14 +- check/check2.go | 36 +++++ check/checkers.go | 8 +- check/example_custom_closures_test.go | 15 +- check/example_custom_test.go | 14 +- check/example_custom_unknown_test.go | 2 +- check/example_newintchecker_test.go | 7 +- check/gen.go | 4 +- check/providers.go | 193 +++++++++++------------ check/providers_base.go | 32 ++-- check/providers_base_internal_test.go | 16 +- check/providers_bool.go | 6 +- check/providers_bool_test.go | 32 +--- check/providers_bytes.go | 62 ++++---- check/providers_bytes_test.go | 74 +++------ check/providers_context.go | 20 +-- check/providers_context_test.go | 45 ++---- check/providers_duration.go | 24 +-- check/providers_duration_test.go | 50 ++---- check/providers_float64.go | 48 +++--- check/providers_float64_test.go | 70 +++----- check/providers_httpheader.go | 34 ++-- check/providers_httpheader_test.go | 50 ++---- check/providers_httprequest.go | 46 +++--- check/providers_httprequest_test.go | 50 ++---- check/providers_httpresponse.go | 56 +++---- check/providers_httpresponse_test.go | 54 ++----- check/providers_int.go | 48 +++--- check/providers_int_test.go | 70 +++----- check/providers_map.go | 62 ++++---- check/providers_map_test.go | 82 ++++------ check/providers_slice.go | 84 +++++----- check/providers_slice_test.go | 68 +++----- check/providers_string.go | 46 +++--- check/providers_string_test.go | 56 ++----- check/providers_struct.go | 28 ++-- check/providers_struct_test.go | 36 ++--- check/providers_test.go | 50 ++++-- check/providers_value.go | 52 +++--- check/providers_value_test.go | 60 +++---- checkconv/README.md | 16 +- checkconv/assert.go | 175 ++++++++++---------- checkconv/assert_test.go | 12 +- checkconv/cast.go | 12 +- checkconv/cast_test.go | 8 +- checkconv/checkconv_test.go | 40 ++--- checkconv/gen.go | 2 +- checkconv/ischecker.go | 4 +- checkconv/ischecker_test.go | 2 +- example_httphandler_test.go | 2 +- example_table_test.go | 6 +- example_value_test.go | 11 +- go.mod | 2 +- internal/fmtexpl/fmtexpl.go | 4 +- internal/gen/gen.go | 8 +- internal/gen/templates/assert.gotmpl | 36 +++-- internal/gen/templates/check.gotmpl | 4 +- internal/gen/templates/checkers.gotmpl | 2 +- internal/gen/types.go | 2 +- internal/reflectutil/func.go | 6 +- internal/reflectutil/func_test.go | 2 +- internal/reflectutil/reflectutil.go | 14 +- internal/reflectutil/reflectutil_test.go | 16 +- internal/testutil/testutil.go | 2 +- runner.go | 6 +- runner_httphandler.go | 6 +- runner_httphandler_test.go | 6 +- runner_table.go | 22 +-- runner_table_test.go | 20 +-- runner_test.go | 6 +- runner_value.go | 36 +++-- runner_value_test.go | 12 +- testx.go | 18 +-- 77 files changed, 1164 insertions(+), 1351 deletions(-) create mode 100644 check/check2.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 5ada2f0..2dce924 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,110 +17,115 @@ commands: name: Install dependencies command: | go mod download + - run: + name: Install gotip + command: | + go install golang.org/dl/gotip@latest + gotip download - save_cache: key: go-mod-v4-{{ checksum "go.sum" }}-{{ .Environment.CACHE_VERSION }} paths: - "/go/pkg/mod" - setup-lint: - description: Set up lint - parameters: - version: - type: string - default: v1.40.1 - steps: - - restore_cache: - keys: - - golangci-lint-<< parameters.version >>-{{ .Environment.CACHE_VERSION }} - - golangci-lint-{{ .Environment.CACHE_VERSION }} - - run: - name: Install golangci-lint - command: | - command -v /go/bin/golangci-lint && echo "Skipping golangci-lint installation" && exit - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b /go/bin << parameters.version >> - - save_cache: - key: golangci-lint-<< parameters.version >>-{{ .Environment.CACHE_VERSION }} - paths: - - /go/bin/golangci-lint + # setup-lint: + # description: Set up lint + # parameters: + # version: + # type: string + # default: v1.40.1 + # steps: + # - restore_cache: + # keys: + # - golangci-lint-<< parameters.version >>-{{ .Environment.CACHE_VERSION }} + # - golangci-lint-{{ .Environment.CACHE_VERSION }} + # - run: + # name: Install golangci-lint + # command: | + # command -v /go/bin/golangci-lint && echo "Skipping golangci-lint installation" && exit + # curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b /go/bin << parameters.version >> + # - save_cache: + # key: golangci-lint-<< parameters.version >>-{{ .Environment.CACHE_VERSION }} + # paths: + # - /go/bin/golangci-lint - setup-coverage: - description: Set up coverage - steps: - - restore_cache: - keys: - - go-acc-{{ .Environment.CACHE_VERSION }} - - run: - name: Install go-acc - command: | - command -v /go/bin/go-acc && echo "Skipping go-acc installation" && exit - go get -v github.com/ory/go-acc - - save_cache: - key: go-acc-{{ .Environment.CACHE_VERSION }} - paths: - - /go/bin/go-acc + # setup-coverage: + # description: Set up coverage + # steps: + # - restore_cache: + # keys: + # - go-acc-{{ .Environment.CACHE_VERSION }} + # - run: + # name: Install go-acc + # command: | + # command -v /go/bin/go-acc && echo "Skipping go-acc installation" && exit + # go get -v github.com/ory/go-acc + # - save_cache: + # key: go-acc-{{ .Environment.CACHE_VERSION }} + # paths: + # - /go/bin/go-acc - setup-gen: - description: Set up gen - steps: - - restore_cache: - keys: - - goimports-{{ .Environment.CACHE_VERSION }} - - run: - name: Install goimports - command: | - command -v /go/bin/goimports && echo "Skipping go-acc installation" && exit - go get -v golang.org/x/tools/cmd/goimports + # setup-gen: + # description: Set up gen + # steps: + # - restore_cache: + # keys: + # - goimports-{{ .Environment.CACHE_VERSION }} + # - run: + # name: Install goimports + # command: | + # command -v /go/bin/goimports && echo "Skipping go-acc installation" && exit + # go get -v golang.org/x/tools/cmd/goimports - - save_cache: - key: goimports-{{ .Environment.CACHE_VERSION }} - paths: - - /go/bin/goimports + # - save_cache: + # key: goimports-{{ .Environment.CACHE_VERSION }} + # paths: + # - /go/bin/goimports jobs: - lint: - <<: *defaults - steps: - - setup - - setup-lint - - run: - name: Run linters - command: | - make lint + # lint: + # <<: *defaults + # steps: + # - setup + # - setup-lint + # - run: + # name: Run linters + # command: | + # make lint test: <<: *defaults steps: - setup - - setup-coverage + # - setup-coverage - run: name: Run unit tests - command: | - make test-cov - - run: - name: Send test coverage results - command: | - bash <(curl -s https://codecov.io/bash) + command: | # TODO: set back to 'make test-cov' + make tests + # - run: + # name: Send test coverage results + # command: | + # bash <(curl -s https://codecov.io/bash) - gen: - <<: *defaults - steps: - - setup - - setup-gen - - setup-lint - - run: - name: Run code gen - command: | - make gen - - run: - name: Check generated code - command: | - make lint tests + # gen: + # <<: *defaults + # steps: + # - setup + # - setup-gen + # - setup-lint + # - run: + # name: Run code gen + # command: | + # make gen + # - run: + # name: Check generated code + # command: | + # make lint tests workflows: version: 2 main: jobs: - - lint + # - lint - test - - gen + # - gen diff --git a/.golangci.yml b/.golangci.yml index db2f26b..de88a52 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -104,7 +104,7 @@ linters-settings: sizeThreshold: 256 gofumpt: - lang-version: "1.16" + lang-version: "1.18" extra-rules: true goimports: @@ -117,11 +117,11 @@ linters-settings: enableAllRules: true staticcheck: - go: "1.16" + go: "1.18" checks: [all] stylecheck: - go: "1.16" + go: "1.18" checks: [all] linters: diff --git a/Makefile b/Makefile index 1142a52..b1a26ff 100644 --- a/Makefile +++ b/Makefile @@ -9,9 +9,9 @@ default: .PHONY: gen gen: @echo "🛠 Building gen binary" - @go build -o ./bin/gen ./cmd/gen/main.go + @gotip build -o ./bin/gen ./cmd/gen/main.go @echo "✅ Done\n" - @go generate ./... + @gotip generate ./... # Check code @@ -26,7 +26,7 @@ lint: .PHONY: tests tests: - @go test ./... + @gotip test ./... .PHONY: test-cov test-cov: # Unit tests with coverage, excluding gen-related files @@ -43,7 +43,7 @@ endif .PHONY: test test: - @go test -timeout 30s -run $(TEST_FUNC) $(TEST_PKG) + @gotip test -timeout 30s -run $(TEST_FUNC) $(TEST_PKG) # Docs diff --git a/check/README.md b/check/README.md index 8122158..6969e86 100644 --- a/check/README.md +++ b/check/README.md @@ -39,7 +39,7 @@ type IntPasser interface { Pass(got int) bool } // Explainer provides a method Explain to describe the reason of a failed check. -type Explainer interface { Explain(label string, got interface{}) string } +type Explainer interface { Explain(label string, got any) string } ``` ## Provided checkers @@ -148,7 +148,7 @@ Package `check` provides a collection of basic checkers for common types and kin - interface{} + any check.Value @@ -214,7 +214,7 @@ and an `ExplainFunc`, then returns a checker for that type. func Test42IsEven(t *testing.T) { checkIsEven := check.NewIntChecker( func(got int) bool { return got&1 == 0 }, - func(label string, got interface{}) string { + func(label string, got any) string { return fmt.Sprintf("%s: expect even int, got %v", label, got) }, ) @@ -235,7 +235,7 @@ Related examples: func Test42IsEven(t *testing.T) { checkIsEven := check.Value.Custom( "even int", - func(got interface{}) bool { + func(got any) bool { gotInt, ok := got.(int) return ok && got&1 == 0 }, diff --git a/check/check.go b/check/check.go index 17652ca..742b8dd 100644 --- a/check/check.go +++ b/check/check.go @@ -1,5 +1,5 @@ // Code generated by go generate ./...; DO NOT EDIT -// Last generated on 30 Oct 21 12:10 UTC +// Last generated on 31 Oct 21 17:02 UTC // Package check provides types to perform checks on values // in a testing context. @@ -53,14 +53,14 @@ type ( // passes the current check. HTTPResponsePassFunc func(got *http.Response) bool // ValuePassFunc is the required method to implement ValuePasser. - // It returns a boolean that indicates whether the gotten interface{} value + // It returns a boolean that indicates whether the gotten any value // passes the current check. - ValuePassFunc func(got interface{}) bool + ValuePassFunc func(got any) bool // ExplainFunc is the required method to implement Explainer. // It returns a string explaining why the gotten value failed the check. // The label provides some context, such as "response code". - ExplainFunc func(label string, got interface{}) string + ExplainFunc func(label string, got any) string ) type ( @@ -97,12 +97,12 @@ type ( // whether the gotten *http.Response value passes the current check. HTTPResponsePasser interface{ Pass(got *http.Response) bool } // ValuePasser provides a method Pass that returns a bool that indicates - // whether the gotten interface{} value passes the current check. - ValuePasser interface{ Pass(got interface{}) bool } + // whether the gotten any value passes the current check. + ValuePasser interface{ Pass(got any) bool } // Explainer provides a method Explain describing the reason of a failed check. Explainer interface { - Explain(label string, got interface{}) string + Explain(label string, got any) string } ) diff --git a/check/check2.go b/check/check2.go new file mode 100644 index 0000000..a0f8794 --- /dev/null +++ b/check/check2.go @@ -0,0 +1,36 @@ +package check + +// TODO: rename file check.go when the other is removed + +type PassFunc[T any] func(got T) bool + +type ( + Passer[T any] interface{ Pass(got T) bool } + Checker[T any] interface { + Passer[T] + Explainer + } +) + +type checker[T any] struct { + pass PassFunc[T] + expl ExplainFunc +} + +func (c checker[T]) Pass(got T) bool { + return c.pass(got) +} + +func (c checker[T]) Explain(label string, got any) string { + return c.expl(label, got) +} + +func NewChecker[T any]( + passFunc PassFunc[T], + explainFunc ExplainFunc, +) Checker[T] { + return checker[T]{ + pass: passFunc, + expl: explainFunc, + } +} diff --git a/check/checkers.go b/check/checkers.go index 1597827..c82d0e9 100644 --- a/check/checkers.go +++ b/check/checkers.go @@ -1,5 +1,5 @@ // Code generated by go generate ./...; DO NOT EDIT -// Last generated on 30 Oct 21 12:10 UTC +// Last generated on 31 Oct 21 17:02 UTC package check @@ -16,7 +16,7 @@ type baseChecker struct { // Explain returns a string explaining the reason of a failed check // for the gotten value. -func (c baseChecker) Explain(label string, got interface{}) string { +func (c baseChecker) Explain(label string, got any) string { return c.explFunc(label, got) } @@ -190,9 +190,9 @@ type valueChecker struct { passFunc ValuePassFunc } -// Pass returns a boolean that indicates whether the gotten interface{} value +// Pass returns a boolean that indicates whether the gotten any value // passes the current check. -func (c valueChecker) Pass(got interface{}) bool { return c.passFunc(got) } +func (c valueChecker) Pass(got any) bool { return c.passFunc(got) } // NewValueChecker returns a custom ValueChecker with the provided // ValuePassFunc and ExplainFunc. diff --git a/check/example_custom_closures_test.go b/check/example_custom_closures_test.go index 14c8c6d..1913cf1 100644 --- a/check/example_custom_closures_test.go +++ b/check/example_custom_closures_test.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/drykit-go/testx" - "github.com/drykit-go/testx/checkconv" ) /* @@ -22,18 +21,18 @@ import ( // func fields pass and explain respectively. type Complex128Checker struct { pass func(got complex128) bool - explain func(label string, got interface{}) string + explain func(label string, got any) string } // Pass do not satisfy any interface declared by check, but has a valid // signature Pass(got T) bool, allowing Complex128Checker to be casted -// as a ValueChecker using checkconv.Cast. +// as a Checker[any] using checkconv.Cast. func (c Complex128Checker) Pass(got complex128) bool { return c.pass(got) } // Explain satisfies check.Explainer interface. -func (c Complex128Checker) Explain(label string, got interface{}) string { +func (c Complex128Checker) Explain(label string, got any) string { return c.explain(label, got) } @@ -45,7 +44,7 @@ func checkComplex128ImagIsInRange(lo, hi float64) Complex128Checker { gotImag = imag(got) return lo <= gotImag && gotImag <= hi } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return fmt.Sprintf( "%s: exp imag(%v) in range [%.0f:%.0f], got %.0f", label, got, lo, hi, gotImag, @@ -57,13 +56,11 @@ func checkComplex128ImagIsInRange(lo, hi float64) Complex128Checker { func Example_customCheckerClosures() { const testedValue = 42i - // We first cast our custom checkers to a typed ValueChecker. - checkers, _ := checkconv.CastMany( + results := testx.Value(testedValue).Pass( checkComplex128ImagIsInRange(41, 43), // pass checkComplex128ImagIsInRange(0, 1), // fail - ) + ).DryRun() - results := testx.Value(testedValue).Pass(checkers...).DryRun() fmt.Println(results.Checks()) // Output: diff --git a/check/example_custom_test.go b/check/example_custom_test.go index bab6b17..b039096 100644 --- a/check/example_custom_test.go +++ b/check/example_custom_test.go @@ -13,17 +13,17 @@ import ( Example: implementation of a custom checker */ -// StatusOKChecker is a custom checker that implements IntChecker interface. -// In consequence in can be used in any function that accepts an IntChecker. +// StatusOKChecker is a custom checker that implements Checker[int] interface. +// In consequence in can be used in any function that accepts an Checker[int]. type StatusOKChecker struct{} -// Pass satisfies IntPasser interface. +// Pass satisfies Passer[int] interface. func (c StatusOKChecker) Pass(got int) bool { return (got >= 200 && got < 300) || got == 304 } // Explain satisfies Explainer interface. -func (c StatusOKChecker) Explain(label string, got interface{}) string { +func (c StatusOKChecker) Explain(label string, got any) string { return fmt.Sprintf("%s: got bad http code: %v", label, got) } @@ -36,8 +36,8 @@ func Example_customChecker() { request := httptest.NewRequest("GET", "/", nil) results := testx.HTTPHandlerFunc(HandleNotFound).WithRequest(request). - // check.HTTPResponse.StatusCode accepts an IntChecker, - // StatusOKChecker satisfies IntChecker interface. + // check.HTTPResponse.StatusCode accepts a Checker[int], + // StatusOKChecker satisfies Checker[int] interface. Response(check.HTTPResponse.StatusCode(StatusOKChecker{})). DryRun() @@ -49,6 +49,6 @@ func Example_customChecker() { // false // 404 // http response: - // exp status code to pass IntChecker + // exp status code to pass Checker[int] // got explanation: status code: got bad http code: 404 } diff --git a/check/example_custom_unknown_test.go b/check/example_custom_unknown_test.go index 6c6ebdc..136d345 100644 --- a/check/example_custom_unknown_test.go +++ b/check/example_custom_unknown_test.go @@ -31,7 +31,7 @@ func (c MyTypeValidityChecker) Pass(got MyType) bool { } // Explain satisfies Explainer interface. -func (c MyTypeValidityChecker) Explain(label string, got interface{}) string { +func (c MyTypeValidityChecker) Explain(label string, got any) string { return fmt.Sprintf("%s: got bad CustomType value: %v", label, got) } diff --git a/check/example_newintchecker_test.go b/check/example_newintchecker_test.go index 5024830..04c28bf 100644 --- a/check/example_newintchecker_test.go +++ b/check/example_newintchecker_test.go @@ -5,7 +5,6 @@ import ( "github.com/drykit-go/testx" "github.com/drykit-go/testx/check" - "github.com/drykit-go/testx/checkconv" ) /* @@ -16,13 +15,13 @@ import ( func Example_newIntChecker() { checkIsEven := check.NewIntChecker( func(got int) bool { return got&1 == 0 }, - func(label string, got interface{}) string { + func(label string, got any) string { return fmt.Sprintf("%s: expect even int, got %v", label, got) }, ) - resultPass := testx.Value(42).Pass(checkconv.FromInt(checkIsEven)).DryRun() - resultFail := testx.Value(43).Pass(checkconv.FromInt(checkIsEven)).DryRun() + resultPass := testx.Value(42).Pass(checkIsEven).DryRun() + resultFail := testx.Value(43).Pass(checkIsEven).DryRun() fmt.Println(resultPass.Passed()) fmt.Println(resultFail.Passed()) diff --git a/check/gen.go b/check/gen.go index 96df8b3..29d56f7 100644 --- a/check/gen.go +++ b/check/gen.go @@ -2,7 +2,7 @@ package check //go:generate ../bin/gen -kind types -name check //go:generate ../bin/gen -kind types -name checkers -//go:generate ../bin/gen -kind interfaces -name providers +// //go:generate ../bin/gen -kind interfaces -name providers // For every type {N,T} defined in ./gen/types.go, running go generate // will create the following definitions: @@ -23,7 +23,7 @@ package check // // func (c nChecker) Pass(got T) bool { return c.passFunc(got) } // -// func (c nChecker) Explain(label string, got interface{}) string { return c.explFunc(label, got) } +// func (c nChecker) Explain(label string, got any) string { return c.explFunc(label, got) } // // func NewNChecker(passFunc NPassFunc, explainFunc ExplainFunc) NChecker { // return nChecker{passFunc: passFunc, explFunc: explainFunc} diff --git a/check/providers.go b/check/providers.go index 6486729..02c2c8f 100644 --- a/check/providers.go +++ b/check/providers.go @@ -1,9 +1,8 @@ -// Code generated by go generate ./...; DO NOT EDIT -// Last generated on 30 Oct 21 12:10 UTC - package check import ( + "context" + "net/http" "regexp" "time" ) @@ -12,157 +11,157 @@ type ( // BoolCheckerProvider provides checks on type bool. BoolCheckerProvider interface { // Is checks the gotten bool is equal to the target. - Is(tar bool) BoolChecker + Is(tar bool) Checker[bool] } // BytesCheckerProvider provides checks on type []byte. BytesCheckerProvider interface { // AsMap checks the gotten []byte passes the given mapChecker - // once json-unmarshaled to a map[string]interface{}. + // once json-unmarshaled to a map[string]any. // It fails if it is not a valid JSON. - AsMap(mapChecker ValueChecker) BytesChecker - // AsString checks the gotten []byte passes the given StringChecker + AsMap(mapChecker Checker[any]) Checker[[]byte] + // AsString checks the gotten []byte passes the given Checker[string] // once converted to a string. - AsString(c StringChecker) BytesChecker + AsString(c Checker[string]) Checker[[]byte] // Contains checks the gotten []byte contains a specific subslice. - Contains(subslice []byte) BytesChecker + Contains(subslice []byte) Checker[[]byte] // Is checks the gotten []byte is equal to the target. - Is(tar []byte) BytesChecker + Is(tar []byte) Checker[[]byte] // Len checks the gotten []byte's length passes the provided - // IntChecker. - Len(c IntChecker) BytesChecker + // Checker[int]. + Len(c Checker[int]) Checker[[]byte] // Not checks the gotten []byte is not equal to the target. - Not(values ...[]byte) BytesChecker + Not(values ...[]byte) Checker[[]byte] // NotContains checks the gotten []byte contains a specific subslice. - NotContains(subslice []byte) BytesChecker + NotContains(subslice []byte) Checker[[]byte] // SameJSON checks the gotten []byte and the target read as the same // JSON value, ignoring formatting and keys order. - SameJSON(tar []byte) BytesChecker + SameJSON(tar []byte) Checker[[]byte] } // ContextCheckerProvider provides checks on type context.Context. ContextCheckerProvider interface { // Done checks the gotten context is done. - Done(expectDone bool) ContextChecker + Done(expectDone bool) Checker[context.Context] // HasKeys checks the gotten context has the given keys set. - HasKeys(keys ...interface{}) ContextChecker + HasKeys(keys ...any) Checker[context.Context] // Value checks the gotten context's value for the given key passes - // the given ValueChecker. It fails if value is nil. + // the given Checker[any]. It fails if value is nil. // // Examples: // Context.Value("userID", Value.Is("abcde")) // Context.Value("userID", checkconv.Assert(String.Contains("abc"))) - Value(key interface{}, c ValueChecker) ContextChecker + Value(key any, c Checker[any]) Checker[context.Context] } // DurationCheckerProvider provides checks on type time.Duration. DurationCheckerProvider interface { // InRange checks the gotten time.Duration is in range [lo:hi] - InRange(lo, hi time.Duration) DurationChecker + InRange(lo, hi time.Duration) Checker[time.Duration] // OutRange checks the gotten time.Duration is not in range [lo:hi] - OutRange(lo, hi time.Duration) DurationChecker + OutRange(lo, hi time.Duration) Checker[time.Duration] // Over checks the gotten time.Duration is over the target duration. - Over(tar time.Duration) DurationChecker + Over(tar time.Duration) Checker[time.Duration] // Under checks the gotten time.Duration is under the target duration. - Under(tar time.Duration) DurationChecker + Under(tar time.Duration) Checker[time.Duration] } // Float64CheckerProvider provides checks on type float64. Float64CheckerProvider interface { // GT checks the gotten float64 is greater than the target. - GT(tar float64) Float64Checker + GT(tar float64) Checker[float64] // GTE checks the gotten float64 is greater or equal to the target. - GTE(tar float64) Float64Checker + GTE(tar float64) Checker[float64] // InRange checks the gotten float64 is in the closed interval [lo:hi]. - InRange(lo, hi float64) Float64Checker + InRange(lo, hi float64) Checker[float64] // Is checks the gotten float64 is equal to the target. - Is(tar float64) Float64Checker + Is(tar float64) Checker[float64] // LT checks the gotten float64 is lesser than the target. - LT(tar float64) Float64Checker + LT(tar float64) Checker[float64] // LTE checks the gotten float64 is lesser or equal to the target. - LTE(tar float64) Float64Checker + LTE(tar float64) Checker[float64] // Not checks the gotten float64 is not equal to the target. - Not(values ...float64) Float64Checker + Not(values ...float64) Checker[float64] // OutRange checks the gotten float64 is not in the closed interval [lo:hi]. - OutRange(lo, hi float64) Float64Checker + OutRange(lo, hi float64) Checker[float64] } // HTTPHeaderCheckerProvider provides checks on type http.Header. HTTPHeaderCheckerProvider interface { // CheckValue checks the gotten http.Header has a value for the matching key - // that passes the given StringChecker. + // that passes the given Checker[string]. // It only checks the first result for the given key. - CheckValue(key string, c StringChecker) HTTPHeaderChecker + CheckValue(key string, c Checker[string]) Checker[http.Header] // HasKey checks the gotten http.Header has a specific key set. // The corresponding value is ignored, meaning an empty value // for that key passes the check. - HasKey(key string) HTTPHeaderChecker + HasKey(key string) Checker[http.Header] // HasNotKey checks the gotten http.Header does not have // a specific key set. - HasNotKey(key string) HTTPHeaderChecker + HasNotKey(key string) Checker[http.Header] // HasNotValue checks the gotten http.Header does not have a value equal to val. // It only compares the first result for each key. - HasNotValue(val string) HTTPHeaderChecker + HasNotValue(val string) Checker[http.Header] // HasValue checks the gotten http.Header has any value equal to val. // It only compares the first result for each key. - HasValue(val string) HTTPHeaderChecker + HasValue(val string) Checker[http.Header] } // HTTPRequestCheckerProvider provides checks on type *http.Request. HTTPRequestCheckerProvider interface { - // Body checks the gotten *http.Request Body passes the input BytesChecker. + // Body checks the gotten *http.Request Body passes the input Checker[[]byte]. // It should be used only once on a same *http.Request as it closes its body // after reading it. - Body(c BytesChecker) HTTPRequestChecker + Body(c Checker[[]byte]) Checker[*http.Request] // ContentLength checks the gotten *http.Request ContentLength passes - // the input IntChecker. - ContentLength(c IntChecker) HTTPRequestChecker + // the input Checker[int]. + ContentLength(c Checker[int]) Checker[*http.Request] // Context checks the gotten *http.Request Context passes - // the input ContextChecker. - Context(c ContextChecker) HTTPRequestChecker + // the input Checker[context.Context]. + Context(c Checker[context.Context]) Checker[*http.Request] // Header checks the gotten *http.Request Header passes - // the input HTTPHeaderChecker. - Header(c HTTPHeaderChecker) HTTPRequestChecker + // the input Checker[http.Header]. + Header(c Checker[http.Header]) Checker[*http.Request] } // HTTPResponseCheckerProvider provides checks on type *http.Response. HTTPResponseCheckerProvider interface { - // Body checks the gotten *http.Response Body passes the input BytesChecker. + // Body checks the gotten *http.Response Body passes the input Checker[[]byte]. // It should be used only once on a same *http.Response as it closes its body // after reading it. - Body(c BytesChecker) HTTPResponseChecker + Body(c Checker[[]byte]) Checker[*http.Response] // ContentLength checks the gotten *http.Response ContentLength passes - // the input IntChecker. - ContentLength(c IntChecker) HTTPResponseChecker + // the input Checker[int]. + ContentLength(c Checker[int]) Checker[*http.Response] // Header checks the gotten *http.Response Header passes - // the input HTTPHeaderChecker. - Header(c HTTPHeaderChecker) HTTPResponseChecker + // the input Checker[http.Header]. + Header(c Checker[http.Header]) Checker[*http.Response] // Status checks the gotten *http.Response Status passes - // the input StringChecker. - Status(c StringChecker) HTTPResponseChecker + // the input Checker[string]. + Status(c Checker[string]) Checker[*http.Response] // StatusCode checks the gotten *http.Response StatusCode passes - // the input IntChecker. - StatusCode(c IntChecker) HTTPResponseChecker + // the input Checker[int]. + StatusCode(c Checker[int]) Checker[*http.Response] } // IntCheckerProvider provides checks on type int. IntCheckerProvider interface { // GT checks the gotten int is greater than the target. - GT(tar int) IntChecker + GT(tar int) Checker[int] // GTE checks the gotten int is greater or equal to the target. - GTE(tar int) IntChecker + GTE(tar int) Checker[int] // InRange checks the gotten int is in the closed interval [lo:hi]. - InRange(lo, hi int) IntChecker + InRange(lo, hi int) Checker[int] // Is checks the gotten int is equal to the target. - Is(tar int) IntChecker + Is(tar int) Checker[int] // LT checks the gotten int is lesser than the target. - LT(tar int) IntChecker + LT(tar int) Checker[int] // LTE checks the gotten int is lesser or equal to the target. - LTE(tar int) IntChecker + LTE(tar int) Checker[int] // Not checks the gotten int is not equal to the target. - Not(values ...int) IntChecker + Not(values ...int) Checker[int] // OutRange checks the gotten int is not in the closed interval [lo:hi]. - OutRange(lo, hi int) IntChecker + OutRange(lo, hi int) Checker[int] } // MapCheckerProvider provides checks on kind map. @@ -172,89 +171,89 @@ type ( // CheckValues checks the gotten map's values corresponding to the given keys // pass the given checker. A key not found is considered a fail. // If len(keys) == 0, the check is made on all map values. - CheckValues(c ValueChecker, keys ...interface{}) ValueChecker + CheckValues(c Checker[any], keys ...any) Checker[any] // HasKeys checks the gotten map has the given keys set. - HasKeys(keys ...interface{}) ValueChecker + HasKeys(keys ...any) Checker[any] // HasNotKeys checks the gotten map has the given keys set. - HasNotKeys(keys ...interface{}) ValueChecker + HasNotKeys(keys ...any) Checker[any] // HasNotValues checks the gotten map has not the given values set. - HasNotValues(values ...interface{}) ValueChecker + HasNotValues(values ...any) Checker[any] // HasValues checks the gotten map has the given values set. - HasValues(values ...interface{}) ValueChecker - // Len checks the gotten map passes the given IntChecker. - Len(c IntChecker) ValueChecker + HasValues(values ...any) Checker[any] + // Len checks the gotten map passes the given Checker[int]. + Len(c Checker[int]) Checker[any] } // SliceCheckerProvider provides checks on kind slice. SliceCheckerProvider interface { ValueCheckerProvider - // Cap checks the capacity of the gotten slice passes the given IntChecker. - Cap(c IntChecker) ValueChecker - // CheckValues checks the values of the gotten slice pass the given ValueChecker. + // Cap checks the capacity of the gotten slice passes the given Checker[int]. + Cap(c Checker[int]) Checker[any] + // CheckValues checks the values of the gotten slice pass the given Checker[any]. // If a filterFunc is provided, the values not passing it are ignored. - CheckValues(c ValueChecker, filters ...func(i int, v interface{}) bool) ValueChecker + CheckValues(c Checker[any], filters ...func(i int, v any) bool) Checker[any] // HasNotValues checks the gotten slice has not the given values set. - HasNotValues(values ...interface{}) ValueChecker + HasNotValues(values ...any) Checker[any] // HasValues checks the gotten slice has the given values set. - HasValues(values ...interface{}) ValueChecker - // Len checks the length of the gotten slice passes the given IntChecker. - Len(c IntChecker) ValueChecker + HasValues(values ...any) Checker[any] + // Len checks the length of the gotten slice passes the given Checker[int]. + Len(c Checker[int]) Checker[any] } // StringCheckerProvider provides checks on type string. StringCheckerProvider interface { // Contains checks the gotten string contains the target substring. - Contains(sub string) StringChecker + Contains(sub string) Checker[string] // Is checks the gotten string is equal to the target. - Is(tar string) StringChecker - // Len checks the gotten string's length passes the given IntChecker. - Len(c IntChecker) StringChecker + Is(tar string) Checker[string] + // Len checks the gotten string's length passes the given Checker[int]. + Len(c Checker[int]) Checker[string] // Match checks the gotten string matches the given regexp. - Match(rgx *regexp.Regexp) StringChecker + Match(rgx *regexp.Regexp) Checker[string] // Not checks the gotten string is not equal to the target. - Not(values ...string) StringChecker + Not(values ...string) Checker[string] // NotContains checks the gotten string do not contain the target // substring. - NotContains(sub string) StringChecker + NotContains(sub string) Checker[string] // NotMatch checks the gotten string do not match the given regexp. - NotMatch(rgx *regexp.Regexp) StringChecker + NotMatch(rgx *regexp.Regexp) Checker[string] } // StructCheckerProvider provides checks on kind struct. StructCheckerProvider interface { ValueCheckerProvider - // CheckFields checks all given fields pass the ValueChecker. + // CheckFields checks all given fields pass the Checker[any]. // It panics if the fields do not exist or are not exported, // or if the tested value is not a struct. - CheckFields(c ValueChecker, fields []string) ValueChecker + CheckFields(c Checker[any], fields []string) Checker[any] // FieldsEqual checks all given fields equal the exp value. // It panics if the fields do not exist or are not exported, // or if the tested value is not a struct. - FieldsEqual(exp interface{}, fields []string) ValueChecker + FieldsEqual(exp any, fields []string) Checker[any] } - // ValueCheckerProvider provides checks on type interface{}. + // ValueCheckerProvider provides checks on type any. ValueCheckerProvider interface { - // Custom checks the gotten value passes the given ValuePassFunc. + // Custom checks the gotten value passes the given PassFunc[any]. // The description should give information about the expected value, // as it outputs in format "exp " in case of failure. - Custom(desc string, f ValuePassFunc) ValueChecker + Custom(desc string, f PassFunc[any]) Checker[any] // Is checks the gotten value is equal to the target. - Is(tar interface{}) ValueChecker + Is(tar any) Checker[any] // IsZero checks the gotten value is a zero value, indicating it might not // have been initialized. - IsZero() ValueChecker + IsZero() Checker[any] // Not checks the gotten value is not equal to the target. - Not(values ...interface{}) ValueChecker + Not(values ...any) Checker[any] // NotZero checks the gotten struct contains at least 1 non-zero value, // meaning it has been initialized. - NotZero() ValueChecker + NotZero() Checker[any] // SameJSON checks the gotten value and the target value // produce the same JSON, ignoring formatting and keys order. // It panics if any error occurs in the marshaling process. - SameJSON(tar interface{}) ValueChecker + SameJSON(tar any) Checker[any] } ) diff --git a/check/providers_base.go b/check/providers_base.go index 2e6a58e..f766fb7 100644 --- a/check/providers_base.go +++ b/check/providers_base.go @@ -18,8 +18,8 @@ type baseCheckerProvider struct{} // with unmarshaled x and y respectively. // // It panics on the first error encountered in the process. -func (p baseCheckerProvider) sameJSON(x, y []byte, xptr, yptr interface{}) bool { - mustUnmarshal := func(b []byte, ptr interface{}) { +func (p baseCheckerProvider) sameJSON(x, y []byte, xptr, yptr any) bool { + mustUnmarshal := func(b []byte, ptr any) { if err := json.Unmarshal(b, &ptr); err != nil { panic(err) } @@ -35,8 +35,8 @@ func (p baseCheckerProvider) sameJSON(x, y []byte, xptr, yptr interface{}) bool // with marshaled+unmarshaled json from xdata and ydata respectively. // // It panics on the first error encountered in the process. -func (p baseCheckerProvider) sameJSONProduced(xdata, ydata, xptr, yptr interface{}) bool { - mustMarshal := func(in interface{}) []byte { +func (p baseCheckerProvider) sameJSONProduced(xdata, ydata, xptr, yptr any) bool { + mustMarshal := func(in any) []byte { b, err := json.Marshal(in) if err != nil { panic(err) @@ -56,15 +56,15 @@ func (p baseCheckerProvider) formatList(values []string) string { return b.String() } -func (p baseCheckerProvider) deq(a, b interface{}) bool { +func (p baseCheckerProvider) deq(a, b any) bool { return reflect.DeepEqual(a, b) } -func (baseCheckerProvider) explain(label string, exp, got interface{}) string { +func (baseCheckerProvider) explain(label string, exp, got any) string { return fmtexpl.Default(label, exp, got) } -func (p baseCheckerProvider) explainNot(label string, exp, got interface{}) string { +func (p baseCheckerProvider) explainNot(label string, exp, got any) string { return p.explain(label, fmt.Sprintf("not %v", exp), got) } @@ -75,36 +75,36 @@ func (p baseCheckerProvider) explainCheck(label, expStr, gotExpl string) string type baseHTTPCheckerProvider struct{ baseCheckerProvider } func (p baseHTTPCheckerProvider) explainContentLengthFunc( - c IntChecker, + c Checker[int], got func() int, ) ExplainFunc { - return func(label string, _ interface{}) string { + return func(label string, _ any) string { return p.explainCheck(label, - "content length to pass IntChecker", + "content length to pass Checker[int]", c.Explain("content length", got()), ) } } func (p baseHTTPCheckerProvider) explainHeaderFunc( - c HTTPHeaderChecker, + c Checker[http.Header], got func() http.Header, ) ExplainFunc { - return func(label string, _ interface{}) string { + return func(label string, _ any) string { return p.explainCheck(label, - "header to pass HTTPHeaderChecker", + "header to pass Checker[http.Header]", c.Explain("http.Header", got()), ) } } func (p baseHTTPCheckerProvider) explainBodyFunc( - c BytesChecker, + c Checker[[]byte], got func() []byte, ) ExplainFunc { - return func(label string, _ interface{}) string { + return func(label string, _ any) string { return p.explainCheck(label, - "body to pass BytesChecker", + "body to pass Checker[[]byte]", c.Explain("bytes", got()), ) } diff --git a/check/providers_base_internal_test.go b/check/providers_base_internal_test.go index 6963f94..73bceef 100644 --- a/check/providers_base_internal_test.go +++ b/check/providers_base_internal_test.go @@ -14,7 +14,7 @@ func TestBaseCheckerProvider_sameJSON(t *testing.T) { ) t.Run("bad dst type", func(t *testing.T) { - var x map[string]interface{} + var x map[string]any var y int defer testutil.AssertPanicMessage(t, "json: cannot unmarshal object into Go value of type int", @@ -25,14 +25,14 @@ func TestBaseCheckerProvider_sameJSON(t *testing.T) { }) t.Run("same json", func(t *testing.T) { - var x, y map[string]interface{} + var x, y map[string]any if !(baseCheckerProvider{}).sameJSON(borig, bsame, &x, &y) { t.Error("exp true, got false", x, y) } }) t.Run("diff json", func(t *testing.T) { - var x, y map[string]interface{} + var x, y map[string]any if (baseCheckerProvider{}).sameJSON(borig, bdiff, &x, &y) { t.Error("exp false, got true") } @@ -45,26 +45,26 @@ func TestBaseCheckerProvider_sameJSONProduced(t *testing.T) { ID int `json:"id"` Name string `json:"name"` }{ID: 42, Name: "Marcel Patulacci"} - same = map[string]interface{}{"id": 42, "name": "Marcel Patulacci"} - diff = map[string]interface{}{"id": 42, "name": "Robert Robichet"} + same = map[string]any{"id": 42, "name": "Marcel Patulacci"} + diff = map[string]any{"id": 42, "name": "Robert Robichet"} ) t.Run("same json produced", func(t *testing.T) { - var x, y map[string]interface{} + var x, y map[string]any if !(baseCheckerProvider{}).sameJSONProduced(orig, same, &x, &y) { t.Error("exp true, got false", x, y) } }) t.Run("diff json produced", func(t *testing.T) { - var x, y map[string]interface{} + var x, y map[string]any if (baseCheckerProvider{}).sameJSONProduced(orig, diff, &x, &y) { t.Error("exp false, got true") } }) t.Run("bad input", func(t *testing.T) { - var x, y map[string]interface{} + var x, y map[string]any badinput := make(chan int) defer testutil.AssertPanicMessage(t, "json: unsupported type: chan int", diff --git a/check/providers_bool.go b/check/providers_bool.go index b4639c3..67f0673 100644 --- a/check/providers_bool.go +++ b/check/providers_bool.go @@ -4,10 +4,10 @@ package check type boolCheckerProvider struct{ baseCheckerProvider } // Is checks the gotten bool is equal to the target. -func (p boolCheckerProvider) Is(tar bool) BoolChecker { +func (p boolCheckerProvider) Is(tar bool) Checker[bool] { pass := func(got bool) bool { return got == tar } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, tar, got) } - return NewBoolChecker(pass, expl) + return NewChecker(pass, expl) } diff --git a/check/providers_bool_test.go b/check/providers_bool_test.go index f1c2300..08e14ab 100644 --- a/check/providers_bool_test.go +++ b/check/providers_bool_test.go @@ -1,6 +1,7 @@ package check_test import ( + "fmt" "testing" "github.com/drykit-go/testx/check" @@ -11,37 +12,16 @@ func TestBoolCheckerProvider(t *testing.T) { t.Run("Is pass", func(t *testing.T) { c := check.Bool.Is(b) - assertPassBoolChecker(t, "Is", c, b) + fmt.Print(c) + assertPassChecker(t, "Bool.Is", c, b) c = check.Bool.Is(!b) - assertPassBoolChecker(t, "Is", c, !b) + assertPassChecker(t, "Bool.Is", c, !b) }) t.Run("Is fail", func(t *testing.T) { c := check.Bool.Is(!b) - assertFailBoolChecker(t, "Is", c, b, makeExpl("false", "true")) + assertFailChecker(t, "Bool.Is", c, b, makeExpl("false", "true")) c = check.Bool.Is(b) - assertFailBoolChecker(t, "Is", c, !b, makeExpl("true", "false")) + assertFailChecker(t, "Bool.Is", c, !b, makeExpl("true", "false")) }) } - -// Helpers - -func assertPassBoolChecker(t *testing.T, method string, c check.BoolChecker, b bool) { - t.Helper() - if !c.Pass(b) { - failBoolCheckerTest(t, true, method, b, c.Explain) - } -} - -func assertFailBoolChecker(t *testing.T, method string, c check.BoolChecker, b bool, expexpl string) { - t.Helper() - if c.Pass(b) { - failBoolCheckerTest(t, false, method, b, c.Explain) - } - assertGoodExplain(t, c, b, expexpl) -} - -func failBoolCheckerTest(t *testing.T, expPass bool, method string, b bool, explain check.ExplainFunc) { - t.Helper() - failCheckerTest(t, expPass, "Bool."+method, explain("Bool value", b)) -} diff --git a/check/providers_bytes.go b/check/providers_bytes.go index 97e41bb..c29b184 100644 --- a/check/providers_bytes.go +++ b/check/providers_bytes.go @@ -10,16 +10,16 @@ import ( type bytesCheckerProvider struct{ baseCheckerProvider } // Is checks the gotten []byte is equal to the target. -func (p bytesCheckerProvider) Is(tar []byte) BytesChecker { +func (p bytesCheckerProvider) Is(tar []byte) Checker[[]byte] { pass := func(got []byte) bool { return p.eq(got, tar) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, tar, got) } - return NewBytesChecker(pass, expl) + return NewChecker(pass, expl) } // Not checks the gotten []byte is not equal to the target. -func (p bytesCheckerProvider) Not(values ...[]byte) BytesChecker { +func (p bytesCheckerProvider) Not(values ...[]byte) Checker[[]byte] { match := []byte{} pass := func(got []byte) bool { for _, v := range values { @@ -30,76 +30,76 @@ func (p bytesCheckerProvider) Not(values ...[]byte) BytesChecker { } return true } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainNot(label, match, got) } - return NewBytesChecker(pass, expl) + return NewChecker(pass, expl) } // SameJSON checks the gotten []byte and the target read as the same // JSON value, ignoring formatting and keys order. -func (p bytesCheckerProvider) SameJSON(tar []byte) BytesChecker { - var decGot, decTar interface{} +func (p bytesCheckerProvider) SameJSON(tar []byte) Checker[[]byte] { + var decGot, decTar any pass := func(got []byte) bool { return p.sameJSON(got, tar, &decGot, &decTar) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("json data: %v", decTar), fmt.Sprintf("json data: %v", decGot), ) } - return NewBytesChecker(pass, expl) + return NewChecker(pass, expl) } // Len checks the gotten []byte's length passes the provided -// IntChecker. -func (p bytesCheckerProvider) Len(c IntChecker) BytesChecker { +// Checker[int]. +func (p bytesCheckerProvider) Len(c Checker[int]) Checker[[]byte] { pass := func(got []byte) bool { return c.Pass(len(got)) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainCheck(label, - "length to pass IntChecker", + "length to pass Checker[int]", c.Explain("length", len(got.([]byte))), ) } - return NewBytesChecker(pass, expl) + return NewChecker(pass, expl) } // Contains checks the gotten []byte contains a specific subslice. -func (p bytesCheckerProvider) Contains(subslice []byte) BytesChecker { +func (p bytesCheckerProvider) Contains(subslice []byte) Checker[[]byte] { pass := func(got []byte) bool { return bytes.Contains(got, subslice) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("to contain subslice %v", subslice), got, ) } - return NewBytesChecker(pass, expl) + return NewChecker(pass, expl) } // NotContains checks the gotten []byte contains a specific subslice. -func (p bytesCheckerProvider) NotContains(subslice []byte) BytesChecker { +func (p bytesCheckerProvider) NotContains(subslice []byte) Checker[[]byte] { pass := func(got []byte) bool { return !bytes.Contains(got, subslice) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainNot(label, fmt.Sprintf("to contain subslice %v", subslice), got, ) } - return NewBytesChecker(pass, expl) + return NewChecker(pass, expl) } // AsMap checks the gotten []byte passes the given mapChecker -// once json-unmarshaled to a map[string]interface{}. +// once json-unmarshaled to a map[string]any. // It fails if it is not a valid JSON. -func (p bytesCheckerProvider) AsMap(mapChecker ValueChecker) BytesChecker { - var m map[string]interface{} +func (p bytesCheckerProvider) AsMap(mapChecker Checker[any]) Checker[[]byte] { + var m map[string]any var goterr error pass := func(got []byte) bool { goterr = json.NewDecoder(bytes.NewReader(got)).Decode(&m) return goterr == nil && mapChecker.Pass(m) } - expl := func(label string, _ interface{}) string { + expl := func(label string, _ any) string { if goterr != nil { return p.explain(label, "to pass MapChecker", @@ -111,24 +111,24 @@ func (p bytesCheckerProvider) AsMap(mapChecker ValueChecker) BytesChecker { mapChecker.Explain("json map", m), ) } - return NewBytesChecker(pass, expl) + return NewChecker(pass, expl) } -// AsString checks the gotten []byte passes the given StringChecker +// AsString checks the gotten []byte passes the given Checker[string] // once converted to a string. -func (p bytesCheckerProvider) AsString(c StringChecker) BytesChecker { +func (p bytesCheckerProvider) AsString(c Checker[string]) Checker[[]byte] { var s string pass := func(got []byte) bool { s = string(got) return c.Pass(s) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainCheck(label, - "to pass StringChecker", + "to pass Checker[string]", c.Explain("converted bytes", s), ) } - return NewBytesChecker(pass, expl) + return NewChecker(pass, expl) } func (bytesCheckerProvider) eq(a, b []byte) bool { diff --git a/check/providers_bytes_test.go b/check/providers_bytes_test.go index 9f30f91..b80c035 100644 --- a/check/providers_bytes_test.go +++ b/check/providers_bytes_test.go @@ -16,19 +16,19 @@ func TestBytesCheckerProvider(t *testing.T) { eqJSON = []byte("{\n\"id\": 42,\n\n\n \"name\":\"Marcel Patulacci\" } ") ) - mapof := func(b []byte) (m map[string]interface{}) { + mapof := func(b []byte) (m map[string]any) { json.Unmarshal(b, &m) //nolint:errcheck return } t.Run("Is pass", func(t *testing.T) { c := check.Bytes.Is(b) - assertPassBytesChecker(t, "Is", c, b) + assertPassChecker(t, "Bytes.Is", c, b) }) t.Run("Is fail", func(t *testing.T) { c := check.Bytes.Is(diff) - assertFailBytesChecker(t, "Is", c, b, makeExpl( + assertFailChecker(t, "Bytes.Is", c, b, makeExpl( fmt.Sprint(diff), fmt.Sprint(b), )) @@ -36,12 +36,12 @@ func TestBytesCheckerProvider(t *testing.T) { t.Run("Not pass", func(t *testing.T) { c := check.Bytes.Not(diff, eqJSON) - assertPassBytesChecker(t, "Not", c, b) + assertPassChecker(t, "Bytes.Not", c, b) }) t.Run("Not fail", func(t *testing.T) { c := check.Bytes.Not(diff, eqJSON, b) - assertFailBytesChecker(t, "Not", c, b, makeExpl( + assertFailChecker(t, "Bytes.Not", c, b, makeExpl( fmt.Sprintf("not %v", b), fmt.Sprint(b), )) @@ -49,15 +49,15 @@ func TestBytesCheckerProvider(t *testing.T) { t.Run("Len pass", func(t *testing.T) { c := check.Bytes.Len(check.Int.Is(len(b))) - assertPassBytesChecker(t, "Len", c, b) + assertPassChecker(t, "Bytes.Len", c, b) }) t.Run("Len fail", func(t *testing.T) { gotlen := len(b) explen := gotlen + 1 c := check.Bytes.Len(check.Int.Is(explen)) - assertFailBytesChecker(t, "Len", c, b, makeExpl( - "length to pass IntChecker", + assertFailChecker(t, "Bytes.Len", c, b, makeExpl( + "length to pass Checker[int]", "explanation: length:\n"+makeExpl( fmt.Sprint(explen), fmt.Sprint(gotlen), @@ -67,14 +67,14 @@ func TestBytesCheckerProvider(t *testing.T) { t.Run("SameJSON pass", func(t *testing.T) { c := check.Bytes.SameJSON(eqJSON) - assertPassBytesChecker(t, "SameJSON", c, b) + assertPassChecker(t, "Bytes.SameJSON", c, b) c = check.Bytes.SameJSON(b) - assertPassBytesChecker(t, "SameJSON", c, b) + assertPassChecker(t, "Bytes.SameJSON", c, b) }) t.Run("SameJSON fail", func(t *testing.T) { c := check.Bytes.SameJSON(diff) - assertFailBytesChecker(t, "SameJSON", c, b, makeExpl( + assertFailChecker(t, "Bytes.SameJSON", c, b, makeExpl( fmt.Sprintf("json data: %v", mapof(diff)), fmt.Sprintf("json data: %v", mapof(b)), )) @@ -82,14 +82,14 @@ func TestBytesCheckerProvider(t *testing.T) { t.Run("Contains pass", func(t *testing.T) { c := check.Bytes.Contains(sub) - assertPassBytesChecker(t, "Contains", c, b) + assertPassChecker(t, "Bytes.Contains", c, b) c = check.Bytes.Contains(b) - assertPassBytesChecker(t, "Contains", c, b) + assertPassChecker(t, "Bytes.Contains", c, b) }) t.Run("Contains fail", func(t *testing.T) { c := check.Bytes.Contains(diff) - assertFailBytesChecker(t, "Contains", c, b, + assertFailChecker(t, "Bytes.Contains", c, b, makeExpl( fmt.Sprintf("to contain subslice %v", diff), fmt.Sprint(b), @@ -97,7 +97,7 @@ func TestBytesCheckerProvider(t *testing.T) { ) c = check.Bytes.Contains(eqJSON) - assertFailBytesChecker(t, "Contains", c, b, + assertFailChecker(t, "Bytes.Contains", c, b, makeExpl( fmt.Sprintf("to contain subslice %v", eqJSON), fmt.Sprint(b), @@ -107,20 +107,20 @@ func TestBytesCheckerProvider(t *testing.T) { t.Run("NotContains pass", func(t *testing.T) { c := check.Bytes.NotContains(diff) - assertPassBytesChecker(t, "NotContains", c, b) + assertPassChecker(t, "Bytes.NotContains", c, b) c = check.Bytes.NotContains(eqJSON) - assertPassBytesChecker(t, "NotContains", c, b) + assertPassChecker(t, "Bytes.NotContains", c, b) }) t.Run("NotContains fail", func(t *testing.T) { c := check.Bytes.NotContains(sub) - assertFailBytesChecker(t, "NotContains", c, b, makeExpl( + assertFailChecker(t, "Bytes.NotContains", c, b, makeExpl( fmt.Sprintf("not to contain subslice %v", sub), fmt.Sprint(b), )) c = check.Bytes.NotContains(b) - assertFailBytesChecker(t, "NotContains", c, b, makeExpl( + assertFailChecker(t, "Bytes.NotContains", c, b, makeExpl( fmt.Sprintf("not to contain subslice %v", b), fmt.Sprint(b), )) @@ -128,13 +128,13 @@ func TestBytesCheckerProvider(t *testing.T) { t.Run("AsMap pass", func(t *testing.T) { c := check.Bytes.AsMap(check.Map.HasKeys("id")) - assertPassBytesChecker(t, "AsMap", c, b) - assertPassBytesChecker(t, "AsMap", c, eqJSON) + assertPassChecker(t, "Bytes.AsMap", c, b) + assertPassChecker(t, "Bytes.AsMap", c, eqJSON) }) t.Run("AsMap fail", func(t *testing.T) { c := check.Bytes.AsMap(check.Map.HasKeys("id", "nomatch")) - assertFailBytesChecker(t, "AsMap", c, b, makeExpl( + assertFailChecker(t, "Bytes.AsMap", c, b, makeExpl( "to pass MapChecker", "explanation: json map:\n"+makeExpl( "to have keys [nomatch]", @@ -143,7 +143,7 @@ func TestBytesCheckerProvider(t *testing.T) { )) c = check.Bytes.AsMap(check.Map.HasKeys("id")) - assertFailBytesChecker(t, "AsMap", c, sub, makeExpl( + assertFailChecker(t, "Bytes.AsMap", c, sub, makeExpl( "to pass MapChecker", "error: json: cannot unmarshal string into Go value of type map[string]interface {}", )) @@ -151,13 +151,13 @@ func TestBytesCheckerProvider(t *testing.T) { t.Run("AsString pass", func(t *testing.T) { c := check.Bytes.AsString(check.String.Is(string(b))) - assertPassBytesChecker(t, "AsString", c, b) + assertPassChecker(t, "Bytes.AsString", c, b) }) t.Run("AsString fail", func(t *testing.T) { c := check.Bytes.AsString(check.String.Is(string(diff))) - assertFailBytesChecker(t, "AsString", c, b, makeExpl( - "to pass StringChecker", + assertFailChecker(t, "Bytes.AsString", c, b, makeExpl( + "to pass Checker[string]", "explanation: converted bytes:\n"+makeExpl( string(diff), string(b), @@ -165,25 +165,3 @@ func TestBytesCheckerProvider(t *testing.T) { )) }) } - -// Helpers - -func assertPassBytesChecker(t *testing.T, method string, c check.BytesChecker, in []byte) { - t.Helper() - if !c.Pass(in) { - failBytesCheckerTest(t, true, method, in, c.Explain) - } -} - -func assertFailBytesChecker(t *testing.T, method string, c check.BytesChecker, in []byte, expexpl string) { - t.Helper() - if c.Pass(in) { - failBytesCheckerTest(t, false, method, in, c.Explain) - } - assertGoodExplain(t, c, in, expexpl) -} - -func failBytesCheckerTest(t *testing.T, expPass bool, method string, in []byte, explain check.ExplainFunc) { - t.Helper() - failCheckerTest(t, expPass, "Bytes."+method, explain("Bytes value", in)) -} diff --git a/check/providers_context.go b/check/providers_context.go index c4269df..28c8c4a 100644 --- a/check/providers_context.go +++ b/check/providers_context.go @@ -11,24 +11,24 @@ import ( type contextCheckerProvider struct{ baseCheckerProvider } // Done checks the gotten context is done. -func (p contextCheckerProvider) Done(expectDone bool) ContextChecker { +func (p contextCheckerProvider) Done(expectDone bool) Checker[context.Context] { var err error done := func() bool { return err != nil } pass := func(got context.Context) bool { err = got.Err() return done() == expectDone } - expl := func(label string, _ interface{}) string { + expl := func(label string, _ any) string { notString := cond.String("", "not ", expectDone) expString := fmt.Sprintf("context %sto be done", notString) gotString := cond.String(fmt.Sprint(err), "context not done", done()) return p.explain(label, expString, gotString) } - return NewContextChecker(pass, expl) + return NewChecker(pass, expl) } // HasKeys checks the gotten context has the given keys set. -func (p contextCheckerProvider) HasKeys(keys ...interface{}) ContextChecker { +func (p contextCheckerProvider) HasKeys(keys ...any) Checker[context.Context] { var missing []string pass := func(got context.Context) bool { for _, expk := range keys { @@ -39,13 +39,13 @@ func (p contextCheckerProvider) HasKeys(keys ...interface{}) ContextChecker { } return len(missing) == 0 } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, "to have keys "+p.formatList(missing), "keys not set", ) } - return NewContextChecker(pass, expl) + return NewChecker(pass, expl) } // Value checks the gotten context's value for the given key passes @@ -54,17 +54,17 @@ func (p contextCheckerProvider) HasKeys(keys ...interface{}) ContextChecker { // Examples: // Context.Value("userID", Value.Is("abcde")) // Context.Value("userID", checkconv.Assert(String.Contains("abc"))) -func (p contextCheckerProvider) Value(key interface{}, c ValueChecker) ContextChecker { - var v interface{} +func (p contextCheckerProvider) Value(key any, c Checker[any]) Checker[context.Context] { + var v any pass := func(got context.Context) bool { v = got.Value(key) return v != nil && c.Pass(v) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainCheck(label, fmt.Sprintf("value for key %v to pass ValueChecker", key), c.Explain("value", v), ) } - return NewContextChecker(pass, expl) + return NewChecker(pass, expl) } diff --git a/check/providers_context_test.go b/check/providers_context_test.go index 0e22a44..78f297e 100644 --- a/check/providers_context_test.go +++ b/check/providers_context_test.go @@ -17,25 +17,25 @@ func TestContextCheckerProvider(t *testing.T) { cancel() return ctx } - ctxVal := func(key, val interface{}) context.Context { + ctxVal := func(key, val any) context.Context { return context.WithValue(context.Background(), key, val) } t.Run("Done pass", func(t *testing.T) { checkDone := check.Context.Done(true) ctxDone := ctxDone() - assertPassContextChecker(t, "Done", checkDone, ctxDone) + assertPassChecker(t, "Context.Done", checkDone, ctxDone) checkNotDone := check.Context.Done(false) ctxNotDone, cancel := ctxNotDone() - assertPassContextChecker(t, "Done", checkNotDone, ctxNotDone) + assertPassChecker(t, "Context.Done", checkNotDone, ctxNotDone) cancel() }) t.Run("Done fail", func(t *testing.T) { checkDone := check.Context.Done(true) ctxNotDone, cancel := ctxNotDone() - assertFailContextChecker(t, "Done", checkDone, ctxNotDone, makeExpl( + assertFailChecker(t, "Context.Done", checkDone, ctxNotDone, makeExpl( "context to be done", "context not done", )) @@ -43,7 +43,7 @@ func TestContextCheckerProvider(t *testing.T) { checkNotDone := check.Context.Done(false) ctxDone := ctxDone() - assertFailContextChecker(t, "Done", checkNotDone, ctxDone, makeExpl( + assertFailChecker(t, "Context.Done", checkNotDone, ctxDone, makeExpl( "context not to be done", "context canceled", )) @@ -52,13 +52,13 @@ func TestContextCheckerProvider(t *testing.T) { t.Run("HasKeys pass", func(t *testing.T) { c := check.Context.HasKeys("user") ctx := ctxVal("user", struct{}{}) - assertPassContextChecker(t, "HasKeys", c, ctx) + assertPassChecker(t, "Context.HasKeys", c, ctx) }) t.Run("HasKeys fail", func(t *testing.T) { c := check.Context.HasKeys("secret", "user", "token") ctx := ctxVal("user", struct{}{}) - assertFailContextChecker(t, "HasKeys", c, ctx, makeExpl( + assertFailChecker(t, "Context.HasKeys", c, ctx, makeExpl( "to have keys [secret, token]", "keys not set", )) @@ -67,47 +67,22 @@ func TestContextCheckerProvider(t *testing.T) { t.Run("Value pass", func(t *testing.T) { c := check.Context.Value("userID", checkconv.FromInt(check.Int.GT(0))) ctx := ctxVal("userID", 42) - assertPassContextChecker(t, "Value", c, ctx) + assertPassChecker(t, "Context.Value", c, ctx) }) t.Run("Value fail", func(t *testing.T) { c := check.Context.Value("userID", check.Value.Is(0)) ctxMissingKey := context.Background() - assertFailContextChecker(t, "Value", c, ctxMissingKey, makeExpl( + assertFailChecker(t, "Context.Value", c, ctxMissingKey, makeExpl( "value for key userID to pass ValueChecker", "explanation: value:\n"+makeExpl("0", ""), )) ctxBadValue := ctxVal("userID", -1) - assertFailContextChecker(t, "Value", c, ctxBadValue, makeExpl( + assertFailChecker(t, "Context.Value", c, ctxBadValue, makeExpl( "value for key userID to pass ValueChecker", "explanation: value:\n"+makeExpl("0", "-1"), )) }) } - -// Helpers - -//nolint: revive // context-as-argument rule not relevant here -func assertPassContextChecker(t *testing.T, method string, c check.ContextChecker, ctx context.Context) { - t.Helper() - if !c.Pass(ctx) { - failContextCheckerTest(t, true, method, ctx, c.Explain) - } -} - -//nolint: revive // context-as-argument rule not relevant here -func assertFailContextChecker(t *testing.T, method string, c check.ContextChecker, ctx context.Context, expexpl string) { - t.Helper() - if c.Pass(ctx) { - failContextCheckerTest(t, false, method, ctx, c.Explain) - } - assertGoodExplain(t, c, ctx, expexpl) -} - -//nolint: revive // context-as-argument rule not relevant here -func failContextCheckerTest(t *testing.T, expPass bool, method string, ctx context.Context, explain check.ExplainFunc) { - t.Helper() - failCheckerTest(t, expPass, "Context."+method, explain("Context value", ctx)) -} diff --git a/check/providers_duration.go b/check/providers_duration.go index 3d9c2e9..594d447 100644 --- a/check/providers_duration.go +++ b/check/providers_duration.go @@ -9,51 +9,51 @@ import ( type durationCheckerProvider struct{ baseCheckerProvider } // Over checks the gotten time.Duration is over the target duration. -func (p durationCheckerProvider) Over(tar time.Duration) DurationChecker { +func (p durationCheckerProvider) Over(tar time.Duration) Checker[time.Duration] { pass := func(got time.Duration) bool { return p.ns(got) > p.ns(tar) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("over %vms", p.ms(tar)), fmt.Sprintf("%vms", p.ms(got.(time.Duration))), ) } - return NewDurationChecker(pass, expl) + return NewChecker(pass, expl) } // Under checks the gotten time.Duration is under the target duration. -func (p durationCheckerProvider) Under(tar time.Duration) DurationChecker { +func (p durationCheckerProvider) Under(tar time.Duration) Checker[time.Duration] { pass := func(got time.Duration) bool { return p.ns(got) < p.ns(tar) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("under %vms", p.ms(tar)), fmt.Sprintf("%vms", p.ms(got.(time.Duration))), ) } - return NewDurationChecker(pass, expl) + return NewChecker(pass, expl) } // InRange checks the gotten time.Duration is in range [lo:hi] -func (p durationCheckerProvider) InRange(lo, hi time.Duration) DurationChecker { +func (p durationCheckerProvider) InRange(lo, hi time.Duration) Checker[time.Duration] { pass := func(got time.Duration) bool { return p.inrange(got, lo, hi) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("in range [%vms:%vms]", p.ms(lo), p.ms(hi)), fmt.Sprintf("%vms", p.ms(got.(time.Duration))), ) } - return NewDurationChecker(pass, expl) + return NewChecker(pass, expl) } // OutRange checks the gotten time.Duration is not in range [lo:hi] -func (p durationCheckerProvider) OutRange(lo, hi time.Duration) DurationChecker { +func (p durationCheckerProvider) OutRange(lo, hi time.Duration) Checker[time.Duration] { pass := func(got time.Duration) bool { return !p.inrange(got, lo, hi) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("not in range [%vms:%vms]", p.ms(lo), p.ms(hi)), fmt.Sprintf("%vms", p.ms(got.(time.Duration))), ) } - return NewDurationChecker(pass, expl) + return NewChecker(pass, expl) } // Helpers diff --git a/check/providers_duration_test.go b/check/providers_duration_test.go index afda5ac..641cd78 100644 --- a/check/providers_duration_test.go +++ b/check/providers_duration_test.go @@ -22,18 +22,18 @@ func TestDurationCheckerProvider(t *testing.T) { t.Run("Under pass", func(t *testing.T) { c := check.Duration.Under(more) - assertPassDurationChecker(t, "Under", c, d) + assertPassChecker(t, "Duration.Under", c, d) }) t.Run("Under fail", func(t *testing.T) { c := check.Duration.Under(less) - assertFailDurationChecker(t, "Under", c, d, makeExpl( + assertFailChecker(t, "Duration.Under", c, d, makeExpl( fmt.Sprintf("under %dms", ms(less)), fmt.Sprintf("%dms", ms(d)), )) c = check.Duration.Under(d) - assertFailDurationChecker(t, "Under", c, d, makeExpl( + assertFailChecker(t, "Duration.Under", c, d, makeExpl( fmt.Sprintf("under %dms", ms(d)), fmt.Sprintf("%dms", ms(d)), )) @@ -41,18 +41,18 @@ func TestDurationCheckerProvider(t *testing.T) { t.Run("Over pass", func(t *testing.T) { c := check.Duration.Over(less) - assertPassDurationChecker(t, "Over", c, d) + assertPassChecker(t, "Duration.Over", c, d) }) t.Run("Over fail", func(t *testing.T) { c := check.Duration.Over(more) - assertFailDurationChecker(t, "Over", c, d, makeExpl( + assertFailChecker(t, "Duration.Over", c, d, makeExpl( fmt.Sprintf("over %dms", ms(more)), fmt.Sprintf("%dms", ms(d)), )) c = check.Duration.Over(d) - assertFailDurationChecker(t, "Over", c, d, makeExpl( + assertFailChecker(t, "Duration.Over", c, d, makeExpl( fmt.Sprintf("over %dms", ms(d)), fmt.Sprintf("%dms", ms(d)), )) @@ -60,21 +60,21 @@ func TestDurationCheckerProvider(t *testing.T) { t.Run("InRange pass", func(t *testing.T) { c := check.Duration.InRange(less, more) - assertPassDurationChecker(t, "InRange", c, d) + assertPassChecker(t, "Duration.InRange", c, d) c = check.Duration.InRange(d, d) - assertPassDurationChecker(t, "InRange", c, d) + assertPassChecker(t, "Duration.InRange", c, d) }) t.Run("InRange fail", func(t *testing.T) { c := check.Duration.InRange(more, moremore) - assertFailDurationChecker(t, "InRange", c, d, makeExpl( + assertFailChecker(t, "Duration.InRange", c, d, makeExpl( fmt.Sprintf("in range [%dms:%dms]", ms(more), ms(moremore)), fmt.Sprintf("%dms", ms(d)), )) c = check.Duration.InRange(more, less) - assertFailDurationChecker(t, "InRange", c, d, makeExpl( + assertFailChecker(t, "Duration.InRange", c, d, makeExpl( fmt.Sprintf("in range [%dms:%dms]", ms(more), ms(less)), fmt.Sprintf("%dms", ms(d)), )) @@ -82,45 +82,23 @@ func TestDurationCheckerProvider(t *testing.T) { t.Run("OutRange pass", func(t *testing.T) { c := check.Duration.OutRange(more, moremore) - assertPassDurationChecker(t, "OutRange", c, d) + assertPassChecker(t, "Duration.OutRange", c, d) c = check.Duration.OutRange(more, less) - assertPassDurationChecker(t, "OutRange", c, d) + assertPassChecker(t, "Duration.OutRange", c, d) }) t.Run("OutRange fail", func(t *testing.T) { c := check.Duration.OutRange(less, more) - assertFailDurationChecker(t, "OutRange", c, d, makeExpl( + assertFailChecker(t, "Duration.OutRange", c, d, makeExpl( fmt.Sprintf("not in range [%dms:%dms]", ms(less), ms(more)), fmt.Sprintf("%dms", ms(d)), )) c = check.Duration.OutRange(d, d) - assertFailDurationChecker(t, "OutRange", c, d, makeExpl( + assertFailChecker(t, "Duration.OutRange", c, d, makeExpl( fmt.Sprintf("not in range [%dms:%dms]", ms(d), ms(d)), fmt.Sprintf("%dms", ms(d)), )) }) } - -// Helpers - -func assertPassDurationChecker(t *testing.T, method string, c check.DurationChecker, d time.Duration) { - t.Helper() - if !c.Pass(d) { - failDurationCheckerTest(t, true, method, d, c.Explain) - } -} - -func assertFailDurationChecker(t *testing.T, method string, c check.DurationChecker, d time.Duration, expexpl string) { - t.Helper() - if c.Pass(d) { - failDurationCheckerTest(t, false, method, d, c.Explain) - } - assertGoodExplain(t, c, d, expexpl) -} - -func failDurationCheckerTest(t *testing.T, expPass bool, method string, d time.Duration, explain check.ExplainFunc) { - t.Helper() - failCheckerTest(t, expPass, "Duration."+method, explain("Duration value", d)) -} diff --git a/check/providers_float64.go b/check/providers_float64.go index be1e7df..57de112 100644 --- a/check/providers_float64.go +++ b/check/providers_float64.go @@ -6,16 +6,16 @@ import "fmt" type float64CheckerProvider struct{ baseCheckerProvider } // Is checks the gotten float64 is equal to the target. -func (p float64CheckerProvider) Is(tar float64) Float64Checker { +func (p float64CheckerProvider) Is(tar float64) Checker[float64] { pass := func(got float64) bool { return got == tar } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, tar, got) } - return NewFloat64Checker(pass, expl) + return NewChecker(pass, expl) } // Not checks the gotten float64 is not equal to the target. -func (p float64CheckerProvider) Not(values ...float64) Float64Checker { +func (p float64CheckerProvider) Not(values ...float64) Checker[float64] { var match float64 pass := func(got float64) bool { for _, v := range values { @@ -26,64 +26,64 @@ func (p float64CheckerProvider) Not(values ...float64) Float64Checker { } return true } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainNot(label, match, got) } - return NewFloat64Checker(pass, expl) + return NewChecker(pass, expl) } // InRange checks the gotten float64 is in the closed interval [lo:hi]. -func (p float64CheckerProvider) InRange(lo, hi float64) Float64Checker { +func (p float64CheckerProvider) InRange(lo, hi float64) Checker[float64] { pass := func(got float64) bool { return p.inrange(got, lo, hi) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("in range [%v:%v]", lo, hi), got) } - return NewFloat64Checker(pass, expl) + return NewChecker(pass, expl) } // OutRange checks the gotten float64 is not in the closed interval [lo:hi]. -func (p float64CheckerProvider) OutRange(lo, hi float64) Float64Checker { +func (p float64CheckerProvider) OutRange(lo, hi float64) Checker[float64] { pass := func(got float64) bool { return !p.inrange(got, lo, hi) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainNot(label, fmt.Sprintf("in range [%v:%v]", lo, hi), got) } - return NewFloat64Checker(pass, expl) + return NewChecker(pass, expl) } // GT checks the gotten float64 is greater than the target. -func (p float64CheckerProvider) GT(tar float64) Float64Checker { +func (p float64CheckerProvider) GT(tar float64) Checker[float64] { pass := func(got float64) bool { return !p.lte(got, tar) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("> %v", tar), got) } - return NewFloat64Checker(pass, expl) + return NewChecker(pass, expl) } // GTE checks the gotten float64 is greater or equal to the target. -func (p float64CheckerProvider) GTE(tar float64) Float64Checker { +func (p float64CheckerProvider) GTE(tar float64) Checker[float64] { pass := func(got float64) bool { return !p.lt(got, tar) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf(">= %v", tar), got) } - return NewFloat64Checker(pass, expl) + return NewChecker(pass, expl) } // LT checks the gotten float64 is lesser than the target. -func (p float64CheckerProvider) LT(tar float64) Float64Checker { +func (p float64CheckerProvider) LT(tar float64) Checker[float64] { pass := func(got float64) bool { return p.lt(got, tar) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("< %v", tar), got) } - return NewFloat64Checker(pass, expl) + return NewChecker(pass, expl) } // LTE checks the gotten float64 is lesser or equal to the target. -func (p float64CheckerProvider) LTE(tar float64) Float64Checker { +func (p float64CheckerProvider) LTE(tar float64) Checker[float64] { pass := func(got float64) bool { return p.lte(got, tar) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("<= %v", tar), got) } - return NewFloat64Checker(pass, expl) + return NewChecker(pass, expl) } // Helpers diff --git a/check/providers_float64_test.go b/check/providers_float64_test.go index 0a4d562..fe7fee5 100644 --- a/check/providers_float64_test.go +++ b/check/providers_float64_test.go @@ -21,89 +21,89 @@ func TestFloat64CheckerProvider(t *testing.T) { t.Run("Is pass", func(t *testing.T) { c := check.Float64.Is(n) - assertPassFloat64Checker(t, "Is", c, n) + assertPassChecker(t, "Float64.Is", c, n) }) t.Run("Is fail", func(t *testing.T) { c := check.Float64.Is(inf) - assertFailFloat64Checker(t, "Is", c, n, makeExpl(infstr, nstr)) + assertFailChecker(t, "Float64.Is", c, n, makeExpl(infstr, nstr)) }) t.Run("Not pass", func(t *testing.T) { c := check.Float64.Not(-1, 314, -n) - assertPassFloat64Checker(t, "Not", c, n) + assertPassChecker(t, "Float64.Not", c, n) }) t.Run("Not fail", func(t *testing.T) { c := check.Float64.Not(-1, 314, n, 1618) - assertFailFloat64Checker(t, "Not", c, n, makeExpl("not "+nstr, nstr)) + assertFailChecker(t, "Float64.Not", c, n, makeExpl("not "+nstr, nstr)) }) t.Run("LT pass", func(t *testing.T) { c := check.Float64.LT(sup) - assertPassFloat64Checker(t, "LT", c, n) + assertPassChecker(t, "Float64.LT", c, n) }) t.Run("LT fail", func(t *testing.T) { c := check.Float64.LT(inf) - assertFailFloat64Checker(t, "LT", c, n, makeExpl("< "+infstr, nstr)) + assertFailChecker(t, "Float64.LT", c, n, makeExpl("< "+infstr, nstr)) c = check.Float64.LT(n) - assertFailFloat64Checker(t, "LT", c, n, makeExpl("< "+nstr, nstr)) + assertFailChecker(t, "Float64.LT", c, n, makeExpl("< "+nstr, nstr)) }) t.Run("LTE pass", func(t *testing.T) { c := check.Float64.LTE(sup) - assertPassFloat64Checker(t, "LTE", c, n) + assertPassChecker(t, "Float64.LTE", c, n) c = check.Float64.LTE(n) - assertPassFloat64Checker(t, "LTE", c, n) + assertPassChecker(t, "Float64.LTE", c, n) }) t.Run("LTE fail", func(t *testing.T) { c := check.Float64.LTE(inf) - assertFailFloat64Checker(t, "LTE", c, n, makeExpl("<= "+infstr, nstr)) + assertFailChecker(t, "Float64.LTE", c, n, makeExpl("<= "+infstr, nstr)) }) t.Run("GT pass", func(t *testing.T) { c := check.Float64.GT(inf) - assertPassFloat64Checker(t, "GT", c, n) + assertPassChecker(t, "Float64.GT", c, n) }) t.Run("GT fail", func(t *testing.T) { c := check.Float64.GT(sup) - assertFailFloat64Checker(t, "GT", c, n, makeExpl("> "+supstr, nstr)) + assertFailChecker(t, "Float64.GT", c, n, makeExpl("> "+supstr, nstr)) c = check.Float64.GT(n) - assertFailFloat64Checker(t, "GT", c, n, makeExpl("> "+nstr, nstr)) + assertFailChecker(t, "Float64.GT", c, n, makeExpl("> "+nstr, nstr)) }) t.Run("GTE pass", func(t *testing.T) { c := check.Float64.GTE(inf) - assertPassFloat64Checker(t, "GTE", c, n) + assertPassChecker(t, "Float64.GTE", c, n) c = check.Float64.GTE(n) - assertPassFloat64Checker(t, "GTE", c, n) + assertPassChecker(t, "Float64.GTE", c, n) }) t.Run("GTE fail", func(t *testing.T) { c := check.Float64.GTE(sup) - assertFailFloat64Checker(t, "GTE", c, n, makeExpl(">= "+supstr, nstr)) + assertFailChecker(t, "Float64.GTE", c, n, makeExpl(">= "+supstr, nstr)) }) t.Run("InRange pass", func(t *testing.T) { c := check.Float64.InRange(inf, sup) - assertPassFloat64Checker(t, "InRange", c, n) + assertPassChecker(t, "Float64.InRange", c, n) c = check.Float64.InRange(n, n) - assertPassFloat64Checker(t, "InRange", c, n) + assertPassChecker(t, "Float64.InRange", c, n) }) t.Run("InRange fail", func(t *testing.T) { c := check.Float64.InRange(sup, sup+1) - assertFailFloat64Checker(t, "InRange", c, n, makeExpl( + assertFailChecker(t, "Float64.InRange", c, n, makeExpl( fmt.Sprintf("in range [%v:%v]", sup, sup+1), nstr, )) c = check.Float64.InRange(sup, inf) - assertFailFloat64Checker(t, "InRange", c, n, makeExpl( + assertFailChecker(t, "Float64.InRange", c, n, makeExpl( fmt.Sprintf("in range [%v:%v]", sup, inf), nstr, )) @@ -111,45 +111,23 @@ func TestFloat64CheckerProvider(t *testing.T) { t.Run("OutRange pass", func(t *testing.T) { c := check.Float64.OutRange(sup, sup+1) - assertPassFloat64Checker(t, "OutRange", c, n) + assertPassChecker(t, "Float64.OutRange", c, n) c = check.Float64.OutRange(sup, inf) - assertPassFloat64Checker(t, "OutRange", c, n) + assertPassChecker(t, "Float64.OutRange", c, n) }) t.Run("OutRange fail", func(t *testing.T) { c := check.Float64.OutRange(inf, sup) - assertFailFloat64Checker(t, "OutRange", c, n, makeExpl( + assertFailChecker(t, "Float64.OutRange", c, n, makeExpl( fmt.Sprintf("not in range [%v:%v]", inf, sup), nstr, )) c = check.Float64.OutRange(n, n) - assertFailFloat64Checker(t, "OutRange", c, n, makeExpl( + assertFailChecker(t, "Float64.OutRange", c, n, makeExpl( fmt.Sprintf("not in range [%v:%v]", n, n), nstr, )) }) } - -// Helpers - -func assertPassFloat64Checker(t *testing.T, method string, c check.Float64Checker, n float64) { - t.Helper() - if !c.Pass(n) { - failFloat64CheckerTest(t, true, method, n, c.Explain) - } -} - -func assertFailFloat64Checker(t *testing.T, method string, c check.Float64Checker, n float64, expexpl string) { - t.Helper() - if c.Pass(n) { - failFloat64CheckerTest(t, false, method, n, c.Explain) - } - assertGoodExplain(t, c, n, expexpl) -} - -func failFloat64CheckerTest(t *testing.T, expPass bool, method string, n float64, explain check.ExplainFunc) { - t.Helper() - failCheckerTest(t, expPass, "Float64."+method, explain("Float64 value", n)) -} diff --git a/check/providers_httpheader.go b/check/providers_httpheader.go index 330d7cd..eb8004b 100644 --- a/check/providers_httpheader.go +++ b/check/providers_httpheader.go @@ -11,48 +11,48 @@ type httpHeaderCheckerProvider struct{ baseCheckerProvider } // HasKey checks the gotten http.Header has a specific key set. // The corresponding value is ignored, meaning an empty value // for that key passes the check. -func (p httpHeaderCheckerProvider) HasKey(key string) HTTPHeaderChecker { +func (p httpHeaderCheckerProvider) HasKey(key string) Checker[http.Header] { pass := func(got http.Header) bool { return p.hasKey(got, key) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, `to have key "`+key+`"`, got) } - return NewHTTPHeaderChecker(pass, expl) + return NewChecker(pass, expl) } // HasNotKey checks the gotten http.Header does not have // a specific key set. -func (p httpHeaderCheckerProvider) HasNotKey(key string) HTTPHeaderChecker { +func (p httpHeaderCheckerProvider) HasNotKey(key string) Checker[http.Header] { pass := func(got http.Header) bool { return !p.hasKey(got, key) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainNot(label, `to have key "`+key+`"`, got) } - return NewHTTPHeaderChecker(pass, expl) + return NewChecker(pass, expl) } // HasValue checks the gotten http.Header has any value equal to val. // It only compares the first result for each key. -func (p httpHeaderCheckerProvider) HasValue(val string) HTTPHeaderChecker { +func (p httpHeaderCheckerProvider) HasValue(val string) Checker[http.Header] { pass := func(got http.Header) bool { return p.hasValue(got, val) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, "to have value "+val, got) } - return NewHTTPHeaderChecker(pass, expl) + return NewChecker(pass, expl) } // HasNotValue checks the gotten http.Header does not have a value equal to val. // It only compares the first result for each key. -func (p httpHeaderCheckerProvider) HasNotValue(val string) HTTPHeaderChecker { +func (p httpHeaderCheckerProvider) HasNotValue(val string) Checker[http.Header] { pass := func(got http.Header) bool { return !p.hasValue(got, val) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainNot(label, "to have value "+val, got) } - return NewHTTPHeaderChecker(pass, expl) + return NewChecker(pass, expl) } // CheckValue checks the gotten http.Header has a value for the matching key -// that passes the given StringChecker. +// that passes the given Checker[string]. // It only checks the first result for the given key. -func (p httpHeaderCheckerProvider) CheckValue(key string, c StringChecker) HTTPHeaderChecker { +func (p httpHeaderCheckerProvider) CheckValue(key string, c Checker[string]) Checker[http.Header] { var val string pass := func(got http.Header) bool { v, ok := p.get(got, key) @@ -62,13 +62,13 @@ func (p httpHeaderCheckerProvider) CheckValue(key string, c StringChecker) HTTPH val = v return c.Pass(v) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainCheck(label, - fmt.Sprintf(`value for key "%s" to pass StringChecker`, key), + fmt.Sprintf(`value for key "%s" to pass Checker[string]`, key), c.Explain(`http.Header["`+key+`"]`, val), ) } - return NewHTTPHeaderChecker(pass, expl) + return NewChecker(pass, expl) } // Helpers diff --git a/check/providers_httpheader_test.go b/check/providers_httpheader_test.go index 9eb924d..9326213 100644 --- a/check/providers_httpheader_test.go +++ b/check/providers_httpheader_test.go @@ -17,73 +17,73 @@ func TestHTTPHeaderCheckerProvider(t *testing.T) { t.Run("HasKey pass", func(t *testing.T) { c := check.HTTPHeader.HasKey("API_KEY") - assertPassHTTPHeaderChecker(t, "HasKey", c, h) + assertPassChecker(t, "HTTPHeader.HasKey", c, h) }) t.Run("HasKey fail", func(t *testing.T) { c := check.HTTPHeader.HasKey("password") - assertFailHTTPHeaderChecker(t, "HasKey", c, h, + assertFailChecker(t, "HTTPHeader.HasKey", c, h, makeExpl(`to have key "password"`, hstr), ) }) t.Run("HasNotKey pass", func(t *testing.T) { c := check.HTTPHeader.HasNotKey("password") - assertPassHTTPHeaderChecker(t, "HasNotKey", c, h) + assertPassChecker(t, "HTTPHeader.HasNotKey", c, h) }) t.Run("HasNotKey fail", func(t *testing.T) { c := check.HTTPHeader.HasNotKey("API_KEY") - assertFailHTTPHeaderChecker(t, "HasNotKey", c, h, + assertFailChecker(t, "HTTPHeader.HasNotKey", c, h, makeExpl(`not to have key "API_KEY"`, hstr), ) }) t.Run("HasValue pass", func(t *testing.T) { c := check.HTTPHeader.HasValue("42") - assertPassHTTPHeaderChecker(t, "HasValue", c, h) + assertPassChecker(t, "HTTPHeader.HasValue", c, h) c = check.HTTPHeader.HasValue("secret0") - assertPassHTTPHeaderChecker(t, "HasValue", c, h) + assertPassChecker(t, "HTTPHeader.HasValue", c, h) }) t.Run("HasValue fail", func(t *testing.T) { c := check.HTTPHeader.HasValue("secret42") - assertFailHTTPHeaderChecker(t, "HasValue", c, h, + assertFailChecker(t, "HTTPHeader.HasValue", c, h, makeExpl(`to have value secret42`, hstr), ) c = check.HTTPHeader.HasValue("secret1") - assertFailHTTPHeaderChecker(t, "HasValue", c, h, + assertFailChecker(t, "HTTPHeader.HasValue", c, h, makeExpl(`to have value secret1`, hstr), ) }) t.Run("HasNotValue pass", func(t *testing.T) { c := check.HTTPHeader.HasNotValue("secret42") - assertPassHTTPHeaderChecker(t, "HasNotValue", c, h) + assertPassChecker(t, "HTTPHeader.HasNotValue", c, h) }) t.Run("HasNotValue fail", func(t *testing.T) { c := check.HTTPHeader.HasNotValue("42") - assertFailHTTPHeaderChecker(t, "HasNotValue", c, h, + assertFailChecker(t, "HTTPHeader.HasNotValue", c, h, makeExpl(`not to have value 42`, hstr), ) c = check.HTTPHeader.HasNotValue("secret0") - assertFailHTTPHeaderChecker(t, "HasNotValue", c, h, + assertFailChecker(t, "HTTPHeader.HasNotValue", c, h, makeExpl(`not to have value secret0`, hstr), ) }) t.Run("CheckValue pass", func(t *testing.T) { c := check.HTTPHeader.CheckValue("API_KEY", check.String.Is("secret0")) - assertPassHTTPHeaderChecker(t, "CheckValue", c, h) + assertPassChecker(t, "HTTPHeader.CheckValue", c, h) }) t.Run("CheckValue fail", func(t *testing.T) { c := check.HTTPHeader.CheckValue("API_KEY", check.String.Not("secret0")) - assertFailHTTPHeaderChecker(t, "CheckValue", c, h, makeExpl( - `value for key "API_KEY" to pass StringChecker`, + assertFailChecker(t, "HTTPHeader.CheckValue", c, h, makeExpl( + `value for key "API_KEY" to pass Checker[string]`, `explanation: http.Header["API_KEY"]:`+"\n"+makeExpl( "not secret0", "secret0", @@ -91,25 +91,3 @@ func TestHTTPHeaderCheckerProvider(t *testing.T) { )) }) } - -// Helpers - -func assertPassHTTPHeaderChecker(t *testing.T, method string, c check.HTTPHeaderChecker, h http.Header) { - t.Helper() - if !c.Pass(h) { - failHTTPHeaderCheckerTest(t, true, method, h, c.Explain) - } -} - -func assertFailHTTPHeaderChecker(t *testing.T, method string, c check.HTTPHeaderChecker, h http.Header, expexpl string) { - t.Helper() - if c.Pass(h) { - failHTTPHeaderCheckerTest(t, false, method, h, c.Explain) - } - assertGoodExplain(t, c, h, expexpl) -} - -func failHTTPHeaderCheckerTest(t *testing.T, expPass bool, method string, h http.Header, explain check.ExplainFunc) { - t.Helper() - failCheckerTest(t, expPass, "HTTPHeader."+method, explain("HTTPHeader value", h)) -} diff --git a/check/providers_httprequest.go b/check/providers_httprequest.go index e63c1fa..f9a20ce 100644 --- a/check/providers_httprequest.go +++ b/check/providers_httprequest.go @@ -11,61 +11,61 @@ import ( type httpRequestCheckerProvider struct{ baseHTTPCheckerProvider } // ContentLength checks the gotten *http.Request ContentLength passes -// the input IntChecker. -func (p httpRequestCheckerProvider) ContentLength(c IntChecker) HTTPRequestChecker { +// the input Checker[int]. +func (p httpRequestCheckerProvider) ContentLength(c Checker[int]) Checker[*http.Request] { var clen int pass := func(got *http.Request) bool { clen = int(got.ContentLength) return c.Pass(clen) } - return NewHTTPRequestChecker( - pass, - p.explainContentLengthFunc(c, func() int { return clen }), - ) + expl := func(label string, got any) string { + return p.explainContentLengthFunc(c, func() int { return clen })(label, got) + } + return NewChecker(pass, expl) } // Header checks the gotten *http.Request Header passes -// the input HTTPHeaderChecker. -func (p httpRequestCheckerProvider) Header(c HTTPHeaderChecker) HTTPRequestChecker { +// the input Checker[http.Header]. +func (p httpRequestCheckerProvider) Header(c Checker[http.Header]) Checker[*http.Request] { var header http.Header pass := func(got *http.Request) bool { header = got.Header return c.Pass(header) } - return NewHTTPRequestChecker( - pass, - p.explainHeaderFunc(c, func() http.Header { return header }), - ) + expl := func(label string, got any) string { + return p.explainHeaderFunc(c, func() http.Header { return header })(label, got) + } + return NewChecker(pass, expl) } -// Body checks the gotten *http.Request Body passes the input BytesChecker. +// Body checks the gotten *http.Request Body passes the input Checker[[]byte]. // It should be used only once on a same *http.Request as it closes its body // after reading it. -func (p httpRequestCheckerProvider) Body(c BytesChecker) HTTPRequestChecker { +func (p httpRequestCheckerProvider) Body(c Checker[[]byte]) Checker[*http.Request] { var body []byte pass := func(got *http.Request) bool { body = ioutil.NopRead(&got.Body) return c.Pass(body) } - return NewHTTPRequestChecker( - pass, - p.explainBodyFunc(c, func() []byte { return body }), - ) + expl := func(label string, got any) string { + return p.explainBodyFunc(c, func() []byte { return body })(label, got) + } + return NewChecker(pass, expl) } // Context checks the gotten *http.Request Context passes -// the input ContextChecker. -func (p httpRequestCheckerProvider) Context(c ContextChecker) HTTPRequestChecker { +// the input Checker[context.Context]. +func (p httpRequestCheckerProvider) Context(c Checker[context.Context]) Checker[*http.Request] { var ctx context.Context pass := func(got *http.Request) bool { ctx = got.Context() return c.Pass(ctx) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainCheck(label, - "context to pass ContextChecker", + "context to pass Checker[context.Context]", c.Explain("context", ctx), ) } - return NewHTTPRequestChecker(pass, expl) + return NewChecker(pass, expl) } diff --git a/check/providers_httprequest_test.go b/check/providers_httprequest_test.go index 9f33b72..ff99614 100644 --- a/check/providers_httprequest_test.go +++ b/check/providers_httprequest_test.go @@ -12,12 +12,12 @@ import ( ) func TestHTTPRequestCheckerProvider(t *testing.T) { - newCtx := func(key, val interface{}) context.Context { + newCtx := func(key, val any) context.Context { return context.WithValue(context.Background(), key, val) } newReq := func() *http.Request { ctx := newCtx("userID", 42) - body, _ := json.Marshal(map[string]interface{}{"answer": 42}) + body, _ := json.Marshal(map[string]any{"answer": 42}) r, _ := http.NewRequestWithContext(ctx, "GET", "/endpoint?id=42", bytes.NewReader(body)) r.Header.Set("Content-Type", "application/json") return r @@ -32,13 +32,13 @@ func TestHTTPRequestCheckerProvider(t *testing.T) { t.Run("ContentLength pass", func(t *testing.T) { c := check.HTTPRequest.ContentLength(check.Int.Is(expContentLength)) - assertPassHTTPRequestChecker(t, "ContentLength", c, newReq()) + assertPassChecker(t, "HTTPRequest.ContentLength", c, newReq()) }) t.Run("ContentLength fail", func(t *testing.T) { c := check.HTTPRequest.ContentLength(check.Int.Not(expContentLength)) - assertFailHTTPRequestChecker(t, "ContentLength", c, newReq(), makeExpl( - "content length to pass IntChecker", + assertFailChecker(t, "HTTPRequest.ContentLength", c, newReq(), makeExpl( + "content length to pass Checker[int]", fmt.Sprintf( "explanation: content length:\nexp not %d\ngot %d", expContentLength, expContentLength, @@ -48,14 +48,14 @@ func TestHTTPRequestCheckerProvider(t *testing.T) { t.Run("Header pass", func(t *testing.T) { c := check.HTTPRequest.Header(check.HTTPHeader.HasKey("Content-Type")) - assertPassHTTPRequestChecker(t, "Header", c, newReq()) + assertPassChecker(t, "HTTPRequest.Header", c, newReq()) }) t.Run("Header fail", func(t *testing.T) { c := check.HTTPRequest.Header(check.HTTPHeader.HasNotKey("Content-Type")) r := newReq() - assertFailHTTPRequestChecker(t, "Header", c, r, makeExpl( - "header to pass HTTPHeaderChecker", + assertFailChecker(t, "HTTPRequest.Header", c, r, makeExpl( + "header to pass Checker[http.Header]", fmt.Sprintf( "explanation: http.Header:\nexp not to have key \"Content-Type\"\ngot %v", r.Header, @@ -65,13 +65,13 @@ func TestHTTPRequestCheckerProvider(t *testing.T) { t.Run("Body pass", func(t *testing.T) { c := check.HTTPRequest.Body(check.Bytes.Is(expBody)) - assertPassHTTPRequestChecker(t, "Body", c, newReq()) + assertPassChecker(t, "HTTPRequest.Body", c, newReq()) }) t.Run("Body fail", func(t *testing.T) { c := check.HTTPRequest.Body(check.Bytes.Not(expBody)) - assertFailHTTPRequestChecker(t, "Body", c, newReq(), makeExpl( - "body to pass BytesChecker", + assertFailChecker(t, "HTTPRequest.Body", c, newReq(), makeExpl( + "body to pass Checker[[]byte]", "explanation: bytes:\n"+makeExpl( "not "+fmt.Sprint(expBody), fmt.Sprint(expBody), @@ -81,13 +81,13 @@ func TestHTTPRequestCheckerProvider(t *testing.T) { t.Run("Context pass", func(t *testing.T) { c := check.HTTPRequest.Context(check.Context.Value(expCtxKey, check.Value.Is(expCtxVal))) - assertPassHTTPRequestChecker(t, "Context", c, newReq()) + assertPassChecker(t, "HTTPRequest.Context", c, newReq()) }) t.Run("Context fail", func(t *testing.T) { c := check.HTTPRequest.Context(check.Context.Value(expCtxKey, check.Value.Not(expCtxVal))) - assertFailHTTPRequestChecker(t, "Context", c, newReq(), makeExpl( - "context to pass ContextChecker", + assertFailChecker(t, "HTTPRequest.Context", c, newReq(), makeExpl( + "context to pass Checker[context.Context]", "explanation: context:\n"+makeExpl( "value for key userID to pass ValueChecker", "explanation: value:\n"+makeExpl( @@ -98,25 +98,3 @@ func TestHTTPRequestCheckerProvider(t *testing.T) { )) }) } - -// Helpers - -func assertPassHTTPRequestChecker(t *testing.T, method string, c check.HTTPRequestChecker, r *http.Request) { - t.Helper() - if !c.Pass(r) { - failHTTPRequestCheckerTest(t, true, method, r, c.Explain) - } -} - -func assertFailHTTPRequestChecker(t *testing.T, method string, c check.HTTPRequestChecker, r *http.Request, expexpl string) { - t.Helper() - if c.Pass(r) { - failHTTPRequestCheckerTest(t, false, method, r, c.Explain) - } - assertGoodExplain(t, c, r, expexpl) -} - -func failHTTPRequestCheckerTest(t *testing.T, expPass bool, method string, r *http.Request, explain check.ExplainFunc) { - t.Helper() - failCheckerTest(t, expPass, "HTTPRequest."+method, explain("HTTPRequest value", r)) -} diff --git a/check/providers_httpresponse.go b/check/providers_httpresponse.go index c5fe995..5b37729 100644 --- a/check/providers_httpresponse.go +++ b/check/providers_httpresponse.go @@ -10,78 +10,78 @@ import ( type httpResponseCheckerProvider struct{ baseHTTPCheckerProvider } // StatusCode checks the gotten *http.Response StatusCode passes -// the input IntChecker. -func (p httpResponseCheckerProvider) StatusCode(c IntChecker) HTTPResponseChecker { +// the input Checker[int]. +func (p httpResponseCheckerProvider) StatusCode(c Checker[int]) Checker[*http.Response] { var code int pass := func(got *http.Response) bool { code = got.StatusCode return c.Pass(code) } - expl := func(label string, _ interface{}) string { + expl := func(label string, _ any) string { return p.explainCheck(label, - "status code to pass IntChecker", + "status code to pass Checker[int]", c.Explain("status code", code), ) } - return NewHTTPResponseChecker(pass, expl) + return NewChecker(pass, expl) } // Status checks the gotten *http.Response Status passes -// the input StringChecker. -func (p httpResponseCheckerProvider) Status(c StringChecker) HTTPResponseChecker { +// the input Checker[string]. +func (p httpResponseCheckerProvider) Status(c Checker[string]) Checker[*http.Response] { var status string pass := func(got *http.Response) bool { status = got.Status return c.Pass(status) } - expl := func(label string, _ interface{}) string { + expl := func(label string, _ any) string { return p.explainCheck(label, - "status to pass StringChecker", + "status to pass Checker[string]", c.Explain("status", status), ) } - return NewHTTPResponseChecker(pass, expl) + return NewChecker(pass, expl) } // ContentLength checks the gotten *http.Response ContentLength passes -// the input IntChecker. -func (p httpResponseCheckerProvider) ContentLength(c IntChecker) HTTPResponseChecker { +// the input Checker[int]. +func (p httpResponseCheckerProvider) ContentLength(c Checker[int]) Checker[*http.Response] { var clen int pass := func(got *http.Response) bool { clen = int(got.ContentLength) return c.Pass(clen) } - return NewHTTPResponseChecker( - pass, - p.explainContentLengthFunc(c, func() int { return clen }), - ) + expl := func(label string, got any) string { + return p.explainContentLengthFunc(c, func() int { return clen })(label, got) + } + return NewChecker(pass, expl) } // Header checks the gotten *http.Response Header passes -// the input HTTPHeaderChecker. -func (p httpResponseCheckerProvider) Header(c HTTPHeaderChecker) HTTPResponseChecker { +// the input Checker[http.Header]. +func (p httpResponseCheckerProvider) Header(c Checker[http.Header]) Checker[*http.Response] { var header http.Header pass := func(got *http.Response) bool { header = got.Header return c.Pass(header) } - return NewHTTPResponseChecker( - pass, - p.explainHeaderFunc(c, func() http.Header { return header }), - ) + expl := func(label string, got any) string { + return p.explainHeaderFunc(c, func() http.Header { return header })(label, got) + } + return NewChecker(pass, expl) } -// Body checks the gotten *http.Response Body passes the input BytesChecker. +// Body checks the gotten *http.Response Body passes the input Checker[[]byte]. // It should be used only once on a same *http.Response as it closes its body // after reading it. -func (p httpResponseCheckerProvider) Body(c BytesChecker) HTTPResponseChecker { +func (p httpResponseCheckerProvider) Body(c Checker[[]byte]) Checker[*http.Response] { var body []byte pass := func(got *http.Response) bool { body = ioutil.NopRead(&got.Body) return c.Pass(body) } - return NewHTTPResponseChecker( - pass, - p.explainBodyFunc(c, func() []byte { return body }), - ) + expl := func(label string, got any) string { + return p.explainBodyFunc(c, func() []byte { return body })(label, got) + } + return NewChecker(pass, expl) } diff --git a/check/providers_httpresponse_test.go b/check/providers_httpresponse_test.go index eb184a6..1da2689 100644 --- a/check/providers_httpresponse_test.go +++ b/check/providers_httpresponse_test.go @@ -15,7 +15,7 @@ func TestHTTPResponseCheckerProvider(t *testing.T) { newResp := func() *http.Response { rr := httptest.NewRecorder() rq := httptest.NewRequest("GET", "/", nil) - body, _ := json.Marshal(map[string]interface{}{"answer": 42}) + body, _ := json.Marshal(map[string]any{"answer": 42}) clen := len(body) func(w http.ResponseWriter, _ *http.Request) { @@ -39,13 +39,13 @@ func TestHTTPResponseCheckerProvider(t *testing.T) { t.Run("StatusCode pass", func(t *testing.T) { c := check.HTTPResponse.StatusCode(check.Int.Is(expStatusCode)) - assertPassHTTPResponseChecker(t, "StatusCode", c, newResp()) + assertPassChecker(t, "HTTPResponse.StatusCode", c, newResp()) }) t.Run("StatusCode fail", func(t *testing.T) { c := check.HTTPResponse.StatusCode(check.Int.Not(expStatusCode)) - assertFailHTTPResponseChecker(t, "StatusCode", c, newResp(), makeExpl( - "status code to pass IntChecker", + assertFailChecker(t, "HTTPResponse.StatusCode", c, newResp(), makeExpl( + "status code to pass Checker[int]", "explanation: status code:\n"+makeExpl( "not "+fmt.Sprint(expStatusCode), fmt.Sprint(expStatusCode), @@ -55,13 +55,13 @@ func TestHTTPResponseCheckerProvider(t *testing.T) { t.Run("Status pass", func(t *testing.T) { c := check.HTTPResponse.Status(check.String.Is(expStatus)) - assertPassHTTPResponseChecker(t, "Status", c, newResp()) + assertPassChecker(t, "HTTPResponse.Status", c, newResp()) }) t.Run("Status fail", func(t *testing.T) { c := check.HTTPResponse.Status(check.String.Not(expStatus)) - assertFailHTTPResponseChecker(t, "Status", c, newResp(), makeExpl( - "status to pass StringChecker", + assertFailChecker(t, "HTTPResponse.Status", c, newResp(), makeExpl( + "status to pass Checker[string]", "explanation: status:\n"+makeExpl( "not "+expStatus, expStatus, @@ -71,13 +71,13 @@ func TestHTTPResponseCheckerProvider(t *testing.T) { t.Run("ContentLength pass", func(t *testing.T) { c := check.HTTPResponse.ContentLength(check.Int.Is(expContentLength)) - assertPassHTTPResponseChecker(t, "ContentLength", c, newResp()) + assertPassChecker(t, "HTTPResponse.ContentLength", c, newResp()) }) t.Run("ContentLength fail", func(t *testing.T) { c := check.HTTPResponse.ContentLength(check.Int.Not(expContentLength)) - assertFailHTTPResponseChecker(t, "ContentLength", c, newResp(), makeExpl( - "content length to pass IntChecker", + assertFailChecker(t, "HTTPResponse.ContentLength", c, newResp(), makeExpl( + "content length to pass Checker[int]", "explanation: content length:\n"+makeExpl( "not "+fmt.Sprint(expContentLength), fmt.Sprint(expContentLength), @@ -87,14 +87,14 @@ func TestHTTPResponseCheckerProvider(t *testing.T) { t.Run("Header pass", func(t *testing.T) { c := check.HTTPResponse.Header(check.HTTPHeader.HasKey("Content-Type")) - assertPassHTTPResponseChecker(t, "Header", c, newResp()) + assertPassChecker(t, "HTTPResponse.Header", c, newResp()) }) t.Run("Header fail", func(t *testing.T) { c := check.HTTPResponse.Header(check.HTTPHeader.HasNotKey("Content-Type")) resp := newResp() - assertFailHTTPResponseChecker(t, "Header", c, resp, makeExpl( - "header to pass HTTPHeaderChecker", + assertFailChecker(t, "HTTPResponse.Header", c, resp, makeExpl( + "header to pass Checker[http.Header]", "explanation: http.Header:\n"+makeExpl( `not to have key "Content-Type"`, fmt.Sprint(resp.Header), @@ -104,13 +104,13 @@ func TestHTTPResponseCheckerProvider(t *testing.T) { t.Run("Body pass", func(t *testing.T) { c := check.HTTPResponse.Body(check.Bytes.Is(expBody)) - assertPassHTTPResponseChecker(t, "Body", c, newResp()) + assertPassChecker(t, "HTTPResponse.Body", c, newResp()) }) t.Run("Body fail", func(t *testing.T) { c := check.HTTPResponse.Body(check.Bytes.Not(expBody)) - assertFailHTTPResponseChecker(t, "Body", c, newResp(), makeExpl( - "body to pass BytesChecker", + assertFailChecker(t, "HTTPResponse.Body", c, newResp(), makeExpl( + "body to pass Checker[[]byte]", "explanation: bytes:\n"+makeExpl( "not "+fmt.Sprint(expBody), fmt.Sprint(expBody), @@ -118,25 +118,3 @@ func TestHTTPResponseCheckerProvider(t *testing.T) { )) }) } - -// Helpers - -func assertPassHTTPResponseChecker(t *testing.T, method string, c check.HTTPResponseChecker, resp *http.Response) { - t.Helper() - if !c.Pass(resp) { - failHTTPResponseCheckerTest(t, true, method, resp, c.Explain) - } -} - -func assertFailHTTPResponseChecker(t *testing.T, method string, c check.HTTPResponseChecker, resp *http.Response, expexpl string) { - t.Helper() - if c.Pass(resp) { - failHTTPResponseCheckerTest(t, false, method, resp, c.Explain) - } - assertGoodExplain(t, c, resp, expexpl) -} - -func failHTTPResponseCheckerTest(t *testing.T, expPass bool, method string, resp *http.Response, explain check.ExplainFunc) { - t.Helper() - failCheckerTest(t, expPass, "HTTPResponse."+method, explain("HTTPResponse value", resp)) -} diff --git a/check/providers_int.go b/check/providers_int.go index e7e907c..5a491c1 100644 --- a/check/providers_int.go +++ b/check/providers_int.go @@ -6,16 +6,16 @@ import "fmt" type intCheckerProvider struct{ baseCheckerProvider } // Is checks the gotten int is equal to the target. -func (p intCheckerProvider) Is(tar int) IntChecker { +func (p intCheckerProvider) Is(tar int) Checker[int] { pass := func(got int) bool { return got == tar } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, tar, got) } - return NewIntChecker(pass, expl) + return NewChecker(pass, expl) } // Not checks the gotten int is not equal to the target. -func (p intCheckerProvider) Not(values ...int) IntChecker { +func (p intCheckerProvider) Not(values ...int) Checker[int] { var match int pass := func(got int) bool { for _, v := range values { @@ -26,64 +26,64 @@ func (p intCheckerProvider) Not(values ...int) IntChecker { } return true } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainNot(label, match, got) } - return NewIntChecker(pass, expl) + return NewChecker(pass, expl) } // InRange checks the gotten int is in the closed interval [lo:hi]. -func (p intCheckerProvider) InRange(lo, hi int) IntChecker { +func (p intCheckerProvider) InRange(lo, hi int) Checker[int] { pass := func(got int) bool { return p.inrange(got, lo, hi) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("in range [%v:%v]", lo, hi), got) } - return NewIntChecker(pass, expl) + return NewChecker(pass, expl) } // OutRange checks the gotten int is not in the closed interval [lo:hi]. -func (p intCheckerProvider) OutRange(lo, hi int) IntChecker { +func (p intCheckerProvider) OutRange(lo, hi int) Checker[int] { pass := func(got int) bool { return !p.inrange(got, lo, hi) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainNot(label, fmt.Sprintf("in range [%v:%v]", lo, hi), got) } - return NewIntChecker(pass, expl) + return NewChecker(pass, expl) } // GT checks the gotten int is greater than the target. -func (p intCheckerProvider) GT(tar int) IntChecker { +func (p intCheckerProvider) GT(tar int) Checker[int] { pass := func(got int) bool { return !p.lte(got, tar) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("> %v", tar), got) } - return NewIntChecker(pass, expl) + return NewChecker(pass, expl) } // GTE checks the gotten int is greater or equal to the target. -func (p intCheckerProvider) GTE(tar int) IntChecker { +func (p intCheckerProvider) GTE(tar int) Checker[int] { pass := func(got int) bool { return !p.lt(got, tar) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf(">= %v", tar), got) } - return NewIntChecker(pass, expl) + return NewChecker(pass, expl) } // LT checks the gotten int is lesser than the target. -func (p intCheckerProvider) LT(tar int) IntChecker { +func (p intCheckerProvider) LT(tar int) Checker[int] { pass := func(got int) bool { return p.lt(got, tar) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("< %v", tar), got) } - return NewIntChecker(pass, expl) + return NewChecker(pass, expl) } // LTE checks the gotten int is lesser or equal to the target. -func (p intCheckerProvider) LTE(tar int) IntChecker { +func (p intCheckerProvider) LTE(tar int) Checker[int] { pass := func(got int) bool { return p.lte(got, tar) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("<= %v", tar), got) } - return NewIntChecker(pass, expl) + return NewChecker(pass, expl) } // Helpers diff --git a/check/providers_int_test.go b/check/providers_int_test.go index 9ff7f7c..e003692 100644 --- a/check/providers_int_test.go +++ b/check/providers_int_test.go @@ -21,89 +21,89 @@ func TestIntCheckerProvider(t *testing.T) { t.Run("Is pass", func(t *testing.T) { c := check.Int.Is(n) - assertPassIntChecker(t, "Is", c, n) + assertPassChecker(t, "Int.Is", c, n) }) t.Run("Is fail", func(t *testing.T) { c := check.Int.Is(inf) - assertFailIntChecker(t, "Is", c, n, makeExpl(infstr, nstr)) + assertFailChecker(t, "Int.Is", c, n, makeExpl(infstr, nstr)) }) t.Run("Not pass", func(t *testing.T) { c := check.Int.Not(-1, 314, -n) - assertPassIntChecker(t, "Not", c, n) + assertPassChecker(t, "Int.Not", c, n) }) t.Run("Not fail", func(t *testing.T) { c := check.Int.Not(-1, 314, n, 1618) - assertFailIntChecker(t, "Not", c, n, makeExpl("not "+nstr, nstr)) + assertFailChecker(t, "Int.Not", c, n, makeExpl("not "+nstr, nstr)) }) t.Run("LT pass", func(t *testing.T) { c := check.Int.LT(sup) - assertPassIntChecker(t, "LT", c, n) + assertPassChecker(t, "Int.LT", c, n) }) t.Run("LT fail", func(t *testing.T) { c := check.Int.LT(inf) - assertFailIntChecker(t, "LT", c, n, makeExpl("< "+infstr, nstr)) + assertFailChecker(t, "Int.LT", c, n, makeExpl("< "+infstr, nstr)) c = check.Int.LT(n) - assertFailIntChecker(t, "LT", c, n, makeExpl("< "+nstr, nstr)) + assertFailChecker(t, "Int.LT", c, n, makeExpl("< "+nstr, nstr)) }) t.Run("LTE pass", func(t *testing.T) { c := check.Int.LTE(sup) - assertPassIntChecker(t, "LTE", c, n) + assertPassChecker(t, "Int.LTE", c, n) c = check.Int.LTE(n) - assertPassIntChecker(t, "LTE", c, n) + assertPassChecker(t, "Int.LTE", c, n) }) t.Run("LTE fail", func(t *testing.T) { c := check.Int.LTE(inf) - assertFailIntChecker(t, "LTE", c, n, makeExpl("<= "+infstr, nstr)) + assertFailChecker(t, "Int.LTE", c, n, makeExpl("<= "+infstr, nstr)) }) t.Run("GT pass", func(t *testing.T) { c := check.Int.GT(inf) - assertPassIntChecker(t, "GT", c, n) + assertPassChecker(t, "Int.GT", c, n) }) t.Run("GT fail", func(t *testing.T) { c := check.Int.GT(sup) - assertFailIntChecker(t, "GT", c, n, makeExpl("> "+supstr, nstr)) + assertFailChecker(t, "Int.GT", c, n, makeExpl("> "+supstr, nstr)) c = check.Int.GT(n) - assertFailIntChecker(t, "GT", c, n, makeExpl("> "+nstr, nstr)) + assertFailChecker(t, "Int.GT", c, n, makeExpl("> "+nstr, nstr)) }) t.Run("GTE pass", func(t *testing.T) { c := check.Int.GTE(inf) - assertPassIntChecker(t, "GTE", c, n) + assertPassChecker(t, "Int.GTE", c, n) c = check.Int.GTE(n) - assertPassIntChecker(t, "GTE", c, n) + assertPassChecker(t, "Int.GTE", c, n) }) t.Run("GTE fail", func(t *testing.T) { c := check.Int.GTE(sup) - assertFailIntChecker(t, "GTE", c, n, makeExpl(">= "+supstr, nstr)) + assertFailChecker(t, "Int.GTE", c, n, makeExpl(">= "+supstr, nstr)) }) t.Run("InRange pass", func(t *testing.T) { c := check.Int.InRange(inf, sup) - assertPassIntChecker(t, "InRange", c, n) + assertPassChecker(t, "Int.InRange", c, n) c = check.Int.InRange(n, n) - assertPassIntChecker(t, "InRange", c, n) + assertPassChecker(t, "Int.InRange", c, n) }) t.Run("InRange fail", func(t *testing.T) { c := check.Int.InRange(sup, sup+1) - assertFailIntChecker(t, "InRange", c, n, makeExpl( + assertFailChecker(t, "Int.InRange", c, n, makeExpl( fmt.Sprintf("in range [%v:%v]", sup, sup+1), nstr, )) c = check.Int.InRange(sup, inf) - assertFailIntChecker(t, "InRange", c, n, makeExpl( + assertFailChecker(t, "Int.InRange", c, n, makeExpl( fmt.Sprintf("in range [%v:%v]", sup, inf), nstr, )) @@ -111,45 +111,23 @@ func TestIntCheckerProvider(t *testing.T) { t.Run("OutRange pass", func(t *testing.T) { c := check.Int.OutRange(sup, sup+1) - assertPassIntChecker(t, "OutRange", c, n) + assertPassChecker(t, "Int.OutRange", c, n) c = check.Int.OutRange(sup, inf) - assertPassIntChecker(t, "OutRange", c, n) + assertPassChecker(t, "Int.OutRange", c, n) }) t.Run("OutRange fail", func(t *testing.T) { c := check.Int.OutRange(inf, sup) - assertFailIntChecker(t, "OutRange", c, n, makeExpl( + assertFailChecker(t, "Int.OutRange", c, n, makeExpl( fmt.Sprintf("not in range [%v:%v]", inf, sup), nstr, )) c = check.Int.OutRange(n, n) - assertFailIntChecker(t, "OutRange", c, n, makeExpl( + assertFailChecker(t, "Int.OutRange", c, n, makeExpl( fmt.Sprintf("not in range [%v:%v]", n, n), nstr, )) }) } - -// Helpers - -func assertPassIntChecker(t *testing.T, method string, c check.IntChecker, n int) { - t.Helper() - if !c.Pass(n) { - failIntCheckerTest(t, true, method, n, c.Explain) - } -} - -func assertFailIntChecker(t *testing.T, method string, c check.IntChecker, n int, expexpl string) { - t.Helper() - if c.Pass(n) { - failIntCheckerTest(t, false, method, n, c.Explain) - } - assertGoodExplain(t, c, n, expexpl) -} - -func failIntCheckerTest(t *testing.T, expPass bool, method string, n int, explain check.ExplainFunc) { - t.Helper() - failCheckerTest(t, expPass, "Int."+method, explain("Int value", n)) -} diff --git a/check/providers_map.go b/check/providers_map.go index 4467453..86b274e 100644 --- a/check/providers_map.go +++ b/check/providers_map.go @@ -13,27 +13,27 @@ import ( // mapCheckerProvider provides checks on kind map. type mapCheckerProvider struct{ valueCheckerProvider } -// Len checks the gotten map passes the given IntChecker. -func (p mapCheckerProvider) Len(c IntChecker) ValueChecker { +// Len checks the gotten map passes the given Checker[int]. +func (p mapCheckerProvider) Len(c Checker[int]) Checker[any] { var gotlen int - pass := func(got interface{}) bool { + pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Map) gotlen = reflect.ValueOf(got).Len() return c.Pass(gotlen) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainCheck(label, - "length to pass IntChecker", + "length to pass Checker[int]", c.Explain("length", gotlen), ) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } // HasKeys checks the gotten map has the given keys set. -func (p mapCheckerProvider) HasKeys(keys ...interface{}) ValueChecker { +func (p mapCheckerProvider) HasKeys(keys ...any) Checker[any] { var missing []string - pass := func(got interface{}) bool { + pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Map) for _, expk := range keys { if _, found := p.get(got, expk); !found { @@ -42,16 +42,16 @@ func (p mapCheckerProvider) HasKeys(keys ...interface{}) ValueChecker { } return len(missing) == 0 } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, "to have keys "+p.formatList(missing), got) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } // HasNotKeys checks the gotten map has the given keys set. -func (p mapCheckerProvider) HasNotKeys(keys ...interface{}) ValueChecker { +func (p mapCheckerProvider) HasNotKeys(keys ...any) Checker[any] { var badkeys []string - pass := func(got interface{}) bool { + pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Map) for _, expk := range keys { if _, found := p.get(got, expk); found { @@ -60,16 +60,16 @@ func (p mapCheckerProvider) HasNotKeys(keys ...interface{}) ValueChecker { } return len(badkeys) == 0 } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainNot(label, "to have keys "+p.formatList(badkeys), got) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } // HasValues checks the gotten map has the given values set. -func (p mapCheckerProvider) HasValues(values ...interface{}) ValueChecker { +func (p mapCheckerProvider) HasValues(values ...any) Checker[any] { var missing []string - pass := func(got interface{}) bool { + pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Map) for _, expv := range values { if !p.hasValue(got, expv) { @@ -78,16 +78,16 @@ func (p mapCheckerProvider) HasValues(values ...interface{}) ValueChecker { } return len(missing) == 0 } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, "to have values "+p.formatList(missing), got) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } // HasNotValues checks the gotten map has not the given values set. -func (p mapCheckerProvider) HasNotValues(values ...interface{}) ValueChecker { +func (p mapCheckerProvider) HasNotValues(values ...any) Checker[any] { var badvalues []string - pass := func(got interface{}) bool { + pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Map) for _, badv := range values { if p.hasValue(got, badv) { @@ -96,22 +96,22 @@ func (p mapCheckerProvider) HasNotValues(values ...interface{}) ValueChecker { } return len(badvalues) == 0 } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainNot(label, "to have values "+p.formatList(badvalues), got) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } // CheckValues checks the gotten map's values corresponding to the given keys // pass the given checker. A key not found is considered a fail. // If len(keys) == 0, the check is made on all map values. -func (p mapCheckerProvider) CheckValues(c ValueChecker, keys ...interface{}) ValueChecker { //nolint: gocognit // TODO: refactor +func (p mapCheckerProvider) CheckValues(c Checker[any], keys ...any) Checker[any] { //nolint: gocognit // TODO: refactor var badentries []string allKeys := len(keys) == 0 - pass := func(got interface{}) bool { + pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Map) if allKeys { - p.walk(got, func(gotk, gotv interface{}) { + p.walk(got, func(gotk, gotv any) { if !c.Pass(gotv) { badentries = append(badentries, fmt.Sprintf("%s:%v", gotk, gotv)) } @@ -127,18 +127,18 @@ func (p mapCheckerProvider) CheckValues(c ValueChecker, keys ...interface{}) Val } return len(badentries) == 0 } - expl := func(label string, _ interface{}) string { + expl := func(label string, _ any) string { checkedKeys := cond.String("all keys", fmt.Sprintf("keys %v", keys), allKeys) return p.explainCheck(label, - fmt.Sprintf("values for %s to pass ValueChecker", checkedKeys), + fmt.Sprintf("values for %s to pass Checker[any]", checkedKeys), c.Explain("values", p.formatList(badentries)), ) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } // get returns gotmap[key] and a bool representing whether a match is found. -func (p mapCheckerProvider) get(gotmap, key interface{}) (interface{}, bool) { +func (p mapCheckerProvider) get(gotmap, key any) (any, bool) { iter := reflect.ValueOf(gotmap).MapRange() for iter.Next() { if k := iter.Key().Interface(); p.deq(k, key) { @@ -149,7 +149,7 @@ func (p mapCheckerProvider) get(gotmap, key interface{}) (interface{}, bool) { } // hasValue returns true if gotmap matches the specified value. -func (p mapCheckerProvider) hasValue(gotmap, value interface{}) bool { +func (p mapCheckerProvider) hasValue(gotmap, value any) bool { iter := reflect.ValueOf(gotmap).MapRange() for iter.Next() { if gotv := iter.Value().Interface(); p.deq(gotv, value) { @@ -159,7 +159,7 @@ func (p mapCheckerProvider) hasValue(gotmap, value interface{}) bool { return false } -func (mapCheckerProvider) walk(gotmap interface{}, f func(k, v interface{})) { +func (mapCheckerProvider) walk(gotmap any, f func(k, v any)) { vmap := reflect.ValueOf(gotmap) iter := vmap.MapRange() for iter.Next() { diff --git a/check/providers_map_test.go b/check/providers_map_test.go index a2cf89d..ca617fd 100644 --- a/check/providers_map_test.go +++ b/check/providers_map_test.go @@ -9,33 +9,37 @@ import ( ) func TestMapCheckerProvider(t *testing.T) { - m := map[string]interface{}{ + m := map[string]any{ "name": "Marcel Patulacci", "age": 42, "friends": []string{"Robert Robichet", "Jean-Pierre Avidol"}, } + // FIXME: remove forced conversion + itf := func(m map[string]any) any { + return m + } t.Run("Len pass", func(t *testing.T) { c := check.Map.Len(check.Int.Is(3)) - assertPassMapChecker(t, "Len", c, m) + assertPassChecker(t, "Map.Len", c, itf(m)) }) t.Run("Len fail", func(t *testing.T) { c := check.Map.Len(check.Int.Not(3)) - assertFailMapChecker(t, "Len", c, m, makeExpl( - "length to pass IntChecker", + assertFailChecker(t, "Map.Len", c, itf(m), makeExpl( + "length to pass Checker[int]", "explanation: length:\n"+makeExpl("not 3", "3"), )) }) t.Run("HasKeys pass", func(t *testing.T) { c := check.Map.HasKeys("name", "friends") - assertPassMapChecker(t, "HasKeys", c, m) + assertPassChecker(t, "Map.HasKeys", c, itf(m)) }) t.Run("HasKeys fail", func(t *testing.T) { c := check.Map.HasKeys("name", "hello", "bad") - assertFailMapChecker(t, "HasKeys", c, m, makeExpl( + assertFailChecker(t, "Map.HasKeys", c, itf(m), makeExpl( "to have keys [hello, bad]", fmt.Sprint(m), )) @@ -43,12 +47,12 @@ func TestMapCheckerProvider(t *testing.T) { t.Run("HasNotKeys pass", func(t *testing.T) { c := check.Map.HasNotKeys("hello", 42) - assertPassMapChecker(t, "HasNotKeys", c, m) + assertPassChecker(t, "Map.HasNotKeys", c, itf(m)) }) t.Run("HasNotKeys fail", func(t *testing.T) { c := check.Map.HasNotKeys("name", "hello", "age") - assertFailMapChecker(t, "HasNotKeys", c, m, makeExpl( + assertFailChecker(t, "Map.HasNotKeys", c, itf(m), makeExpl( "not to have keys [name, age]", fmt.Sprint(m), )) @@ -56,12 +60,12 @@ func TestMapCheckerProvider(t *testing.T) { t.Run("HasValues pass", func(t *testing.T) { c := check.Map.HasValues(42, []string{"Robert Robichet", "Jean-Pierre Avidol"}) - assertPassMapChecker(t, "HasValues", c, m) + assertPassChecker(t, "Map.HasValues", c, itf(m)) }) t.Run("HasValues fail", func(t *testing.T) { c := check.Map.HasValues(42, "hello", true) - assertFailMapChecker(t, "HasValues", c, m, makeExpl( + assertFailChecker(t, "Map.HasValues", c, itf(m), makeExpl( "to have values [hello, true]", fmt.Sprint(m), )) @@ -69,12 +73,12 @@ func TestMapCheckerProvider(t *testing.T) { t.Run("HasNotValues pass", func(t *testing.T) { c := check.Map.HasNotValues("hello", -1) - assertPassMapChecker(t, "HasNotValues", c, m) + assertPassChecker(t, "Map.HasNotValues", c, itf(m)) }) t.Run("HasNotValues fail", func(t *testing.T) { c := check.Map.HasNotValues(42, "hi", []string{"Robert Robichet", "Jean-Pierre Avidol"}) - assertFailMapChecker(t, "HasNotValues", c, m, makeExpl( + assertFailChecker(t, "Map.HasNotValues", c, itf(m), makeExpl( "not to have values [42, [Robert Robichet Jean-Pierre Avidol]]", fmt.Sprint(m), )) @@ -86,31 +90,31 @@ func TestMapCheckerProvider(t *testing.T) { checkconv.FromInt(check.Int.InRange(41, 43)), "age", ) - assertPassMapChecker(t, "CheckValues", c, m) + assertPassChecker(t, "Map.CheckValues", c, itf(m)) // all keys c = check.Map.CheckValues(check.Value.Not(0)) - assertPassMapChecker(t, "CheckValues", c, m) + assertPassChecker(t, "Map.CheckValues", c, itf(m)) }) t.Run("CheckValues fail", func(t *testing.T) { // keys subset - c := check.Map.CheckValues( - checkconv.FromInt(check.Int.OutRange(41, 43)), - "age", "badkey", - ) - assertFailMapChecker(t, "CheckValues", c, m, makeExpl( - "values for keys [age badkey] to pass ValueChecker", - "explanation: values:\n"+makeExpl( - "not in range [41:43]", - "[age:42, badkey:]", - ), - )) + // c := check.Map.CheckValues( + // checkconv.FromInt(check.Int.OutRange(41, 43)), + // "age", "badkey", + // ) + // assertFailChecker(t, "Map.CheckValues", c, itf(m), makeExpl( + // "values for keys [age badkey] to pass Checker[any]", + // "explanation: values:\n"+makeExpl( + // "not in range [41:43]", + // "[age:42, badkey:]", + // ), + // )) // all keys - c = check.Map.CheckValues(check.Value.Is("Marcel Patulacci")) - assertFailMapChecker(t, "CheckValues", c, m, makeExpl( - "values for all keys to pass ValueChecker", + c := check.Map.CheckValues(check.Value.Is("Marcel Patulacci")) + assertFailChecker(t, "Map.CheckValues", c, itf(m), makeExpl( + "values for all keys to pass Checker[any]", "explanation: values:\n"+makeExpl( "Marcel Patulacci", "[age:42, friends:[Robert Robichet Jean-Pierre Avidol]]", @@ -118,25 +122,3 @@ func TestMapCheckerProvider(t *testing.T) { )) }) } - -// Helpers - -func assertPassMapChecker(t *testing.T, method string, c check.ValueChecker, gotm interface{}) { - t.Helper() - if !c.Pass(gotm) { - failMapCheckerTest(t, true, method, gotm, c.Explain) - } -} - -func assertFailMapChecker(t *testing.T, method string, c check.ValueChecker, gotm interface{}, expexpl string) { - t.Helper() - if c.Pass(gotm) { - failMapCheckerTest(t, false, method, gotm, c.Explain) - } - assertGoodExplain(t, c, gotm, expexpl) -} - -func failMapCheckerTest(t *testing.T, expPass bool, method string, gotm interface{}, explain check.ExplainFunc) { - t.Helper() - failCheckerTest(t, expPass, "Map."+method, explain("Map value", gotm)) -} diff --git a/check/providers_slice.go b/check/providers_slice.go index e7f7fb2..51a1a41 100644 --- a/check/providers_slice.go +++ b/check/providers_slice.go @@ -10,44 +10,44 @@ import ( // sliceCheckerProvider provides checks on kind slice. type sliceCheckerProvider struct{ valueCheckerProvider } -// Len checks the length of the gotten slice passes the given IntChecker. -func (p sliceCheckerProvider) Len(c IntChecker) ValueChecker { +// Len checks the length of the gotten slice passes the given Checker[int]. +func (p sliceCheckerProvider) Len(c Checker[int]) Checker[any] { var gotlen int - pass := func(got interface{}) bool { + pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Slice) gotlen = reflect.ValueOf(got).Len() return c.Pass(gotlen) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainCheck(label, - "length to pass IntChecker", + "length to pass Checker[int]", c.Explain("length", gotlen), ) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } -// Cap checks the capacity of the gotten slice passes the given IntChecker. -func (p sliceCheckerProvider) Cap(c IntChecker) ValueChecker { +// Cap checks the capacity of the gotten slice passes the given Checker[int]. +func (p sliceCheckerProvider) Cap(c Checker[int]) Checker[any] { var gotcap int - pass := func(got interface{}) bool { + pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Slice) gotcap = reflect.ValueOf(got).Cap() return c.Pass(gotcap) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainCheck(label, - "capacity to pass IntChecker", + "capacity to pass Checker[int]", c.Explain("capacity", gotcap), ) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } // HasValues checks the gotten slice has the given values set. -func (p sliceCheckerProvider) HasValues(values ...interface{}) ValueChecker { +func (p sliceCheckerProvider) HasValues(values ...any) Checker[any] { var missing []string - pass := func(got interface{}) bool { + pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Slice) for _, expv := range values { if !p.hasValue(got, expv) { @@ -56,19 +56,19 @@ func (p sliceCheckerProvider) HasValues(values ...interface{}) ValueChecker { } return len(missing) == 0 } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, "to have values "+p.formatList(missing), got, ) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } // HasNotValues checks the gotten slice has not the given values set. -func (p sliceCheckerProvider) HasNotValues(values ...interface{}) ValueChecker { +func (p sliceCheckerProvider) HasNotValues(values ...any) Checker[any] { var badvalues []string - pass := func(got interface{}) bool { + pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Slice) for _, badv := range values { if p.hasValue(got, badv) { @@ -77,42 +77,46 @@ func (p sliceCheckerProvider) HasNotValues(values ...interface{}) ValueChecker { } return len(badvalues) == 0 } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainNot(label, "to have values "+p.formatList(badvalues), got, ) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } -// CheckValues checks the values of the gotten slice pass the given ValueChecker. +// CheckValues checks the values of the gotten slice passes +// the given Checker[any]. // If a filterFunc is provided, the values not passing it are ignored. -func (p sliceCheckerProvider) CheckValues(c ValueChecker, filters ...func(i int, v interface{}) bool) ValueChecker { +func (p sliceCheckerProvider) CheckValues( + c Checker[any], + filters ...func(i int, v any) bool, +) Checker[any] { var badvalues []string - pass := func(got interface{}) bool { + pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Slice) - p.walk(got, filters, func(i int, v interface{}) { + p.walk(got, filters, func(i int, v any) { if !c.Pass(v) { badvalues = append(badvalues, fmt.Sprintf("%d:%v", i, v)) } }) return len(badvalues) == 0 } - expl := func(label string, _ interface{}) string { + expl := func(label string, _ any) string { return p.explainCheck(label, - "values to pass ValueChecker", + "values to pass Checker[any]", c.Explain("values", p.formatList(badvalues)), ) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } // Helpers // hasValue returns true if slice has a value equal to expv. -func (p sliceCheckerProvider) hasValue(slice, expv interface{}) bool { - return p.walkUntil(slice, nil, func(_ int, v interface{}) bool { +func (p sliceCheckerProvider) hasValue(slice, expv any) bool { + return p.walkUntil(slice, nil, func(_ int, v any) bool { return p.deq(v, expv) }) } @@ -120,11 +124,11 @@ func (p sliceCheckerProvider) hasValue(slice, expv interface{}) bool { // walk iterates over a slice until the end is reached. // It calls f(i, v) each iteration if (i, v) pass the given filters. func (p sliceCheckerProvider) walk( - slice interface{}, - filters []func(int, interface{}) bool, - f func(i int, v interface{}), + slice any, + filters []func(int, any) bool, + f func(i int, v any), ) { - p.walkUntil(slice, filters, func(i int, v interface{}) bool { + p.walkUntil(slice, filters, func(i int, v any) bool { f(i, v) return false }) @@ -134,9 +138,9 @@ func (p sliceCheckerProvider) walk( // returns true for the current iteration. In returns true if it was stopped // early, false otherwise. func (p sliceCheckerProvider) walkUntil( - slice interface{}, - filters []func(int, interface{}) bool, - stop func(int, interface{}) bool, + slice any, + filters []func(int, any) bool, + stop func(int, any) bool, ) bool { vslice := reflect.ValueOf(slice) l := vslice.Len() @@ -153,12 +157,12 @@ func (p sliceCheckerProvider) walkUntil( // mergeFilters combinates several filtering funcs into one. func (p sliceCheckerProvider) mergeFilters( - filters ...func(int, interface{}) bool, -) func(int, interface{}) bool { + filters ...func(int, any) bool, +) func(int, any) bool { if len(filters) == 0 { - return func(int, interface{}) bool { return true } + return func(int, any) bool { return true } } - return func(i int, v interface{}) bool { + return func(i int, v any) bool { curr := filters[0] next := p.mergeFilters(filters[1:]...) return curr(i, v) && next(i, v) diff --git a/check/providers_slice_test.go b/check/providers_slice_test.go index 37d3e67..b966233 100644 --- a/check/providers_slice_test.go +++ b/check/providers_slice_test.go @@ -9,42 +9,46 @@ import ( ) func TestSliceCheckerProvider(t *testing.T) { - s := []interface{}{"hello", 42, "Marcel Patulacci", []float32{3.14}} + s := []any{"hello", 42, "Marcel Patulacci", []float32{3.14}} + // FIXME: remove forced conversion + itf := func(v []any) any { + return v + } t.Run("Len pass", func(t *testing.T) { c := check.Slice.Len(check.Int.Is(4)) - assertPassSliceChecker(t, "Len", c, s) + assertPassChecker(t, "Slice.Len", c, itf(s)) }) t.Run("Len fail", func(t *testing.T) { c := check.Slice.Len(check.Int.Not(4)) - assertFailSliceChecker(t, "Len", c, s, makeExpl( - "length to pass IntChecker", + assertFailChecker(t, "Slice.Len", c, itf(s), makeExpl( + "length to pass Checker[int]", "explanation: length:\n"+makeExpl("not 4", "4"), )) }) t.Run("Cap pass", func(t *testing.T) { c := check.Slice.Cap(check.Int.Is(4)) - assertPassSliceChecker(t, "Cap", c, s) + assertPassChecker(t, "Slice.Cap", c, itf(s)) }) t.Run("Cap fail", func(t *testing.T) { c := check.Slice.Cap(check.Int.Not(4)) - assertFailSliceChecker(t, "Cap", c, s, makeExpl( - "capacity to pass IntChecker", + assertFailChecker(t, "Slice.Cap", c, itf(s), makeExpl( + "capacity to pass Checker[int]", "explanation: capacity:\n"+makeExpl("not 4", "4"), )) }) t.Run("HasValues pass", func(t *testing.T) { c := check.Slice.HasValues("hello", 42, []float32{3.14}) - assertPassSliceChecker(t, "HasValues", c, s) + assertPassChecker(t, "Slice.HasValues", c, itf(s)) }) t.Run("HasValues fail", func(t *testing.T) { c := check.Slice.HasValues([]float64{3.14}) - assertFailSliceChecker(t, "HasValues", c, s, makeExpl( + assertFailChecker(t, "Slice.HasValues", c, itf(s), makeExpl( "to have values [[3.14]]", fmt.Sprint(s), )) @@ -52,55 +56,33 @@ func TestSliceCheckerProvider(t *testing.T) { t.Run("HasNotValues pass", func(t *testing.T) { c := check.Slice.HasNotValues("hi", -1, []float64{3.14}) - assertPassSliceChecker(t, "HasNotValues", c, s) + assertPassChecker(t, "Slice.HasNotValues", c, itf(s)) }) t.Run("HasNotValues fail", func(t *testing.T) { c := check.Slice.HasNotValues("hi", -1, []float32{3.14}) - assertFailSliceChecker(t, "HasNotValues", c, s, makeExpl( + assertFailChecker(t, "Slice.HasNotValues", c, itf(s), makeExpl( "not to have values [[3.14]]", fmt.Sprint(s), )) }) - t.Run("CheckValues pass", func(t *testing.T) { - c := check.Slice.CheckValues( - checkconv.FromInt(check.Int.InRange(41, 43)), - func(_ int, v interface{}) bool { _, ok := v.(int); return ok }, - ) - assertPassSliceChecker(t, "CheckValues", c, s) - }) + // t.Run("CheckValues pass", func(t *testing.T) { + // c := check.Slice.CheckValues( + // checkconv.FromInt(check.Int.InRange(41, 43)), + // func(_ int, v any) bool { _, ok := v.(int); return ok }, + // ) + // assertPassChecker(t, "Slice.CheckValues", c, itf(s)) + // }) t.Run("CheckValues fail", func(t *testing.T) { c := check.Slice.CheckValues( checkconv.FromInt(check.Int.OutRange(41, 43)), - func(_ int, v interface{}) bool { _, ok := v.(int); return ok }, + func(_ int, v any) bool { _, ok := v.(int); return ok }, ) - assertFailSliceChecker(t, "CheckValues", c, s, makeExpl( - "values to pass ValueChecker", + assertFailChecker(t, "Slice.CheckValues", c, itf(s), makeExpl( + "values to pass Checker[any]", "explanation: values:\n"+makeExpl("not in range [41:43]", "[1:42]"), )) }) } - -// Helpers - -func assertPassSliceChecker(t *testing.T, method string, c check.ValueChecker, slc interface{}) { - t.Helper() - if !c.Pass(slc) { - failSliceCheckerTest(t, true, method, slc, c.Explain) - } -} - -func assertFailSliceChecker(t *testing.T, method string, c check.ValueChecker, slc interface{}, expexpl string) { - t.Helper() - if c.Pass(slc) { - failSliceCheckerTest(t, false, method, slc, c.Explain) - } - assertGoodExplain(t, c, slc, expexpl) -} - -func failSliceCheckerTest(t *testing.T, expPass bool, method string, slc interface{}, explain check.ExplainFunc) { - t.Helper() - failCheckerTest(t, expPass, "Slice."+method, explain("Slice value", slc)) -} diff --git a/check/providers_string.go b/check/providers_string.go index 817541a..c86ec59 100644 --- a/check/providers_string.go +++ b/check/providers_string.go @@ -10,16 +10,16 @@ import ( type stringCheckerProvider struct{ baseCheckerProvider } // Is checks the gotten string is equal to the target. -func (p stringCheckerProvider) Is(tar string) StringChecker { +func (p stringCheckerProvider) Is(tar string) Checker[string] { pass := func(got string) bool { return got == tar } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, tar, got) } - return NewStringChecker(pass, expl) + return NewChecker(pass, expl) } // Not checks the gotten string is not equal to the target. -func (p stringCheckerProvider) Not(values ...string) StringChecker { +func (p stringCheckerProvider) Not(values ...string) Checker[string] { var match string pass := func(got string) bool { for _, v := range values { @@ -30,63 +30,63 @@ func (p stringCheckerProvider) Not(values ...string) StringChecker { } return true } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainNot(label, match, got) } - return NewStringChecker(pass, expl) + return NewChecker(pass, expl) } -// Len checks the gotten string's length passes the given IntChecker. -func (p stringCheckerProvider) Len(c IntChecker) StringChecker { +// Len checks the gotten string's length passes the given Checker[int]. +func (p stringCheckerProvider) Len(c Checker[int]) Checker[string] { pass := func(got string) bool { return c.Pass(len(got)) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainCheck(label, - "length to pass IntChecker", + "length to pass Checker[int]", c.Explain("length", len(got.(string))), ) } - return NewStringChecker(pass, expl) + return NewChecker(pass, expl) } // Match checks the gotten string matches the given regexp. -func (p stringCheckerProvider) Match(rgx *regexp.Regexp) StringChecker { +func (p stringCheckerProvider) Match(rgx *regexp.Regexp) Checker[string] { pass := func(got string) bool { return rgx.MatchString(got) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("to match regexp %s", rgx.String()), got, ) } - return NewStringChecker(pass, expl) + return NewChecker(pass, expl) } // NotMatch checks the gotten string do not match the given regexp. -func (p stringCheckerProvider) NotMatch(rgx *regexp.Regexp) StringChecker { +func (p stringCheckerProvider) NotMatch(rgx *regexp.Regexp) Checker[string] { pass := func(got string) bool { return !rgx.MatchString(got) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainNot(label, fmt.Sprintf("to match regexp %s", rgx.String()), got, ) } - return NewStringChecker(pass, expl) + return NewChecker(pass, expl) } // Contains checks the gotten string contains the target substring. -func (p stringCheckerProvider) Contains(sub string) StringChecker { +func (p stringCheckerProvider) Contains(sub string) Checker[string] { pass := func(got string) bool { return strings.Contains(got, sub) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, "to contain substring "+sub, got) } - return NewStringChecker(pass, expl) + return NewChecker(pass, expl) } // NotContains checks the gotten string do not contain the target // substring. -func (p stringCheckerProvider) NotContains(sub string) StringChecker { +func (p stringCheckerProvider) NotContains(sub string) Checker[string] { pass := func(got string) bool { return !strings.Contains(got, sub) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainNot(label, "to contain substring "+sub, got) } - return NewStringChecker(pass, expl) + return NewChecker(pass, expl) } diff --git a/check/providers_string_test.go b/check/providers_string_test.go index f920826..b2c42a2 100644 --- a/check/providers_string_test.go +++ b/check/providers_string_test.go @@ -17,35 +17,35 @@ func TestStringCheckerProvider(t *testing.T) { t.Run("Is pass", func(t *testing.T) { c := check.String.Is(s) - assertPassStringChecker(t, "Is", c, s) + assertPassChecker(t, "String.Is", c, s) }) t.Run("Is fail", func(t *testing.T) { c := check.String.Is(exp) - assertFailStringChecker(t, "Is", c, s, makeExpl(exp, s)) + assertFailChecker(t, "String.Is", c, s, makeExpl(exp, s)) }) t.Run("Not pass", func(t *testing.T) { c := check.String.Not("hello", sub, exp) - assertPassStringChecker(t, "Not", c, s) + assertPassChecker(t, "String.Not", c, s) }) t.Run("Not fail", func(t *testing.T) { c := check.String.Not("hello", sub, s, exp) - assertFailStringChecker(t, "Not", c, s, makeExpl("not "+s, s)) + assertFailChecker(t, "String.Not", c, s, makeExpl("not "+s, s)) }) t.Run("Len pass", func(t *testing.T) { c := check.String.Len(check.Int.Is(len(s))) - assertPassStringChecker(t, "Len", c, s) + assertPassChecker(t, "String.Len", c, s) }) t.Run("Len fail", func(t *testing.T) { gotlen := len(s) explen := gotlen + 1 c := check.String.Len(check.Int.Is(explen)) - assertFailStringChecker(t, "Len", c, s, makeExpl( - "length to pass IntChecker", + assertFailChecker(t, "String.Len", c, s, makeExpl( + "length to pass Checker[int]", "explanation: length:\n"+makeExpl( fmt.Sprint(explen), fmt.Sprint(gotlen), @@ -55,26 +55,26 @@ func TestStringCheckerProvider(t *testing.T) { t.Run("Match pass", func(t *testing.T) { c := check.String.Match(regexp.MustCompile(`(?i)\sTENET\s`)) - assertPassStringChecker(t, "Match", c, s) + assertPassChecker(t, "String.Match", c, s) }) t.Run("Match fail", func(t *testing.T) { r := regexp.MustCompile(`\sTENET\s`) c := check.String.Match(r) - assertFailStringChecker(t, "Match", c, s, + assertFailChecker(t, "String.Match", c, s, makeExpl("to match regexp "+r.String(), s), ) }) t.Run("NotMatch pass", func(t *testing.T) { c := check.String.NotMatch(regexp.MustCompile(`\sTENET\s`)) - assertPassStringChecker(t, "NotMatch", c, s) + assertPassChecker(t, "String.NotMatch", c, s) }) t.Run("NotMatch fail", func(t *testing.T) { r := regexp.MustCompile(`(?i)\sTENET\s`) c := check.String.NotMatch(r) - assertFailStringChecker(t, "NotMatch", c, s, makeExpl( + assertFailChecker(t, "String.NotMatch", c, s, makeExpl( "not to match regexp "+r.String(), s, )) @@ -82,14 +82,14 @@ func TestStringCheckerProvider(t *testing.T) { t.Run("Contains pass", func(t *testing.T) { c := check.String.Contains(sub) - assertPassStringChecker(t, "Contains", c, s) + assertPassChecker(t, "String.Contains", c, s) c = check.String.Contains(s) - assertPassStringChecker(t, "Contains", c, s) + assertPassChecker(t, "String.Contains", c, s) }) t.Run("Contains fail", func(t *testing.T) { c := check.String.Contains(exp) - assertFailStringChecker(t, "Contains", c, s, makeExpl( + assertFailChecker(t, "String.Contains", c, s, makeExpl( "to contain substring "+exp, s, )) @@ -97,41 +97,19 @@ func TestStringCheckerProvider(t *testing.T) { t.Run("NotContains pass", func(t *testing.T) { c := check.String.NotContains(exp) - assertPassStringChecker(t, "NotContains", c, s) + assertPassChecker(t, "String.NotContains", c, s) }) t.Run("NotContains fail", func(t *testing.T) { c := check.String.NotContains(sub) - assertFailStringChecker(t, "NotContains", c, s, makeExpl( + assertFailChecker(t, "String.NotContains", c, s, makeExpl( "not to contain substring "+sub, s, )) c = check.String.NotContains(s) - assertFailStringChecker(t, "NotContains", c, s, makeExpl( + assertFailChecker(t, "String.NotContains", c, s, makeExpl( "not to contain substring "+s, s, )) }) } - -// Helpers - -func assertPassStringChecker(t *testing.T, method string, c check.StringChecker, in string) { - t.Helper() - if !c.Pass(in) { - failStringCheckerTest(t, true, method, in, c.Explain) - } -} - -func assertFailStringChecker(t *testing.T, method string, c check.StringChecker, in, expexpl string) { - t.Helper() - if c.Pass(in) { - failStringCheckerTest(t, false, method, in, c.Explain) - } - assertGoodExplain(t, c, in, expexpl) -} - -func failStringCheckerTest(t *testing.T, expPass bool, method, in string, explain check.ExplainFunc) { - t.Helper() - failCheckerTest(t, expPass, "String."+method, explain("String value", in)) -} diff --git a/check/providers_struct.go b/check/providers_struct.go index 16c3f66..f397c03 100644 --- a/check/providers_struct.go +++ b/check/providers_struct.go @@ -14,49 +14,49 @@ type structCheckerProvider struct{ valueCheckerProvider } // FieldsEqual checks all given fields equal the exp value. // It panics if the fields do not exist or are not exported, // or if the tested value is not a struct. -func (p structCheckerProvider) FieldsEqual(exp interface{}, fields []string) ValueChecker { +func (p structCheckerProvider) FieldsEqual(exp any, fields []string) Checker[any] { var bads []string - pass := func(got interface{}) bool { + pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Struct) - bads = p.badFields(got, fields, func(k string, v interface{}) bool { + bads = p.badFields(got, fields, func(k string, v any) bool { return p.deq(v, exp) }) return len(bads) == 0 } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("fields [%s] to equal %v", p.formatFields(fields), exp), strings.Join(bads, ", "), ) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } -// CheckFields checks all given fields pass the ValueChecker. +// CheckFields checks all given fields pass the Checker[any]. // It panics if the fields do not exist or are not exported, // or if the tested value is not a struct. -func (p structCheckerProvider) CheckFields(c ValueChecker, fields []string) ValueChecker { +func (p structCheckerProvider) CheckFields(c Checker[any], fields []string) Checker[any] { var bads []string - pass := func(got interface{}) bool { + pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Struct) - bads = p.badFields(got, fields, func(k string, v interface{}) bool { + bads = p.badFields(got, fields, func(k string, v any) bool { return c.Pass(v) }) return len(bads) == 0 } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainCheck(label, - fmt.Sprintf("fields [%s] to pass ValueChecker", p.formatFields(fields)), + fmt.Sprintf("fields [%s] to pass Checker[any]", p.formatFields(fields)), c.Explain("fields", strings.Join(bads, ", ")), ) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } func (p structCheckerProvider) badFields( - gotstruct interface{}, + gotstruct any, fields []string, - pass func(k string, v interface{}) bool, + pass func(k string, v any) bool, ) (bads []string) { vstruct := reflect.ValueOf(gotstruct) for _, k := range fields { diff --git a/check/providers_struct_test.go b/check/providers_struct_test.go index e575f79..efe22ce 100644 --- a/check/providers_struct_test.go +++ b/check/providers_struct_test.go @@ -18,15 +18,19 @@ func TestStructCheckerProvider(t *testing.T) { vXY = 20 ) s := structTest{A: vAB, B: vAB, X: vXY, Y: vXY} + // FIXME: remove forced conversion + itf := func(v structTest) any { + return v + } t.Run("FieldsEqual pass", func(t *testing.T) { c := check.Struct.FieldsEqual(vAB, []string{"A", "B"}) - assertPassStructChecker(t, "FieldsEqual", c, s) + assertPassChecker(t, "Struct.FieldsEqual", c, itf(s)) }) t.Run("FieldsEqual fail", func(t *testing.T) { c := check.Struct.FieldsEqual(vAB, []string{"A", "B", "X", "Y"}) - assertFailStructChecker(t, "FieldsEqual", c, s, makeExpl( + assertFailChecker(t, "Struct.FieldsEqual", c, itf(s), makeExpl( fmt.Sprintf("fields [.A, .B, .X, .Y] to equal %v", vAB), fmt.Sprintf(".X=%v, .Y=%v", vXY, vXY), )) @@ -37,7 +41,7 @@ func TestStructCheckerProvider(t *testing.T) { checkconv.FromInt(check.Int.LT(vAB+1)), []string{"A", "B"}, ) - assertPassStructChecker(t, "CheckFields", c, s) + assertPassChecker(t, "Struct.CheckFields", c, itf(s)) }) t.Run("CheckFields fail", func(t *testing.T) { @@ -45,31 +49,9 @@ func TestStructCheckerProvider(t *testing.T) { checkconv.FromInt(check.Int.LT(vAB+1)), []string{"A", "B", "X", "Y"}, ) - assertFailStructChecker(t, "CheckFields", c, s, makeExpl( - "fields [.A, .B, .X, .Y] to pass ValueChecker", + assertFailChecker(t, "Struct.CheckFields", c, itf(s), makeExpl( + "fields [.A, .B, .X, .Y] to pass Checker[any]", "explanation: fields:\n"+makeExpl("< 11", ".X=20, .Y=20"), )) }) } - -// Helpers - -func assertPassStructChecker(t *testing.T, method string, c check.ValueChecker, s structTest) { - t.Helper() - if !c.Pass(s) { - failStructCheckerTest(t, true, method, s, c.Explain) - } -} - -func assertFailStructChecker(t *testing.T, method string, c check.ValueChecker, s structTest, expexpl string) { - t.Helper() - if c.Pass(s) { - failStructCheckerTest(t, false, method, s, c.Explain) - } - assertGoodExplain(t, c, s, expexpl) -} - -func failStructCheckerTest(t *testing.T, expPass bool, method string, s structTest, explain check.ExplainFunc) { - t.Helper() - failCheckerTest(t, expPass, "Struct."+method, explain("struct value", s)) -} diff --git a/check/providers_test.go b/check/providers_test.go index a64a3cc..9e11b82 100644 --- a/check/providers_test.go +++ b/check/providers_test.go @@ -7,29 +7,57 @@ import ( "github.com/drykit-go/testx/check" ) -func failCheckerTest(t *testing.T, expPass bool, name, expl string) { +func assertPassChecker[T any]( + t *testing.T, + methodName string, + c check.Checker[T], + in T, +) { t.Helper() - passOrFail := "PASS" - if !expPass { - passOrFail = "FAIL" + if !c.Pass(in) { + failCheckerTest(t, true, methodName, c.Explain("value", in)) } - t.Errorf("\n❌ exp %s to %s, explain:\n%s", name, passOrFail, expl) } -func makeExpl(expstr, gotstr string) string { - return fmt.Sprintf("exp %s\ngot %s", expstr, gotstr) +func assertFailChecker[T any]( + t *testing.T, + methodName string, + c check.Checker[T], + in T, + expexpl string, +) { + t.Helper() + if c.Pass(in) { + failCheckerTest(t, false, methodName, c.Explain("value", in)) + } + assertGoodExplain(t, c, in, expexpl) } -func assertGoodExplain( +func assertGoodExplain[T any]( t *testing.T, - c check.Explainer, - gotval interface{}, + // FIXME: check.Explainer2[T] not working: + // type check.Checker[bool] of c does not match check.Explainer2[T] (cannot infer T)compilerErrorCode(135) + c check.Checker[T], + in T, expexpl string, ) { t.Helper() - gotexpl := c.Explain("label", gotval) + gotexpl := c.Explain("label", in) expexpl = "label:\n" + expexpl if gotexpl != expexpl { t.Errorf("bad Explain output:\nexp \"%s\"\ngot \"%s\"", expexpl, gotexpl) } } + +func failCheckerTest(t *testing.T, expPass bool, name, expl string) { + t.Helper() + passOrFail := "PASS" + if !expPass { + passOrFail = "FAIL" + } + t.Errorf("\n❌ exp %s to %s, explain:\n%s", name, passOrFail, expl) +} + +func makeExpl(expstr, gotstr string) string { + return fmt.Sprintf("exp %s\ngot %s", expstr, gotstr) +} diff --git a/check/providers_value.go b/check/providers_value.go index 413ccad..8356022 100644 --- a/check/providers_value.go +++ b/check/providers_value.go @@ -6,32 +6,32 @@ import ( "github.com/drykit-go/testx/internal/reflectutil" ) -// valueCheckerProvider provides checks on type interface{}. +// valueCheckerProvider provides checks on type any. type valueCheckerProvider struct{ baseCheckerProvider } -// Custom checks the gotten value passes the given ValuePassFunc. +// Custom checks the gotten value passes the given PassFunc[any]. // The description should give information about the expected value, // as it outputs in format "exp " in case of failure. -func (p valueCheckerProvider) Custom(desc string, f ValuePassFunc) ValueChecker { - expl := func(label string, got interface{}) string { +func (p valueCheckerProvider) Custom(desc string, f PassFunc[any]) Checker[any] { + expl := func(label string, got any) string { return p.explain(label, desc, got) } - return NewValueChecker(f, expl) + return NewChecker(f, expl) } // Is checks the gotten value is equal to the target. -func (p valueCheckerProvider) Is(tar interface{}) ValueChecker { - pass := func(got interface{}) bool { return p.deq(got, tar) } - expl := func(label string, got interface{}) string { +func (p valueCheckerProvider) Is(tar any) Checker[any] { + pass := func(got any) bool { return p.deq(got, tar) } + expl := func(label string, got any) string { return p.explain(label, tar, got) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } // Not checks the gotten value is not equal to the target. -func (p valueCheckerProvider) Not(values ...interface{}) ValueChecker { - var match interface{} - pass := func(got interface{}) bool { +func (p valueCheckerProvider) Not(values ...any) Checker[any] { + var match any + pass := func(got any) bool { for _, v := range values { if p.deq(got, v) { match = v @@ -40,44 +40,44 @@ func (p valueCheckerProvider) Not(values ...interface{}) ValueChecker { } return true } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explainNot(label, match, got) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } // IsZero checks the gotten value is a zero value, indicating it might not // have been initialized. -func (p valueCheckerProvider) IsZero() ValueChecker { - expl := func(label string, got interface{}) string { +func (p valueCheckerProvider) IsZero() Checker[any] { + expl := func(label string, got any) string { return p.explain(label, "to be a zero value", got) } - return NewValueChecker(reflectutil.IsZero, expl) + return NewChecker(reflectutil.IsZero, expl) } // NotZero checks the gotten struct contains at least 1 non-zero value, // meaning it has been initialized. -func (p valueCheckerProvider) NotZero() ValueChecker { - pass := func(got interface{}) bool { return !reflectutil.IsZero(got) } - expl := func(label string, got interface{}) string { +func (p valueCheckerProvider) NotZero() Checker[any] { + pass := func(got any) bool { return !reflectutil.IsZero(got) } + expl := func(label string, got any) string { return p.explainNot(label, "to be a zero value", got) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } // SameJSON checks the gotten value and the target value // produce the same JSON, ignoring formatting and keys order. // It panics if any error occurs in the marshaling process. -func (p valueCheckerProvider) SameJSON(tar interface{}) ValueChecker { - var gotDec, tarDec interface{} - pass := func(got interface{}) bool { +func (p valueCheckerProvider) SameJSON(tar any) Checker[any] { + var gotDec, tarDec any + pass := func(got any) bool { return p.sameJSONProduced(got, tar, &gotDec, &tarDec) } - expl := func(label string, got interface{}) string { + expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("json data: %v", tarDec), fmt.Sprintf("json data: %v", gotDec), ) } - return NewValueChecker(pass, expl) + return NewChecker(pass, expl) } diff --git a/check/providers_value_test.go b/check/providers_value_test.go index d8e2755..253a30d 100644 --- a/check/providers_value_test.go +++ b/check/providers_value_test.go @@ -22,41 +22,45 @@ func TestValueCheckerProvider(t *testing.T) { emptyMap map[int]bool emptySlice []float32 - zeros = []interface{}{0, "", 0i + 0, vzero, emptyMap, emptySlice} - nozeros = []interface{}{1, "hi", 0i + 1, vorig, map[int]bool{}, []float32{}} + zeros = []any{0, "", 0i + 0, vzero, emptyMap, emptySlice} + nozeros = []any{1, "hi", 0i + 1, vorig, map[int]bool{}, []float32{}} ) + itf := func(v any) any { + return v + } + t.Run("Is pass", func(t *testing.T) { c := check.Value.Is(vsame) - assertPassValueChecker(t, "Is", c, vorig) + assertPassChecker(t, "Value.Is", c, itf(vorig)) }) t.Run("Is fail", func(t *testing.T) { c := check.Value.Is(badval) - assertFailValueChecker(t, "Is", c, vorig, makeExpl("{hello}", "{hi}")) + assertFailChecker(t, "Value.Is", c, itf(vorig), makeExpl("{hello}", "{hi}")) }) t.Run("Not pass", func(t *testing.T) { c := check.Value.Not(badval, badtyp) - assertPassValueChecker(t, "Not", c, vorig) + assertPassChecker(t, "Value.Not", c, itf(vorig)) }) t.Run("Not fail", func(t *testing.T) { c := check.Value.Not(badval, vsame, badtyp) - assertFailValueChecker(t, "Not", c, vorig, makeExpl("not {hi}", "{hi}")) + assertFailChecker(t, "Value.Not", c, itf(vorig), makeExpl("not {hi}", "{hi}")) }) t.Run("IsZero pass", func(t *testing.T) { c := check.Value.IsZero() for _, z := range zeros { - assertPassValueChecker(t, "IsZero", c, z) + assertPassChecker(t, "Value.IsZero", c, z) } }) t.Run("IsZero fail", func(t *testing.T) { c := check.Value.IsZero() for _, nz := range nozeros { - assertFailValueChecker(t, "IsZero", c, nz, makeExpl( + assertFailChecker(t, "Value.IsZero", c, nz, makeExpl( "to be a zero value", fmt.Sprint(nz), )) @@ -66,73 +70,51 @@ func TestValueCheckerProvider(t *testing.T) { t.Run("NotZero pass", func(t *testing.T) { c := check.Value.NotZero() for _, nz := range nozeros { - assertPassValueChecker(t, "NotZero", c, nz) + assertPassChecker(t, "Value.NotZero", c, nz) } }) t.Run("NotZero fail", func(t *testing.T) { c := check.Value.NotZero() for _, z := range zeros { - assertFailValueChecker(t, "NotZero", c, z, makeExpl( + assertFailChecker(t, "Value.NotZero", c, z, makeExpl( "not to be a zero value", fmt.Sprint(z), )) } }) - isEvenInt := func(n interface{}) bool { + isEvenInt := func(n any) bool { nint, ok := n.(int) return ok && nint&1 == 0 } t.Run("Custom pass", func(t *testing.T) { c := check.Value.Custom("", isEvenInt) - assertPassValueChecker(t, "Custom", c, 42) + assertPassChecker(t, "Value.Custom", c, 42) }) t.Run("Custom fail", func(t *testing.T) { c := check.Value.Custom("even int", isEvenInt) - assertFailValueChecker(t, "Custom", c, -1, makeExpl("even int", "-1")) + assertFailChecker(t, "Value.Custom", c, -1, makeExpl("even int", "-1")) }) t.Run("SameJSON pass", func(t *testing.T) { - mapequiv := map[string]interface{}{ + mapequiv := map[string]any{ "Name": "hi", } c := check.Value.SameJSON(mapequiv) - assertPassValueChecker(t, "SameJSON", c, vorig) + assertPassChecker(t, "Value.SameJSON", c, itf(vorig)) }) t.Run("SameJSON fail", func(t *testing.T) { - mapdiff := map[string]interface{}{ + mapdiff := map[string]any{ "Name": "bad", } c := check.Value.SameJSON(mapdiff) - assertFailValueChecker(t, "SameJSON", c, vorig, makeExpl( + assertFailChecker(t, "Value.SameJSON", c, itf(vorig), makeExpl( "json data: map[Name:bad]", "json data: map[Name:hi]", )) }) } - -// Helpers - -func assertPassValueChecker(t *testing.T, method string, c check.ValueChecker, v interface{}) { - t.Helper() - if !c.Pass(v) { - failValueCheckerTest(t, true, method, v, c.Explain) - } -} - -func assertFailValueChecker(t *testing.T, method string, c check.ValueChecker, v interface{}, expexpl string) { - t.Helper() - if c.Pass(v) { - failValueCheckerTest(t, false, method, v, c.Explain) - } - assertGoodExplain(t, c, v, expexpl) -} - -func failValueCheckerTest(t *testing.T, expPass bool, method string, v interface{}, explain check.ExplainFunc) { - t.Helper() - failCheckerTest(t, expPass, "Value."+method, explain("Value value", v)) -} diff --git a/checkconv/README.md b/checkconv/README.md index ec41a85..161b99f 100644 --- a/checkconv/README.md +++ b/checkconv/README.md @@ -59,7 +59,7 @@ type StringChecker interface { As a consequence, there is no way to combine `IntChecker` and `StringChecker` into a generic `Checker` interface. -For that reason, we use `check.ValueChecker` checker (checker on type `interface{}`), +For that reason, we use `check.ValueChecker` checker (checker on type `any`), because it can wrap any checker type. That's what this package provides: functions to wrap any typed checker @@ -89,8 +89,8 @@ testx. ### Assert functions -- `Assert(checker interface{}) check.ValueChecker` -- `AssertMany(checkers ...interface{}) []check.ValueChecker` +- `Assert(checker any) check.ValueChecker` +- `AssertMany(checkers ...any) []check.ValueChecker` Assert functions basically return `From(inputChecker)` if that `From` function exists for the input checker. @@ -129,13 +129,13 @@ testx. ### Cast functions -- `Cast(checker interface{}) (check.ValueChecker, bool)` -- `CastMany(checkers ...interface{}) []check.ValueChecker` +- `Cast(checker any) (check.ValueChecker, bool)` +- `CastMany(checkers ...any) []check.ValueChecker` Cast functions serve the same purpose as Assert functions: they wrap the given checker in a `check.ValueChecker`. The difference is that it works with _any_ type that implement -a checker interface (`Pass(T) bool` and `Explain(string, interface{} string`) +a checker interface (`Pass(T) bool` and `Explain(string, any string`) while Assert functions are restricted to the types defined in package `check`. ### `Assert` vs `Cast` @@ -145,7 +145,7 @@ There is a fundamental difference between `Assert` and `Cast` implementations: - `Assert` uses `From` functions that rely on type assertion to wrap the input checker: ```go - func Assert(knownChecker interface{}) check.ValueChecker { + func Assert(knownChecker any) check.ValueChecker { switch c := knownChecker.(type) { case check.StringChecker: return FromString(c) @@ -159,7 +159,7 @@ There is a fundamental difference between `Assert` and `Cast` implementations: func FromString(c check.StringChecker) check.ValueChecker { return check.NewValueChecker( - func(got interface{}) bool { return c.Pass(got.(string)) }, + func(got any) bool { return c.Pass(got.(string)) }, c.Explain, // ^^^^^^^^^^^^^^^^^^^ got is safely asserted ) } diff --git a/checkconv/assert.go b/checkconv/assert.go index eac6408..9ba93e2 100644 --- a/checkconv/assert.go +++ b/checkconv/assert.go @@ -1,8 +1,10 @@ // Code generated by go generate ./...; DO NOT EDIT -// Last generated on 30 Oct 21 12:10 UTC +// Last generated on 31 Oct 21 17:02 UTC // Package checkconv provides functions to convert typed checkers // into generic ones. +// +// TODO: delete package checkconv package checkconv import ( @@ -13,140 +15,153 @@ import ( "github.com/drykit-go/testx/check" ) -// FromBool returns a check.ValueChecker that wraps the given -// check.BoolChecker, so it can be used as a generic checker. -func FromBool(c check.BoolChecker) check.ValueChecker { - return check.NewValueChecker( - func(got interface{}) bool { return c.Pass(got.(bool)) }, + +// FromBool returns a check.Checker[any] that wraps the given +// check.Checker[bool], so it can be used as a generic checker. +func FromBool(c check.Checker[bool]) check.Checker[any] { + return check.NewChecker( + func(got any) bool { return c.Pass(got.(bool)) }, c.Explain, ) } -// FromBytes returns a check.ValueChecker that wraps the given -// check.BytesChecker, so it can be used as a generic checker. -func FromBytes(c check.BytesChecker) check.ValueChecker { - return check.NewValueChecker( - func(got interface{}) bool { return c.Pass(got.([]byte)) }, + +// FromBytes returns a check.Checker[any] that wraps the given +// check.Checker[[]byte], so it can be used as a generic checker. +func FromBytes(c check.Checker[[]byte]) check.Checker[any] { + return check.NewChecker( + func(got any) bool { return c.Pass(got.([]byte)) }, c.Explain, ) } -// FromString returns a check.ValueChecker that wraps the given -// check.StringChecker, so it can be used as a generic checker. -func FromString(c check.StringChecker) check.ValueChecker { - return check.NewValueChecker( - func(got interface{}) bool { return c.Pass(got.(string)) }, + +// FromString returns a check.Checker[any] that wraps the given +// check.Checker[string], so it can be used as a generic checker. +func FromString(c check.Checker[string]) check.Checker[any] { + return check.NewChecker( + func(got any) bool { return c.Pass(got.(string)) }, c.Explain, ) } -// FromInt returns a check.ValueChecker that wraps the given -// check.IntChecker, so it can be used as a generic checker. -func FromInt(c check.IntChecker) check.ValueChecker { - return check.NewValueChecker( - func(got interface{}) bool { return c.Pass(got.(int)) }, + +// FromInt returns a check.Checker[any] that wraps the given +// check.Checker[int], so it can be used as a generic checker. +func FromInt(c check.Checker[int]) check.Checker[any] { + return check.NewChecker( + func(got any) bool { return c.Pass(got.(int)) }, c.Explain, ) } -// FromFloat64 returns a check.ValueChecker that wraps the given -// check.Float64Checker, so it can be used as a generic checker. -func FromFloat64(c check.Float64Checker) check.ValueChecker { - return check.NewValueChecker( - func(got interface{}) bool { return c.Pass(got.(float64)) }, + +// FromFloat64 returns a check.Checker[any] that wraps the given +// check.Checker[float64], so it can be used as a generic checker. +func FromFloat64(c check.Checker[float64]) check.Checker[any] { + return check.NewChecker( + func(got any) bool { return c.Pass(got.(float64)) }, c.Explain, ) } -// FromDuration returns a check.ValueChecker that wraps the given -// check.DurationChecker, so it can be used as a generic checker. -func FromDuration(c check.DurationChecker) check.ValueChecker { - return check.NewValueChecker( - func(got interface{}) bool { return c.Pass(got.(time.Duration)) }, + +// FromDuration returns a check.Checker[any] that wraps the given +// check.Checker[time.Duration], so it can be used as a generic checker. +func FromDuration(c check.Checker[time.Duration]) check.Checker[any] { + return check.NewChecker( + func(got any) bool { return c.Pass(got.(time.Duration)) }, c.Explain, ) } -// FromContext returns a check.ValueChecker that wraps the given -// check.ContextChecker, so it can be used as a generic checker. -func FromContext(c check.ContextChecker) check.ValueChecker { - return check.NewValueChecker( - func(got interface{}) bool { return c.Pass(got.(context.Context)) }, + +// FromContext returns a check.Checker[any] that wraps the given +// check.Checker[context.Context], so it can be used as a generic checker. +func FromContext(c check.Checker[context.Context]) check.Checker[any] { + return check.NewChecker( + func(got any) bool { return c.Pass(got.(context.Context)) }, c.Explain, ) } -// FromHTTPHeader returns a check.ValueChecker that wraps the given -// check.HTTPHeaderChecker, so it can be used as a generic checker. -func FromHTTPHeader(c check.HTTPHeaderChecker) check.ValueChecker { - return check.NewValueChecker( - func(got interface{}) bool { return c.Pass(got.(http.Header)) }, + +// FromHTTPHeader returns a check.Checker[any] that wraps the given +// check.Checker[http.Header], so it can be used as a generic checker. +func FromHTTPHeader(c check.Checker[http.Header]) check.Checker[any] { + return check.NewChecker( + func(got any) bool { return c.Pass(got.(http.Header)) }, c.Explain, ) } -// FromHTTPRequest returns a check.ValueChecker that wraps the given -// check.HTTPRequestChecker, so it can be used as a generic checker. -func FromHTTPRequest(c check.HTTPRequestChecker) check.ValueChecker { - return check.NewValueChecker( - func(got interface{}) bool { return c.Pass(got.(*http.Request)) }, + +// FromHTTPRequest returns a check.Checker[any] that wraps the given +// check.Checker[*http.Request], so it can be used as a generic checker. +func FromHTTPRequest(c check.Checker[*http.Request]) check.Checker[any] { + return check.NewChecker( + func(got any) bool { return c.Pass(got.(*http.Request)) }, c.Explain, ) } -// FromHTTPResponse returns a check.ValueChecker that wraps the given -// check.HTTPResponseChecker, so it can be used as a generic checker. -func FromHTTPResponse(c check.HTTPResponseChecker) check.ValueChecker { - return check.NewValueChecker( - func(got interface{}) bool { return c.Pass(got.(*http.Response)) }, + +// FromHTTPResponse returns a check.Checker[any] that wraps the given +// check.Checker[*http.Response], so it can be used as a generic checker. +func FromHTTPResponse(c check.Checker[*http.Response]) check.Checker[any] { + return check.NewChecker( + func(got any) bool { return c.Pass(got.(*http.Response)) }, c.Explain, ) } -// Assert returns a check.ValueChecker that wraps the given -// check.Checker (such as check.IntChecker). + + + +// Assert returns a check.Checker[any] that wraps the given +// check.Checker[T]. // // It panics if checker is not a known checker type. For instance, // a custom checker that implements check.IntChecker will be successfully // converted, while a valid implementation of an unknown interface, // such as Complex128Checker, will panic. // For that matter, Cast should be used instead. -func Assert(knownChecker interface{}) check.ValueChecker { +func Assert(knownChecker any) check.Checker[any] { switch c := knownChecker.(type) { - case check.BoolChecker: - return FromBool(c) - case check.BytesChecker: - return FromBytes(c) - case check.StringChecker: - return FromString(c) - case check.IntChecker: - return FromInt(c) - case check.Float64Checker: - return FromFloat64(c) - case check.DurationChecker: - return FromDuration(c) - case check.ContextChecker: - return FromContext(c) - case check.HTTPHeaderChecker: - return FromHTTPHeader(c) - case check.HTTPRequestChecker: - return FromHTTPRequest(c) - case check.HTTPResponseChecker: - return FromHTTPResponse(c) - case check.ValueChecker: - return c + case check.Checker[bool]: + return FromBool(c) + case check.Checker[[]byte]: + return FromBytes(c) + case check.Checker[string]: + return FromString(c) + case check.Checker[int]: + return FromInt(c) + case check.Checker[float64]: + return FromFloat64(c) + case check.Checker[time.Duration]: + return FromDuration(c) + case check.Checker[context.Context]: + return FromContext(c) + case check.Checker[http.Header]: + return FromHTTPHeader(c) + case check.Checker[*http.Request]: + return FromHTTPRequest(c) + case check.Checker[*http.Response]: + return FromHTTPResponse(c) + case check.Checker[any]: + return c default: panic("assert from unknown checker type") } } -// AssertMany returns a slice of check.ValueChecker that wrap the given -// check.Checkers (such as check.IntChecker). +// AssertMany returns a slice of check.Checker[any] that wrap the given +// check.Checker[T]. // // It panics if any checker is not a known checker type. See Assert // for further documentation. -func AssertMany(knownCheckers ...interface{}) []check.ValueChecker { - valueCheckers := []check.ValueChecker{} +func AssertMany(knownCheckers ...any) []check.Checker[any] { + valueCheckers := []check.Checker[any]{} for _, c := range knownCheckers { valueCheckers = append(valueCheckers, Assert(c)) } diff --git a/checkconv/assert_test.go b/checkconv/assert_test.go index 1703744..dd6a5f8 100644 --- a/checkconv/assert_test.go +++ b/checkconv/assert_test.go @@ -58,8 +58,8 @@ func TestAssert(t *testing.T) { func TestAssertMany(t *testing.T) { t.Run("all provided checkers", func(t *testing.T) { testcases := []struct { - checker interface{} - in interface{} + checker any + in any }{ {checker: check.Bool.Is(true), in: true}, {checker: check.Bytes.Is([]byte{'a'}), in: []byte{'a'}}, @@ -77,7 +77,7 @@ func TestAssertMany(t *testing.T) { {checker: check.Struct.NotZero(), in: struct{ n int }{1}}, } - providedCheckers := func() (checkers []interface{}) { + providedCheckers := func() (checkers []any) { for _, tc := range testcases { checkers = append(checkers, tc.checker) } @@ -104,8 +104,8 @@ func TestAssertMany(t *testing.T) { }) t.Run("custom checkers known type", func(t *testing.T) { - knownCheckers := []interface{}{ - check.Value.Custom("", func(_ interface{}) bool { return true }), + knownCheckers := []any{ + check.Value.Custom("", func(_ any) bool { return true }), check.NewIntChecker(isEven, validExplainFunc), validCheckerInt{}, validCheckerInterface{}, @@ -118,7 +118,7 @@ func TestAssertMany(t *testing.T) { t.Run("custom checkers unknown type", func(t *testing.T) { defer testutil.AssertPanicMessage(t, "assert from unknown checker type") - unknownCheckers := []interface{}{validCheckerFloat32{}} + unknownCheckers := []any{validCheckerFloat32{}} checkconv.AssertMany(unknownCheckers...) }) } diff --git a/checkconv/cast.go b/checkconv/cast.go index b6baa12..5704288 100644 --- a/checkconv/cast.go +++ b/checkconv/cast.go @@ -6,7 +6,7 @@ import ( "github.com/drykit-go/testx/check" ) -// Cast returns a check.ValueChecker built upon the given checker +// Cast returns a check.Checker[any] built upon the given checker // and a bool indicating whether it was successful. // // Contrary to Assert, it can perform conversions from checker types @@ -15,20 +15,20 @@ import ( // // However, Assert should be the first choice for a known checker type // as Cast is about 10 times slower. -func Cast(anyChecker interface{}) (c check.ValueChecker, ok bool) { +func Cast(anyChecker any) (c check.Checker[any], ok bool) { if !IsChecker(anyChecker) { return } v := reflect.ValueOf(anyChecker) - c = check.NewValueChecker( - func(got interface{}) bool { + c = check.NewChecker( + func(got any) bool { gotv := reflect.ValueOf(got) return v.MethodByName(signaturePass.Name). Call([]reflect.Value{gotv})[0]. Bool() }, - func(label string, got interface{}) string { + func(label string, got any) string { labv := reflect.ValueOf(label) gotv := reflect.ValueOf(got) return v.MethodByName(signatureExpl.Name). @@ -47,7 +47,7 @@ func Cast(anyChecker interface{}) (c check.ValueChecker, ok bool) { // An invalid checker in the args list is silently dismissed, // this the resulting checkers length can be inferior to the number of args // if ok === false. -func CastMany(anyCheckers ...interface{}) (checkers []check.ValueChecker, ok bool) { +func CastMany(anyCheckers ...any) (checkers []check.Checker[any], ok bool) { ok = true for _, in := range anyCheckers { c, valid := Cast(in) diff --git a/checkconv/cast_test.go b/checkconv/cast_test.go index 07d4125..7f8ab9c 100644 --- a/checkconv/cast_test.go +++ b/checkconv/cast_test.go @@ -8,8 +8,8 @@ import ( ) type checkerTestcase struct { - checker interface{} - in interface{} + checker any + in any expPass bool expExpl string } @@ -30,7 +30,7 @@ func TestCast(t *testing.T) { expExpl: "value:\nexp in range [41:43]\ngot -1", }, { - checker: check.Value.Custom("", func(got interface{}) bool { return true }), + checker: check.Value.Custom("", func(got any) bool { return true }), in: "", expPass: true, expExpl: "", @@ -122,7 +122,7 @@ func assertCastable(t *testing.T, tc checkerTestcase) { assertValidValueChecker(t, c, tc) } -func assertNotCastable(t *testing.T, badChecker interface{}) { +func assertNotCastable(t *testing.T, badChecker any) { t.Helper() got, ok := checkconv.Cast(badChecker) if ok { diff --git a/checkconv/checkconv_test.go b/checkconv/checkconv_test.go index 8c31fa9..65fa06a 100644 --- a/checkconv/checkconv_test.go +++ b/checkconv/checkconv_test.go @@ -16,45 +16,45 @@ func (onlyPasser) Pass(int) bool { return true } type onlyExplainer struct{} -func (onlyExplainer) PassX(int) bool { return true } -func (onlyExplainer) Explain(string, interface{}) string { return "" } +func (onlyExplainer) PassX(int) bool { return true } +func (onlyExplainer) Explain(string, any) string { return "" } type badPasser struct{} -func (badPasser) Pass(int) int { return 0 } -func (badPasser) Explain(string, interface{}) string { return "" } +func (badPasser) Pass(int) int { return 0 } +func (badPasser) Explain(string, any) string { return "" } type badExplainerIn struct{} -func (badExplainerIn) Pass(int) bool { return true } -func (badExplainerIn) Explain(interface{}, interface{}) string { return "" } +func (badExplainerIn) Pass(int) bool { return true } +func (badExplainerIn) Explain(any, any) string { return "" } type badExplainerOut struct{} -func (badExplainerOut) Pass(int) bool { return true } -func (badExplainerOut) Explain(string, interface{}) interface{} { return "" } +func (badExplainerOut) Pass(int) bool { return true } +func (badExplainerOut) Explain(string, any) any { return "" } type checkerAsFields struct { Pass func(int) bool - Explain func(string, interface{}) string + Explain func(string, any) string } type validCheckerInt struct{} -func (validCheckerInt) Pass(int) bool { return true } -func (validCheckerInt) Explain(string, interface{}) string { return "ok" } +func (validCheckerInt) Pass(int) bool { return true } +func (validCheckerInt) Explain(string, any) string { return "ok" } type validCheckerFloat32 struct{} -func (validCheckerFloat32) Pass(float32) bool { return true } -func (validCheckerFloat32) Explain(string, interface{}) string { return "ok" } +func (validCheckerFloat32) Pass(float32) bool { return true } +func (validCheckerFloat32) Explain(string, any) string { return "ok" } type validCheckerInterface struct{} -func (validCheckerInterface) Pass(interface{}) bool { return true } -func (validCheckerInterface) Explain(string, interface{}) string { return "ok" } +func (validCheckerInterface) Pass(any) bool { return true } +func (validCheckerInterface) Explain(string, any) string { return "ok" } -var badCheckers = []interface{}{ +var badCheckers = []any{ -1, "hi", errors.New(""), @@ -65,17 +65,17 @@ var badCheckers = []interface{}{ badExplainerOut{}, checkerAsFields{ Pass: func(int) bool { return true }, - Explain: func(string, interface{}) string { return "" }, + Explain: func(string, any) string { return "" }, }, } -var goodCheckers = []interface{}{ +var goodCheckers = []any{ validCheckerInt{}, validCheckerFloat32{}, validCheckerInterface{}, } -func validExplainFunc(_ string, _ interface{}) string { +func validExplainFunc(_ string, _ any) string { return "ok" } @@ -83,7 +83,7 @@ func validExplainFunc(_ string, _ interface{}) string { func isEven(n int) bool { return n&1 == 0 } // isEvenExpl is a dummy explainFunc for custom checkers -func isEvenExpl(_ string, got interface{}) string { +func isEvenExpl(_ string, got any) string { return fmt.Sprintf("expect value to be even, got %v", got) } diff --git a/checkconv/gen.go b/checkconv/gen.go index 3345dc4..4d36fc4 100644 --- a/checkconv/gen.go +++ b/checkconv/gen.go @@ -7,7 +7,7 @@ package checkconv // // func FromN(c check.NChecker) check.ValueChecker { // return check.NewValueCheck( -// func(got interface{}) bool { return c.Pass(got.(T) }, +// func(got any) bool { return c.Pass(got.(T) }, // c.Explain, // ) // } diff --git a/checkconv/ischecker.go b/checkconv/ischecker.go index da71d32..fcf9735 100644 --- a/checkconv/ischecker.go +++ b/checkconv/ischecker.go @@ -23,14 +23,14 @@ var ( // IsChecker returns true if the provided value is a valid checker. // A valid checker is any type exposing two methods: // - Pass(got T) bool -// - Explain(label string, got interface{}) string +// - Explain(label string, got any) string // Any custom implementation is considered valid whether or not it uses // the package check. // // Note: The nature of Pass(got T) method, whose signature depend on the // type of the tested value, prevents using a regular interface to identify // a checker, hence the need of this helper. -func IsChecker(in interface{}) bool { +func IsChecker(in any) bool { v := reflect.ValueOf(in) return isPasser(v) && isExplainer(v) } diff --git a/checkconv/ischecker_test.go b/checkconv/ischecker_test.go index e9a3bf8..1fbbd0a 100644 --- a/checkconv/ischecker_test.go +++ b/checkconv/ischecker_test.go @@ -8,7 +8,7 @@ import ( func TestIsChecker(t *testing.T) { t.Run("invalid checkers", func(t *testing.T) { - values := append([]interface{}{ + values := append([]any{ "a string", 42, func(int) bool { return true }, diff --git a/example_httphandler_test.go b/example_httphandler_test.go index d133611..87c2859 100644 --- a/example_httphandler_test.go +++ b/example_httphandler_test.go @@ -55,7 +55,7 @@ func ExampleHTTPHandler_middlewares() { // withContextValue middleware attaches the input key-val pair // to the request context. - withContextValue := func(key, val interface{}) func(http.Handler) http.Handler { + withContextValue := func(key, val any) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), key, val) diff --git a/example_table_test.go b/example_table_test.go index 3187330..28637ce 100644 --- a/example_table_test.go +++ b/example_table_test.go @@ -47,9 +47,9 @@ func ExampleTable_dyadic() { // of 42.0 at position 0 (param x) for all cases. testx.Table(divide).Config(testx.TableConfig{ // Positions start at 0 - InPos: 1, // Case.In injected in param position 1 (y) - OutPos: 1, // Case.Exp compared to return value position 1 (error value) - FixedArgs: []interface{}{0: 42.0}, // param 0 (x) set to 42.0 for all cases + InPos: 1, // Case.In injected in param position 1 (y) + OutPos: 1, // Case.Exp compared to return value position 1 (error value) + FixedArgs: []any{0: 42.0}, // param 0 (x) set to 42.0 for all cases }).Cases([]testx.Case{ {In: 1.0, Exp: testx.ExpNil}, // divide(42.0, 1.0) -> (_, nil) {In: 0.0, Exp: errors.New("division by 0")}, // divide(42.0, 0.0) -> (_, err) diff --git a/example_value_test.go b/example_value_test.go index b3089e1..cc34faa 100644 --- a/example_value_test.go +++ b/example_value_test.go @@ -6,7 +6,6 @@ import ( "github.com/drykit-go/testx" "github.com/drykit-go/testx/check" - "github.com/drykit-go/testx/checkconv" ) /* @@ -19,9 +18,9 @@ func ExampleValueRunner() { // Run Value test via Run(t *testing.T) testx.Value(get42()). - Exp(42). // pass - Not(3, "hello"). // pass - Pass(checkconv.Assert(check.Int.InRange(41, 43))). // pass + Exp(42). // pass + Not(3, -1). // pass + Pass(check.Int.InRange(41, 43)). // pass Run(t) } @@ -30,10 +29,10 @@ func ExampleValueRunner_dryRun() { results := testx.Value(get42()). Exp(42). // pass - Pass(checkconv.AssertMany( + Pass( check.Int.GTE(41), // pass check.Int.InRange(-1, 1), // fail - )...). + ). DryRun() fmt.Println(results.Passed()) diff --git a/go.mod b/go.mod index b053f26..14c78f6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/drykit-go/testx -go 1.16 +go 1.18 require ( github.com/drykit-go/cond v0.1.0 diff --git a/internal/fmtexpl/fmtexpl.go b/internal/fmtexpl/fmtexpl.go index f984363..23f6c1f 100644 --- a/internal/fmtexpl/fmtexpl.go +++ b/internal/fmtexpl/fmtexpl.go @@ -8,14 +8,14 @@ import ( ) // Default computes and return an explain string in the default format. -func Default(label string, exp, got interface{}) string { +func Default(label string, exp, got any) string { return fmt.Sprintf("%s:\nexp %v\ngot %v", label, exp, got) } // Pretty computes and return an explain string in a pretty output. // Compatibility is not guaranteed, usage should be restricted to // local tests -- not in checkers explain func. -func Pretty(label string, exp, got interface{}) string { +func Pretty(label string, exp, got any) string { return "❌ " + Default(label, exp, got) } diff --git a/internal/gen/gen.go b/internal/gen/gen.go index deecf01..635a7f5 100644 --- a/internal/gen/gen.go +++ b/internal/gen/gen.go @@ -18,7 +18,7 @@ var tplFuncs = template.FuncMap{ type config struct { name, tpl, out string tplFuncs template.FuncMap - data interface{} + data any } // Types generates checkers declarations in packages check and checkconv @@ -86,7 +86,11 @@ func generate(cfg config) error { return err } - return runFormatter(cfg.out) + // FIXME: make goimports work with generics syntax + if err := runFormatter(cfg.out); err != nil { + fmt.Printf("goimports returned the following error (likely due to unsupported generics syntax):\n%s\n", err) + } + return nil } func newTemplate(name, src string, funcMap template.FuncMap) (*template.Template, error) { diff --git a/internal/gen/templates/assert.gotmpl b/internal/gen/templates/assert.gotmpl index 88bbff6..e37f238 100644 --- a/internal/gen/templates/assert.gotmpl +++ b/internal/gen/templates/assert.gotmpl @@ -1,32 +1,42 @@ // Package checkconv provides functions to convert typed checkers // into generic ones. +// +// TODO: delete package checkconv package checkconv +import ( + "context" + "net/http" + "time" + + "github.com/drykit-go/testx/check" +) + {{range . -}} {{if ne .N "Value"}} -// From{{.N}} returns a check.ValueChecker that wraps the given -// check.{{.N}}Checker, so it can be used as a generic checker. -func From{{.N}}(c check.{{.N}}Checker) check.ValueChecker { - return check.NewValueChecker( - func(got interface{}) bool { return c.Pass(got.({{.T}})) }, +// From{{.N}} returns a check.Checker[any] that wraps the given +// check.Checker[{{.T}}], so it can be used as a generic checker. +func From{{.N}}(c check.Checker[{{.T}}]) check.Checker[any] { + return check.NewChecker( + func(got any) bool { return c.Pass(got.({{.T}})) }, c.Explain, ) } {{end}} {{end}} -// Assert returns a check.ValueChecker that wraps the given -// check.Checker (such as check.IntChecker). +// Assert returns a check.Checker[any] that wraps the given +// check.Checker[T]. // // It panics if checker is not a known checker type. For instance, // a custom checker that implements check.IntChecker will be successfully // converted, while a valid implementation of an unknown interface, // such as Complex128Checker, will panic. // For that matter, Cast should be used instead. -func Assert(knownChecker interface{}) check.ValueChecker { +func Assert(knownChecker any) check.Checker[any] { switch c := knownChecker.(type) { {{range . -}} - case check.{{.N}}Checker: + case check.Checker[{{.T}}]: {{if ne .N "Value" -}} return From{{.N}}(c) {{else -}} @@ -38,13 +48,13 @@ func Assert(knownChecker interface{}) check.ValueChecker { } } -// AssertMany returns a slice of check.ValueChecker that wrap the given -// check.Checkers (such as check.IntChecker). +// AssertMany returns a slice of check.Checker[any] that wrap the given +// check.Checker[T]. // // It panics if any checker is not a known checker type. See Assert // for further documentation. -func AssertMany(knownCheckers ...interface{}) []check.ValueChecker { - valueCheckers := []check.ValueChecker{} +func AssertMany(knownCheckers ...any) []check.Checker[any] { + valueCheckers := []check.Checker[any]{} for _, c := range knownCheckers { valueCheckers = append(valueCheckers, Assert(c)) } diff --git a/internal/gen/templates/check.gotmpl b/internal/gen/templates/check.gotmpl index 4620413..8970922 100644 --- a/internal/gen/templates/check.gotmpl +++ b/internal/gen/templates/check.gotmpl @@ -13,7 +13,7 @@ type ( // ExplainFunc is the required method to implement Explainer. // It returns a string explaining why the gotten value failed the check. // The label provides some context, such as "response code". - ExplainFunc func(label string, got interface{}) string + ExplainFunc func(label string, got any) string ) type ( @@ -24,7 +24,7 @@ type ( {{end}} // Explainer provides a method Explain describing the reason of a failed check. - Explainer interface { Explain(label string, got interface{}) string } + Explainer interface { Explain(label string, got any) string } ) type ( diff --git a/internal/gen/templates/checkers.gotmpl b/internal/gen/templates/checkers.gotmpl index bd8da4d..0e94e58 100644 --- a/internal/gen/templates/checkers.gotmpl +++ b/internal/gen/templates/checkers.gotmpl @@ -7,7 +7,7 @@ type baseChecker struct { // Explain returns a string explaining the reason of a failed check // for the gotten value. -func (c baseChecker) Explain(label string, got interface{}) string { +func (c baseChecker) Explain(label string, got any) string { return c.explFunc(label, got) } diff --git a/internal/gen/types.go b/internal/gen/types.go index bf55635..2442c2c 100644 --- a/internal/gen/types.go +++ b/internal/gen/types.go @@ -16,5 +16,5 @@ var types = []struct { {N: "HTTPHeader", T: "http.Header"}, {N: "HTTPRequest", T: "*http.Request"}, {N: "HTTPResponse", T: "*http.Response"}, - {N: "Value", T: "interface{}"}, + {N: "Value", T: "any"}, } diff --git a/internal/reflectutil/func.go b/internal/reflectutil/func.go index 804bba3..3c1f845 100644 --- a/internal/reflectutil/func.go +++ b/internal/reflectutil/func.go @@ -14,7 +14,7 @@ func IsFunc(v reflect.Value) bool { // FuncName returns the name of the given func prefixed with // the name of the package it is from. It panics if f is not a func. -func FuncName(fn interface{}) string { +func FuncName(fn any) string { return funcName(reflect.ValueOf(fn)) } @@ -38,13 +38,13 @@ type Func struct { // Call calls Func's underlying func with given args and returns the results // as a slice of empty interfaces. -func (f *Func) Call(args []interface{}) []interface{} { +func (f *Func) Call(args []any) []any { return UnwrapValues(f.Value.Call(WrapValues(args))) } // NewFunc returns a *Func from the given func input, or a non-nil error // if fn's kind is not reflect.Func.' -func NewFunc(fn interface{}) (*Func, error) { +func NewFunc(fn any) (*Func, error) { fval := reflect.ValueOf(fn) if !IsFunc(fval) { return nil, fmt.Errorf("%w: got %v", ErrNotAFunc, fn) diff --git a/internal/reflectutil/func_test.go b/internal/reflectutil/func_test.go index db45c27..29c65aa 100644 --- a/internal/reflectutil/func_test.go +++ b/internal/reflectutil/func_test.go @@ -69,7 +69,7 @@ func TestNewFunc(t *testing.T) { func TestFunc_Call(t *testing.T) { f, _ := reflectutil.NewFunc(ValidFunc) - if got, exp := f.Call([]interface{}{42})[0], 42; got != exp { + if got, exp := f.Call([]any{42})[0], 42; got != exp { t.Errorf("exp %v, got %v", exp, got) } } diff --git a/internal/reflectutil/reflectutil.go b/internal/reflectutil/reflectutil.go index 5ae8876..91571b8 100644 --- a/internal/reflectutil/reflectutil.go +++ b/internal/reflectutil/reflectutil.go @@ -9,18 +9,18 @@ import ( const AnyKind reflect.Kind = 27 // IsZero returns true if v is a zero value. -func IsZero(v interface{}) bool { +func IsZero(v any) bool { return reflect.ValueOf(v).IsZero() } // CallUnwrap calls fn with args and returns the output values -// as []interface{}. -func CallUnwrap(fval reflect.Value, args []interface{}) (output []interface{}) { +// as []any. +func CallUnwrap(fval reflect.Value, args []any) (output []any) { return UnwrapValues(fval.Call(WrapValues(args))) } // WrapValues wraps the input values into reflect.Values. -func WrapValues(values []interface{}) (wrapped []reflect.Value) { +func WrapValues(values []any) (wrapped []reflect.Value) { wrapped = make([]reflect.Value, len(values)) for i, v := range values { wrapped[i] = reflect.ValueOf(v) @@ -29,8 +29,8 @@ func WrapValues(values []interface{}) (wrapped []reflect.Value) { } // UnwrapValues unwraps reflect.Values to empty interfaces. -func UnwrapValues(wrapped []reflect.Value) (values []interface{}) { - values = make([]interface{}, len(wrapped)) +func UnwrapValues(wrapped []reflect.Value) (values []any) { + values = make([]any, len(wrapped)) for i, w := range wrapped { values[i] = w.Interface() } @@ -38,7 +38,7 @@ func UnwrapValues(wrapped []reflect.Value) (values []interface{}) { } // MustBeOfKind panics if v's kind is not exp. -func MustBeOfKind(v interface{}, exp reflect.Kind) { +func MustBeOfKind(v any, exp reflect.Kind) { mustBeOfKind(reflect.ValueOf(v), exp) } diff --git a/internal/reflectutil/reflectutil_test.go b/internal/reflectutil/reflectutil_test.go index 53c222e..33bc8a1 100644 --- a/internal/reflectutil/reflectutil_test.go +++ b/internal/reflectutil/reflectutil_test.go @@ -10,9 +10,9 @@ import ( func TestIsZero(t *testing.T) { t.Run("zeros", func(t *testing.T) { - var zeroMap map[string]interface{} + var zeroMap map[string]any var zeroSlice []int - zeros := []interface{}{ + zeros := []any{ 0, "", 0i + 0, zeroMap, zeroSlice, struct{ n int }{n: 0}, } @@ -24,7 +24,7 @@ func TestIsZero(t *testing.T) { }) t.Run("non zeros", func(t *testing.T) { - nozeros := []interface{}{ + nozeros := []any{ 1, "hi", 0i + 1, map[int]bool{}, []float32{}, struct{ n int }{n: -1}, } @@ -41,8 +41,8 @@ func TestCallUnwrap(t *testing.T) { return y, x } fval := reflect.ValueOf(swap) - got := reflectutil.CallUnwrap(fval, []interface{}{-1., 1.}) - exp := []interface{}{1., -1.} + got := reflectutil.CallUnwrap(fval, []any{-1., 1.}) + exp := []any{1., -1.} if !reflect.DeepEqual(got, exp) { t.Errorf("unexpected output: exp %v, got %v", exp, got) } @@ -51,9 +51,9 @@ func TestCallUnwrap(t *testing.T) { func TestMustBeOfKind(t *testing.T) { t.Run("bad kind", func(t *testing.T) { const kind = reflect.Int8 - badValues := []interface{}{"hi", 1, true, []int8{1}} + badValues := []any{"hi", 1, true, []int8{1}} for _, v := range badValues { - func(v interface{}) { + func(v any) { defer testutil.AssertPanicMessage(t, "expect kind int8, got "+reflect.ValueOf(v).Kind().String(), ) @@ -64,7 +64,7 @@ func TestMustBeOfKind(t *testing.T) { t.Run("good kind", func(_ *testing.T) { for _, tc := range []struct { - val interface{} + val any exp reflect.Kind }{ {val: 42, exp: reflect.Int}, diff --git a/internal/testutil/testutil.go b/internal/testutil/testutil.go index 9b94f94..d48840c 100644 --- a/internal/testutil/testutil.go +++ b/internal/testutil/testutil.go @@ -31,7 +31,7 @@ func AssertPanic(t *testing.T) { assertPanicked(t, r, "", false) } -func assertPanicked(t *testing.T, rec interface{}, msg string, checkmsg bool) { +func assertPanicked(t *testing.T, rec any, msg string, checkmsg bool) { t.Helper() if rec == nil { t.Error("expected to panic but did not") diff --git a/runner.go b/runner.go index 061bf39..7d57469 100644 --- a/runner.go +++ b/runner.go @@ -7,7 +7,7 @@ import ( ) type ( - gottype interface{} + gottype any getfunc func() gottype baseCheck struct { @@ -26,7 +26,7 @@ func (r *baseRunner) addCheck(bc baseCheck) { r.checks = append(r.checks, bc) } -func (r *baseRunner) addChecks(label string, get getfunc, checkers []check.ValueChecker) { +func (r *baseRunner) addChecks(label string, get getfunc, checkers []check.Checker[any]) { for _, c := range checkers { r.addCheck(baseCheck{label: label, get: get, checker: c}) } @@ -59,7 +59,7 @@ func (r *baseRunner) dryRun() baseResults { return res } -func (r *baseRunner) explainCheck(bc baseCheck, got interface{}, passed bool) string { +func (r *baseRunner) explainCheck(bc baseCheck, got any, passed bool) string { if passed { return "" } diff --git a/runner_httphandler.go b/runner_httphandler.go index f70fc94..ff356a3 100644 --- a/runner_httphandler.go +++ b/runner_httphandler.go @@ -28,7 +28,7 @@ func (r *httpHandlerRunner) WithRequest(request *http.Request) HTTPHandlerRunner } } -func (r *httpHandlerRunner) Duration(checkers ...check.DurationChecker) HTTPHandlerRunner { +func (r *httpHandlerRunner) Duration(checkers ...check.Checker[time.Duration]) HTTPHandlerRunner { for _, c := range checkers { r.addCheck(baseCheck{ label: "handling duration", @@ -39,7 +39,7 @@ func (r *httpHandlerRunner) Duration(checkers ...check.DurationChecker) HTTPHand return r } -func (r *httpHandlerRunner) Request(checkers ...check.HTTPRequestChecker) HTTPHandlerRunner { +func (r *httpHandlerRunner) Request(checkers ...check.Checker[*http.Request]) HTTPHandlerRunner { for _, c := range checkers { r.addCheck(baseCheck{ label: "http request", @@ -50,7 +50,7 @@ func (r *httpHandlerRunner) Request(checkers ...check.HTTPRequestChecker) HTTPHa return r } -func (r *httpHandlerRunner) Response(checkers ...check.HTTPResponseChecker) HTTPHandlerRunner { +func (r *httpHandlerRunner) Response(checkers ...check.Checker[*http.Response]) HTTPHandlerRunner { for _, c := range checkers { r.addCheck(baseCheck{ label: "http response", diff --git a/runner_httphandler_test.go b/runner_httphandler_test.go index 9a0abf6..fd74a37 100644 --- a/runner_httphandler_test.go +++ b/runner_httphandler_test.go @@ -13,7 +13,7 @@ import ( func TestHTTPHandlerRunner(t *testing.T) { hf := func(w http.ResponseWriter, _ *http.Request) { - b, _ := json.Marshal(map[string]interface{}{"message": "Hello, World!"}) + b, _ := json.Marshal(map[string]any{"message": "Hello, World!"}) w.WriteHeader(200) w.Write(b) } @@ -74,8 +74,8 @@ func TestHTTPHandlerRunner(t *testing.T) { nFailed: 2, nChecks: 3, checks: []testx.CheckResult{ - {Passed: false, Reason: "http request:\nexp context to pass ContextChecker\ngot explanation: context:\nexp to have keys [abc]\ngot keys not set"}, - {Passed: false, Reason: "http response:\nexp status code to pass IntChecker\ngot explanation: status code:\nexp -1\ngot 200"}, + {Passed: false, Reason: "http request:\nexp context to pass Checker[context.Context]\ngot explanation: context:\nexp to have keys [abc]\ngot keys not set"}, + {Passed: false, Reason: "http response:\nexp status code to pass Checker[int]\ngot explanation: status code:\nexp -1\ngot 200"}, {Passed: true, Reason: ""}, }, }, diff --git a/runner_table.go b/runner_table.go index c5b3837..2a80657 100644 --- a/runner_table.go +++ b/runner_table.go @@ -23,7 +23,7 @@ type Case struct { Lab string // In is the input value injected in the tested func. - In interface{} + In any // Exp is the value expected to be returned when calling the tested func. // If Case.Exp == nil (zero value), no check is added. This is a necessary @@ -36,14 +36,14 @@ type Case struct { // {In: 123, Exp: nil}, // Exp == nil, no Exp check added // {In: 123, Exp: testx.ExpNil}, // Exp == ExpNil, expect nil value // }) - Exp interface{} + Exp any // Not is a slice of values expected not to be returned by the tested func. - Not []interface{} + Not []any // Pass is a slice of check.ValueChecker that the return value of the // tested func is expected to pass. - Pass []check.ValueChecker + Pass []check.Checker[any] } // TableConfig is configuration object for TableRunner. @@ -85,10 +85,10 @@ type TableConfig struct { FixedArgs Args } -// Args is an alias to []interface{}. -type Args []interface{} +// Args is an alias to []any. +type Args []any -func (args Args) replaceAt(pos int, arg interface{}) Args { +func (args Args) replaceAt(pos int, arg any) Args { if pos >= len(args) { log.Panic("Args.replaceAt(i, v): i is out of range") } @@ -111,7 +111,7 @@ type tableRunner struct { baseRunner config TableConfig - get func(in interface{}) gottype + get func(in any) gottype rfunc *reflectutil.Func args Args @@ -138,7 +138,7 @@ func (r *tableRunner) setGetFunc() error { return err } - r.get = func(in interface{}) gottype { + r.get = func(in any) gottype { pin, pout := r.config.InPos, r.config.OutPos r.args = args.replaceAt(pin, in) return r.rfunc.Call(r.args)[pout] @@ -189,7 +189,7 @@ func (r *tableRunner) Config(cfg TableConfig) TableRunner { return r } -func (r *tableRunner) setRfunc(in interface{}) error { +func (r *tableRunner) setRfunc(in any) error { rfunc, err := reflectutil.NewFunc(in) if err != nil { return fmt.Errorf("Table(func): %w", err) @@ -240,7 +240,7 @@ func (r *tableRunner) makeFixedArgs(rfunc *reflectutil.Func, cfg TableConfig) (A } } -func newTableRunner(testedFunc interface{}) TableRunner { +func newTableRunner(testedFunc any) TableRunner { r := &tableRunner{} cond.PanicOnErr(r.setRfunc(testedFunc)) return r diff --git a/runner_table_test.go b/runner_table_test.go index 7697460..7757740 100644 --- a/runner_table_test.go +++ b/runner_table_test.go @@ -14,7 +14,7 @@ import ( // Tests -var expFixedArgs = map[string]interface{}{ +var expFixedArgs = map[string]any{ "a0": []byte("arg0"), "a2": map[rune][][]float64{'π': {[]float64{3.14}}}, } @@ -49,7 +49,7 @@ func TestTableRunner(t *testing.T) { t.Run("multiple in single out", func(t *testing.T) { testx.Table(evenMultipleIn).Config(testx.TableConfig{ InPos: inPos, - FixedArgs: []interface{}{a0, a2}, // len(FixedArgs) == nparams-1 + FixedArgs: []any{a0, a2}, // len(FixedArgs) == nparams-1 }). Cases(cases). Run(t) @@ -59,7 +59,7 @@ func TestTableRunner(t *testing.T) { testx.Table(evenMultipleInOut).Config(testx.TableConfig{ InPos: inPos, OutPos: outPos, - FixedArgs: []interface{}{0: a0, 2: a2}, // len(FixedArgs) == nparams + FixedArgs: []any{0: a0, 2: a2}, // len(FixedArgs) == nparams }). Cases(cases). Run(t) @@ -75,7 +75,7 @@ func TestTableRunner(t *testing.T) { }) t.Run("expect nil value", func(t *testing.T) { - runner := testx.Table(func(wantnil bool) interface{} { + runner := testx.Table(func(wantnil bool) any { if wantnil { return nil } @@ -96,8 +96,8 @@ func TestTableRunner(t *testing.T) { t.Run("Case.Not checks", func(t *testing.T) { results := testx.Table(func(n int) int { return n }). Cases([]testx.Case{ - {In: 0, Not: []interface{}{-1, 1}}, // pass - {In: 0, Not: []interface{}{0}}, // fail + {In: 0, Not: []any{-1, 1}}, // pass + {In: 0, Not: []any{0}}, // fail }). DryRun() @@ -139,7 +139,7 @@ func TestTableRunner(t *testing.T) { func TestExpNil(t *testing.T) { t.Run("Exp=ExpNil expects nil", func(t *testing.T) { - f := func(int) interface{} { return nil } + f := func(int) any { return nil } res := testx.Table(f).Cases([]testx.Case{ {In: 0, Exp: testx.ExpNil}, }).DryRun() @@ -167,7 +167,7 @@ func TestExpNil(t *testing.T) { }) t.Run("Exp=0 does not expect nil", func(t *testing.T) { - f := func(int) interface{} { return nil } + f := func(int) any { return nil } res := testx.Table(f).Cases([]testx.Case{ {In: 0, Exp: 0}, }).DryRun() @@ -251,7 +251,7 @@ func evenSingle(a1 int) bool { return a1&1 == 0 } -func evenMultipleOut(a1 int) (string, interface{}, bool, int) { +func evenMultipleOut(a1 int) (string, any, bool, int) { return "", struct{}{}, evenSingle(a1), -1 } @@ -260,7 +260,7 @@ func evenMultipleIn(a0 []byte, a1 int, a2 map[rune][][]float64) bool { return evenSingle(a1) } -func evenMultipleInOut(a0 []byte, a1 int, a2 map[rune][][]float64) (string, interface{}, bool, int) { +func evenMultipleInOut(a0 []byte, a1 int, a2 map[rune][][]float64) (string, any, bool, int) { panicOnUnexpectedArgs(a0, a2) return evenMultipleOut(a1) } diff --git a/runner_test.go b/runner_test.go index a962d13..bd22b85 100644 --- a/runner_test.go +++ b/runner_test.go @@ -32,8 +32,8 @@ func assertEqualBaseResults(t *testing.T, res testx.Resulter, exp baseResults) { // Validate remaining fields for _, fv := range []struct { lab string - got interface{} - exp interface{} + got any + exp any }{ {lab: "passed", got: got.passed, exp: exp.passed}, {lab: "failed", got: got.failed, exp: exp.failed}, @@ -74,7 +74,7 @@ func failWithErrors(t *testing.T, label string, errs ...string) { t.Errorf("bad results: %s\n%s", label, strings.Join(errs, "\n")) } -func failBadResults(t *testing.T, label string, got, exp interface{}) { +func failBadResults(t *testing.T, label string, got, exp any) { t.Helper() t.Errorf("bad results: %s\nexp %#v\ngot %#v", label, exp, got) } diff --git a/runner_value.go b/runner_value.go index 34b446b..db0bf88 100644 --- a/runner_value.go +++ b/runner_value.go @@ -4,42 +4,52 @@ import ( "testing" "github.com/drykit-go/testx/check" + "github.com/drykit-go/testx/checkconv" ) -var _ ValueRunner = (*valueRunner)(nil) +var _ ValueRunner[any] = (*valueRunner[any])(nil) -type valueRunner struct { +type valueRunner[T any] struct { baseRunner - value interface{} + value T } -func (r *valueRunner) Run(t *testing.T) { +func (r *valueRunner[T]) Run(t *testing.T) { t.Helper() r.run(t) } -func (r *valueRunner) DryRun() Resulter { +func (r *valueRunner[T]) DryRun() Resulter { return r.dryRun() } -func (r *valueRunner) Exp(value interface{}) ValueRunner { +func (r *valueRunner[T]) Exp(value T) ValueRunner[T] { r.addValueCheck(check.Value.Is(value)) return r } -func (r *valueRunner) Not(values ...interface{}) ValueRunner { - r.addValueCheck(check.Value.Not(values...)) +func (r *valueRunner[T]) Not(values ...T) ValueRunner[T] { + // TODO: find a way to cast properly + valuesitf := []any{} + for _, v := range values { + valuesitf = append(valuesitf, v) + } + r.addValueCheck(check.Value.Not(valuesitf...)) return r } -func (r *valueRunner) Pass(checkers ...check.ValueChecker) ValueRunner { +func (r *valueRunner[T]) Pass(checkers ...check.Checker[T]) ValueRunner[T] { for _, c := range checkers { - r.addValueCheck(c) + cc, ok := checkconv.Cast(c) + if !ok { + panic("ValueRunner.Pass: bad conversion") + } + r.addValueCheck(cc) } return r } -func (r *valueRunner) addValueCheck(c check.ValueChecker) { +func (r *valueRunner[T]) addValueCheck(c check.Checker[any]) { r.addCheck(baseCheck{ label: "value", get: func() gottype { return r.value }, @@ -47,6 +57,6 @@ func (r *valueRunner) addValueCheck(c check.ValueChecker) { }) } -func newValueRunner(v interface{}) ValueRunner { - return &valueRunner{value: v} +func newValueRunner[T any](v T) ValueRunner[T] { + return &valueRunner[T]{value: v} } diff --git a/runner_value_test.go b/runner_value_test.go index ec971e3..fac93f4 100644 --- a/runner_value_test.go +++ b/runner_value_test.go @@ -5,27 +5,27 @@ import ( "github.com/drykit-go/testx" "github.com/drykit-go/testx/check" - "github.com/drykit-go/testx/checkconv" ) func TestValueRunner(t *testing.T) { t.Run("should pass", func(t *testing.T) { res := testx.Value(42). Exp(42). - Not(3, "hello"). - Pass(checkconv.FromInt(check.Int.InRange(41, 43))). + Not(3, -1). + Pass(check.Int.InRange(41, 43), check.Int.GT(10)). DryRun() exp := baseResults{ passed: true, failed: false, - nPassed: 3, + nPassed: 4, nFailed: 0, - nChecks: 3, + nChecks: 4, checks: []testx.CheckResult{ {Passed: true, Reason: ""}, {Passed: true, Reason: ""}, {Passed: true, Reason: ""}, + {Passed: true, Reason: ""}, }, } @@ -37,7 +37,7 @@ func TestValueRunner(t *testing.T) { Exp(99). Not(99). Not(99, 42). - Pass(checkconv.FromInt(check.Int.LT(10))). + Pass(check.Int.LT(10)). DryRun() exp := baseResults{ diff --git a/testx.go b/testx.go index 84cf3ac..dfe6eea 100644 --- a/testx.go +++ b/testx.go @@ -23,17 +23,17 @@ type Runner interface { } // ValueRunner provides methods to perform tests on a single value. -type ValueRunner interface { +type ValueRunner[T any] interface { Runner // DryRun returns a Resulter to access test results // without running *testing.T. DryRun() Resulter // Exp adds an equality check on the tested value. - Exp(value interface{}) ValueRunner + Exp(value T) ValueRunner[T] // Not adds inequality checks on the tested value. - Not(values ...interface{}) ValueRunner + Not(values ...T) ValueRunner[T] // Pass adds checkers on the tested value. - Pass(checkers ...check.ValueChecker) ValueRunner + Pass(checkers ...check.Checker[T]) ValueRunner[T] } // TableRunner provides methods to run a series of test cases @@ -63,11 +63,11 @@ type HTTPHandlerRunner interface { WithRequest(*http.Request) HTTPHandlerRunner // Request adds checkers on the resulting request, // after the last middleware is called and before the handler is called. - Request(...check.HTTPRequestChecker) HTTPHandlerRunner + Request(...check.Checker[*http.Request]) HTTPHandlerRunner // Response adds checkers on the written response. - Response(...check.HTTPResponseChecker) HTTPHandlerRunner + Response(...check.Checker[*http.Response]) HTTPHandlerRunner // Duration adds checkers on the handler's execution time; - Duration(...check.DurationChecker) HTTPHandlerRunner + Duration(...check.Checker[time.Duration]) HTTPHandlerRunner } /* @@ -139,7 +139,7 @@ func (cr CheckResult) String() string { */ // Value returns a ValueRunner to run tests on a single value. -func Value(v interface{}) ValueRunner { +func Value[T any](v T) ValueRunner[T] { return newValueRunner(v) } @@ -170,6 +170,6 @@ func HTTPHandlerFunc( // Table returns a TableRunner to run test cases on a func. By default, // it works with funcs having a single input and output value. // Use TableRunner.Config to configure it for a more complex functions. -func Table(testedFunc interface{}) TableRunner { +func Table(testedFunc any) TableRunner { return newTableRunner(testedFunc) } From bfd8a9bf35dbada2e2e21e8a8741e8c90bcd3c63 Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Sun, 31 Oct 2021 19:28:18 +0100 Subject: [PATCH 02/19] ops(generics): add gotip to CircleCI cache (#67) --- .circleci/config.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2dce924..b7b6e75 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,6 +10,7 @@ commands: setup: steps: - checkout + - restore_cache: keys: - go-mod-v4-{{ checksum "go.sum" }}-{{ .Environment.CACHE_VERSION }} @@ -17,15 +18,28 @@ commands: name: Install dependencies command: | go mod download + - save_cache: + key: go-mod-v4-{{ checksum "go.sum" }}-{{ .Environment.CACHE_VERSION }} + paths: + - /go/pkg/mod + + - restore_cache: + keys: + - gotip-{{ .Environment.CACHE_VERSION }} - run: name: Install gotip command: | + command -v /go/bin/gotip && echo "Skipping gotip installation" && exit go install golang.org/dl/gotip@latest gotip download - save_cache: - key: go-mod-v4-{{ checksum "go.sum" }}-{{ .Environment.CACHE_VERSION }} + key: gotip-{{ .Environment.CACHE_VERSION }} paths: - - "/go/pkg/mod" + - /go/bin/gotip + - run: + name: Run gotip download + command: | + gotip download # setup-lint: # description: Set up lint From a888d6aa4f363e3c91a736f44b794b38ae59979d Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Sun, 31 Oct 2021 19:35:58 +0100 Subject: [PATCH 03/19] dev(generics): remove typed checkers (#65) * Remove code generation directives and templates * Remove typed checkers declarations * Update remaining usages of typed checkers --- check/check.go | 180 +++------------- check/check2.go | 36 ---- check/checkers.go | 201 ------------------ ...ker_test.go => example_newchecker_test.go} | 2 +- check/gen.go | 26 --- checkconv/assert_test.go | 4 +- checkconv/cast_test.go | 4 +- checkconv/checkconv_test.go | 2 +- internal/gen/templates/check.gotmpl | 38 ---- internal/gen/templates/checkers.gotmpl | 35 --- runner.go | 2 +- runner_table.go | 2 +- 12 files changed, 42 insertions(+), 490 deletions(-) delete mode 100644 check/check2.go delete mode 100644 check/checkers.go rename check/{example_newintchecker_test.go => example_newchecker_test.go} (94%) delete mode 100644 internal/gen/templates/check.gotmpl delete mode 100644 internal/gen/templates/checkers.gotmpl diff --git a/check/check.go b/check/check.go index 742b8dd..ca97329 100644 --- a/check/check.go +++ b/check/check.go @@ -1,61 +1,10 @@ -// Code generated by go generate ./...; DO NOT EDIT -// Last generated on 31 Oct 21 17:02 UTC - -// Package check provides types to perform checks on values -// in a testing context. package check -import ( - "context" - "net/http" - "time" -) - type ( - // BoolPassFunc is the required method to implement BoolPasser. - // It returns a boolean that indicates whether the gotten bool value - // passes the current check. - BoolPassFunc func(got bool) bool - // BytesPassFunc is the required method to implement BytesPasser. - // It returns a boolean that indicates whether the gotten []byte value - // passes the current check. - BytesPassFunc func(got []byte) bool - // StringPassFunc is the required method to implement StringPasser. - // It returns a boolean that indicates whether the gotten string value - // passes the current check. - StringPassFunc func(got string) bool - // IntPassFunc is the required method to implement IntPasser. - // It returns a boolean that indicates whether the gotten int value - // passes the current check. - IntPassFunc func(got int) bool - // Float64PassFunc is the required method to implement Float64Passer. - // It returns a boolean that indicates whether the gotten float64 value - // passes the current check. - Float64PassFunc func(got float64) bool - // DurationPassFunc is the required method to implement DurationPasser. - // It returns a boolean that indicates whether the gotten time.Duration value + // PassFunc is the required method to implement Passer. + // It returns a boolean that indicates whether the got value // passes the current check. - DurationPassFunc func(got time.Duration) bool - // ContextPassFunc is the required method to implement ContextPasser. - // It returns a boolean that indicates whether the gotten context.Context value - // passes the current check. - ContextPassFunc func(got context.Context) bool - // HTTPHeaderPassFunc is the required method to implement HTTPHeaderPasser. - // It returns a boolean that indicates whether the gotten http.Header value - // passes the current check. - HTTPHeaderPassFunc func(got http.Header) bool - // HTTPRequestPassFunc is the required method to implement HTTPRequestPasser. - // It returns a boolean that indicates whether the gotten *http.Request value - // passes the current check. - HTTPRequestPassFunc func(got *http.Request) bool - // HTTPResponsePassFunc is the required method to implement HTTPResponsePasser. - // It returns a boolean that indicates whether the gotten *http.Response value - // passes the current check. - HTTPResponsePassFunc func(got *http.Response) bool - // ValuePassFunc is the required method to implement ValuePasser. - // It returns a boolean that indicates whether the gotten any value - // passes the current check. - ValuePassFunc func(got any) bool + PassFunc[T any] func(got T) bool // ExplainFunc is the required method to implement Explainer. // It returns a string explaining why the gotten value failed the check. @@ -64,41 +13,9 @@ type ( ) type ( - // BoolPasser provides a method Pass that returns a bool that indicates - // whether the gotten bool value passes the current check. - BoolPasser interface{ Pass(got bool) bool } - // BytesPasser provides a method Pass that returns a bool that indicates - // whether the gotten []byte value passes the current check. - BytesPasser interface{ Pass(got []byte) bool } - // StringPasser provides a method Pass that returns a bool that indicates - // whether the gotten string value passes the current check. - StringPasser interface{ Pass(got string) bool } - // IntPasser provides a method Pass that returns a bool that indicates - // whether the gotten int value passes the current check. - IntPasser interface{ Pass(got int) bool } - // Float64Passer provides a method Pass that returns a bool that indicates - // whether the gotten float64 value passes the current check. - Float64Passer interface{ Pass(got float64) bool } - // DurationPasser provides a method Pass that returns a bool that indicates - // whether the gotten time.Duration value passes the current check. - DurationPasser interface{ Pass(got time.Duration) bool } - // ContextPasser provides a method Pass that returns a bool that indicates - // whether the gotten context.Context value passes the current check. - ContextPasser interface { - Pass(got context.Context) bool - } - // HTTPHeaderPasser provides a method Pass that returns a bool that indicates - // whether the gotten http.Header value passes the current check. - HTTPHeaderPasser interface{ Pass(got http.Header) bool } - // HTTPRequestPasser provides a method Pass that returns a bool that indicates - // whether the gotten *http.Request value passes the current check. - HTTPRequestPasser interface{ Pass(got *http.Request) bool } - // HTTPResponsePasser provides a method Pass that returns a bool that indicates - // whether the gotten *http.Response value passes the current check. - HTTPResponsePasser interface{ Pass(got *http.Response) bool } - // ValuePasser provides a method Pass that returns a bool that indicates - // whether the gotten any value passes the current check. - ValuePasser interface{ Pass(got any) bool } + // Passer provides a method Pass that returns a bool that indicates + // whether the got value passes the current check. + Passer[T any] interface{ Pass(got T) bool } // Explainer provides a method Explain describing the reason of a failed check. Explainer interface { @@ -106,60 +23,31 @@ type ( } ) -type ( - // BoolChecker implements both BoolPasser and Explainer interfaces. - BoolChecker interface { - BoolPasser - Explainer - } - // BytesChecker implements both BytesPasser and Explainer interfaces. - BytesChecker interface { - BytesPasser - Explainer - } - // StringChecker implements both StringPasser and Explainer interfaces. - StringChecker interface { - StringPasser - Explainer - } - // IntChecker implements both IntPasser and Explainer interfaces. - IntChecker interface { - IntPasser - Explainer - } - // Float64Checker implements both Float64Passer and Explainer interfaces. - Float64Checker interface { - Float64Passer - Explainer - } - // DurationChecker implements both DurationPasser and Explainer interfaces. - DurationChecker interface { - DurationPasser - Explainer - } - // ContextChecker implements both ContextPasser and Explainer interfaces. - ContextChecker interface { - ContextPasser - Explainer - } - // HTTPHeaderChecker implements both HTTPHeaderPasser and Explainer interfaces. - HTTPHeaderChecker interface { - HTTPHeaderPasser - Explainer - } - // HTTPRequestChecker implements both HTTPRequestPasser and Explainer interfaces. - HTTPRequestChecker interface { - HTTPRequestPasser - Explainer - } - // HTTPResponseChecker implements both HTTPResponsePasser and Explainer interfaces. - HTTPResponseChecker interface { - HTTPResponsePasser - Explainer - } - // ValueChecker implements both ValuePasser and Explainer interfaces. - ValueChecker interface { - ValuePasser - Explainer - } -) +// Checker satisfies both Passer and Explainer interfaces. +type Checker[T any] interface { + Passer[T] + Explainer +} + +type checker[T any] struct { + pass PassFunc[T] + expl ExplainFunc +} + +func (c checker[T]) Pass(got T) bool { + return c.pass(got) +} + +func (c checker[T]) Explain(label string, got any) string { + return c.expl(label, got) +} + +func NewChecker[T any]( + passFunc PassFunc[T], + explainFunc ExplainFunc, +) Checker[T] { + return checker[T]{ + pass: passFunc, + expl: explainFunc, + } +} diff --git a/check/check2.go b/check/check2.go deleted file mode 100644 index a0f8794..0000000 --- a/check/check2.go +++ /dev/null @@ -1,36 +0,0 @@ -package check - -// TODO: rename file check.go when the other is removed - -type PassFunc[T any] func(got T) bool - -type ( - Passer[T any] interface{ Pass(got T) bool } - Checker[T any] interface { - Passer[T] - Explainer - } -) - -type checker[T any] struct { - pass PassFunc[T] - expl ExplainFunc -} - -func (c checker[T]) Pass(got T) bool { - return c.pass(got) -} - -func (c checker[T]) Explain(label string, got any) string { - return c.expl(label, got) -} - -func NewChecker[T any]( - passFunc PassFunc[T], - explainFunc ExplainFunc, -) Checker[T] { - return checker[T]{ - pass: passFunc, - expl: explainFunc, - } -} diff --git a/check/checkers.go b/check/checkers.go deleted file mode 100644 index c82d0e9..0000000 --- a/check/checkers.go +++ /dev/null @@ -1,201 +0,0 @@ -// Code generated by go generate ./...; DO NOT EDIT -// Last generated on 31 Oct 21 17:02 UTC - -package check - -import ( - "context" - "net/http" - "time" -) - -// baseChecker is a base checker for all checkers. It implements Explainer. -type baseChecker struct { - explFunc ExplainFunc -} - -// Explain returns a string explaining the reason of a failed check -// for the gotten value. -func (c baseChecker) Explain(label string, got any) string { - return c.explFunc(label, got) -} - -func newBaseChecker(explFunc ExplainFunc) baseChecker { - return baseChecker{explFunc: explFunc} -} - -// boolChecker is an implementation of BoolChecker interface -type boolChecker struct { - baseChecker - passFunc BoolPassFunc -} - -// Pass returns a boolean that indicates whether the gotten bool value -// passes the current check. -func (c boolChecker) Pass(got bool) bool { return c.passFunc(got) } - -// NewBoolChecker returns a custom BoolChecker with the provided -// BoolPassFunc and ExplainFunc. -func NewBoolChecker(passFunc BoolPassFunc, explainFunc ExplainFunc) BoolChecker { - return boolChecker{baseChecker: newBaseChecker(explainFunc), passFunc: passFunc} -} - -// bytesChecker is an implementation of BytesChecker interface -type bytesChecker struct { - baseChecker - passFunc BytesPassFunc -} - -// Pass returns a boolean that indicates whether the gotten []byte value -// passes the current check. -func (c bytesChecker) Pass(got []byte) bool { return c.passFunc(got) } - -// NewBytesChecker returns a custom BytesChecker with the provided -// BytesPassFunc and ExplainFunc. -func NewBytesChecker(passFunc BytesPassFunc, explainFunc ExplainFunc) BytesChecker { - return bytesChecker{baseChecker: newBaseChecker(explainFunc), passFunc: passFunc} -} - -// stringChecker is an implementation of StringChecker interface -type stringChecker struct { - baseChecker - passFunc StringPassFunc -} - -// Pass returns a boolean that indicates whether the gotten string value -// passes the current check. -func (c stringChecker) Pass(got string) bool { return c.passFunc(got) } - -// NewStringChecker returns a custom StringChecker with the provided -// StringPassFunc and ExplainFunc. -func NewStringChecker(passFunc StringPassFunc, explainFunc ExplainFunc) StringChecker { - return stringChecker{baseChecker: newBaseChecker(explainFunc), passFunc: passFunc} -} - -// intChecker is an implementation of IntChecker interface -type intChecker struct { - baseChecker - passFunc IntPassFunc -} - -// Pass returns a boolean that indicates whether the gotten int value -// passes the current check. -func (c intChecker) Pass(got int) bool { return c.passFunc(got) } - -// NewIntChecker returns a custom IntChecker with the provided -// IntPassFunc and ExplainFunc. -func NewIntChecker(passFunc IntPassFunc, explainFunc ExplainFunc) IntChecker { - return intChecker{baseChecker: newBaseChecker(explainFunc), passFunc: passFunc} -} - -// float64Checker is an implementation of Float64Checker interface -type float64Checker struct { - baseChecker - passFunc Float64PassFunc -} - -// Pass returns a boolean that indicates whether the gotten float64 value -// passes the current check. -func (c float64Checker) Pass(got float64) bool { return c.passFunc(got) } - -// NewFloat64Checker returns a custom Float64Checker with the provided -// Float64PassFunc and ExplainFunc. -func NewFloat64Checker(passFunc Float64PassFunc, explainFunc ExplainFunc) Float64Checker { - return float64Checker{baseChecker: newBaseChecker(explainFunc), passFunc: passFunc} -} - -// durationChecker is an implementation of DurationChecker interface -type durationChecker struct { - baseChecker - passFunc DurationPassFunc -} - -// Pass returns a boolean that indicates whether the gotten time.Duration value -// passes the current check. -func (c durationChecker) Pass(got time.Duration) bool { return c.passFunc(got) } - -// NewDurationChecker returns a custom DurationChecker with the provided -// DurationPassFunc and ExplainFunc. -func NewDurationChecker(passFunc DurationPassFunc, explainFunc ExplainFunc) DurationChecker { - return durationChecker{baseChecker: newBaseChecker(explainFunc), passFunc: passFunc} -} - -// contextChecker is an implementation of ContextChecker interface -type contextChecker struct { - baseChecker - passFunc ContextPassFunc -} - -// Pass returns a boolean that indicates whether the gotten context.Context value -// passes the current check. -func (c contextChecker) Pass(got context.Context) bool { return c.passFunc(got) } - -// NewContextChecker returns a custom ContextChecker with the provided -// ContextPassFunc and ExplainFunc. -func NewContextChecker(passFunc ContextPassFunc, explainFunc ExplainFunc) ContextChecker { - return contextChecker{baseChecker: newBaseChecker(explainFunc), passFunc: passFunc} -} - -// httpHeaderChecker is an implementation of HTTPHeaderChecker interface -type httpHeaderChecker struct { - baseChecker - passFunc HTTPHeaderPassFunc -} - -// Pass returns a boolean that indicates whether the gotten http.Header value -// passes the current check. -func (c httpHeaderChecker) Pass(got http.Header) bool { return c.passFunc(got) } - -// NewHTTPHeaderChecker returns a custom HTTPHeaderChecker with the provided -// HTTPHeaderPassFunc and ExplainFunc. -func NewHTTPHeaderChecker(passFunc HTTPHeaderPassFunc, explainFunc ExplainFunc) HTTPHeaderChecker { - return httpHeaderChecker{baseChecker: newBaseChecker(explainFunc), passFunc: passFunc} -} - -// httpRequestChecker is an implementation of HTTPRequestChecker interface -type httpRequestChecker struct { - baseChecker - passFunc HTTPRequestPassFunc -} - -// Pass returns a boolean that indicates whether the gotten *http.Request value -// passes the current check. -func (c httpRequestChecker) Pass(got *http.Request) bool { return c.passFunc(got) } - -// NewHTTPRequestChecker returns a custom HTTPRequestChecker with the provided -// HTTPRequestPassFunc and ExplainFunc. -func NewHTTPRequestChecker(passFunc HTTPRequestPassFunc, explainFunc ExplainFunc) HTTPRequestChecker { - return httpRequestChecker{baseChecker: newBaseChecker(explainFunc), passFunc: passFunc} -} - -// httpResponseChecker is an implementation of HTTPResponseChecker interface -type httpResponseChecker struct { - baseChecker - passFunc HTTPResponsePassFunc -} - -// Pass returns a boolean that indicates whether the gotten *http.Response value -// passes the current check. -func (c httpResponseChecker) Pass(got *http.Response) bool { return c.passFunc(got) } - -// NewHTTPResponseChecker returns a custom HTTPResponseChecker with the provided -// HTTPResponsePassFunc and ExplainFunc. -func NewHTTPResponseChecker(passFunc HTTPResponsePassFunc, explainFunc ExplainFunc) HTTPResponseChecker { - return httpResponseChecker{baseChecker: newBaseChecker(explainFunc), passFunc: passFunc} -} - -// valueChecker is an implementation of ValueChecker interface -type valueChecker struct { - baseChecker - passFunc ValuePassFunc -} - -// Pass returns a boolean that indicates whether the gotten any value -// passes the current check. -func (c valueChecker) Pass(got any) bool { return c.passFunc(got) } - -// NewValueChecker returns a custom ValueChecker with the provided -// ValuePassFunc and ExplainFunc. -func NewValueChecker(passFunc ValuePassFunc, explainFunc ExplainFunc) ValueChecker { - return valueChecker{baseChecker: newBaseChecker(explainFunc), passFunc: passFunc} -} diff --git a/check/example_newintchecker_test.go b/check/example_newchecker_test.go similarity index 94% rename from check/example_newintchecker_test.go rename to check/example_newchecker_test.go index 04c28bf..efde329 100644 --- a/check/example_newintchecker_test.go +++ b/check/example_newchecker_test.go @@ -13,7 +13,7 @@ import ( */ func Example_newIntChecker() { - checkIsEven := check.NewIntChecker( + checkIsEven := check.NewChecker( func(got int) bool { return got&1 == 0 }, func(label string, got any) string { return fmt.Sprintf("%s: expect even int, got %v", label, got) diff --git a/check/gen.go b/check/gen.go index 29d56f7..bc8fbbc 100644 --- a/check/gen.go +++ b/check/gen.go @@ -1,30 +1,4 @@ package check -//go:generate ../bin/gen -kind types -name check //go:generate ../bin/gen -kind types -name checkers // //go:generate ../bin/gen -kind interfaces -name providers - -// For every type {N,T} defined in ./gen/types.go, running go generate -// will create the following definitions: -// -// NPassFunc func(got T) bool -// -// NPasser interface{ Pass(got T) bool } -// -// NChecker interface { -// NPasser -// Explainer -// } -// -// type nChecker struct { -// passFunc NPassFunc -// explFunc ExplainFunc -// } -// -// func (c nChecker) Pass(got T) bool { return c.passFunc(got) } -// -// func (c nChecker) Explain(label string, got any) string { return c.explFunc(label, got) } -// -// func NewNChecker(passFunc NPassFunc, explainFunc ExplainFunc) NChecker { -// return nChecker{passFunc: passFunc, explFunc: explainFunc} -// } diff --git a/checkconv/assert_test.go b/checkconv/assert_test.go index dd6a5f8..d68297b 100644 --- a/checkconv/assert_test.go +++ b/checkconv/assert_test.go @@ -21,7 +21,7 @@ func TestAssert(t *testing.T) { expExpl: "", }, { - checker: check.NewIntChecker(isEven, isEvenExpl), + checker: check.NewChecker(isEven, isEvenExpl), in: -1, expPass: false, expExpl: "expect value to be even, got -1", @@ -106,7 +106,7 @@ func TestAssertMany(t *testing.T) { t.Run("custom checkers known type", func(t *testing.T) { knownCheckers := []any{ check.Value.Custom("", func(_ any) bool { return true }), - check.NewIntChecker(isEven, validExplainFunc), + check.NewChecker(isEven, validExplainFunc), validCheckerInt{}, validCheckerInterface{}, } diff --git a/checkconv/cast_test.go b/checkconv/cast_test.go index 7f8ab9c..6e82428 100644 --- a/checkconv/cast_test.go +++ b/checkconv/cast_test.go @@ -45,13 +45,13 @@ func TestCast(t *testing.T) { t.Run("custom checker", func(t *testing.T) { cases := []checkerTestcase{ { - checker: check.NewIntChecker(isEven, isEvenExpl), + checker: check.NewChecker(isEven, isEvenExpl), in: 0, expPass: true, expExpl: "", }, { - checker: check.NewIntChecker(isEven, isEvenExpl), + checker: check.NewChecker(isEven, isEvenExpl), in: 1, expPass: false, expExpl: "expect value to be even, got 1", diff --git a/checkconv/checkconv_test.go b/checkconv/checkconv_test.go index 65fa06a..7595e9c 100644 --- a/checkconv/checkconv_test.go +++ b/checkconv/checkconv_test.go @@ -89,7 +89,7 @@ func isEvenExpl(_ string, got any) string { // Test helpers -func assertValidValueChecker(t *testing.T, c check.ValueChecker, tc checkerTestcase) { +func assertValidValueChecker(t *testing.T, c check.Checker[any], tc checkerTestcase) { t.Helper() if pass := c.Pass(tc.in); pass != tc.expPass { t.Errorf( diff --git a/internal/gen/templates/check.gotmpl b/internal/gen/templates/check.gotmpl deleted file mode 100644 index 8970922..0000000 --- a/internal/gen/templates/check.gotmpl +++ /dev/null @@ -1,38 +0,0 @@ -// Package check provides types to perform checks on values -// in a testing context. -package check - -type ( - {{range . -}} - // {{.N}}PassFunc is the required method to implement {{.N}}Passer. - // It returns a boolean that indicates whether the gotten {{.T}} value - // passes the current check. - {{.N}}PassFunc func(got {{.T}}) bool - {{end}} - - // ExplainFunc is the required method to implement Explainer. - // It returns a string explaining why the gotten value failed the check. - // The label provides some context, such as "response code". - ExplainFunc func(label string, got any) string -) - -type ( - {{range . -}} - // {{.N}}Passer provides a method Pass that returns a bool that indicates - // whether the gotten {{.T}} value passes the current check. - {{.N}}Passer interface { Pass(got {{.T}}) bool } - {{end}} - - // Explainer provides a method Explain describing the reason of a failed check. - Explainer interface { Explain(label string, got any) string } -) - -type ( - {{range . -}} - // {{.N}}Checker implements both {{.N}}Passer and Explainer interfaces. - {{.N}}Checker interface { - {{.N}}Passer - Explainer - } - {{end}} -) diff --git a/internal/gen/templates/checkers.gotmpl b/internal/gen/templates/checkers.gotmpl deleted file mode 100644 index 0e94e58..0000000 --- a/internal/gen/templates/checkers.gotmpl +++ /dev/null @@ -1,35 +0,0 @@ -package check - -// baseChecker is a base checker for all checkers. It implements Explainer. -type baseChecker struct { - explFunc ExplainFunc -} - -// Explain returns a string explaining the reason of a failed check -// for the gotten value. -func (c baseChecker) Explain(label string, got any) string { - return c.explFunc(label, got) -} - -func newBaseChecker(explFunc ExplainFunc) baseChecker { - return baseChecker{explFunc: explFunc} -} - -{{range . -}} -// {{camelcase .N}}Checker is an implementation of {{.N}}Checker interface -type {{camelcase .N}}Checker struct { - baseChecker - passFunc {{.N}}PassFunc -} - -// Pass returns a boolean that indicates whether the gotten {{.T}} value -// passes the current check. -func (c {{camelcase .N}}Checker) Pass(got {{.T}}) bool { return c.passFunc(got) } - - -// New{{.N}}Checker returns a custom {{.N}}Checker with the provided -// {{.N}}PassFunc and ExplainFunc. -func New{{.N}}Checker(passFunc {{.N}}PassFunc, explainFunc ExplainFunc) {{.N}}Checker { - return {{camelcase .N}}Checker{baseChecker: newBaseChecker(explainFunc), passFunc: passFunc} -} -{{end}} diff --git a/runner.go b/runner.go index 7d57469..c59911f 100644 --- a/runner.go +++ b/runner.go @@ -14,7 +14,7 @@ type ( get getfunc getLabel func() string label string - checker check.ValueChecker + checker check.Checker[any] } ) diff --git a/runner_table.go b/runner_table.go index 2a80657..d2ead28 100644 --- a/runner_table.go +++ b/runner_table.go @@ -156,7 +156,7 @@ func (r *tableRunner) Cases(cases []Case) TableRunner { return fmtexpl.TableCaseLabel(r.rfunc.Name, i, tc.Lab, r.args) } - addCaseCheck := func(c check.ValueChecker) { + addCaseCheck := func(c check.Checker[any]) { r.addCheck(baseCheck{ get: get, getLabel: getLabel, From 31dd96a5f5f891f075ae3d01b6bc29ae26ccec3c Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Mon, 1 Nov 2021 18:18:56 +0100 Subject: [PATCH 04/19] feat(generics): remove package checkconv (#68) * Implement check.Wrap, check.WrapMany * Create temp package slices * Write unit tests for check.Wrap, WrapMany * Replace all checkconv usages with check.Wrap * Delete package checkconv * Use package drykit-go/slicex - Remove local package internal/slices - Install and use github.com/drykit-go/slicex v0.0.1 --- check/example_custom_unknown_test.go | 6 +- check/providers_context_test.go | 3 +- check/providers_map_test.go | 27 ++-- check/providers_slice_test.go | 17 ++- check/providers_struct_test.go | 5 +- check/wrap.go | 18 +++ check/wrap_test.go | 167 ++++++++++++++++++++++ checkconv/README.md | 198 --------------------------- checkconv/assert.go | 169 ----------------------- checkconv/assert_test.go | 130 ------------------ checkconv/benchmark_test.go | 58 -------- checkconv/cast.go | 61 --------- checkconv/cast_test.go | 134 ------------------ checkconv/checkconv_test.go | 106 -------------- checkconv/gen.go | 18 --- checkconv/ischecker.go | 44 ------ checkconv/ischecker_test.go | 31 ----- example_table_test.go | 3 +- go.mod | 1 + go.sum | 2 + runner_httphandler.go | 7 +- runner_table_test.go | 5 +- runner_value.go | 7 +- 23 files changed, 222 insertions(+), 995 deletions(-) create mode 100644 check/wrap.go create mode 100644 check/wrap_test.go delete mode 100644 checkconv/README.md delete mode 100644 checkconv/assert.go delete mode 100644 checkconv/assert_test.go delete mode 100644 checkconv/benchmark_test.go delete mode 100644 checkconv/cast.go delete mode 100644 checkconv/cast_test.go delete mode 100644 checkconv/checkconv_test.go delete mode 100644 checkconv/gen.go delete mode 100644 checkconv/ischecker.go delete mode 100644 checkconv/ischecker_test.go diff --git a/check/example_custom_unknown_test.go b/check/example_custom_unknown_test.go index 136d345..5e396af 100644 --- a/check/example_custom_unknown_test.go +++ b/check/example_custom_unknown_test.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/drykit-go/testx" - "github.com/drykit-go/testx/checkconv" + "github.com/drykit-go/testx/check" ) /* @@ -41,8 +41,8 @@ func identityCustomType(v MyType) MyType { } func Example_customCheckerUnknownType() { - checkIsValid := MyTypeValidityChecker{} - checkers, _ := checkconv.CastMany(checkIsValid) + var checkIsValid check.Checker[MyType] = MyTypeValidityChecker{} + checkers := check.WrapMany(checkIsValid) results := testx.Table(identityCustomType). Cases([]testx.Case{ {In: MyType{ID: 0, Name: "yes"}, Pass: checkers}, // pass diff --git a/check/providers_context_test.go b/check/providers_context_test.go index 78f297e..607c9fd 100644 --- a/check/providers_context_test.go +++ b/check/providers_context_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/drykit-go/testx/check" - "github.com/drykit-go/testx/checkconv" ) func TestContextCheckerProvider(t *testing.T) { @@ -65,7 +64,7 @@ func TestContextCheckerProvider(t *testing.T) { }) t.Run("Value pass", func(t *testing.T) { - c := check.Context.Value("userID", checkconv.FromInt(check.Int.GT(0))) + c := check.Context.Value("userID", check.Wrap(check.Int.GT(0))) ctx := ctxVal("userID", 42) assertPassChecker(t, "Context.Value", c, ctx) }) diff --git a/check/providers_map_test.go b/check/providers_map_test.go index ca617fd..291c6b0 100644 --- a/check/providers_map_test.go +++ b/check/providers_map_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/drykit-go/testx/check" - "github.com/drykit-go/testx/checkconv" ) func TestMapCheckerProvider(t *testing.T) { @@ -87,7 +86,7 @@ func TestMapCheckerProvider(t *testing.T) { t.Run("CheckValues pass", func(t *testing.T) { // keys subset c := check.Map.CheckValues( - checkconv.FromInt(check.Int.InRange(41, 43)), + check.Wrap(check.Int.InRange(41, 43)), "age", ) assertPassChecker(t, "Map.CheckValues", c, itf(m)) @@ -99,20 +98,20 @@ func TestMapCheckerProvider(t *testing.T) { t.Run("CheckValues fail", func(t *testing.T) { // keys subset - // c := check.Map.CheckValues( - // checkconv.FromInt(check.Int.OutRange(41, 43)), - // "age", "badkey", - // ) - // assertFailChecker(t, "Map.CheckValues", c, itf(m), makeExpl( - // "values for keys [age badkey] to pass Checker[any]", - // "explanation: values:\n"+makeExpl( - // "not in range [41:43]", - // "[age:42, badkey:]", - // ), - // )) + c := check.Map.CheckValues( + check.Wrap(check.Int.OutRange(41, 43)), + "age", "badkey", + ) + assertFailChecker(t, "Map.CheckValues", c, itf(m), makeExpl( + "values for keys [age badkey] to pass Checker[any]", + "explanation: values:\n"+makeExpl( + "not in range [41:43]", + "[age:42, badkey:]", + ), + )) // all keys - c := check.Map.CheckValues(check.Value.Is("Marcel Patulacci")) + c = check.Map.CheckValues(check.Value.Is("Marcel Patulacci")) assertFailChecker(t, "Map.CheckValues", c, itf(m), makeExpl( "values for all keys to pass Checker[any]", "explanation: values:\n"+makeExpl( diff --git a/check/providers_slice_test.go b/check/providers_slice_test.go index b966233..4238b5d 100644 --- a/check/providers_slice_test.go +++ b/check/providers_slice_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/drykit-go/testx/check" - "github.com/drykit-go/testx/checkconv" ) func TestSliceCheckerProvider(t *testing.T) { @@ -67,17 +66,17 @@ func TestSliceCheckerProvider(t *testing.T) { )) }) - // t.Run("CheckValues pass", func(t *testing.T) { - // c := check.Slice.CheckValues( - // checkconv.FromInt(check.Int.InRange(41, 43)), - // func(_ int, v any) bool { _, ok := v.(int); return ok }, - // ) - // assertPassChecker(t, "Slice.CheckValues", c, itf(s)) - // }) + t.Run("CheckValues pass", func(t *testing.T) { + c := check.Slice.CheckValues( + check.Wrap(check.Int.InRange(41, 43)), + func(_ int, v any) bool { _, ok := v.(int); return ok }, + ) + assertPassChecker(t, "Slice.CheckValues", c, itf(s)) + }) t.Run("CheckValues fail", func(t *testing.T) { c := check.Slice.CheckValues( - checkconv.FromInt(check.Int.OutRange(41, 43)), + check.Wrap(check.Int.OutRange(41, 43)), func(_ int, v any) bool { _, ok := v.(int); return ok }, ) assertFailChecker(t, "Slice.CheckValues", c, itf(s), makeExpl( diff --git a/check/providers_struct_test.go b/check/providers_struct_test.go index efe22ce..1288b56 100644 --- a/check/providers_struct_test.go +++ b/check/providers_struct_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/drykit-go/testx/check" - "github.com/drykit-go/testx/checkconv" ) type structTest struct { @@ -38,7 +37,7 @@ func TestStructCheckerProvider(t *testing.T) { t.Run("CheckFields pass", func(t *testing.T) { c := check.Struct.CheckFields( - checkconv.FromInt(check.Int.LT(vAB+1)), + check.Wrap(check.Int.LT(vAB+1)), []string{"A", "B"}, ) assertPassChecker(t, "Struct.CheckFields", c, itf(s)) @@ -46,7 +45,7 @@ func TestStructCheckerProvider(t *testing.T) { t.Run("CheckFields fail", func(t *testing.T) { c := check.Struct.CheckFields( - checkconv.FromInt(check.Int.LT(vAB+1)), + check.Wrap(check.Int.LT(vAB+1)), []string{"A", "B", "X", "Y"}, ) assertFailChecker(t, "Struct.CheckFields", c, itf(s), makeExpl( diff --git a/check/wrap.go b/check/wrap.go new file mode 100644 index 0000000..613725e --- /dev/null +++ b/check/wrap.go @@ -0,0 +1,18 @@ +package check + +// Wrap returns a Checker[any] that wraps the given checker. +func Wrap[T any](checker Checker[T]) Checker[any] { + return NewChecker( + func(got interface{}) bool { return checker.Pass(got.(T)) }, + checker.Explain, + ) +} + +// Wrap returns a slice of Checker[any] that wrap the given checkers. +func WrapMany[T any](checkers ...Checker[T]) []Checker[any] { + anyCheckers := make([]Checker[any], len(checkers)) + for i, c := range checkers { + anyCheckers[i] = Wrap(c) + } + return anyCheckers +} diff --git a/check/wrap_test.go b/check/wrap_test.go new file mode 100644 index 0000000..cc230fc --- /dev/null +++ b/check/wrap_test.go @@ -0,0 +1,167 @@ +package check_test + +import ( + "testing" + + "github.com/drykit-go/slicex" + "github.com/drykit-go/testx/check" +) + +type checkerTestcase[T any] struct { + checker check.Checker[T] + in T + exppass bool + expexpl string +} + +func TestWrap(t *testing.T) { + t.Run("native checkers", func(t *testing.T) { + testcases := []checkerTestcase[any]{ + { + checker: check.Wrap(check.Bytes.Is([]byte{42})), + in: []byte{42}, + exppass: true, + expexpl: "", + }, + { + checker: check.Wrap(check.Int.InRange(41, 43)), + in: -1, + exppass: false, + expexpl: "value:\nexp in range [41:43]\ngot -1", + }, + { + checker: check.Wrap(check.Value.Custom("", func(got any) bool { return true })), + in: "", + exppass: true, + expexpl: "", + }, + } + + for _, tc := range testcases { + assertValidAnyChecker(t, tc) + } + }) + + t.Run("new checkers", func(t *testing.T) { + testcases := []checkerTestcase[any]{ + { + checker: check.Wrap(newComplex128Checker(true, "")), + in: 1i + 1, + exppass: true, + expexpl: "", + }, + { + checker: check.Wrap(newComplex128Checker(false, "bad")), + in: 1i + 1, + exppass: false, + expexpl: "bad", + }, + } + + for _, tc := range testcases { + assertValidAnyChecker(t, tc) + } + }) + + t.Run("custom checkers", func(t *testing.T) { + testcases := []checkerTestcase[any]{ + { + checker: check.Wrap(customComplex128Checker(true, "")), + in: 1i + 1, + exppass: true, + expexpl: "", + }, + { + checker: check.Wrap(customComplex128Checker(false, "bad")), + in: 1i + 1, + exppass: false, + expexpl: "bad", + }, + } + + for _, tc := range testcases { + assertValidAnyChecker(t, tc) + } + }) +} + +func TestWrapMany(t *testing.T) { + testcases := []checkerTestcase[complex128]{ + { + checker: newComplex128Checker(true, ""), + in: 1i + 1, + exppass: true, + expexpl: "", + }, + { + checker: newComplex128Checker(false, "bad"), + in: 1i + 1, + exppass: false, + expexpl: "bad", + }, + { + checker: customComplex128Checker(true, ""), + in: 1i + 1, + exppass: true, + expexpl: "", + }, + { + checker: customComplex128Checker(false, "bad"), + in: 1i + 1, + exppass: false, + expexpl: "bad", + }, + } + wrappedCheckers := check.WrapMany(slicex.Map(testcases, + func(v checkerTestcase[complex128]) check.Checker[complex128] { + return v.checker + }, + )...) + + for i, tc := range testcases { + assertValidAnyChecker(t, checkerTestcase[any]{ + checker: wrappedCheckers[i], + in: tc.in, + exppass: tc.exppass, + expexpl: tc.expexpl, + }) + } +} + +func assertValidAnyChecker(t *testing.T, tc checkerTestcase[any]) { + t.Helper() + c := tc.checker + if pass := c.Pass(tc.in); pass != tc.exppass { + t.Errorf( + "unexpected Pass return value with checker %v: exp %v, got %v", + tc.checker, tc.exppass, pass, + ) + } + if expl := c.Explain("value", tc.in); tc.expexpl != "" && expl != tc.expexpl { + t.Errorf( + "unexpected Explain return value with checker %#v:\nexp:\n%v\n\ngot:\n%v", + tc.checker, tc.expexpl, expl, + ) + } +} + +// Helpers + +type customComplex128CheckerImpl struct { + exppass bool + expexpl string +} + +func (c customComplex128CheckerImpl) Pass(complex128) bool { return c.exppass } +func (c customComplex128CheckerImpl) Explain(string, interface{}) string { return c.expexpl } + +func newComplex128Checker(exppass bool, expexpl string) check.Checker[complex128] { + return check.NewChecker( + func(complex128) bool { return exppass }, + func(string, interface{}) string { return expexpl }, + ) +} + +func customComplex128Checker(exppass bool, expexpl string) check.Checker[complex128] { + return customComplex128CheckerImpl{exppass: exppass, expexpl: expexpl} +} diff --git a/checkconv/README.md b/checkconv/README.md deleted file mode 100644 index 161b99f..0000000 --- a/checkconv/README.md +++ /dev/null @@ -1,198 +0,0 @@ -

testx/checkconv

- - - - - - -
testxtestx/checktestx/checkconv
- -Package `checkconv` provides conversion utilities to convert any typed checker to a `check.ValueChecker` - -## Table of contents - -- [Why convert checkers](#why-convert-checkers) -- [Available functions](#from-functions) - - [`From` functions](#from-functions) - - [`Assert` functions](#assert-functions) - - [`Cast` functions](#cast-functions) -- [`Assert` vs `Cast`](#assert-vs-cast) - - [Should I use `Assert` or `Cast`](#should-i-use-assert-or-cast) - -## Why convert checkers - -Some runners from `testx` have methods that require generic checkers as parameters, -such as `testx.ValueRunner.Pass`: - -```go -type ValueRunner interface { - // Pass adds checkers on the tested value. - Pass(checkers ...check.ValueChecker) ValueRunner - // ... -} -``` - -These methods theorically accept any checker, either from package `check` -or locally implemented with custom types. - -However, without a language support for generic types, -package `check` cannot provide a generic checker interface because -they have distinct signatures for method `Pass(got T) bool`. - -```go -// What we need: -type GenericChecker[T any] interface { - Pass(got T) bool - Explainer -} - -// What we have: -type IntChecker interface { - Pass(got int) bool - Explainer -} -type StringChecker interface { - Pass(got string) bool - Explainer -} -``` - -As a consequence, there is no way to combine `IntChecker` and `StringChecker` -into a generic `Checker` interface. -For that reason, we use `check.ValueChecker` checker (checker on type `any`), -because it can wrap any checker type. - -That's what this package provides: functions to wrap any typed checker -into a generic `check.ValueChecker`. - -## Available functions - -### `From` functions - -`From` functions return a generic `check.ValueChecker` that wraps -the input `check.Checker`. - -It is typically used for runners that expect a `check.ValueRunner` -when one wants to use a typed checker: - -```go -testx. - Value(42). - // Pass expects a check.ValueChecker - Pass( - // We use check.IntChecker then we convert it using checkconv.FromInt. - checkconv.FromInt(check.Int.InRange(41, 43)), - ). - Run(t) -) -``` - -### Assert functions - -- `Assert(checker any) check.ValueChecker` -- `AssertMany(checkers ...any) []check.ValueChecker` - -Assert functions basically return `From(inputChecker)` -if that `From` function exists for the input checker. -Else, it panics. - -```go -testx. - Value(42). - // Pass expects a check.ValueChecker - Pass( - // We use check.IntChecker then we convert it using checkconv.Assert. - checkconv.Assert(check.Int.InRange(41, 43)), - ). - Run(t) -) -``` - -Alternatively, `AssertMany` can be used to convert several checkers at once: - -```go -testx. - Value(42). - // Pass expects a check.ValueChecker - Pass( - // We use several check.IntChecker then we convert them - // using checkconv.AssertMany. - checkconv.AssertMany( - check.Int.InRange(41, 43), - check.Int.Not(-1), - check.Int.GTE(99), - )..., - ). - Run(t) -) -``` - -### Cast functions - -- `Cast(checker any) (check.ValueChecker, bool)` -- `CastMany(checkers ...any) []check.ValueChecker` - -Cast functions serve the same purpose as Assert functions: -they wrap the given checker in a `check.ValueChecker`. -The difference is that it works with _any_ type that implement -a checker interface (`Pass(T) bool` and `Explain(string, any string`) -while Assert functions are restricted to the types defined in package `check`. - -### `Assert` vs `Cast` - -There is a fundamental difference between `Assert` and `Cast` implementations: - -- `Assert` uses `From` functions that rely on type assertion - to wrap the input checker: - ```go - func Assert(knownChecker any) check.ValueChecker { - switch c := knownChecker.(type) { - case check.StringChecker: - return FromString(c) - // ... - default: - // Assert panics if the input checker is not defined in package check. - log.Panic("assert from unknown checker type") - return nil - } - } - - func FromString(c check.StringChecker) check.ValueChecker { - return check.NewValueChecker( - func(got any) bool { return c.Pass(got.(string)) }, - c.Explain, // ^^^^^^^^^^^^^^^^^^^ got is safely asserted - ) - } - ``` - This is faster, but requires the input checker to implement one of the native - `check.Checker` interfaces. - -- `Cast` uses **reflection** to determinate whether a checker is valid and call -its methods in the resulting checker. As a consequence it is slower than -`Assert`, however it remains the only way to wrap a `check.ValueChecker` -around a full-fledged custom checker that performs checks on types that are not -defined in package `check`. - -#### Should I use `Assert` or `Cast` - -- Use `Assert`/`From` to convert any checker from package `check`, - or custom checkers that implement any `check.Checker` interface - (see [provided checkers](../check/README.md#provided-checkers)). - - ```go - checkconv.AssertMany( - check.Int.Range(41, 43), // satisfies check.IntChecker - check.NewIntChecker(isEven, explainIsEven), // satisfies check.IntChecker - myCustomIntChecker, // satisfies check.IntChecker - ) - ``` - -- Use `Cast` to convert any checker on types that are not defined - in package `check`. - - ```go - checkconv.CastMany( - myCustomComplex128Checker, - myCustomUserChecker, - ) - ``` \ No newline at end of file diff --git a/checkconv/assert.go b/checkconv/assert.go deleted file mode 100644 index 9ba93e2..0000000 --- a/checkconv/assert.go +++ /dev/null @@ -1,169 +0,0 @@ -// Code generated by go generate ./...; DO NOT EDIT -// Last generated on 31 Oct 21 17:02 UTC - -// Package checkconv provides functions to convert typed checkers -// into generic ones. -// -// TODO: delete package checkconv -package checkconv - -import ( - "context" - "net/http" - "time" - - "github.com/drykit-go/testx/check" -) - - -// FromBool returns a check.Checker[any] that wraps the given -// check.Checker[bool], so it can be used as a generic checker. -func FromBool(c check.Checker[bool]) check.Checker[any] { - return check.NewChecker( - func(got any) bool { return c.Pass(got.(bool)) }, - c.Explain, - ) -} - - -// FromBytes returns a check.Checker[any] that wraps the given -// check.Checker[[]byte], so it can be used as a generic checker. -func FromBytes(c check.Checker[[]byte]) check.Checker[any] { - return check.NewChecker( - func(got any) bool { return c.Pass(got.([]byte)) }, - c.Explain, - ) -} - - -// FromString returns a check.Checker[any] that wraps the given -// check.Checker[string], so it can be used as a generic checker. -func FromString(c check.Checker[string]) check.Checker[any] { - return check.NewChecker( - func(got any) bool { return c.Pass(got.(string)) }, - c.Explain, - ) -} - - -// FromInt returns a check.Checker[any] that wraps the given -// check.Checker[int], so it can be used as a generic checker. -func FromInt(c check.Checker[int]) check.Checker[any] { - return check.NewChecker( - func(got any) bool { return c.Pass(got.(int)) }, - c.Explain, - ) -} - - -// FromFloat64 returns a check.Checker[any] that wraps the given -// check.Checker[float64], so it can be used as a generic checker. -func FromFloat64(c check.Checker[float64]) check.Checker[any] { - return check.NewChecker( - func(got any) bool { return c.Pass(got.(float64)) }, - c.Explain, - ) -} - - -// FromDuration returns a check.Checker[any] that wraps the given -// check.Checker[time.Duration], so it can be used as a generic checker. -func FromDuration(c check.Checker[time.Duration]) check.Checker[any] { - return check.NewChecker( - func(got any) bool { return c.Pass(got.(time.Duration)) }, - c.Explain, - ) -} - - -// FromContext returns a check.Checker[any] that wraps the given -// check.Checker[context.Context], so it can be used as a generic checker. -func FromContext(c check.Checker[context.Context]) check.Checker[any] { - return check.NewChecker( - func(got any) bool { return c.Pass(got.(context.Context)) }, - c.Explain, - ) -} - - -// FromHTTPHeader returns a check.Checker[any] that wraps the given -// check.Checker[http.Header], so it can be used as a generic checker. -func FromHTTPHeader(c check.Checker[http.Header]) check.Checker[any] { - return check.NewChecker( - func(got any) bool { return c.Pass(got.(http.Header)) }, - c.Explain, - ) -} - - -// FromHTTPRequest returns a check.Checker[any] that wraps the given -// check.Checker[*http.Request], so it can be used as a generic checker. -func FromHTTPRequest(c check.Checker[*http.Request]) check.Checker[any] { - return check.NewChecker( - func(got any) bool { return c.Pass(got.(*http.Request)) }, - c.Explain, - ) -} - - -// FromHTTPResponse returns a check.Checker[any] that wraps the given -// check.Checker[*http.Response], so it can be used as a generic checker. -func FromHTTPResponse(c check.Checker[*http.Response]) check.Checker[any] { - return check.NewChecker( - func(got any) bool { return c.Pass(got.(*http.Response)) }, - c.Explain, - ) -} - - - - -// Assert returns a check.Checker[any] that wraps the given -// check.Checker[T]. -// -// It panics if checker is not a known checker type. For instance, -// a custom checker that implements check.IntChecker will be successfully -// converted, while a valid implementation of an unknown interface, -// such as Complex128Checker, will panic. -// For that matter, Cast should be used instead. -func Assert(knownChecker any) check.Checker[any] { - switch c := knownChecker.(type) { - case check.Checker[bool]: - return FromBool(c) - case check.Checker[[]byte]: - return FromBytes(c) - case check.Checker[string]: - return FromString(c) - case check.Checker[int]: - return FromInt(c) - case check.Checker[float64]: - return FromFloat64(c) - case check.Checker[time.Duration]: - return FromDuration(c) - case check.Checker[context.Context]: - return FromContext(c) - case check.Checker[http.Header]: - return FromHTTPHeader(c) - case check.Checker[*http.Request]: - return FromHTTPRequest(c) - case check.Checker[*http.Response]: - return FromHTTPResponse(c) - case check.Checker[any]: - return c - default: - panic("assert from unknown checker type") - } -} - -// AssertMany returns a slice of check.Checker[any] that wrap the given -// check.Checker[T]. -// -// It panics if any checker is not a known checker type. See Assert -// for further documentation. -func AssertMany(knownCheckers ...any) []check.Checker[any] { - valueCheckers := []check.Checker[any]{} - for _, c := range knownCheckers { - valueCheckers = append(valueCheckers, Assert(c)) - } - return valueCheckers -} diff --git a/checkconv/assert_test.go b/checkconv/assert_test.go deleted file mode 100644 index d68297b..0000000 --- a/checkconv/assert_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package checkconv_test - -import ( - "context" - "net/http" - "testing" - "time" - - "github.com/drykit-go/testx/check" - "github.com/drykit-go/testx/checkconv" - "github.com/drykit-go/testx/internal/testutil" -) - -func TestAssert(t *testing.T) { - t.Run("known checker type", func(t *testing.T) { - cases := []checkerTestcase{ - { - checker: check.String.Contains("a"), - in: "aaa", - expPass: true, - expExpl: "", - }, - { - checker: check.NewChecker(isEven, isEvenExpl), - in: -1, - expPass: false, - expExpl: "expect value to be even, got -1", - }, - { - checker: validCheckerInt{}, - in: 0, - expPass: true, - expExpl: "ok", - }, - } - - for _, tc := range cases { - c := checkconv.Assert(tc.checker) - assertValidValueChecker(t, c, tc) - } - }) - - t.Run("unknown checker type", func(t *testing.T) { - defer testutil.AssertPanicMessage(t, "assert from unknown checker type") - checkconv.Assert(validCheckerFloat32{}) - }) - - t.Run("invalid checkers", func(t *testing.T) { - for _, badChecker := range badCheckers { - func() { - defer testutil.AssertPanicMessage(t, "assert from unknown checker type") - checkconv.Assert(badChecker) - }() - } - }) -} - -func TestAssertMany(t *testing.T) { - t.Run("all provided checkers", func(t *testing.T) { - testcases := []struct { - checker any - in any - }{ - {checker: check.Bool.Is(true), in: true}, - {checker: check.Bytes.Is([]byte{'a'}), in: []byte{'a'}}, - {checker: check.String.Is("a"), in: "a"}, - {checker: check.Int.Is(1), in: 1}, - {checker: check.Float64.Is(1), in: 1.}, - {checker: check.Duration.Over(time.Millisecond), in: time.Second}, - {checker: check.Context.Done(true), in: ctxDone()}, - {checker: check.HTTPHeader.HasKey("a"), in: http.Header{"a": []string{"b"}}}, - {checker: check.HTTPRequest.ContentLength(check.Int.GT(1)), in: &http.Request{ContentLength: 2}}, - {checker: check.HTTPResponse.StatusCode(check.Int.GT(1)), in: &http.Response{StatusCode: 2}}, - {checker: check.Value.Is(1), in: 1}, - {checker: check.Map.HasKeys("a"), in: map[string]int{"a": 1}}, - {checker: check.Slice.HasValues("a"), in: []string{"a"}}, - {checker: check.Struct.NotZero(), in: struct{ n int }{1}}, - } - - providedCheckers := func() (checkers []any) { - for _, tc := range testcases { - checkers = append(checkers, tc.checker) - } - return - }() - - res := checkconv.AssertMany(providedCheckers...) - - if gotLen, expLen := len(res), len(providedCheckers); gotLen != expLen { - t.Errorf( - "failed to assert provided checkers: exp len %d, got %d", - expLen, gotLen, - ) - } - - for i, tc := range testcases { - if !res[i].Pass(tc.in) { - t.Errorf( - "failed to assert provided checkers: exp test case %d to pass", - i, - ) - } - } - }) - - t.Run("custom checkers known type", func(t *testing.T) { - knownCheckers := []any{ - check.Value.Custom("", func(_ any) bool { return true }), - check.NewChecker(isEven, validExplainFunc), - validCheckerInt{}, - validCheckerInterface{}, - } - res := checkconv.AssertMany(knownCheckers...) - if len(res) != len(knownCheckers) { - t.Error("failed to assert many known checkers") - } - }) - - t.Run("custom checkers unknown type", func(t *testing.T) { - defer testutil.AssertPanicMessage(t, "assert from unknown checker type") - unknownCheckers := []any{validCheckerFloat32{}} - checkconv.AssertMany(unknownCheckers...) - }) -} - -func ctxDone() context.Context { - ctx, cancel := context.WithCancel(context.Background()) - cancel() - return ctx -} diff --git a/checkconv/benchmark_test.go b/checkconv/benchmark_test.go deleted file mode 100644 index c5d3744..0000000 --- a/checkconv/benchmark_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package checkconv_test - -import ( - "testing" - "time" - - "github.com/drykit-go/testx/check" - "github.com/drykit-go/testx/checkconv" -) - -func BenchmarkAll(b *testing.B) { - b.Run("IsChecker", BenchmarkIsChecker) - b.Run("Cast", BenchmarkCast) - b.Run("Assert", BenchmarkAssert) - b.Run("From", BenchmarkFrom) -} - -func BenchmarkIsChecker(b *testing.B) { - c := validCheckerInt{} - for i := 0; i < b.N; i++ { - checkconv.IsChecker(c) - } -} - -func BenchmarkAssert(b *testing.B) { - b.Run("check.BoolChecker first_case", func(b *testing.B) { - c := check.Bool.Is(true) - for i := 0; i < b.N; i++ { - checkconv.Assert(c) - } - }) - b.Run("check.DurationChecker midway_case", func(b *testing.B) { - c := check.Duration.Over(time.Second) - for i := 0; i < b.N; i++ { - checkconv.Assert(c) - } - }) - b.Run("check.HTTPResponseChecker last_case", func(b *testing.B) { - c := check.HTTPResponse.Body(check.Bytes.Is([]byte{0})) - for i := 0; i < b.N; i++ { - checkconv.Assert(c) - } - }) -} - -func BenchmarkCast(b *testing.B) { - c := validCheckerInt{} - for i := 0; i < b.N; i++ { - checkconv.Cast(c) - } -} - -func BenchmarkFrom(b *testing.B) { - c := validCheckerInt{} - for i := 0; i < b.N; i++ { - checkconv.FromInt(c) - } -} diff --git a/checkconv/cast.go b/checkconv/cast.go deleted file mode 100644 index 5704288..0000000 --- a/checkconv/cast.go +++ /dev/null @@ -1,61 +0,0 @@ -package checkconv - -import ( - "reflect" - - "github.com/drykit-go/testx/check" -) - -// Cast returns a check.Checker[any] built upon the given checker -// and a bool indicating whether it was successful. -// -// Contrary to Assert, it can perform conversions from checker types -// unknown by package check. That means it can work with any custom -// implementation provided it is valid (see IsChecker for details). -// -// However, Assert should be the first choice for a known checker type -// as Cast is about 10 times slower. -func Cast(anyChecker any) (c check.Checker[any], ok bool) { - if !IsChecker(anyChecker) { - return - } - - v := reflect.ValueOf(anyChecker) - c = check.NewChecker( - func(got any) bool { - gotv := reflect.ValueOf(got) - return v.MethodByName(signaturePass.Name). - Call([]reflect.Value{gotv})[0]. - Bool() - }, - func(label string, got any) string { - labv := reflect.ValueOf(label) - gotv := reflect.ValueOf(got) - return v.MethodByName(signatureExpl.Name). - Call([]reflect.Value{labv, gotv})[0]. - String() - }, - ) - ok = true - return -} - -// CastMany converts the given checkers as described by Cast, -// and returns them as a slice of check.ValueChecker and a bool -// indicating whether all conversions were successful. -// -// An invalid checker in the args list is silently dismissed, -// this the resulting checkers length can be inferior to the number of args -// if ok === false. -func CastMany(anyCheckers ...any) (checkers []check.Checker[any], ok bool) { - ok = true - for _, in := range anyCheckers { - c, valid := Cast(in) - if valid { - checkers = append(checkers, c) - } else { - ok = false - } - } - return -} diff --git a/checkconv/cast_test.go b/checkconv/cast_test.go deleted file mode 100644 index 6e82428..0000000 --- a/checkconv/cast_test.go +++ /dev/null @@ -1,134 +0,0 @@ -package checkconv_test - -import ( - "testing" - - "github.com/drykit-go/testx/check" - "github.com/drykit-go/testx/checkconv" -) - -type checkerTestcase struct { - checker any - in any - expPass bool - expExpl string -} - -func TestCast(t *testing.T) { - t.Run("native checker", func(t *testing.T) { - cases := []checkerTestcase{ - { - checker: check.Bytes.Is([]byte{42}), - in: []byte{42}, - expPass: true, - expExpl: "", - }, - { - checker: check.Int.InRange(41, 43), - in: -1, - expPass: false, - expExpl: "value:\nexp in range [41:43]\ngot -1", - }, - { - checker: check.Value.Custom("", func(got any) bool { return true }), - in: "", - expPass: true, - expExpl: "", - }, - } - - for _, c := range cases { - assertCastable(t, c) - } - }) - - t.Run("custom checker", func(t *testing.T) { - cases := []checkerTestcase{ - { - checker: check.NewChecker(isEven, isEvenExpl), - in: 0, - expPass: true, - expExpl: "", - }, - { - checker: check.NewChecker(isEven, isEvenExpl), - in: 1, - expPass: false, - expExpl: "expect value to be even, got 1", - }, - } - - for _, c := range cases { - assertCastable(t, c) - } - }) - - t.Run("unknown checker", func(t *testing.T) { - cases := []checkerTestcase{ - { - checker: validCheckerInt{}, - in: 0, - expPass: true, - expExpl: "ok", - }, - { - checker: validCheckerInterface{}, - in: "anything", - expPass: true, - expExpl: "ok", - }, - } - - for _, c := range cases { - assertCastable(t, c) - } - }) - - t.Run("invalid checker", func(t *testing.T) { - for _, c := range badCheckers { - assertNotCastable(t, c) - } - }) -} - -func TestCastMany(t *testing.T) { - t.Run("valid checkers", func(t *testing.T) { - res, ok := checkconv.CastMany(goodCheckers...) - if !ok { - t.Error("returned !ok from good checkers") - } - if len(res) != len(goodCheckers) { - t.Error("failed to cast valid checkers") - } - }) - - t.Run("invalid checkers", func(t *testing.T) { - res, ok := checkconv.CastMany(badCheckers...) - if ok { - t.Error("returned ok from invalid checkers") - } - if len(res) != 0 { - t.Error("casted invalid checkers") - } - }) -} - -func assertCastable(t *testing.T, tc checkerTestcase) { - t.Helper() - c, ok := checkconv.Cast(tc.checker) - if !ok { - t.Errorf("failed to cast checker: %#v", tc.checker) - } - assertValidValueChecker(t, c, tc) -} - -func assertNotCastable(t *testing.T, badChecker any) { - t.Helper() - got, ok := checkconv.Cast(badChecker) - if ok { - t.Errorf("returned ok from bad input: %#v", badChecker) - } - if got != nil { - t.Errorf("returned a non-nil checker from bad input: %#v", badChecker) - } -} diff --git a/checkconv/checkconv_test.go b/checkconv/checkconv_test.go deleted file mode 100644 index 7595e9c..0000000 --- a/checkconv/checkconv_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package checkconv_test - -import ( - "errors" - "fmt" - "testing" - - "github.com/drykit-go/testx/check" -) - -// Common definitions used across test files. - -type onlyPasser struct{} - -func (onlyPasser) Pass(int) bool { return true } - -type onlyExplainer struct{} - -func (onlyExplainer) PassX(int) bool { return true } -func (onlyExplainer) Explain(string, any) string { return "" } - -type badPasser struct{} - -func (badPasser) Pass(int) int { return 0 } -func (badPasser) Explain(string, any) string { return "" } - -type badExplainerIn struct{} - -func (badExplainerIn) Pass(int) bool { return true } -func (badExplainerIn) Explain(any, any) string { return "" } - -type badExplainerOut struct{} - -func (badExplainerOut) Pass(int) bool { return true } -func (badExplainerOut) Explain(string, any) any { return "" } - -type checkerAsFields struct { - Pass func(int) bool - Explain func(string, any) string -} - -type validCheckerInt struct{} - -func (validCheckerInt) Pass(int) bool { return true } -func (validCheckerInt) Explain(string, any) string { return "ok" } - -type validCheckerFloat32 struct{} - -func (validCheckerFloat32) Pass(float32) bool { return true } -func (validCheckerFloat32) Explain(string, any) string { return "ok" } - -type validCheckerInterface struct{} - -func (validCheckerInterface) Pass(any) bool { return true } -func (validCheckerInterface) Explain(string, any) string { return "ok" } - -var badCheckers = []any{ - -1, - "hi", - errors.New(""), - onlyPasser{}, - onlyExplainer{}, - badPasser{}, - badExplainerIn{}, - badExplainerOut{}, - checkerAsFields{ - Pass: func(int) bool { return true }, - Explain: func(string, any) string { return "" }, - }, -} - -var goodCheckers = []any{ - validCheckerInt{}, - validCheckerFloat32{}, - validCheckerInterface{}, -} - -func validExplainFunc(_ string, _ any) string { - return "ok" -} - -// isEven is a dummy passFunc for custom checkers -func isEven(n int) bool { return n&1 == 0 } - -// isEvenExpl is a dummy explainFunc for custom checkers -func isEvenExpl(_ string, got any) string { - return fmt.Sprintf("expect value to be even, got %v", got) -} - -// Test helpers - -func assertValidValueChecker(t *testing.T, c check.Checker[any], tc checkerTestcase) { - t.Helper() - if pass := c.Pass(tc.in); pass != tc.expPass { - t.Errorf( - "unexpected Pass return value with checker %v: exp %v, got %v", - tc.checker, tc.expPass, pass, - ) - } - if expl := c.Explain("value", tc.in); tc.expExpl != "" && expl != tc.expExpl { - t.Errorf( - "unexpected Explain return value with checker %#v:\nexp:\n%v\n\ngot:\n%v", - tc.checker, tc.expExpl, expl, - ) - } -} diff --git a/checkconv/gen.go b/checkconv/gen.go deleted file mode 100644 index 4d36fc4..0000000 --- a/checkconv/gen.go +++ /dev/null @@ -1,18 +0,0 @@ -package checkconv - -//go:generate ../bin/gen -kind types -name assert - -// For every type {N,T} defined in ../gen/types.go, running go generate -// will create the following definitions: -// -// func FromN(c check.NChecker) check.ValueChecker { -// return check.NewValueCheck( -// func(got any) bool { return c.Pass(got.(T) }, -// c.Explain, -// ) -// } -// -// It will also add a new case in the switch statement of func Assert: -// -// case check.NChecker: -// return FromN(c) diff --git a/checkconv/ischecker.go b/checkconv/ischecker.go deleted file mode 100644 index fcf9735..0000000 --- a/checkconv/ischecker.go +++ /dev/null @@ -1,44 +0,0 @@ -package checkconv - -import ( - "reflect" - - "github.com/drykit-go/testx/internal/reflectutil" -) - -var ( - signaturePass = reflectutil.FuncSignature{ - Name: "Pass", - In: []reflect.Kind{reflectutil.AnyKind}, - Out: []reflect.Kind{reflect.Bool}, - } - - signatureExpl = reflectutil.FuncSignature{ - Name: "Explain", - In: []reflect.Kind{reflect.String, reflect.Interface}, - Out: []reflect.Kind{reflect.String}, - } -) - -// IsChecker returns true if the provided value is a valid checker. -// A valid checker is any type exposing two methods: -// - Pass(got T) bool -// - Explain(label string, got any) string -// Any custom implementation is considered valid whether or not it uses -// the package check. -// -// Note: The nature of Pass(got T) method, whose signature depend on the -// type of the tested value, prevents using a regular interface to identify -// a checker, hence the need of this helper. -func IsChecker(in any) bool { - v := reflect.ValueOf(in) - return isPasser(v) && isExplainer(v) -} - -func isPasser(v reflect.Value) bool { - return signaturePass.ImplementedBy(v) -} - -func isExplainer(v reflect.Value) bool { - return signatureExpl.ImplementedBy(v) -} diff --git a/checkconv/ischecker_test.go b/checkconv/ischecker_test.go deleted file mode 100644 index 1fbbd0a..0000000 --- a/checkconv/ischecker_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package checkconv_test - -import ( - "testing" - - "github.com/drykit-go/testx/checkconv" -) - -func TestIsChecker(t *testing.T) { - t.Run("invalid checkers", func(t *testing.T) { - values := append([]any{ - "a string", - 42, - func(int) bool { return true }, - }, badCheckers...) - - for _, v := range values { - if checkconv.IsChecker(v) { - t.Errorf("value %v was wrongly considered a checker", v) - } - } - }) - - t.Run("valid checkers", func(t *testing.T) { - for _, v := range goodCheckers { - if !checkconv.IsChecker(v) { - t.Errorf("checker %v was wrongly considered not a checker", v) - } - } - }) -} diff --git a/example_table_test.go b/example_table_test.go index 28637ce..1b4a9e6 100644 --- a/example_table_test.go +++ b/example_table_test.go @@ -6,7 +6,6 @@ import ( "github.com/drykit-go/testx" "github.com/drykit-go/testx/check" - "github.com/drykit-go/testx/checkconv" ) func ExampleTable_monadic() { @@ -19,7 +18,7 @@ func ExampleTable_monadic() { // hence no config is needed testx.Table(double).Cases([]testx.Case{ {In: 0.0, Exp: 0.0}, - {In: -2.0, Pass: checkconv.AssertMany(check.Float64.InRange(-5, -3))}, + {In: -2.0, Pass: check.WrapMany(check.Float64.InRange(-5, -3))}, }).Run(t) } diff --git a/go.mod b/go.mod index 14c78f6..77c5ec3 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,6 @@ go 1.18 require ( github.com/drykit-go/cond v0.1.0 + github.com/drykit-go/slicex v0.0.1 github.com/drykit-go/strcase v0.2.0 ) diff --git a/go.sum b/go.sum index a01b19e..884102c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/drykit-go/cond v0.1.0 h1:y7MNxREQLT83vGfcfSKjyFPLC/ZDjYBNp6KuaVVjOg4= github.com/drykit-go/cond v0.1.0/go.mod h1:7MXBFjjaB5ZCEB8Q4w2euNOaWuTqf7NjOFZAyV1Jpfg= +github.com/drykit-go/slicex v0.0.1 h1:w8+9x3opWIN4cwhmPLUOgzxgE3vWZV/sDuo42a6eCig= +github.com/drykit-go/slicex v0.0.1/go.mod h1:l68Za0PIxhCJ9Sm25dlIJEw1nYVonyfoBBc3aKfajaM= github.com/drykit-go/strcase v0.2.0 h1:Yzb2DZB6PcOzwMXKYOOQqaNYp5FqqlvZ1nfbGhlLvLU= github.com/drykit-go/strcase v0.2.0/go.mod h1:cWK0/az2f09UPIbJ42Sb8Iqdv01uENrFX+XXKGjPo+8= github.com/drykit-go/testx v0.1.0/go.mod h1:qGXb49a8CzQ82crBeCVW8R3kGU1KRgWHnI+Q6CNVbz8= diff --git a/runner_httphandler.go b/runner_httphandler.go index ff356a3..93c47ce 100644 --- a/runner_httphandler.go +++ b/runner_httphandler.go @@ -7,7 +7,6 @@ import ( "time" "github.com/drykit-go/testx/check" - "github.com/drykit-go/testx/checkconv" "github.com/drykit-go/testx/internal/httpconv" "github.com/drykit-go/testx/internal/ioutil" ) @@ -33,7 +32,7 @@ func (r *httpHandlerRunner) Duration(checkers ...check.Checker[time.Duration]) H r.addCheck(baseCheck{ label: "handling duration", get: func() gottype { return r.got.duration }, - checker: checkconv.FromDuration(c), + checker: check.Wrap(c), }) } return r @@ -44,7 +43,7 @@ func (r *httpHandlerRunner) Request(checkers ...check.Checker[*http.Request]) HT r.addCheck(baseCheck{ label: "http request", get: func() gottype { return r.got.request }, - checker: checkconv.FromHTTPRequest(c), + checker: check.Wrap(c), }) } return r @@ -55,7 +54,7 @@ func (r *httpHandlerRunner) Response(checkers ...check.Checker[*http.Response]) r.addCheck(baseCheck{ label: "http response", get: func() gottype { return r.got.response }, - checker: checkconv.FromHTTPResponse(c), + checker: check.Wrap(c), }) } return r diff --git a/runner_table_test.go b/runner_table_test.go index 7757740..25ed774 100644 --- a/runner_table_test.go +++ b/runner_table_test.go @@ -9,7 +9,6 @@ import ( "github.com/drykit-go/testx" "github.com/drykit-go/testx/check" - "github.com/drykit-go/testx/checkconv" ) // Tests @@ -68,8 +67,8 @@ func TestTableRunner(t *testing.T) { t.Run("using check.IntChecker", func(t *testing.T) { testx.Table(double). Cases([]testx.Case{ - {In: 21, Pass: checkconv.AssertMany(check.Int.Is(42))}, - {In: -4, Pass: checkconv.AssertMany(check.Int.InRange(-10, 0))}, + {In: 21, Pass: check.WrapMany(check.Int.Is(42))}, + {In: -4, Pass: check.WrapMany(check.Int.InRange(-10, 0))}, }). Run(t) }) diff --git a/runner_value.go b/runner_value.go index db0bf88..bca230b 100644 --- a/runner_value.go +++ b/runner_value.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/drykit-go/testx/check" - "github.com/drykit-go/testx/checkconv" ) var _ ValueRunner[any] = (*valueRunner[any])(nil) @@ -40,11 +39,7 @@ func (r *valueRunner[T]) Not(values ...T) ValueRunner[T] { func (r *valueRunner[T]) Pass(checkers ...check.Checker[T]) ValueRunner[T] { for _, c := range checkers { - cc, ok := checkconv.Cast(c) - if !ok { - panic("ValueRunner.Pass: bad conversion") - } - r.addValueCheck(cc) + r.addValueCheck(check.Wrap(c)) } return r } From 5bc00397ad97ac5f343c1ee292bf30e3b68cd38c Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Mon, 1 Nov 2021 18:53:38 +0100 Subject: [PATCH 05/19] feat(generics): implement generic number checker provider (#69) * Implement generic numberCheckerProvider[T Numeric] * Update interfaces accordingly * Write unit tests for numberCheckerProvider * Delete previous numeric checker implementations - intCheckerProvider - float64CheckerProvider --- check/check.go | 6 + check/providers.go | 50 ++++++- check/providers_float64.go | 95 ------------- check/providers_int.go | 95 ------------- check/providers_int_test.go | 133 ------------------ check/providers_number.go | 97 +++++++++++++ ...oat64_test.go => providers_number_test.go} | 52 +++---- 7 files changed, 175 insertions(+), 353 deletions(-) delete mode 100644 check/providers_float64.go delete mode 100644 check/providers_int.go delete mode 100644 check/providers_int_test.go create mode 100644 check/providers_number.go rename check/{providers_float64_test.go => providers_number_test.go} (59%) diff --git a/check/check.go b/check/check.go index ca97329..5b521c6 100644 --- a/check/check.go +++ b/check/check.go @@ -1,5 +1,11 @@ package check +import "constraints" + +type Numeric interface { + constraints.Integer | constraints.Float +} + type ( // PassFunc is the required method to implement Passer. // It returns a boolean that indicates whether the got value diff --git a/check/providers.go b/check/providers.go index 02c2c8f..5c4203a 100644 --- a/check/providers.go +++ b/check/providers.go @@ -184,6 +184,27 @@ type ( Len(c Checker[int]) Checker[any] } + // NumberCheckerProvider provides checks on numeric types: + // int, uint, float and their variants. + NumberCheckerProvider[T Numeric] interface { + // GT checks the gotten Number is greater than the target. + GT(tar T) Checker[T] + // GTE checks the gotten Number is greater or equal to the target. + GTE(tar T) Checker[T] + // InRange checks the gotten Number is in the closed interval [lo:hi]. + InRange(lo, hi T) Checker[T] + // Is checks the gotten Number is equal to the target. + Is(tar T) Checker[T] + // LT checks the gotten Number is lesser than the target. + LT(tar T) Checker[T] + // LTE checks the gotten Number is lesser or equal to the target. + LTE(tar T) Checker[T] + // Not checks the gotten Number is not equal to the target. + Not(values ...T) Checker[T] + // OutRange checks the gotten Number is not in the closed interval [lo:hi]. + OutRange(lo, hi T) Checker[T] + } + // SliceCheckerProvider provides checks on kind slice. SliceCheckerProvider interface { ValueCheckerProvider @@ -258,6 +279,31 @@ type ( ) var ( + // Int implements NumberCheckerProvider[int]. + Int NumberCheckerProvider[int] = numberCheckerProvider[int]{} + // Int8 implements NumberCheckerProvider[int8]. + Int8 NumberCheckerProvider[int8] = numberCheckerProvider[int8]{} + // Int16 implements NumberCheckerProvider[int16]. + Int16 NumberCheckerProvider[int16] = numberCheckerProvider[int16]{} + // Int32 implements NumberCheckerProvider[int32]. + Int32 NumberCheckerProvider[int32] = numberCheckerProvider[int32]{} + // Int64 implements NumberCheckerProvider[int64]. + Int64 NumberCheckerProvider[int64] = numberCheckerProvider[int64]{} + // Uint implements NumberCheckerProvider[uint]. + Uint NumberCheckerProvider[uint] = numberCheckerProvider[uint]{} + // Uint8 implements NumberCheckerProvider[uint8]. + Uint8 NumberCheckerProvider[uint8] = numberCheckerProvider[uint8]{} + // Uint16 implements NumberCheckerProvider[uint16]. + Uint16 NumberCheckerProvider[uint16] = numberCheckerProvider[uint16]{} + // Uint32 implements NumberCheckerProvider[uint32]. + Uint32 NumberCheckerProvider[uint32] = numberCheckerProvider[uint32]{} + // Uint64 implements NumberCheckerProvider[uint64]. + Uint64 NumberCheckerProvider[uint64] = numberCheckerProvider[uint64]{} + // Float32 implements NumberCheckerProvider[float32]. + Float32 NumberCheckerProvider[float32] = numberCheckerProvider[float32]{} + // Float64 implements NumberCheckerProvider[float64]. + Float64 NumberCheckerProvider[float64] = numberCheckerProvider[float64]{} + // Bool implements BoolCheckerProvider. Bool BoolCheckerProvider = boolCheckerProvider{} // Bytes implements BytesCheckerProvider. @@ -266,16 +312,12 @@ var ( Context ContextCheckerProvider = contextCheckerProvider{} // Duration implements DurationCheckerProvider. Duration DurationCheckerProvider = durationCheckerProvider{} - // Float64 implements Float64CheckerProvider. - Float64 Float64CheckerProvider = float64CheckerProvider{} // HTTPHeader implements HTTPHeaderCheckerProvider. HTTPHeader HTTPHeaderCheckerProvider = httpHeaderCheckerProvider{} // HTTPRequest implements HTTPRequestCheckerProvider. HTTPRequest HTTPRequestCheckerProvider = httpRequestCheckerProvider{} // HTTPResponse implements HTTPResponseCheckerProvider. HTTPResponse HTTPResponseCheckerProvider = httpResponseCheckerProvider{} - // Int implements IntCheckerProvider. - Int IntCheckerProvider = intCheckerProvider{} // Map implements MapCheckerProvider. Map MapCheckerProvider = mapCheckerProvider{} // Slice implements SliceCheckerProvider. diff --git a/check/providers_float64.go b/check/providers_float64.go deleted file mode 100644 index 57de112..0000000 --- a/check/providers_float64.go +++ /dev/null @@ -1,95 +0,0 @@ -package check - -import "fmt" - -// float64CheckerProvider provides checks on type float64. -type float64CheckerProvider struct{ baseCheckerProvider } - -// Is checks the gotten float64 is equal to the target. -func (p float64CheckerProvider) Is(tar float64) Checker[float64] { - pass := func(got float64) bool { return got == tar } - expl := func(label string, got any) string { - return p.explain(label, tar, got) - } - return NewChecker(pass, expl) -} - -// Not checks the gotten float64 is not equal to the target. -func (p float64CheckerProvider) Not(values ...float64) Checker[float64] { - var match float64 - pass := func(got float64) bool { - for _, v := range values { - if got == v { - match = v - return false - } - } - return true - } - expl := func(label string, got any) string { - return p.explainNot(label, match, got) - } - return NewChecker(pass, expl) -} - -// InRange checks the gotten float64 is in the closed interval [lo:hi]. -func (p float64CheckerProvider) InRange(lo, hi float64) Checker[float64] { - pass := func(got float64) bool { return p.inrange(got, lo, hi) } - expl := func(label string, got any) string { - return p.explain(label, fmt.Sprintf("in range [%v:%v]", lo, hi), got) - } - return NewChecker(pass, expl) -} - -// OutRange checks the gotten float64 is not in the closed interval [lo:hi]. -func (p float64CheckerProvider) OutRange(lo, hi float64) Checker[float64] { - pass := func(got float64) bool { return !p.inrange(got, lo, hi) } - expl := func(label string, got any) string { - return p.explainNot(label, fmt.Sprintf("in range [%v:%v]", lo, hi), got) - } - return NewChecker(pass, expl) -} - -// GT checks the gotten float64 is greater than the target. -func (p float64CheckerProvider) GT(tar float64) Checker[float64] { - pass := func(got float64) bool { return !p.lte(got, tar) } - expl := func(label string, got any) string { - return p.explain(label, fmt.Sprintf("> %v", tar), got) - } - return NewChecker(pass, expl) -} - -// GTE checks the gotten float64 is greater or equal to the target. -func (p float64CheckerProvider) GTE(tar float64) Checker[float64] { - pass := func(got float64) bool { return !p.lt(got, tar) } - expl := func(label string, got any) string { - return p.explain(label, fmt.Sprintf(">= %v", tar), got) - } - return NewChecker(pass, expl) -} - -// LT checks the gotten float64 is lesser than the target. -func (p float64CheckerProvider) LT(tar float64) Checker[float64] { - pass := func(got float64) bool { return p.lt(got, tar) } - expl := func(label string, got any) string { - return p.explain(label, fmt.Sprintf("< %v", tar), got) - } - return NewChecker(pass, expl) -} - -// LTE checks the gotten float64 is lesser or equal to the target. -func (p float64CheckerProvider) LTE(tar float64) Checker[float64] { - pass := func(got float64) bool { return p.lte(got, tar) } - expl := func(label string, got any) string { - return p.explain(label, fmt.Sprintf("<= %v", tar), got) - } - return NewChecker(pass, expl) -} - -// Helpers - -func (float64CheckerProvider) lt(a, b float64) bool { return a < b } -func (float64CheckerProvider) lte(a, b float64) bool { return a <= b } -func (p float64CheckerProvider) inrange(n, lo, hi float64) bool { - return !p.lt(n, lo) && p.lte(n, hi) -} diff --git a/check/providers_int.go b/check/providers_int.go deleted file mode 100644 index 5a491c1..0000000 --- a/check/providers_int.go +++ /dev/null @@ -1,95 +0,0 @@ -package check - -import "fmt" - -// intCheckerProvider provides checks on type int. -type intCheckerProvider struct{ baseCheckerProvider } - -// Is checks the gotten int is equal to the target. -func (p intCheckerProvider) Is(tar int) Checker[int] { - pass := func(got int) bool { return got == tar } - expl := func(label string, got any) string { - return p.explain(label, tar, got) - } - return NewChecker(pass, expl) -} - -// Not checks the gotten int is not equal to the target. -func (p intCheckerProvider) Not(values ...int) Checker[int] { - var match int - pass := func(got int) bool { - for _, v := range values { - if got == v { - match = v - return false - } - } - return true - } - expl := func(label string, got any) string { - return p.explainNot(label, match, got) - } - return NewChecker(pass, expl) -} - -// InRange checks the gotten int is in the closed interval [lo:hi]. -func (p intCheckerProvider) InRange(lo, hi int) Checker[int] { - pass := func(got int) bool { return p.inrange(got, lo, hi) } - expl := func(label string, got any) string { - return p.explain(label, fmt.Sprintf("in range [%v:%v]", lo, hi), got) - } - return NewChecker(pass, expl) -} - -// OutRange checks the gotten int is not in the closed interval [lo:hi]. -func (p intCheckerProvider) OutRange(lo, hi int) Checker[int] { - pass := func(got int) bool { return !p.inrange(got, lo, hi) } - expl := func(label string, got any) string { - return p.explainNot(label, fmt.Sprintf("in range [%v:%v]", lo, hi), got) - } - return NewChecker(pass, expl) -} - -// GT checks the gotten int is greater than the target. -func (p intCheckerProvider) GT(tar int) Checker[int] { - pass := func(got int) bool { return !p.lte(got, tar) } - expl := func(label string, got any) string { - return p.explain(label, fmt.Sprintf("> %v", tar), got) - } - return NewChecker(pass, expl) -} - -// GTE checks the gotten int is greater or equal to the target. -func (p intCheckerProvider) GTE(tar int) Checker[int] { - pass := func(got int) bool { return !p.lt(got, tar) } - expl := func(label string, got any) string { - return p.explain(label, fmt.Sprintf(">= %v", tar), got) - } - return NewChecker(pass, expl) -} - -// LT checks the gotten int is lesser than the target. -func (p intCheckerProvider) LT(tar int) Checker[int] { - pass := func(got int) bool { return p.lt(got, tar) } - expl := func(label string, got any) string { - return p.explain(label, fmt.Sprintf("< %v", tar), got) - } - return NewChecker(pass, expl) -} - -// LTE checks the gotten int is lesser or equal to the target. -func (p intCheckerProvider) LTE(tar int) Checker[int] { - pass := func(got int) bool { return p.lte(got, tar) } - expl := func(label string, got any) string { - return p.explain(label, fmt.Sprintf("<= %v", tar), got) - } - return NewChecker(pass, expl) -} - -// Helpers - -func (intCheckerProvider) lt(a, b int) bool { return a < b } -func (intCheckerProvider) lte(a, b int) bool { return a <= b } -func (p intCheckerProvider) inrange(n, lo, hi int) bool { - return !p.lt(n, lo) && p.lte(n, hi) -} diff --git a/check/providers_int_test.go b/check/providers_int_test.go deleted file mode 100644 index e003692..0000000 --- a/check/providers_int_test.go +++ /dev/null @@ -1,133 +0,0 @@ -package check_test - -import ( - "fmt" - "testing" - - "github.com/drykit-go/testx/check" -) - -func TestIntCheckerProvider(t *testing.T) { - const ( - n = 42 - inf = n - 1 - sup = n + 1 - ) - var ( - nstr = fmt.Sprint(n) - infstr = fmt.Sprint(inf) - supstr = fmt.Sprint(sup) - ) - - t.Run("Is pass", func(t *testing.T) { - c := check.Int.Is(n) - assertPassChecker(t, "Int.Is", c, n) - }) - - t.Run("Is fail", func(t *testing.T) { - c := check.Int.Is(inf) - assertFailChecker(t, "Int.Is", c, n, makeExpl(infstr, nstr)) - }) - - t.Run("Not pass", func(t *testing.T) { - c := check.Int.Not(-1, 314, -n) - assertPassChecker(t, "Int.Not", c, n) - }) - - t.Run("Not fail", func(t *testing.T) { - c := check.Int.Not(-1, 314, n, 1618) - assertFailChecker(t, "Int.Not", c, n, makeExpl("not "+nstr, nstr)) - }) - - t.Run("LT pass", func(t *testing.T) { - c := check.Int.LT(sup) - assertPassChecker(t, "Int.LT", c, n) - }) - - t.Run("LT fail", func(t *testing.T) { - c := check.Int.LT(inf) - assertFailChecker(t, "Int.LT", c, n, makeExpl("< "+infstr, nstr)) - c = check.Int.LT(n) - assertFailChecker(t, "Int.LT", c, n, makeExpl("< "+nstr, nstr)) - }) - - t.Run("LTE pass", func(t *testing.T) { - c := check.Int.LTE(sup) - assertPassChecker(t, "Int.LTE", c, n) - c = check.Int.LTE(n) - assertPassChecker(t, "Int.LTE", c, n) - }) - - t.Run("LTE fail", func(t *testing.T) { - c := check.Int.LTE(inf) - assertFailChecker(t, "Int.LTE", c, n, makeExpl("<= "+infstr, nstr)) - }) - - t.Run("GT pass", func(t *testing.T) { - c := check.Int.GT(inf) - assertPassChecker(t, "Int.GT", c, n) - }) - - t.Run("GT fail", func(t *testing.T) { - c := check.Int.GT(sup) - assertFailChecker(t, "Int.GT", c, n, makeExpl("> "+supstr, nstr)) - c = check.Int.GT(n) - assertFailChecker(t, "Int.GT", c, n, makeExpl("> "+nstr, nstr)) - }) - - t.Run("GTE pass", func(t *testing.T) { - c := check.Int.GTE(inf) - assertPassChecker(t, "Int.GTE", c, n) - c = check.Int.GTE(n) - assertPassChecker(t, "Int.GTE", c, n) - }) - - t.Run("GTE fail", func(t *testing.T) { - c := check.Int.GTE(sup) - assertFailChecker(t, "Int.GTE", c, n, makeExpl(">= "+supstr, nstr)) - }) - - t.Run("InRange pass", func(t *testing.T) { - c := check.Int.InRange(inf, sup) - assertPassChecker(t, "Int.InRange", c, n) - - c = check.Int.InRange(n, n) - assertPassChecker(t, "Int.InRange", c, n) - }) - - t.Run("InRange fail", func(t *testing.T) { - c := check.Int.InRange(sup, sup+1) - assertFailChecker(t, "Int.InRange", c, n, makeExpl( - fmt.Sprintf("in range [%v:%v]", sup, sup+1), - nstr, - )) - - c = check.Int.InRange(sup, inf) - assertFailChecker(t, "Int.InRange", c, n, makeExpl( - fmt.Sprintf("in range [%v:%v]", sup, inf), - nstr, - )) - }) - - t.Run("OutRange pass", func(t *testing.T) { - c := check.Int.OutRange(sup, sup+1) - assertPassChecker(t, "Int.OutRange", c, n) - - c = check.Int.OutRange(sup, inf) - assertPassChecker(t, "Int.OutRange", c, n) - }) - - t.Run("OutRange fail", func(t *testing.T) { - c := check.Int.OutRange(inf, sup) - assertFailChecker(t, "Int.OutRange", c, n, makeExpl( - fmt.Sprintf("not in range [%v:%v]", inf, sup), - nstr, - )) - - c = check.Int.OutRange(n, n) - assertFailChecker(t, "Int.OutRange", c, n, makeExpl( - fmt.Sprintf("not in range [%v:%v]", n, n), - nstr, - )) - }) -} diff --git a/check/providers_number.go b/check/providers_number.go new file mode 100644 index 0000000..8a773d1 --- /dev/null +++ b/check/providers_number.go @@ -0,0 +1,97 @@ +package check + +import ( + "fmt" +) + +// numberCheckerProvider provides checks on numeric types. +type numberCheckerProvider[T Numeric] struct{ baseCheckerProvider } + +// Is checks the gotten Number is equal to the target. +func (p numberCheckerProvider[T]) Is(tar T) Checker[T] { + pass := func(got T) bool { return got == tar } + expl := func(label string, got any) string { + return p.explain(label, tar, got) + } + return NewChecker(pass, expl) +} + +// Not checks the gotten Number is not equal to the target. +func (p numberCheckerProvider[T]) Not(values ...T) Checker[T] { + var match T + pass := func(got T) bool { + for _, v := range values { + if got == v { + match = v + return false + } + } + return true + } + expl := func(label string, got any) string { + return p.explainNot(label, match, got) + } + return NewChecker(pass, expl) +} + +// InRange checks the gotten Number is in the closed interval [lo:hi]. +func (p numberCheckerProvider[T]) InRange(lo, hi T) Checker[T] { + pass := func(got T) bool { return p.inrange(got, lo, hi) } + expl := func(label string, got any) string { + return p.explain(label, fmt.Sprintf("in range [%v:%v]", lo, hi), got) + } + return NewChecker(pass, expl) +} + +// OutRange checks the gotten Number is not in the closed interval [lo:hi]. +func (p numberCheckerProvider[T]) OutRange(lo, hi T) Checker[T] { + pass := func(got T) bool { return !p.inrange(got, lo, hi) } + expl := func(label string, got any) string { + return p.explainNot(label, fmt.Sprintf("in range [%v:%v]", lo, hi), got) + } + return NewChecker(pass, expl) +} + +// GT checks the gotten Number is greater than the target. +func (p numberCheckerProvider[T]) GT(tar T) Checker[T] { + pass := func(got T) bool { return !p.lte(got, tar) } + expl := func(label string, got any) string { + return p.explain(label, fmt.Sprintf("> %v", tar), got) + } + return NewChecker(pass, expl) +} + +// GTE checks the gotten Number is greater or equal to the target. +func (p numberCheckerProvider[T]) GTE(tar T) Checker[T] { + pass := func(got T) bool { return !p.lt(got, tar) } + expl := func(label string, got any) string { + return p.explain(label, fmt.Sprintf(">= %v", tar), got) + } + return NewChecker(pass, expl) +} + +// LT checks the gotten Number is lesser than the target. +func (p numberCheckerProvider[T]) LT(tar T) Checker[T] { + pass := func(got T) bool { return p.lt(got, tar) } + expl := func(label string, got any) string { + return p.explain(label, fmt.Sprintf("< %v", tar), got) + } + return NewChecker(pass, expl) +} + +// LTE checks the gotten Number is lesser or equal to the target. +func (p numberCheckerProvider[T]) LTE(tar T) Checker[T] { + pass := func(got T) bool { return p.lte(got, tar) } + expl := func(label string, got any) string { + return p.explain(label, fmt.Sprintf("<= %v", tar), got) + } + return NewChecker(pass, expl) +} + +// Helpers + +func (numberCheckerProvider[T]) lt(a, b T) bool { return a < b } +func (numberCheckerProvider[T]) lte(a, b T) bool { return a <= b } +func (p numberCheckerProvider[T]) inrange(n, lo, hi T) bool { + return !p.lt(n, lo) && p.lte(n, hi) +} diff --git a/check/providers_float64_test.go b/check/providers_number_test.go similarity index 59% rename from check/providers_float64_test.go rename to check/providers_number_test.go index fe7fee5..542de54 100644 --- a/check/providers_float64_test.go +++ b/check/providers_number_test.go @@ -7,9 +7,9 @@ import ( "github.com/drykit-go/testx/check" ) -func TestFloat64CheckerProvider(t *testing.T) { +func TestNumberCheckerProvider(t *testing.T) { const ( - n = 42 + n = 42. inf = n - 1 sup = n + 1 ) @@ -21,89 +21,89 @@ func TestFloat64CheckerProvider(t *testing.T) { t.Run("Is pass", func(t *testing.T) { c := check.Float64.Is(n) - assertPassChecker(t, "Float64.Is", c, n) + assertPassChecker(t, "Number.Is", c, n) }) t.Run("Is fail", func(t *testing.T) { c := check.Float64.Is(inf) - assertFailChecker(t, "Float64.Is", c, n, makeExpl(infstr, nstr)) + assertFailChecker(t, "Number.Is", c, n, makeExpl(infstr, nstr)) }) t.Run("Not pass", func(t *testing.T) { c := check.Float64.Not(-1, 314, -n) - assertPassChecker(t, "Float64.Not", c, n) + assertPassChecker(t, "Number.Not", c, n) }) t.Run("Not fail", func(t *testing.T) { c := check.Float64.Not(-1, 314, n, 1618) - assertFailChecker(t, "Float64.Not", c, n, makeExpl("not "+nstr, nstr)) + assertFailChecker(t, "Number.Not", c, n, makeExpl("not "+nstr, nstr)) }) t.Run("LT pass", func(t *testing.T) { c := check.Float64.LT(sup) - assertPassChecker(t, "Float64.LT", c, n) + assertPassChecker(t, "Number.LT", c, n) }) t.Run("LT fail", func(t *testing.T) { c := check.Float64.LT(inf) - assertFailChecker(t, "Float64.LT", c, n, makeExpl("< "+infstr, nstr)) + assertFailChecker(t, "Number.LT", c, n, makeExpl("< "+infstr, nstr)) c = check.Float64.LT(n) - assertFailChecker(t, "Float64.LT", c, n, makeExpl("< "+nstr, nstr)) + assertFailChecker(t, "Number.LT", c, n, makeExpl("< "+nstr, nstr)) }) t.Run("LTE pass", func(t *testing.T) { c := check.Float64.LTE(sup) - assertPassChecker(t, "Float64.LTE", c, n) + assertPassChecker(t, "Number.LTE", c, n) c = check.Float64.LTE(n) - assertPassChecker(t, "Float64.LTE", c, n) + assertPassChecker(t, "Number.LTE", c, n) }) t.Run("LTE fail", func(t *testing.T) { c := check.Float64.LTE(inf) - assertFailChecker(t, "Float64.LTE", c, n, makeExpl("<= "+infstr, nstr)) + assertFailChecker(t, "Number.LTE", c, n, makeExpl("<= "+infstr, nstr)) }) t.Run("GT pass", func(t *testing.T) { c := check.Float64.GT(inf) - assertPassChecker(t, "Float64.GT", c, n) + assertPassChecker(t, "Number.GT", c, n) }) t.Run("GT fail", func(t *testing.T) { c := check.Float64.GT(sup) - assertFailChecker(t, "Float64.GT", c, n, makeExpl("> "+supstr, nstr)) + assertFailChecker(t, "Number.GT", c, n, makeExpl("> "+supstr, nstr)) c = check.Float64.GT(n) - assertFailChecker(t, "Float64.GT", c, n, makeExpl("> "+nstr, nstr)) + assertFailChecker(t, "Number.GT", c, n, makeExpl("> "+nstr, nstr)) }) t.Run("GTE pass", func(t *testing.T) { c := check.Float64.GTE(inf) - assertPassChecker(t, "Float64.GTE", c, n) + assertPassChecker(t, "Number.GTE", c, n) c = check.Float64.GTE(n) - assertPassChecker(t, "Float64.GTE", c, n) + assertPassChecker(t, "Number.GTE", c, n) }) t.Run("GTE fail", func(t *testing.T) { c := check.Float64.GTE(sup) - assertFailChecker(t, "Float64.GTE", c, n, makeExpl(">= "+supstr, nstr)) + assertFailChecker(t, "Number.GTE", c, n, makeExpl(">= "+supstr, nstr)) }) t.Run("InRange pass", func(t *testing.T) { c := check.Float64.InRange(inf, sup) - assertPassChecker(t, "Float64.InRange", c, n) + assertPassChecker(t, "Number.InRange", c, n) c = check.Float64.InRange(n, n) - assertPassChecker(t, "Float64.InRange", c, n) + assertPassChecker(t, "Number.InRange", c, n) }) t.Run("InRange fail", func(t *testing.T) { c := check.Float64.InRange(sup, sup+1) - assertFailChecker(t, "Float64.InRange", c, n, makeExpl( + assertFailChecker(t, "Number.InRange", c, n, makeExpl( fmt.Sprintf("in range [%v:%v]", sup, sup+1), nstr, )) c = check.Float64.InRange(sup, inf) - assertFailChecker(t, "Float64.InRange", c, n, makeExpl( + assertFailChecker(t, "Number.InRange", c, n, makeExpl( fmt.Sprintf("in range [%v:%v]", sup, inf), nstr, )) @@ -111,21 +111,21 @@ func TestFloat64CheckerProvider(t *testing.T) { t.Run("OutRange pass", func(t *testing.T) { c := check.Float64.OutRange(sup, sup+1) - assertPassChecker(t, "Float64.OutRange", c, n) + assertPassChecker(t, "Number.OutRange", c, n) c = check.Float64.OutRange(sup, inf) - assertPassChecker(t, "Float64.OutRange", c, n) + assertPassChecker(t, "Number.OutRange", c, n) }) t.Run("OutRange fail", func(t *testing.T) { c := check.Float64.OutRange(inf, sup) - assertFailChecker(t, "Float64.OutRange", c, n, makeExpl( + assertFailChecker(t, "Number.OutRange", c, n, makeExpl( fmt.Sprintf("not in range [%v:%v]", inf, sup), nstr, )) c = check.Float64.OutRange(n, n) - assertFailChecker(t, "Float64.OutRange", c, n, makeExpl( + assertFailChecker(t, "Number.OutRange", c, n, makeExpl( fmt.Sprintf("not in range [%v:%v]", n, n), nstr, )) From b8222efb08b1bed3f5a69694e1abe53aa1eeae2d Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Mon, 1 Nov 2021 19:34:46 +0100 Subject: [PATCH 06/19] dev(generics): cherrypick #52 (#71) --- internal/gen/gen.go | 16 +-- internal/gen/interfaces.go | 4 +- internal/gen/serialize/serialize.go | 167 ---------------------------- internal/gen/types.go | 4 +- 4 files changed, 12 insertions(+), 179 deletions(-) delete mode 100644 internal/gen/serialize/serialize.go diff --git a/internal/gen/gen.go b/internal/gen/gen.go index 635a7f5..cdff73c 100644 --- a/internal/gen/gen.go +++ b/internal/gen/gen.go @@ -17,13 +17,13 @@ var tplFuncs = template.FuncMap{ type config struct { name, tpl, out string + src interface{} tplFuncs template.FuncMap - data any } // Types generates checkers declarations in packages check and checkconv -// for each type defined in var `types`. It should be run every time this -// list is modified. +// for each type defined in var `checkertypes`. It should be run every time +// that list is modified. // // For instance, the following entry: // {N: "Int", T: "int"}, @@ -47,10 +47,10 @@ type config struct { func Types(tpl, out string) error { return generate(config{ name: "types", + tplFuncs: tplFuncs, tpl: tpl, out: out, - tplFuncs: tplFuncs, - data: types, + src: checkertypes, }) } @@ -59,7 +59,7 @@ func Types(tpl, out string) error { // every time their API is modified (method signature change, doc comment, // new method, method removal, ...) func Interfaces(tpl, out string) error { - data, err := computeInterfaces() + src, err := computeInterfaces() if err != nil { return err } @@ -67,7 +67,7 @@ func Interfaces(tpl, out string) error { name: "interfaces", tpl: tpl, out: out, - data: data, + src: src, }) } @@ -82,7 +82,7 @@ func generate(cfg config) error { return err } - if err := t.Execute(f, cfg.data); err != nil { + if err := t.Execute(f, cfg.src); err != nil { return err } diff --git a/internal/gen/interfaces.go b/internal/gen/interfaces.go index 898bcb6..0518a03 100644 --- a/internal/gen/interfaces.go +++ b/internal/gen/interfaces.go @@ -5,12 +5,12 @@ import ( "go/doc" "go/parser" "go/token" + "go/types" "io/fs" "strings" "github.com/drykit-go/testx/internal/gen/docparser" "github.com/drykit-go/testx/internal/gen/metatype" - "github.com/drykit-go/testx/internal/gen/serialize" ) // ProvidersMetaData is a representation of the parsed doc for providers files @@ -62,7 +62,7 @@ func computeMetaInterface(t *doc.Type) metatype.Interface { continue } mitf.AddFunc(metatype.Func{ - Sign: serialize.FuncSignature(m.Name, m.Decl.Type), + Sign: m.Name + strings.TrimPrefix(types.ExprString(m.Decl.Type), "func"), DocLines: docparser.ParseDocLines(m.Doc, nil), }) } diff --git a/internal/gen/serialize/serialize.go b/internal/gen/serialize/serialize.go deleted file mode 100644 index 846a03a..0000000 --- a/internal/gen/serialize/serialize.go +++ /dev/null @@ -1,167 +0,0 @@ -package serialize - -import ( - "go/ast" - "log" - "strings" -) - -// stringBuilder is a wrapper of strings.Builder with additionnal methods -// to write serialized representation of go/ast types. -type stringBuilder struct{ strings.Builder } - -// FuncSignature builds a func signature given a name an *ast.FuncType -// and returns it as a string. -func FuncSignature(name string, ftyp *ast.FuncType) string { - b := stringBuilder{} - b.writeFunc(name, ftyp) - return b.String() -} - -// writeExpr writes a serialized ast.Expr to the builder. -func (b *stringBuilder) writeExpr(expr ast.Expr) { - switch t := expr.(type) { - case *ast.Ident: - b.WriteString(t.Name) - case *ast.ArrayType: - b.WriteString("[]") - b.writeExpr(t.Elt) - case *ast.Ellipsis: - b.WriteString("...") - b.writeExpr(t.Elt) - case *ast.FuncType: - b.writeFunc("func", t) - case *ast.SelectorExpr: - b.writeSelector(t) - case *ast.StarExpr: - b.WriteString("*") - b.writeExpr(t.X) - case *ast.ChanType: - b.writeChan(t) - case *ast.MapType: - b.writeMap(t) - case *ast.StructType: - b.writeStruct(t) - case *ast.InterfaceType: - b.writeInterface(t) - default: - log.Panicf("❌ unhandled ast.Expr: %#v", t) - } -} - -// writeFunc writes a serialized func type to the builder. -func (b *stringBuilder) writeFunc(name string, t *ast.FuncType) { - b.WriteString(name + "(") - b.writeFuncParams(t.Params) - b.WriteString(") ") - b.writeFuncResults(t.Results) -} - -// writeFuncParam writes a serialized func params list to the builder. -// It does nothing if params == nil -func (b *stringBuilder) writeFuncParams(params *ast.FieldList) { - if params == nil { - return - } - b.writeJoinedFields(params.List, ", ") -} - -// writeFuncResults writes a serialized func results list to the builder. -// It does nothing if results == nil. If results contains several values, -// they are wrapped in parentheses. -func (b *stringBuilder) writeFuncResults(results *ast.FieldList) { - if results == nil { - return - } - if results.NumFields() > 1 { - b.WriteString("(") - defer b.WriteString(")") - } - b.writeJoinedFields(results.List, ", ") -} - -// writeChan writes a serialized chan type to the builder. -func (b *stringBuilder) writeChan(t *ast.ChanType) { - var chanstr string - switch t.Dir { - case ast.RECV: - chanstr = "<-chan" - case ast.SEND: - chanstr = "chan<-" - default: - chanstr = "chan" - } - b.WriteString(chanstr) - b.WriteString(" ") - b.writeExpr(t.Value) -} - -// writeStruct writes a serialized struct type to the builder. -func (b *stringBuilder) writeStruct(t *ast.StructType) { - b.WriteString("struct{") - b.writeJoinedFields(t.Fields.List, "; ") - b.WriteString("}") -} - -// writeStruct writes a serialized map type to the builder. -func (b *stringBuilder) writeMap(t *ast.MapType) { - b.WriteString("map[") - b.writeExpr(t.Key) - b.WriteString("]") - b.writeExpr(t.Value) -} - -// writeStruct writes a serialized interface type to the builder. -func (b *stringBuilder) writeInterface(t *ast.InterfaceType) { - b.WriteString("interface{") - b.writeJoinedInterfaceFields(t.Methods.List, "; ") - b.WriteString("}") -} - -// writeStruct writes a serialized selector expression to the builder. -func (b *stringBuilder) writeSelector(t *ast.SelectorExpr) { - b.writeExpr(t.X) - b.WriteString(".") - b.WriteString(t.Sel.Name) -} - -// writeStruct writes joined identifiers separated by sep to the builder -func (b *stringBuilder) writeJoinedIdents(idents []*ast.Ident, sep string) { - for i, ident := range idents { - b.WriteString(ident.Name) - if i != len(idents)-1 { - b.WriteString(sep) - } - } -} - -// writeJoinedFields writes joined fields separated by sep to the builder. -// It can be used to serialiaze a list of func params or results, -// as well as struct fields. -// For interface fields and methods, use writeJoinedInterfaceFields instead -func (b *stringBuilder) writeJoinedFields(fields []*ast.Field, sep string) { - for i, f := range fields { - b.writeJoinedIdents(f.Names, ", ") - b.WriteString(" ") - b.writeExpr(f.Type) - if i != len(fields)-1 { - b.WriteString(sep) - } - } -} - -func (b *stringBuilder) writeJoinedInterfaceFields(fields []*ast.Field, sep string) { - for i, f := range fields { - switch t := f.Type.(type) { - case *ast.Ident: - b.WriteString(t.Name) - case *ast.FuncType: - b.WriteString(FuncSignature(f.Names[0].Name, t)) - default: - log.Panicf("joinInterfaceFields: unhandled type %#v", t) - } - if i != len(fields) { - b.WriteString(sep) - } - } -} diff --git a/internal/gen/types.go b/internal/gen/types.go index 2442c2c..f578134 100644 --- a/internal/gen/types.go +++ b/internal/gen/types.go @@ -1,8 +1,8 @@ package gen -// types is a slice of source types used to generate +// checkertypes is a slice of source types used to generate // their subsequent definitions. -var types = []struct { +var checkertypes = []struct { // Name, underlying Type N, T string }{ From 24223bc99d350b2e355f1972019b64b3dc7563b1 Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Mon, 1 Nov 2021 20:36:32 +0100 Subject: [PATCH 07/19] dev(generics): remove obsolete generation code (#72) * Delete go generate directive for typed checkers * Remove kind "types" from cmd * Remove gen.Types * Remove template: assert.gotmpl * Update contributing guidelines --- CONTRIBUTING.md | 63 ++++++---------------------- check/gen.go | 1 - cmd/gen/main.go | 1 - internal/gen/gen.go | 39 ----------------- internal/gen/templates/assert.gotmpl | 62 --------------------------- internal/gen/types.go | 20 --------- 6 files changed, 12 insertions(+), 174 deletions(-) delete mode 100644 internal/gen/templates/assert.gotmpl delete mode 100644 internal/gen/types.go diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d98386..963b44d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,47 +55,19 @@ The main documentation can help understand how the repository globally works: We use code generation to reduce code repetition, and consequently reduce the complexity of implementations and the risks of errors. -We have 2 use cases for that: -- Generate repetitive declarations for each checker type declared in `internal/gen/types.go`. -- Generate public interfaces of checker providers from their implementation. +We have 1 use case for that: generate public interfaces of checker providers +from their implementation. #### Generated files The following files are generated: -
- Generated files - - - - - - - - - - - - - - - - - - - - - - - - - -
FileUse case
check/check.goCheckers types declarations
check/checkers.goCheckers types declarations
check/providers.goCheckers providers interfaces
checkconv/assert.goCheckers types declarations
-
+- `check/providers.go` These files should **never** be manually edited, as specified in their header (your IDE will likely inform you it shouldn't be edited if you attempt to). +If an update is needed, re-generated them using the command described below. #### Command @@ -111,11 +83,8 @@ then runs all `//go:generate` directives found in the whole repository. Located in `gen.go` files, these directives execute `bin/gen` binary with various arguments. -Run this command each time: - -- you declare a new checker type (in file `internal/gen/types.go`) -- you implement a new `check.CheckerProvider` or update one -in a way that changes its public interface, which includes: +Run this command each time you implement a new `check.CheckerProvider` +or update one in a way that changes its public interface, which includes: - Adding/removing a method - Changing a method signature (a parameter name change counts) - Editing a doc comment for a method @@ -126,18 +95,6 @@ To illustrate the process described above, let's implement `check.Complex128`, a `check.Complex128CheckerProvider` that performs checks on type `complex128`: -1. In file `internal/gen/types.go`, add a new entry to `types`: - ```go - {N: "Complex128", T: "complex128"}, - ``` - Note: `N` is the **Name** of the checker provider, while **T** refers - to the **Type** of the value it checks. Technically, the former could be - any valid name, whereas the second must be a valid Go type. - -1. Run `make gen` -This generates `check.Complex128Checker` interface along with other -necessary declarations to work with this new type. - 1. Create file `check/providers_complex128.go` and implement `complex128CheckerProvider` following the existing models. @@ -157,11 +114,13 @@ Here are some contributing suggestions: ## Dev environment -For an optimal dev experience, we recommend: +This branches uses Go 1.18 type parameters, which is yet to be released. +As a consequence it has specific requirements: -- Go 1.16 or higher ([download](https://golang.org/dl/)) +- Go 1.18 (we recommand using the wrapper [`gotip`](https://pkg.go.dev/golang.org/dl/gotip)) - `make` commands available (native on Unix-based systems) - `golangci-lint` to run linters locally ([installation](https://golangci-lint.run/usage/install/#local-installation)) + Note: it currently doesn't work properly with type parameters syntax. ## Conventions @@ -170,6 +129,8 @@ For an optimal dev experience, we recommend: Code style conventions are enforced by `golangci-lint`. Run `make lint` to ensure your code is compliant. +Note: the linter currently doesn't work properly with type parameters syntax. + ### Unit tests We try to maintain a high level of test coverage, so we encourage you diff --git a/check/gen.go b/check/gen.go index bc8fbbc..17ea92f 100644 --- a/check/gen.go +++ b/check/gen.go @@ -1,4 +1,3 @@ package check -//go:generate ../bin/gen -kind types -name checkers // //go:generate ../bin/gen -kind interfaces -name providers diff --git a/cmd/gen/main.go b/cmd/gen/main.go index b4f2270..6c069c1 100644 --- a/cmd/gen/main.go +++ b/cmd/gen/main.go @@ -31,7 +31,6 @@ var ( var kindsFuncs = map[string]func(tpl, out string) error{ "interfaces": gen.Interfaces, - "types": gen.Types, } func main() { diff --git a/internal/gen/gen.go b/internal/gen/gen.go index cdff73c..e8f6f11 100644 --- a/internal/gen/gen.go +++ b/internal/gen/gen.go @@ -7,53 +7,14 @@ import ( "strings" "text/template" "time" - - "github.com/drykit-go/strcase" ) -var tplFuncs = template.FuncMap{ - "camelcase": strcase.Camel, -} - type config struct { name, tpl, out string src interface{} tplFuncs template.FuncMap } -// Types generates checkers declarations in packages check and checkconv -// for each type defined in var `checkertypes`. It should be run every time -// that list is modified. -// -// For instance, the following entry: -// {N: "Int", T: "int"}, -// generates the following declarations: -// -// // file check/check.go -// type IntPassFunc func(got int) bool -// type IntPasser interface { Pass(got int) bool } -// type IntChecker interface { IntPasser; Explainer } -// -// // file check/checkers.go -// type intChecker struct { ... } -// func (c intChecker) Pass(got int) bool { ... } -// func NewIntChecker(passFunc IntPassFunc, explainFunc ExplainFunc) IntChecker { ... } -// -// // file checkconv/assert.go -// func FromInt(c check.IntChecker) check.ValueChecker { ... } -// // also adds a case in checkconv.Assert: -// case check.IntChecker: -// return FromInt(c) -func Types(tpl, out string) error { - return generate(config{ - name: "types", - tplFuncs: tplFuncs, - tpl: tpl, - out: out, - src: checkertypes, - }) -} - // Interfaces generates the public interfaces for checker providers // implementations in package check (provider_*.go files). It should be run // every time their API is modified (method signature change, doc comment, diff --git a/internal/gen/templates/assert.gotmpl b/internal/gen/templates/assert.gotmpl deleted file mode 100644 index e37f238..0000000 --- a/internal/gen/templates/assert.gotmpl +++ /dev/null @@ -1,62 +0,0 @@ -// Package checkconv provides functions to convert typed checkers -// into generic ones. -// -// TODO: delete package checkconv -package checkconv - -import ( - "context" - "net/http" - "time" - - "github.com/drykit-go/testx/check" -) - -{{range . -}} -{{if ne .N "Value"}} -// From{{.N}} returns a check.Checker[any] that wraps the given -// check.Checker[{{.T}}], so it can be used as a generic checker. -func From{{.N}}(c check.Checker[{{.T}}]) check.Checker[any] { - return check.NewChecker( - func(got any) bool { return c.Pass(got.({{.T}})) }, - c.Explain, - ) -} -{{end}} -{{end}} - -// Assert returns a check.Checker[any] that wraps the given -// check.Checker[T]. -// -// It panics if checker is not a known checker type. For instance, -// a custom checker that implements check.IntChecker will be successfully -// converted, while a valid implementation of an unknown interface, -// such as Complex128Checker, will panic. -// For that matter, Cast should be used instead. -func Assert(knownChecker any) check.Checker[any] { - switch c := knownChecker.(type) { - {{range . -}} - case check.Checker[{{.T}}]: - {{if ne .N "Value" -}} - return From{{.N}}(c) - {{else -}} - return c - {{end -}} - {{end -}} - default: - panic("assert from unknown checker type") - } -} - -// AssertMany returns a slice of check.Checker[any] that wrap the given -// check.Checker[T]. -// -// It panics if any checker is not a known checker type. See Assert -// for further documentation. -func AssertMany(knownCheckers ...any) []check.Checker[any] { - valueCheckers := []check.Checker[any]{} - for _, c := range knownCheckers { - valueCheckers = append(valueCheckers, Assert(c)) - } - return valueCheckers -} diff --git a/internal/gen/types.go b/internal/gen/types.go deleted file mode 100644 index f578134..0000000 --- a/internal/gen/types.go +++ /dev/null @@ -1,20 +0,0 @@ -package gen - -// checkertypes is a slice of source types used to generate -// their subsequent definitions. -var checkertypes = []struct { - // Name, underlying Type - N, T string -}{ - {N: "Bool", T: "bool"}, - {N: "Bytes", T: "[]byte"}, - {N: "String", T: "string"}, - {N: "Int", T: "int"}, - {N: "Float64", T: "float64"}, - {N: "Duration", T: "time.Duration"}, - {N: "Context", T: "context.Context"}, - {N: "HTTPHeader", T: "http.Header"}, - {N: "HTTPRequest", T: "*http.Request"}, - {N: "HTTPResponse", T: "*http.Response"}, - {N: "Value", T: "any"}, -} From 5152d0231bdef8bfc34e6b32e6996d37c381c52d Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Mon, 1 Nov 2021 22:04:27 +0100 Subject: [PATCH 08/19] fix(generics): fix checker providers interfaces generation (#73) * Pre-compute numeric vars for numberCheckerProvider * Re-enable code generation for providers interfaces * Fix numberCheckerProvider interface generation * Run make gen --- check/gen.go | 2 +- check/providers.go | 129 ++++++++++-------------- check/providers_number.go | 7 ++ internal/gen/interfaces.go | 37 ++++++- internal/gen/templates/providers.gotmpl | 31 +++++- 5 files changed, 126 insertions(+), 80 deletions(-) diff --git a/check/gen.go b/check/gen.go index 17ea92f..feced00 100644 --- a/check/gen.go +++ b/check/gen.go @@ -1,3 +1,3 @@ package check -// //go:generate ../bin/gen -kind interfaces -name providers +//go:generate ../bin/gen -kind interfaces -name providers diff --git a/check/providers.go b/check/providers.go index 5c4203a..f3ff69a 100644 --- a/check/providers.go +++ b/check/providers.go @@ -1,3 +1,6 @@ +// Code generated by go generate ./...; DO NOT EDIT +// Last generated on 01 Nov 21 20:52 UTC + package check import ( @@ -8,12 +11,34 @@ import ( ) type ( + // NumberCheckerProvider provides checks on numeric types: + // int, uint, float and their variants. + NumberCheckerProvider[T Numeric] interface { + // GT checks the gotten Number is greater than the target. + GT(tar T) Checker[T] + // GTE checks the gotten Number is greater or equal to the target. + GTE(tar T) Checker[T] + // InRange checks the gotten Number is in the closed interval [lo:hi]. + InRange(lo, hi T) Checker[T] + // Is checks the gotten Number is equal to the target. + Is(tar T) Checker[T] + // LT checks the gotten Number is lesser than the target. + LT(tar T) Checker[T] + // LTE checks the gotten Number is lesser or equal to the target. + LTE(tar T) Checker[T] + // Not checks the gotten Number is not equal to the target. + Not(values ...T) Checker[T] + // OutRange checks the gotten Number is not in the closed interval [lo:hi]. + OutRange(lo, hi T) Checker[T] + } + // BoolCheckerProvider provides checks on type bool. BoolCheckerProvider interface { // Is checks the gotten bool is equal to the target. Is(tar bool) Checker[bool] + } - + // BytesCheckerProvider provides checks on type []byte. BytesCheckerProvider interface { // AsMap checks the gotten []byte passes the given mapChecker @@ -37,8 +62,9 @@ type ( // SameJSON checks the gotten []byte and the target read as the same // JSON value, ignoring formatting and keys order. SameJSON(tar []byte) Checker[[]byte] + } - + // ContextCheckerProvider provides checks on type context.Context. ContextCheckerProvider interface { // Done checks the gotten context is done. @@ -46,14 +72,15 @@ type ( // HasKeys checks the gotten context has the given keys set. HasKeys(keys ...any) Checker[context.Context] // Value checks the gotten context's value for the given key passes - // the given Checker[any]. It fails if value is nil. - // + // the given ValueChecker. It fails if value is nil. + // // Examples: // Context.Value("userID", Value.Is("abcde")) // Context.Value("userID", checkconv.Assert(String.Contains("abc"))) Value(key any, c Checker[any]) Checker[context.Context] + } - + // DurationCheckerProvider provides checks on type time.Duration. DurationCheckerProvider interface { // InRange checks the gotten time.Duration is in range [lo:hi] @@ -64,28 +91,9 @@ type ( Over(tar time.Duration) Checker[time.Duration] // Under checks the gotten time.Duration is under the target duration. Under(tar time.Duration) Checker[time.Duration] + } - - // Float64CheckerProvider provides checks on type float64. - Float64CheckerProvider interface { - // GT checks the gotten float64 is greater than the target. - GT(tar float64) Checker[float64] - // GTE checks the gotten float64 is greater or equal to the target. - GTE(tar float64) Checker[float64] - // InRange checks the gotten float64 is in the closed interval [lo:hi]. - InRange(lo, hi float64) Checker[float64] - // Is checks the gotten float64 is equal to the target. - Is(tar float64) Checker[float64] - // LT checks the gotten float64 is lesser than the target. - LT(tar float64) Checker[float64] - // LTE checks the gotten float64 is lesser or equal to the target. - LTE(tar float64) Checker[float64] - // Not checks the gotten float64 is not equal to the target. - Not(values ...float64) Checker[float64] - // OutRange checks the gotten float64 is not in the closed interval [lo:hi]. - OutRange(lo, hi float64) Checker[float64] - } - + // HTTPHeaderCheckerProvider provides checks on type http.Header. HTTPHeaderCheckerProvider interface { // CheckValue checks the gotten http.Header has a value for the matching key @@ -105,8 +113,9 @@ type ( // HasValue checks the gotten http.Header has any value equal to val. // It only compares the first result for each key. HasValue(val string) Checker[http.Header] + } - + // HTTPRequestCheckerProvider provides checks on type *http.Request. HTTPRequestCheckerProvider interface { // Body checks the gotten *http.Request Body passes the input Checker[[]byte]. @@ -122,8 +131,9 @@ type ( // Header checks the gotten *http.Request Header passes // the input Checker[http.Header]. Header(c Checker[http.Header]) Checker[*http.Request] + } - + // HTTPResponseCheckerProvider provides checks on type *http.Response. HTTPResponseCheckerProvider interface { // Body checks the gotten *http.Response Body passes the input Checker[[]byte]. @@ -142,28 +152,9 @@ type ( // StatusCode checks the gotten *http.Response StatusCode passes // the input Checker[int]. StatusCode(c Checker[int]) Checker[*http.Response] + } - - // IntCheckerProvider provides checks on type int. - IntCheckerProvider interface { - // GT checks the gotten int is greater than the target. - GT(tar int) Checker[int] - // GTE checks the gotten int is greater or equal to the target. - GTE(tar int) Checker[int] - // InRange checks the gotten int is in the closed interval [lo:hi]. - InRange(lo, hi int) Checker[int] - // Is checks the gotten int is equal to the target. - Is(tar int) Checker[int] - // LT checks the gotten int is lesser than the target. - LT(tar int) Checker[int] - // LTE checks the gotten int is lesser or equal to the target. - LTE(tar int) Checker[int] - // Not checks the gotten int is not equal to the target. - Not(values ...int) Checker[int] - // OutRange checks the gotten int is not in the closed interval [lo:hi]. - OutRange(lo, hi int) Checker[int] - } - + // MapCheckerProvider provides checks on kind map. MapCheckerProvider interface { ValueCheckerProvider @@ -182,36 +173,17 @@ type ( HasValues(values ...any) Checker[any] // Len checks the gotten map passes the given Checker[int]. Len(c Checker[int]) Checker[any] + } - - // NumberCheckerProvider provides checks on numeric types: - // int, uint, float and their variants. - NumberCheckerProvider[T Numeric] interface { - // GT checks the gotten Number is greater than the target. - GT(tar T) Checker[T] - // GTE checks the gotten Number is greater or equal to the target. - GTE(tar T) Checker[T] - // InRange checks the gotten Number is in the closed interval [lo:hi]. - InRange(lo, hi T) Checker[T] - // Is checks the gotten Number is equal to the target. - Is(tar T) Checker[T] - // LT checks the gotten Number is lesser than the target. - LT(tar T) Checker[T] - // LTE checks the gotten Number is lesser or equal to the target. - LTE(tar T) Checker[T] - // Not checks the gotten Number is not equal to the target. - Not(values ...T) Checker[T] - // OutRange checks the gotten Number is not in the closed interval [lo:hi]. - OutRange(lo, hi T) Checker[T] - } - + // SliceCheckerProvider provides checks on kind slice. SliceCheckerProvider interface { ValueCheckerProvider // Cap checks the capacity of the gotten slice passes the given Checker[int]. Cap(c Checker[int]) Checker[any] - // CheckValues checks the values of the gotten slice pass the given Checker[any]. + // CheckValues checks the values of the gotten slice passes + // the given Checker[any]. // If a filterFunc is provided, the values not passing it are ignored. CheckValues(c Checker[any], filters ...func(i int, v any) bool) Checker[any] // HasNotValues checks the gotten slice has not the given values set. @@ -220,8 +192,9 @@ type ( HasValues(values ...any) Checker[any] // Len checks the length of the gotten slice passes the given Checker[int]. Len(c Checker[int]) Checker[any] + } - + // StringCheckerProvider provides checks on type string. StringCheckerProvider interface { // Contains checks the gotten string contains the target substring. @@ -239,8 +212,9 @@ type ( NotContains(sub string) Checker[string] // NotMatch checks the gotten string do not match the given regexp. NotMatch(rgx *regexp.Regexp) Checker[string] + } - + // StructCheckerProvider provides checks on kind struct. StructCheckerProvider interface { ValueCheckerProvider @@ -253,8 +227,9 @@ type ( // It panics if the fields do not exist or are not exported, // or if the tested value is not a struct. FieldsEqual(exp any, fields []string) Checker[any] + } - + // ValueCheckerProvider provides checks on type any. ValueCheckerProvider interface { // Custom checks the gotten value passes the given PassFunc[any]. @@ -275,7 +250,9 @@ type ( // produce the same JSON, ignoring formatting and keys order. // It panics if any error occurs in the marshaling process. SameJSON(tar any) Checker[any] + } + ) var ( @@ -303,7 +280,6 @@ var ( Float32 NumberCheckerProvider[float32] = numberCheckerProvider[float32]{} // Float64 implements NumberCheckerProvider[float64]. Float64 NumberCheckerProvider[float64] = numberCheckerProvider[float64]{} - // Bool implements BoolCheckerProvider. Bool BoolCheckerProvider = boolCheckerProvider{} // Bytes implements BytesCheckerProvider. @@ -328,4 +304,5 @@ var ( Struct StructCheckerProvider = structCheckerProvider{} // Value implements ValueCheckerProvider. Value ValueCheckerProvider = valueCheckerProvider{} + ) diff --git a/check/providers_number.go b/check/providers_number.go index 8a773d1..f18086f 100644 --- a/check/providers_number.go +++ b/check/providers_number.go @@ -4,6 +4,13 @@ import ( "fmt" ) +// FIXME: The interface for this type can't be properly generated yet. +// For the moment it is hardcoded in the template file located at +// internal/gen/templates/providers.gotmpl. +// As a consequence, changes made in this file won't update the generated +// interface automatically: the template mentionned above must be updated +// manually. + // numberCheckerProvider provides checks on numeric types. type numberCheckerProvider[T Numeric] struct{ baseCheckerProvider } diff --git a/internal/gen/interfaces.go b/internal/gen/interfaces.go index 0518a03..4f02820 100644 --- a/internal/gen/interfaces.go +++ b/internal/gen/interfaces.go @@ -9,6 +9,8 @@ import ( "io/fs" "strings" + "github.com/drykit-go/slicex" + "github.com/drykit-go/testx/internal/gen/docparser" "github.com/drykit-go/testx/internal/gen/metatype" ) @@ -27,10 +29,16 @@ func computeInterfaces() (ProvidersMetaData, error) { return ProvidersMetaData{}, err } - data := ProvidersMetaData{} + data := ProvidersMetaData{Vars: numericMetaVars()} for _, t := range docp.Types { data.Vars = append(data.Vars, computeMetaVar(t)) - data.Interfaces = append(data.Interfaces, computeMetaInterface(t)) + + // FIXME: t.Method is empty for numberCheckerProvider, + // probably a bug due to it being type-parameterized. + // Thus we hardcode its interface in the template file. + if t.Name != "numberCheckerProvider" { + data.Interfaces = append(data.Interfaces, computeMetaInterface(t)) + } } return data, nil @@ -96,6 +104,31 @@ func newDocPackage(packageName string, filter func(fs.FileInfo) bool) (*doc.Pack return doc.New(astp, "./", doc.AllDecls), nil } +func numericMetaVars() []metatype.Var { + type namedType struct{ N, T string } + numerics := []namedType{ + {N: "Int", T: "int"}, + {N: "Int8", T: "int8"}, + {N: "Int16", T: "int16"}, + {N: "Int32", T: "int32"}, + {N: "Int64", T: "int64"}, + {N: "Uint", T: "uint"}, + {N: "Uint8", T: "uint8"}, + {N: "Uint16", T: "uint16"}, + {N: "Uint32", T: "uint32"}, + {N: "Uint64", T: "uint64"}, + {N: "Float32", T: "float32"}, + {N: "Float64", T: "float64"}, + } + return slicex.Map(numerics, func(nt namedType) metatype.Var { + return metatype.Var{ + Name: nt.N, + Type: "NumberCheckerProvider[" + nt.T + "]", + Value: "numberCheckerProvider[" + nt.T + "]{}", + } + }) +} + func isProviderFile(file fs.FileInfo) bool { return strings.HasPrefix(file.Name(), "providers_") && !isTestFile(file) && diff --git a/internal/gen/templates/providers.gotmpl b/internal/gen/templates/providers.gotmpl index 858fc01..02d88b5 100644 --- a/internal/gen/templates/providers.gotmpl +++ b/internal/gen/templates/providers.gotmpl @@ -1,7 +1,34 @@ package check +import ( + "context" + "net/http" + "regexp" + "time" +) + type ( - {{- range .Interfaces -}} + // NumberCheckerProvider provides checks on numeric types: + // int, uint, float and their variants. + NumberCheckerProvider[T Numeric] interface { + // GT checks the gotten Number is greater than the target. + GT(tar T) Checker[T] + // GTE checks the gotten Number is greater or equal to the target. + GTE(tar T) Checker[T] + // InRange checks the gotten Number is in the closed interval [lo:hi]. + InRange(lo, hi T) Checker[T] + // Is checks the gotten Number is equal to the target. + Is(tar T) Checker[T] + // LT checks the gotten Number is lesser than the target. + LT(tar T) Checker[T] + // LTE checks the gotten Number is lesser or equal to the target. + LTE(tar T) Checker[T] + // Not checks the gotten Number is not equal to the target. + Not(values ...T) Checker[T] + // OutRange checks the gotten Number is not in the closed interval [lo:hi]. + OutRange(lo, hi T) Checker[T] + } + {{range .Interfaces -}} {{range .DocLines}} // {{.}} {{- end}} @@ -22,7 +49,9 @@ type ( var ( {{range .Vars -}} + {{if ne .Name "Number" -}} // {{.Name}} implements {{.Type}}. {{.Name}} {{.Type}} = {{.Value}} + {{end -}} {{end}} ) From 81b495f10f1b0c642f50e3bc36a06e0134ba4e95 Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Mon, 1 Nov 2021 22:56:28 +0100 Subject: [PATCH 09/19] docs(generics): remove typed checkers mentions (#74) * Delete obsolete example * Update readmes * Remove last mentions of Checkers --- README.md | 5 -- check/README.md | 69 ++++++++++++++-------------- check/example_custom_unknown_test.go | 61 ------------------------ check/example_newchecker_test.go | 5 +- check/providers_context.go | 2 +- check/providers_context_test.go | 4 +- check/providers_httprequest_test.go | 2 +- internal/fmtexpl/ioutil_test.go | 4 +- runner_table.go | 2 +- runner_table_test.go | 2 +- 10 files changed, 44 insertions(+), 112 deletions(-) delete mode 100644 check/example_custom_unknown_test.go diff --git a/README.md b/README.md index bf5da05..151ff69 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ testx
testx/check - testx/checkconv @@ -185,7 +184,3 @@ Related examples: - Package `check` 📄 [Readme](./check/README.md) > Package `check` provides extensible and customizable checkers to perform checks on typed values. - -- Package `checkconv` 📄 [Readme](./checkconv/README.md) - > Package `checkconv` provides conversion utilities to convert any typed checker to a `check.ValueChecker` - diff --git a/check/README.md b/check/README.md index 6969e86..d46ea6e 100644 --- a/check/README.md +++ b/check/README.md @@ -4,7 +4,6 @@ testx testx/check - testx/checkconv @@ -24,22 +23,14 @@ for `testx` test runners. ## Checker interfaces -A checker is a type-specific interface composed of 2 funcs `Pass` and `Explain`: +A _checker_ is a generic interface composed of 2 methods `Pass` and `Explain`: ```go -// IntChecker provides methods to run checks on type int. -type IntChecker interface { - IntPasser - Explainer +// Checker provides methods to run checks on any typed value. +type Checker[T any] interface { + Pass(got T) bool + Explain(label string, got interface{}) string } - -// IntPasser provides a method Pass(got int) bool to determinate -// whether the got int passes a check. -type IntPasser interface { Pass(got int) bool } - - -// Explainer provides a method Explain to describe the reason of a failed check. -type Explainer interface { Explain(label string, got any) string } ``` ## Provided checkers @@ -58,29 +49,38 @@ Package `check` provides a collection of basic checkers for common types and kin - bool - check.Bool + int, intN (8, 16, 32, 64) + check.Int, check.IntN - - TypeCheckerProvider + + NumberCheckerProvider[int], NumberCheckerProvider[intN] - int - check.Int + uint, uintN (8, 16, 32, 64) + check.Uint, check.UintN - - IntCheckerProvider + + NumberCheckerProvider[uint], NumberCheckerProvider[uintN] - float64 - check.Float64 + floatN (32, 64) + check.FloatN - - Float64CheckerProvider + + NumberCheckerProvider[floatN] + + + + + bool + check.Bool + + + BoolCheckerProvider @@ -184,7 +184,7 @@ Package `check` provides a collection of basic checkers for common types and kin struct - check.Bool + check.Struct StructCheckerProvider @@ -205,14 +205,15 @@ There are 2 ways to extend a provided checker. #### Using a `New` function -With every `` covered by this package comes a `NewChecker` function. +Package `check` provides a `NewChecker` function to create a custom checker. -It takes a `PassFunc` for the corresponding type (`func(got Type) bool`) -and an `ExplainFunc`, then returns a checker for that type. +It takes a `PassFunc` that determinates if a got value passes the check +and an `ExplainFunc` that outputs the reason of a failed check, +then returns a checker for that type. ```go func Test42IsEven(t *testing.T) { - checkIsEven := check.NewIntChecker( + checkIsEven := check.NewChecker( func(got int) bool { return got&1 == 0 }, func(label string, got any) string { return fmt.Sprintf("%s: expect even int, got %v", label, got) @@ -227,7 +228,7 @@ func Test42IsEven(t *testing.T) { Related examples: -- [NewIntChecker](https://pkg.go.dev/github.com/drykit-go/testx/check#example-package-NewIntChecker) +- [NewChecker](https://pkg.go.dev/github.com/drykit-go/testx/check#example-package-NewChecker) #### Using `check.Value.Custom` @@ -263,9 +264,7 @@ can be used for `testx` runners via `checkconv.Cast`. Related examples: - [CustomChecker](https://pkg.go.dev/github.com/drykit-go/testx/check#example-package-CustomChecker): - implementation of a custom checker that implements `IntChecker` -- [CustomCheckerUnknownType](https://pkg.go.dev/github.com/drykit-go/testx/check#example-package-CustomCheckerUnknownType): - implementation of a custom checker for a local type `MyType` struct + implementation of a custom checker. - [CustomCheckerClosures](https://pkg.go.dev/github.com/drykit-go/testx/check#example-package-CustomCheckerClosures): advanced implementation of a parameterized custom checker for uncovered type `complex128`, using closures. diff --git a/check/example_custom_unknown_test.go b/check/example_custom_unknown_test.go deleted file mode 100644 index 5e396af..0000000 --- a/check/example_custom_unknown_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package check_test - -import ( - "fmt" - - "github.com/drykit-go/testx" - "github.com/drykit-go/testx/check" -) - -/* - Example: implementation of a custom checker of unknown type - having no match in interfaces declared by package check. -*/ - -type MyType struct { - ID int - Name string -} - -// MyTypeValidityChecker is a custom checker for type MyType -// that is not handled by package check. -// However, it works with any test runner that requires a generic checker -// because it implements Pass(got T) bool and check.Explainer. -type MyTypeValidityChecker struct{} - -// Pass do not satisfy any interface declared by check, but has a valid -// signature Pass(got T) bool, thus allowing IsPositiveComplexChecker -// to be recognized as a valid checker. -func (c MyTypeValidityChecker) Pass(got MyType) bool { - return got.ID >= 0 && len(got.Name) >= 3 -} - -// Explain satisfies Explainer interface. -func (c MyTypeValidityChecker) Explain(label string, got any) string { - return fmt.Sprintf("%s: got bad CustomType value: %v", label, got) -} - -// identityCustomType returns the input CustomType -func identityCustomType(v MyType) MyType { - return v -} - -func Example_customCheckerUnknownType() { - var checkIsValid check.Checker[MyType] = MyTypeValidityChecker{} - checkers := check.WrapMany(checkIsValid) - results := testx.Table(identityCustomType). - Cases([]testx.Case{ - {In: MyType{ID: 0, Name: "yes"}, Pass: checkers}, // pass - {In: MyType{ID: -1, Name: "no"}, Pass: checkers}, // fail - }). - DryRun() - - fmt.Println(results.PassedAt(0)) - fmt.Println(results.PassedAt(1)) - fmt.Println(results.Checks()) - - // Output: - // true - // false - // [{passed} {failed : got bad CustomType value: {-1 no}}] -} diff --git a/check/example_newchecker_test.go b/check/example_newchecker_test.go index efde329..b1f5713 100644 --- a/check/example_newchecker_test.go +++ b/check/example_newchecker_test.go @@ -8,11 +8,10 @@ import ( ) /* - Example: implementation of a custom checker of a type - defined by package check + Example: implementation of a custom checker using check.NewChecker */ -func Example_newIntChecker() { +func Example_newChecker() { checkIsEven := check.NewChecker( func(got int) bool { return got&1 == 0 }, func(label string, got any) string { diff --git a/check/providers_context.go b/check/providers_context.go index 28c8c4a..2182ddd 100644 --- a/check/providers_context.go +++ b/check/providers_context.go @@ -62,7 +62,7 @@ func (p contextCheckerProvider) Value(key any, c Checker[any]) Checker[context.C } expl := func(label string, got any) string { return p.explainCheck(label, - fmt.Sprintf("value for key %v to pass ValueChecker", key), + fmt.Sprintf("value for key %v to pass Checker[any]", key), c.Explain("value", v), ) } diff --git a/check/providers_context_test.go b/check/providers_context_test.go index 607c9fd..37a0e45 100644 --- a/check/providers_context_test.go +++ b/check/providers_context_test.go @@ -74,13 +74,13 @@ func TestContextCheckerProvider(t *testing.T) { ctxMissingKey := context.Background() assertFailChecker(t, "Context.Value", c, ctxMissingKey, makeExpl( - "value for key userID to pass ValueChecker", + "value for key userID to pass Checker[any]", "explanation: value:\n"+makeExpl("0", ""), )) ctxBadValue := ctxVal("userID", -1) assertFailChecker(t, "Context.Value", c, ctxBadValue, makeExpl( - "value for key userID to pass ValueChecker", + "value for key userID to pass Checker[any]", "explanation: value:\n"+makeExpl("0", "-1"), )) }) diff --git a/check/providers_httprequest_test.go b/check/providers_httprequest_test.go index ff99614..4b12675 100644 --- a/check/providers_httprequest_test.go +++ b/check/providers_httprequest_test.go @@ -89,7 +89,7 @@ func TestHTTPRequestCheckerProvider(t *testing.T) { assertFailChecker(t, "HTTPRequest.Context", c, newReq(), makeExpl( "context to pass Checker[context.Context]", "explanation: context:\n"+makeExpl( - "value for key userID to pass ValueChecker", + "value for key userID to pass Checker[any]", "explanation: value:\n"+makeExpl( "not 42", "42", diff --git a/internal/fmtexpl/ioutil_test.go b/internal/fmtexpl/ioutil_test.go index 798da5d..fcbd9f9 100644 --- a/internal/fmtexpl/ioutil_test.go +++ b/internal/fmtexpl/ioutil_test.go @@ -24,8 +24,8 @@ func TestPretty(t *testing.T) { } func TestChecker(t *testing.T) { - exp := "int value:\nexp to pass IntChecker\ngot explanation: Oh hi Mark!" - got := fmtexpl.Checker("int value", "to pass IntChecker", "Oh hi Mark!") + exp := "int value:\nexp to pass Checker[int]\ngot explanation: Oh hi Mark!" + got := fmtexpl.Checker("int value", "to pass Checker[int]", "Oh hi Mark!") if got != exp { t.Errorf("\nexp %s\ngot %s", exp, got) } diff --git a/runner_table.go b/runner_table.go index d2ead28..afe4f13 100644 --- a/runner_table.go +++ b/runner_table.go @@ -41,7 +41,7 @@ type Case struct { // Not is a slice of values expected not to be returned by the tested func. Not []any - // Pass is a slice of check.ValueChecker that the return value of the + // Pass is a slice of checkers that the return value of the // tested func is expected to pass. Pass []check.Checker[any] } diff --git a/runner_table_test.go b/runner_table_test.go index 25ed774..f31c39e 100644 --- a/runner_table_test.go +++ b/runner_table_test.go @@ -64,7 +64,7 @@ func TestTableRunner(t *testing.T) { Run(t) }) - t.Run("using check.IntChecker", func(t *testing.T) { + t.Run("using checkers", func(t *testing.T) { testx.Table(double). Cases([]testx.Case{ {In: 21, Pass: check.WrapMany(check.Int.Is(42))}, From 06bebb37514541f835aaec94ee5e7e0215a8b6e9 Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Wed, 3 Nov 2021 21:58:08 +0100 Subject: [PATCH 10/19] docs(generics): remove checkconv from readme example (#75) * Remove mention to checkconv in readme * Fix impossible example --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 151ff69..a9d3923 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@

`testx` is a Go testing library that provides test runners to write reliable -and expressive unit tests effortlessly, with minimal boilerplate. +and expressive unit tests with minimal boilerplate. ## Table of contents @@ -57,11 +57,11 @@ go get -u github.com/drykit-go/testx func TestGet42(t *testing.T) { testx.Value(Get42()). Exp(42). // expect 42 - Not(3, "hello"). // expect not 3 nor "hello" - Pass(checkconv.AssertMany( // expect to pass input checkers: + Not(3, 4, 5). // expect not 3 nor 4 nor 5 + Pass( // expect to pass all input checkers: check.Int.InRange(41, 43), // expect in range [41:43] // ... - )...). + ). Run(t) } ``` From c21d7c5024c59c08945bb63eebf46ed9ba05f113 Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Fri, 5 Nov 2021 22:42:07 +0100 Subject: [PATCH 11/19] feat(generics): type TableRunner with generics (#77) * Type TableRunner with generics * Remove testx.ExpNil * Update unit tests to new Case.Exp behaviour * Reorganize unit tests --- example_table_test.go | 11 +-- go.mod | 2 + go.sum | 2 + runner_table.go | 92 ++++++++++----------- runner_table_test.go | 181 ++++++++++++++++++------------------------ testx.go | 10 +-- 6 files changed, 135 insertions(+), 163 deletions(-) diff --git a/example_table_test.go b/example_table_test.go index 1b4a9e6..810ef2f 100644 --- a/example_table_test.go +++ b/example_table_test.go @@ -16,9 +16,9 @@ func ExampleTable_monadic() { // func double has 1 parameter and 1 return value, // hence no config is needed - testx.Table(double).Cases([]testx.Case{ + testx.Table[float64, float64](double).Cases([]testx.Case[float64, float64]{ {In: 0.0, Exp: 0.0}, - {In: -2.0, Pass: check.WrapMany(check.Float64.InRange(-5, -3))}, + {In: -2.0, Pass: []check.Checker[float64]{check.Float64.InRange(-5, -3)}}, }).Run(t) } @@ -44,13 +44,14 @@ func ExampleTable_dyadic() { // at position 1). // We inject Case.In at position 1 (param y) and use a fixed value // of 42.0 at position 0 (param x) for all cases. - testx.Table(divide).Config(testx.TableConfig{ + testx.Table[float64, error](divide).Config(testx.TableConfig{ // Positions start at 0 InPos: 1, // Case.In injected in param position 1 (y) OutPos: 1, // Case.Exp compared to return value position 1 (error value) FixedArgs: []any{0: 42.0}, // param 0 (x) set to 42.0 for all cases - }).Cases([]testx.Case{ - {In: 1.0, Exp: testx.ExpNil}, // divide(42.0, 1.0) -> (_, nil) + }).Cases([]testx.Case[float64, error]{ + // FIXME: ExpNil typing issues + // {In: 1.0, Exp: testx.ExpNil}, // divide(42.0, 1.0) -> (_, nil) {In: 0.0, Exp: errors.New("division by 0")}, // divide(42.0, 0.0) -> (_, err) }).Run(t) } diff --git a/go.mod b/go.mod index 77c5ec3..7bbad14 100644 --- a/go.mod +++ b/go.mod @@ -7,3 +7,5 @@ require ( github.com/drykit-go/slicex v0.0.1 github.com/drykit-go/strcase v0.2.0 ) + +require github.com/drykit-go/slices v0.0.4 // indirect diff --git a/go.sum b/go.sum index 884102c..20f950a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/drykit-go/cond v0.1.0 h1:y7MNxREQLT83vGfcfSKjyFPLC/ZDjYBNp6KuaVVjOg4= github.com/drykit-go/cond v0.1.0/go.mod h1:7MXBFjjaB5ZCEB8Q4w2euNOaWuTqf7NjOFZAyV1Jpfg= +github.com/drykit-go/slices v0.0.4 h1:DNdJwPKJ3O4aq3oaXGQFZMTBYrrs9458ucN9uuX4txo= +github.com/drykit-go/slices v0.0.4/go.mod h1:xU3NP7e7CFy3lZpECQDCjn/jLdcLBeA7+duOyOFsLRg= github.com/drykit-go/slicex v0.0.1 h1:w8+9x3opWIN4cwhmPLUOgzxgE3vWZV/sDuo42a6eCig= github.com/drykit-go/slicex v0.0.1/go.mod h1:l68Za0PIxhCJ9Sm25dlIJEw1nYVonyfoBBc3aKfajaM= github.com/drykit-go/strcase v0.2.0 h1:Yzb2DZB6PcOzwMXKYOOQqaNYp5FqqlvZ1nfbGhlLvLU= diff --git a/runner_table.go b/runner_table.go index afe4f13..261145b 100644 --- a/runner_table.go +++ b/runner_table.go @@ -7,43 +7,36 @@ import ( "testing" "github.com/drykit-go/cond" + "github.com/drykit-go/slices" "github.com/drykit-go/testx/check" "github.com/drykit-go/testx/internal/fmtexpl" "github.com/drykit-go/testx/internal/reflectutil" ) -var _ TableRunner = (*tableRunner)(nil) - // Case represents a Table test case. It must be provided values for -// Case.In, and Case.Exp or Case.Not or Case.Pass at least. -type Case struct { +// Case.In at least. +type Case[In, Exp any] struct { // Lab is the label of the current case to be printed if the current // case fails. Lab string // In is the input value injected in the tested func. - In any + In In // Exp is the value expected to be returned when calling the tested func. - // If Case.Exp == nil (zero value), no check is added. This is a necessary - // behavior if one wants to use Case.Pass or Case.Not but not Case.Exp. - // To specifically check for a nil value, use ExpNil. - // - // testx.Table(myFunc, nil).Cases([]testx.Case{ - // {In: 123, Pass: checkers}, // Exp == nil, no Exp check added - // {In: 123}, // Exp == nil, no Exp check added - // {In: 123, Exp: nil}, // Exp == nil, no Exp check added - // {In: 123, Exp: testx.ExpNil}, // Exp == ExpNil, expect nil value - // }) - Exp any + // It is ignored if Case.Not or Case.Pass is not empty. + // Otherwise Case.Exp is always evaluated even if not set. + Exp Exp // Not is a slice of values expected not to be returned by the tested func. - Not []any + // If set, Case.Exp is ignored. + Not []Exp // Pass is a slice of checkers that the return value of the // tested func is expected to pass. - Pass []check.Checker[any] + // If set, Case.Exp is ignored. + Pass []check.Checker[Exp] } // TableConfig is configuration object for TableRunner. @@ -107,28 +100,28 @@ func (args Args) String() string { return b.String() } -type tableRunner struct { +type tableRunner[In, Exp any] struct { baseRunner config TableConfig - get func(in any) gottype + get func(in In) Exp rfunc *reflectutil.Func args Args } -func (r *tableRunner) Run(t *testing.T) { +func (r *tableRunner[In, Exp]) Run(t *testing.T) { t.Helper() cond.PanicOnErr(r.setGetFunc()) r.run(t) } -func (r *tableRunner) DryRun() TableResulter { +func (r *tableRunner[In, Exp]) DryRun() TableResulter { cond.PanicOnErr(r.setGetFunc()) return tableResults{baseResults: r.dryRun()} } -func (r *tableRunner) setGetFunc() error { +func (r *tableRunner[In, Exp]) setGetFunc() error { if err := r.validateConfig(); err != nil { return err } @@ -138,15 +131,20 @@ func (r *tableRunner) setGetFunc() error { return err } - r.get = func(in any) gottype { + r.get = func(in In) Exp { pin, pout := r.config.InPos, r.config.OutPos r.args = args.replaceAt(pin, in) - return r.rfunc.Call(r.args)[pout] + out, ok := r.rfunc.Call(r.args)[pout].(Exp) + if !ok { + var vnil Exp + return vnil + } + return out } return nil } -func (r *tableRunner) Cases(cases []Case) TableRunner { +func (r *tableRunner[In, Exp]) Cases(cases []Case[In, Exp]) TableRunner[In, Exp] { for i, tc := range cases { i, tc := i, tc @@ -165,31 +163,34 @@ func (r *tableRunner) Cases(cases []Case) TableRunner { }) } - // add Case.Exp check - if tc.Exp != nil { - exp := cond.Value(nil, tc.Exp, tc.Exp == ExpNil) - addCaseCheck(check.Value.Is(exp)) - } + hasNotChecks := len(tc.Not) != 0 + hasPassChecks := len(tc.Pass) != 0 + hasExpCheck := !hasNotChecks && !hasPassChecks // add Case.Not checks - if len(tc.Not) != 0 { - addCaseCheck(check.Value.Not(tc.Not...)) + if hasNotChecks { + addCaseCheck(check.Value.Not(slices.AsAny(tc.Not)...)) } // add Case.Pass checks - if len(tc.Pass) != 0 { - r.addChecks(tc.Lab, get, tc.Pass) + if hasPassChecks { + r.addChecks(tc.Lab, get, check.WrapMany(tc.Pass...)) + } + + // add Case.Exp checks + if hasExpCheck { + addCaseCheck(check.Value.Is(tc.Exp)) } } return r } -func (r *tableRunner) Config(cfg TableConfig) TableRunner { +func (r *tableRunner[In, Exp]) Config(cfg TableConfig) TableRunner[In, Exp] { r.config = cfg return r } -func (r *tableRunner) setRfunc(in any) error { +func (r *tableRunner[In, Exp]) setRfunc(in any) error { rfunc, err := reflectutil.NewFunc(in) if err != nil { return fmt.Errorf("Table(func): %w", err) @@ -205,7 +206,7 @@ func (r *tableRunner) setRfunc(in any) error { return nil } -func (r *tableRunner) validateConfig() error { +func (r *tableRunner[In, Exp]) validateConfig() error { validPos := func(pos, max int) bool { return pos >= 0 && pos < max } ftyp := r.rfunc.Value.Type() if pin, nin := r.config.InPos, ftyp.NumIn(); !validPos(pin, nin) { @@ -217,7 +218,7 @@ func (r *tableRunner) validateConfig() error { return nil } -func (r *tableRunner) makeFixedArgs(rfunc *reflectutil.Func, cfg TableConfig) (Args, error) { +func (r *tableRunner[In, Exp]) makeFixedArgs(rfunc *reflectutil.Func, cfg TableConfig) (Args, error) { nparams := rfunc.Value.Type().NumIn() nargs := len(cfg.FixedArgs) @@ -240,8 +241,8 @@ func (r *tableRunner) makeFixedArgs(rfunc *reflectutil.Func, cfg TableConfig) (A } } -func newTableRunner(testedFunc any) TableRunner { - r := &tableRunner{} +func newTableRunner[In, Exp any](testedFunc any) TableRunner[In, Exp] { + r := &tableRunner[In, Exp]{} cond.PanicOnErr(r.setRfunc(testedFunc)) return r } @@ -277,12 +278,3 @@ func (res tableResults) PassedLabel(label string) bool { func (res tableResults) FailedLabel(label string) bool { return !res.PassedLabel(label) } - -/* - ExpNil -*/ - -type expNil int - -// ExpNil is a special value for Case.Exp that sets the expected value to nil. -const ExpNil expNil = 0 diff --git a/runner_table_test.go b/runner_table_test.go index f31c39e..b025ea0 100644 --- a/runner_table_test.go +++ b/runner_table_test.go @@ -21,7 +21,7 @@ var expFixedArgs = map[string]any{ // TestTableRunner ensures testx.Table behaves correctly, in particular // when dealing with functions with multiple inputs and outputs. func TestTableRunner(t *testing.T) { - cases := []testx.Case{ + cases := []testx.Case[int, bool]{ {In: 42, Exp: true}, {In: 99, Exp: false, Lab: "odd number"}, } @@ -34,161 +34,138 @@ func TestTableRunner(t *testing.T) { a0, a2 := expFixedArgs["a0"], expFixedArgs["a2"] t.Run("single in single out", func(t *testing.T) { - testx.Table(evenSingle).Cases(cases).Run(t) + testx.Table[int, bool](evenSingle).Cases(cases).Run(t) }) t.Run("single in multiple out", func(t *testing.T) { - testx.Table(evenMultipleOut).Config(testx.TableConfig{ + testx.Table[int, bool](evenMultipleOut).Config(testx.TableConfig{ OutPos: outPos, - }). - Cases(cases). - Run(t) + }).Cases(cases).Run(t) }) t.Run("multiple in single out", func(t *testing.T) { - testx.Table(evenMultipleIn).Config(testx.TableConfig{ + testx.Table[int, bool](evenMultipleIn).Config(testx.TableConfig{ InPos: inPos, FixedArgs: []any{a0, a2}, // len(FixedArgs) == nparams-1 - }). - Cases(cases). - Run(t) + }).Cases(cases).Run(t) }) t.Run("multiple in multiple out", func(t *testing.T) { - testx.Table(evenMultipleInOut).Config(testx.TableConfig{ + testx.Table[int, bool](evenMultipleInOut).Config(testx.TableConfig{ InPos: inPos, OutPos: outPos, FixedArgs: []any{0: a0, 2: a2}, // len(FixedArgs) == nparams - }). - Cases(cases). - Run(t) - }) - - t.Run("using checkers", func(t *testing.T) { - testx.Table(double). - Cases([]testx.Case{ - {In: 21, Pass: check.WrapMany(check.Int.Is(42))}, - {In: -4, Pass: check.WrapMany(check.Int.InRange(-10, 0))}, - }). - Run(t) + }).Cases(cases).Run(t) }) +} - t.Run("expect nil value", func(t *testing.T) { - runner := testx.Table(func(wantnil bool) any { - if wantnil { - return nil - } - return 0 - }).Cases([]testx.Case{ - {In: false, Exp: 0}, - {In: true}, // Exp == nil, no check added - {In: true, Exp: testx.ExpNil}, // expect nil value - }) +func TestTableRunner_Cases(t *testing.T) { + f := func(int) int { return 42 } - runner.Run(t) + t.Run("Case.Not overrides Case.Exp", func(t *testing.T) { + res := testx.Table[int, int](f).Cases([]testx.Case[int, int]{ + {In: 0, Exp: -1, Not: []int{1, 2}}, + }).DryRun() - if n := runner.DryRun().NChecks(); n != 2 { - t.Errorf("exp 2 checks, got %d", n) + if n := res.NChecks(); n != 1 { + t.Errorf("exp 1 check, got %d", n) + } + if res.Failed() { + t.Error("exp to pass, failed") } }) - t.Run("Case.Not checks", func(t *testing.T) { - results := testx.Table(func(n int) int { return n }). - Cases([]testx.Case{ - {In: 0, Not: []any{-1, 1}}, // pass - {In: 0, Not: []any{0}}, // fail - }). - DryRun() + t.Run("Case.Pass overrides Case.Exp", func(t *testing.T) { + res := testx.Table[int, int](f).Cases([]testx.Case[int, int]{ + {In: 0, Exp: -1, Pass: []check.Checker[int]{check.Int.InRange(41, 43)}}, + }).DryRun() - if nc := results.NChecks(); nc != 2 { - t.Errorf("exp 2 checks, got %d", nc) - } - if results.FailedAt(0) { - t.Error("exp Case 0 to pass, got fail") + if n := res.NChecks(); n != 1 { + t.Errorf("exp 1 check, got %d", n) } - if results.PassedAt(1) { - t.Error("exp Case 1 to fail, got pass") + if res.Failed() { + t.Error("exp to pass, failed") } }) - t.Run("test case labels", func(t *testing.T) { - results := testx.Table(divide).Config(testx.TableConfig{ - InPos: 1, - OutPos: 1, - FixedArgs: testx.Args{42.0}, - }).Cases([]testx.Case{ - {In: 0.0, Exp: testx.ExpNil, Lab: "zeroth case"}, // fail - {In: 0.0, Exp: testx.ExpNil, Lab: "first case"}, // fail + t.Run("Case.Exp used if Case.Not and Case.Pass not set", func(t *testing.T) { + res := testx.Table[int, int](f).Cases([]testx.Case[int, int]{ + {In: 0, Exp: 42}, }).DryRun() - expLabels := []string{ - `Table.Cases[0] "zeroth case" testx_test.divide(42, 0)`, - `Table.Cases[1] "first case" testx_test.divide(42, 0)`, + if n := res.NChecks(); n != 1 { + t.Errorf("exp 1 check, got %d", n) } - - for i, c := range results.Checks() { - got := c.Reason - exp := expLabels[i] - if !strings.HasPrefix(got, exp) { - t.Errorf("bad label output\nexp %s\ngot %s", got, exp) - } + if res.Failed() { + t.Error("exp to pass, failed") } }) -} -func TestExpNil(t *testing.T) { - t.Run("Exp=ExpNil expects nil", func(t *testing.T) { - f := func(int) any { return nil } - res := testx.Table(f).Cases([]testx.Case{ - {In: 0, Exp: testx.ExpNil}, + t.Run("Case.Exp used if no value set", func(t *testing.T) { + res := testx.Table[int, int](f).Cases([]testx.Case[int, int]{ + {}, }).DryRun() if n := res.NChecks(); n != 1 { t.Errorf("exp 1 check, got %d", n) } - if res.Failed() { - t.Error("nil did not pass Case.Exp == ExpNil") + if expl := res.Checks()[0].Reason; !strings.Contains(expl, "exp 0\ngot 42") { + t.Error("got unexpected explain:\n" + expl) } }) - t.Run("Exp=ExpNil does not expect 0", func(t *testing.T) { - f := func(int) int { return 0 } - res := testx.Table(f).Cases([]testx.Case{ - {In: 0, Exp: testx.ExpNil}, + t.Run("Case.Exp == nil", func(t *testing.T) { + res := testx.Table[bool, any](func(expnil bool) any { + if expnil { + return nil + } + return 0 + }).Cases([]testx.Case[bool, any]{ + {In: false, Exp: 0}, + {In: true, Exp: nil}, // expect nil + {In: true}, // expect nil }).DryRun() - if n := res.NChecks(); n != 1 { - t.Errorf("exp 1 check, got %d", n) + if n := res.NChecks(); n != 3 { + t.Errorf("exp 3 checks, got %d", n) } - if res.Passed() { - t.Error("0 did pass Case.Exp == ExpNil") + if res.Failed() { + t.Error("exp to pass, failed") } }) - t.Run("Exp=0 does not expect nil", func(t *testing.T) { - f := func(int) any { return nil } - res := testx.Table(f).Cases([]testx.Case{ - {In: 0, Exp: 0}, + t.Run("Case.Lab", func(t *testing.T) { + results := testx.Table[float64, error](divide).Config(testx.TableConfig{ + InPos: 1, + OutPos: 1, + FixedArgs: testx.Args{42.0}, + }).Cases([]testx.Case[float64, error]{ + {In: 0.0, Exp: nil, Lab: "zeroth case"}, // fail + {In: 0.0, Exp: nil, Lab: "first case"}, // fail }).DryRun() - if n := res.NChecks(); n != 1 { - t.Errorf("exp 1 check, got %d", n) + expLabelPrefixes := []string{ + `Table.Cases[0] "zeroth case" testx_test.divide(42, 0)`, + `Table.Cases[1] "first case" testx_test.divide(42, 0)`, } - if res.Passed() { - t.Error("nil did pass Case.Exp == 0") + + for i, c := range results.Checks() { + got := c.Reason + exp := expLabelPrefixes[i] + if !strings.HasPrefix(got, exp) { + t.Errorf("bad label output\nexp %s\ngot %s", got, exp) + } } }) } func TestTableRunnerResults(t *testing.T) { t.Run("pass", func(t *testing.T) { - res := testx. - Table(evenSingle). - Cases([]testx.Case{ + res := testx.Table[int, bool](evenSingle). + Cases([]testx.Case[int, bool]{ {In: 10, Exp: true, Lab: "even number"}, {In: 11, Exp: false, Lab: "odd number"}, - }). - DryRun() + }).DryRun() exp := tableResults{ baseResults: baseResults{ @@ -212,14 +189,12 @@ func TestTableRunnerResults(t *testing.T) { }) t.Run("fail", func(t *testing.T) { - res := testx. - Table(evenSingle). - Cases([]testx.Case{ + res := testx.Table[int, bool](evenSingle). + Cases([]testx.Case[int, bool]{ {In: 10, Exp: true, Lab: "even number"}, // pass {In: -1, Exp: true, Lab: "odd number"}, // fail {In: -1, Exp: true}, // fail - }). - DryRun() + }).DryRun() exp := tableResults{ baseResults: baseResults{ diff --git a/testx.go b/testx.go index dfe6eea..2c495f4 100644 --- a/testx.go +++ b/testx.go @@ -38,16 +38,16 @@ type ValueRunner[T any] interface { // TableRunner provides methods to run a series of test cases // on a single function. -type TableRunner interface { +type TableRunner[In, Exp any] interface { Runner // DryRun returns a TableResulter to access test results // without running *testing.T. DryRun() TableResulter // Config sets configures the TableRunner for functions of multiple // parameters or multiple return values. - Config(cfg TableConfig) TableRunner + Config(cfg TableConfig) TableRunner[In, Exp] // Cases adds test cases to be run on the tested func. - Cases(cases []Case) TableRunner + Cases(cases []Case[In, Exp]) TableRunner[In, Exp] } // HTTPHandlerRunner provides methods to run tests on http handlers @@ -170,6 +170,6 @@ func HTTPHandlerFunc( // Table returns a TableRunner to run test cases on a func. By default, // it works with funcs having a single input and output value. // Use TableRunner.Config to configure it for a more complex functions. -func Table(testedFunc any) TableRunner { - return newTableRunner(testedFunc) +func Table[In, Exp any](testedFunc any) TableRunner[In, Exp] { + return newTableRunner[In, Exp](testedFunc) } From 9bff281cbb0bb2593dee30d1f0183d824125c781 Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Fri, 5 Nov 2021 22:44:03 +0100 Subject: [PATCH 12/19] docs(generics): remove all mentions to checkconv (#76) * Remove mentions to checkconv and uncovered types * Run make gen --- CONTRIBUTING.md | 2 -- check/README.md | 25 ++++++++----------------- check/example_custom_closures_test.go | 6 ++---- check/providers.go | 4 ++-- check/providers_context.go | 2 +- 5 files changed, 13 insertions(+), 26 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 963b44d..30b5cdd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,6 @@ The main documentation can help understand how the repository globally works: - [Go package documentation](https://pkg.go.dev/github.com/drykit-go/testx#section-documentation) - [Main Readme](./README.md) - [Package `check` Readme](./check/README.md) -- [Package `checkconv` Readme](./checkconv/README.md) ## Internal documentation @@ -36,7 +35,6 @@ The main documentation can help understand how the repository globally works: . # Package testx (test runners) ├── bin # Binary files (for code generation) ├── check # Package check (checkers interfaces & providers) -├── checkconv # Package checkconv (checkers conversion) ├── cmd # Runnable source files │   └── gen # Code generation main command └── internal # Cross-packages utilities for internal use only diff --git a/check/README.md b/check/README.md index d46ea6e..7be0885 100644 --- a/check/README.md +++ b/check/README.md @@ -220,9 +220,7 @@ func Test42IsEven(t *testing.T) { }, ) - testx.Value(42). - Pass(checkconv.FromInt(checkIsEven)). - Run(t) + testx.Value(42).Pass(checkIsEven).Run(t) } ``` @@ -242,24 +240,17 @@ func Test42IsEven(t *testing.T) { }, ) - testx.Value(42). - Pass(checkIsEven). - Run(t) + testx.Value(42).Pass(checkIsEven).Run(t) } ``` -### Custom checkers of any type +### Custom checker implementation -At some point you may need a checker for a type that is not (yet?) covered -by `check`, such as `float32` or `complex128`. -You may also want custom checkers for your own local types, like `User`. +Any type that implements `check.Checker[T any]` interface is a valid checker +and can be used for `testx` runners. -Fortunately, because _checkers_ are interfaces, it is easy to implement -a custom checker for any type. - -In practice, any type that implements `func Pass(got Type) bool` -and `Explainer` interface is recognized as a valid checker and -can be used for `testx` runners via `checkconv.Cast`. +For runners that require a `check.Checker[interface{}]` specifically +(such as `testx.Case.Pass`), use `check.Wrap` to perform the conversion. Related examples: @@ -267,7 +258,7 @@ Related examples: implementation of a custom checker. - [CustomCheckerClosures](https://pkg.go.dev/github.com/drykit-go/testx/check#example-package-CustomCheckerClosures): advanced implementation of a parameterized custom checker - for uncovered type `complex128`, using closures. + for type `complex128`, using closures. ### Contribute! diff --git a/check/example_custom_closures_test.go b/check/example_custom_closures_test.go index 1913cf1..842b077 100644 --- a/check/example_custom_closures_test.go +++ b/check/example_custom_closures_test.go @@ -24,14 +24,12 @@ type Complex128Checker struct { explain func(label string, got any) string } -// Pass do not satisfy any interface declared by check, but has a valid -// signature Pass(got T) bool, allowing Complex128Checker to be casted -// as a Checker[any] using checkconv.Cast. +// Pass implements check.Passer[complex128] interface. func (c Complex128Checker) Pass(got complex128) bool { return c.pass(got) } -// Explain satisfies check.Explainer interface. +// Explain implements check.Explainer interface. func (c Complex128Checker) Explain(label string, got any) string { return c.explain(label, got) } diff --git a/check/providers.go b/check/providers.go index f3ff69a..b909d94 100644 --- a/check/providers.go +++ b/check/providers.go @@ -1,5 +1,5 @@ // Code generated by go generate ./...; DO NOT EDIT -// Last generated on 01 Nov 21 20:52 UTC +// Last generated on 03 Nov 21 21:18 UTC package check @@ -76,7 +76,7 @@ type ( // // Examples: // Context.Value("userID", Value.Is("abcde")) - // Context.Value("userID", checkconv.Assert(String.Contains("abc"))) + // Context.Value("userID", Wrap(String.Contains("abc"))) Value(key any, c Checker[any]) Checker[context.Context] } diff --git a/check/providers_context.go b/check/providers_context.go index 2182ddd..a327826 100644 --- a/check/providers_context.go +++ b/check/providers_context.go @@ -53,7 +53,7 @@ func (p contextCheckerProvider) HasKeys(keys ...any) Checker[context.Context] { // // Examples: // Context.Value("userID", Value.Is("abcde")) -// Context.Value("userID", checkconv.Assert(String.Contains("abc"))) +// Context.Value("userID", Wrap(String.Contains("abc"))) func (p contextCheckerProvider) Value(key any, c Checker[any]) Checker[context.Context] { var v any pass := func(got context.Context) bool { From 70ae4a3c76e5e915310e0a2a9b33152b37e2e615 Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Thu, 11 Nov 2021 20:17:59 +0100 Subject: [PATCH 13/19] ops(generics): restore lint and coverage (#80) * Exclude lint errors due to unsupported 1.18 syntax * Restore CI job: lint * Use gotip in make command: test-cov * Restore CI step: coverage * Restore CI job: gen * Use golangci-lint v1.43.0 in CI --- .circleci/config.yml | 165 +++++++++++++++++++++---------------------- .golangci.yml | 9 +++ Makefile | 2 +- 3 files changed, 92 insertions(+), 84 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b7b6e75..7e83bb5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,105 +41,104 @@ commands: command: | gotip download - # setup-lint: - # description: Set up lint - # parameters: - # version: - # type: string - # default: v1.40.1 - # steps: - # - restore_cache: - # keys: - # - golangci-lint-<< parameters.version >>-{{ .Environment.CACHE_VERSION }} - # - golangci-lint-{{ .Environment.CACHE_VERSION }} - # - run: - # name: Install golangci-lint - # command: | - # command -v /go/bin/golangci-lint && echo "Skipping golangci-lint installation" && exit - # curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b /go/bin << parameters.version >> - # - save_cache: - # key: golangci-lint-<< parameters.version >>-{{ .Environment.CACHE_VERSION }} - # paths: - # - /go/bin/golangci-lint + setup-lint: + description: Set up lint + parameters: + version: + type: string + default: v1.43.0 + steps: + - restore_cache: + keys: + - golangci-lint-<< parameters.version >>-{{ .Environment.CACHE_VERSION }} + - golangci-lint-{{ .Environment.CACHE_VERSION }} + - run: + name: Install golangci-lint + command: | + command -v /go/bin/golangci-lint && echo "Skipping golangci-lint installation" && exit + curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b /go/bin << parameters.version >> + - save_cache: + key: golangci-lint-<< parameters.version >>-{{ .Environment.CACHE_VERSION }} + paths: + - /go/bin/golangci-lint - # setup-coverage: - # description: Set up coverage - # steps: - # - restore_cache: - # keys: - # - go-acc-{{ .Environment.CACHE_VERSION }} - # - run: - # name: Install go-acc - # command: | - # command -v /go/bin/go-acc && echo "Skipping go-acc installation" && exit - # go get -v github.com/ory/go-acc - # - save_cache: - # key: go-acc-{{ .Environment.CACHE_VERSION }} - # paths: - # - /go/bin/go-acc + setup-coverage: + description: Set up coverage + steps: + - restore_cache: + keys: + - go-acc-{{ .Environment.CACHE_VERSION }} + - run: + name: Install go-acc + command: | + command -v /go/bin/go-acc && echo "Skipping go-acc installation" && exit + go get -v github.com/ory/go-acc + - save_cache: + key: go-acc-{{ .Environment.CACHE_VERSION }} + paths: + - /go/bin/go-acc - # setup-gen: - # description: Set up gen - # steps: - # - restore_cache: - # keys: - # - goimports-{{ .Environment.CACHE_VERSION }} - # - run: - # name: Install goimports - # command: | - # command -v /go/bin/goimports && echo "Skipping go-acc installation" && exit - # go get -v golang.org/x/tools/cmd/goimports + setup-gen: + description: Set up gen + steps: + - restore_cache: + keys: + - goimports-{{ .Environment.CACHE_VERSION }} + - run: + name: Install goimports + command: | + command -v /go/bin/goimports && echo "Skipping goimports installation" && exit + go get -v golang.org/x/tools/cmd/goimports - # - save_cache: - # key: goimports-{{ .Environment.CACHE_VERSION }} - # paths: - # - /go/bin/goimports + - save_cache: + key: goimports-{{ .Environment.CACHE_VERSION }} + paths: + - /go/bin/goimports jobs: - # lint: - # <<: *defaults - # steps: - # - setup - # - setup-lint - # - run: - # name: Run linters - # command: | - # make lint + lint: + <<: *defaults + steps: + - setup + - setup-lint + - run: + name: Run linters + command: | + make lint test: <<: *defaults steps: - setup - # - setup-coverage + - setup-coverage - run: name: Run unit tests - command: | # TODO: set back to 'make test-cov' - make tests - # - run: - # name: Send test coverage results - # command: | - # bash <(curl -s https://codecov.io/bash) + command: | + make test-cov + - run: + name: Send test coverage results + command: | + bash <(curl -s https://codecov.io/bash) - # gen: - # <<: *defaults - # steps: - # - setup - # - setup-gen - # - setup-lint - # - run: - # name: Run code gen - # command: | - # make gen - # - run: - # name: Check generated code - # command: | - # make lint tests + gen: + <<: *defaults + steps: + - setup + - setup-gen + - setup-lint + - run: + name: Run code gen + command: | + make gen + - run: + name: Check generated code + command: | + make lint tests workflows: version: 2 main: jobs: - # - lint + - lint - test - # - gen - + - gen diff --git a/.golangci.yml b/.golangci.yml index de88a52..6b44d82 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -147,6 +147,7 @@ linters: - thelper # enforce t.Helper() - varcheck # unused global var and const - wastedassign + disable: fast: false issues: @@ -156,3 +157,11 @@ issues: - dupl - gocognit - gocyclo + + # temporary fixes for unsupported type parameters syntax + exclude: + - "expected .*, found .*" + - "expected expression" + - "undeclared name: `any`" + - "missing ',' in parameter list" + - "mixed named and unnamed parameters" diff --git a/Makefile b/Makefile index b1a26ff..845b719 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ tests: .PHONY: test-cov test-cov: # Unit tests with coverage, excluding gen-related files - @go-acc --ignore cmd/gen,internal/gen ./... + @GO_TEST_BINARY="gotip test" go-acc --ignore cmd/gen,internal/gen ./... TEST_FUNC=^.*$$ ifdef t From 137c7fc98b3e490f2c4ccac88f694fbe60168392 Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Thu, 11 Nov 2021 20:22:01 +0100 Subject: [PATCH 14/19] feat(generics): remove checker providers interfaces (#79) * Remove interfaces generation logics * Run make gen * Update package gen namings and docs * Fix docparser.ParseDocLines docs * Export checker providers structs * Update providers gen * Run make gen * Refactor some var names generation --- check/providers.go | 351 +++--------------- check/providers_bool.go | 6 +- check/providers_bytes.go | 22 +- check/providers_context.go | 10 +- check/providers_duration.go | 18 +- check/providers_httpheader.go | 20 +- check/providers_httprequest.go | 12 +- check/providers_httpresponse.go | 14 +- check/providers_map.go | 22 +- check/providers_number.go | 26 +- check/providers_slice.go | 22 +- check/providers_string.go | 18 +- check/providers_struct.go | 12 +- check/providers_value.go | 16 +- cmd/gen/main.go | 4 +- internal/gen/docparser/docparser.go | 8 +- internal/gen/gen.go | 10 +- .../gen/{interfaces.go => gen_providers.go} | 85 ++--- internal/gen/metatype/metatype.go | 36 +- internal/gen/providers.go | 35 -- internal/gen/templates/providers.gotmpl | 57 +-- 21 files changed, 211 insertions(+), 593 deletions(-) rename internal/gen/{interfaces.go => gen_providers.go} (58%) delete mode 100644 internal/gen/providers.go diff --git a/check/providers.go b/check/providers.go index b909d94..5d931ff 100644 --- a/check/providers.go +++ b/check/providers.go @@ -1,308 +1,55 @@ // Code generated by go generate ./...; DO NOT EDIT -// Last generated on 03 Nov 21 21:18 UTC +// Last generated on 11 Nov 21 18:00 UTC package check -import ( - "context" - "net/http" - "regexp" - "time" -) - -type ( - // NumberCheckerProvider provides checks on numeric types: - // int, uint, float and their variants. - NumberCheckerProvider[T Numeric] interface { - // GT checks the gotten Number is greater than the target. - GT(tar T) Checker[T] - // GTE checks the gotten Number is greater or equal to the target. - GTE(tar T) Checker[T] - // InRange checks the gotten Number is in the closed interval [lo:hi]. - InRange(lo, hi T) Checker[T] - // Is checks the gotten Number is equal to the target. - Is(tar T) Checker[T] - // LT checks the gotten Number is lesser than the target. - LT(tar T) Checker[T] - // LTE checks the gotten Number is lesser or equal to the target. - LTE(tar T) Checker[T] - // Not checks the gotten Number is not equal to the target. - Not(values ...T) Checker[T] - // OutRange checks the gotten Number is not in the closed interval [lo:hi]. - OutRange(lo, hi T) Checker[T] - } - - // BoolCheckerProvider provides checks on type bool. - BoolCheckerProvider interface { - // Is checks the gotten bool is equal to the target. - Is(tar bool) Checker[bool] - - } - - // BytesCheckerProvider provides checks on type []byte. - BytesCheckerProvider interface { - // AsMap checks the gotten []byte passes the given mapChecker - // once json-unmarshaled to a map[string]any. - // It fails if it is not a valid JSON. - AsMap(mapChecker Checker[any]) Checker[[]byte] - // AsString checks the gotten []byte passes the given Checker[string] - // once converted to a string. - AsString(c Checker[string]) Checker[[]byte] - // Contains checks the gotten []byte contains a specific subslice. - Contains(subslice []byte) Checker[[]byte] - // Is checks the gotten []byte is equal to the target. - Is(tar []byte) Checker[[]byte] - // Len checks the gotten []byte's length passes the provided - // Checker[int]. - Len(c Checker[int]) Checker[[]byte] - // Not checks the gotten []byte is not equal to the target. - Not(values ...[]byte) Checker[[]byte] - // NotContains checks the gotten []byte contains a specific subslice. - NotContains(subslice []byte) Checker[[]byte] - // SameJSON checks the gotten []byte and the target read as the same - // JSON value, ignoring formatting and keys order. - SameJSON(tar []byte) Checker[[]byte] - - } - - // ContextCheckerProvider provides checks on type context.Context. - ContextCheckerProvider interface { - // Done checks the gotten context is done. - Done(expectDone bool) Checker[context.Context] - // HasKeys checks the gotten context has the given keys set. - HasKeys(keys ...any) Checker[context.Context] - // Value checks the gotten context's value for the given key passes - // the given ValueChecker. It fails if value is nil. - // - // Examples: - // Context.Value("userID", Value.Is("abcde")) - // Context.Value("userID", Wrap(String.Contains("abc"))) - Value(key any, c Checker[any]) Checker[context.Context] - - } - - // DurationCheckerProvider provides checks on type time.Duration. - DurationCheckerProvider interface { - // InRange checks the gotten time.Duration is in range [lo:hi] - InRange(lo, hi time.Duration) Checker[time.Duration] - // OutRange checks the gotten time.Duration is not in range [lo:hi] - OutRange(lo, hi time.Duration) Checker[time.Duration] - // Over checks the gotten time.Duration is over the target duration. - Over(tar time.Duration) Checker[time.Duration] - // Under checks the gotten time.Duration is under the target duration. - Under(tar time.Duration) Checker[time.Duration] - - } - - // HTTPHeaderCheckerProvider provides checks on type http.Header. - HTTPHeaderCheckerProvider interface { - // CheckValue checks the gotten http.Header has a value for the matching key - // that passes the given Checker[string]. - // It only checks the first result for the given key. - CheckValue(key string, c Checker[string]) Checker[http.Header] - // HasKey checks the gotten http.Header has a specific key set. - // The corresponding value is ignored, meaning an empty value - // for that key passes the check. - HasKey(key string) Checker[http.Header] - // HasNotKey checks the gotten http.Header does not have - // a specific key set. - HasNotKey(key string) Checker[http.Header] - // HasNotValue checks the gotten http.Header does not have a value equal to val. - // It only compares the first result for each key. - HasNotValue(val string) Checker[http.Header] - // HasValue checks the gotten http.Header has any value equal to val. - // It only compares the first result for each key. - HasValue(val string) Checker[http.Header] - - } - - // HTTPRequestCheckerProvider provides checks on type *http.Request. - HTTPRequestCheckerProvider interface { - // Body checks the gotten *http.Request Body passes the input Checker[[]byte]. - // It should be used only once on a same *http.Request as it closes its body - // after reading it. - Body(c Checker[[]byte]) Checker[*http.Request] - // ContentLength checks the gotten *http.Request ContentLength passes - // the input Checker[int]. - ContentLength(c Checker[int]) Checker[*http.Request] - // Context checks the gotten *http.Request Context passes - // the input Checker[context.Context]. - Context(c Checker[context.Context]) Checker[*http.Request] - // Header checks the gotten *http.Request Header passes - // the input Checker[http.Header]. - Header(c Checker[http.Header]) Checker[*http.Request] - - } - - // HTTPResponseCheckerProvider provides checks on type *http.Response. - HTTPResponseCheckerProvider interface { - // Body checks the gotten *http.Response Body passes the input Checker[[]byte]. - // It should be used only once on a same *http.Response as it closes its body - // after reading it. - Body(c Checker[[]byte]) Checker[*http.Response] - // ContentLength checks the gotten *http.Response ContentLength passes - // the input Checker[int]. - ContentLength(c Checker[int]) Checker[*http.Response] - // Header checks the gotten *http.Response Header passes - // the input Checker[http.Header]. - Header(c Checker[http.Header]) Checker[*http.Response] - // Status checks the gotten *http.Response Status passes - // the input Checker[string]. - Status(c Checker[string]) Checker[*http.Response] - // StatusCode checks the gotten *http.Response StatusCode passes - // the input Checker[int]. - StatusCode(c Checker[int]) Checker[*http.Response] - - } - - // MapCheckerProvider provides checks on kind map. - MapCheckerProvider interface { - ValueCheckerProvider - - // CheckValues checks the gotten map's values corresponding to the given keys - // pass the given checker. A key not found is considered a fail. - // If len(keys) == 0, the check is made on all map values. - CheckValues(c Checker[any], keys ...any) Checker[any] - // HasKeys checks the gotten map has the given keys set. - HasKeys(keys ...any) Checker[any] - // HasNotKeys checks the gotten map has the given keys set. - HasNotKeys(keys ...any) Checker[any] - // HasNotValues checks the gotten map has not the given values set. - HasNotValues(values ...any) Checker[any] - // HasValues checks the gotten map has the given values set. - HasValues(values ...any) Checker[any] - // Len checks the gotten map passes the given Checker[int]. - Len(c Checker[int]) Checker[any] - - } - - // SliceCheckerProvider provides checks on kind slice. - SliceCheckerProvider interface { - ValueCheckerProvider - - // Cap checks the capacity of the gotten slice passes the given Checker[int]. - Cap(c Checker[int]) Checker[any] - // CheckValues checks the values of the gotten slice passes - // the given Checker[any]. - // If a filterFunc is provided, the values not passing it are ignored. - CheckValues(c Checker[any], filters ...func(i int, v any) bool) Checker[any] - // HasNotValues checks the gotten slice has not the given values set. - HasNotValues(values ...any) Checker[any] - // HasValues checks the gotten slice has the given values set. - HasValues(values ...any) Checker[any] - // Len checks the length of the gotten slice passes the given Checker[int]. - Len(c Checker[int]) Checker[any] - - } - - // StringCheckerProvider provides checks on type string. - StringCheckerProvider interface { - // Contains checks the gotten string contains the target substring. - Contains(sub string) Checker[string] - // Is checks the gotten string is equal to the target. - Is(tar string) Checker[string] - // Len checks the gotten string's length passes the given Checker[int]. - Len(c Checker[int]) Checker[string] - // Match checks the gotten string matches the given regexp. - Match(rgx *regexp.Regexp) Checker[string] - // Not checks the gotten string is not equal to the target. - Not(values ...string) Checker[string] - // NotContains checks the gotten string do not contain the target - // substring. - NotContains(sub string) Checker[string] - // NotMatch checks the gotten string do not match the given regexp. - NotMatch(rgx *regexp.Regexp) Checker[string] - - } - - // StructCheckerProvider provides checks on kind struct. - StructCheckerProvider interface { - ValueCheckerProvider - - // CheckFields checks all given fields pass the Checker[any]. - // It panics if the fields do not exist or are not exported, - // or if the tested value is not a struct. - CheckFields(c Checker[any], fields []string) Checker[any] - // FieldsEqual checks all given fields equal the exp value. - // It panics if the fields do not exist or are not exported, - // or if the tested value is not a struct. - FieldsEqual(exp any, fields []string) Checker[any] - - } - - // ValueCheckerProvider provides checks on type any. - ValueCheckerProvider interface { - // Custom checks the gotten value passes the given PassFunc[any]. - // The description should give information about the expected value, - // as it outputs in format "exp " in case of failure. - Custom(desc string, f PassFunc[any]) Checker[any] - // Is checks the gotten value is equal to the target. - Is(tar any) Checker[any] - // IsZero checks the gotten value is a zero value, indicating it might not - // have been initialized. - IsZero() Checker[any] - // Not checks the gotten value is not equal to the target. - Not(values ...any) Checker[any] - // NotZero checks the gotten struct contains at least 1 non-zero value, - // meaning it has been initialized. - NotZero() Checker[any] - // SameJSON checks the gotten value and the target value - // produce the same JSON, ignoring formatting and keys order. - // It panics if any error occurs in the marshaling process. - SameJSON(tar any) Checker[any] - - } - -) - var ( - // Int implements NumberCheckerProvider[int]. - Int NumberCheckerProvider[int] = numberCheckerProvider[int]{} - // Int8 implements NumberCheckerProvider[int8]. - Int8 NumberCheckerProvider[int8] = numberCheckerProvider[int8]{} - // Int16 implements NumberCheckerProvider[int16]. - Int16 NumberCheckerProvider[int16] = numberCheckerProvider[int16]{} - // Int32 implements NumberCheckerProvider[int32]. - Int32 NumberCheckerProvider[int32] = numberCheckerProvider[int32]{} - // Int64 implements NumberCheckerProvider[int64]. - Int64 NumberCheckerProvider[int64] = numberCheckerProvider[int64]{} - // Uint implements NumberCheckerProvider[uint]. - Uint NumberCheckerProvider[uint] = numberCheckerProvider[uint]{} - // Uint8 implements NumberCheckerProvider[uint8]. - Uint8 NumberCheckerProvider[uint8] = numberCheckerProvider[uint8]{} - // Uint16 implements NumberCheckerProvider[uint16]. - Uint16 NumberCheckerProvider[uint16] = numberCheckerProvider[uint16]{} - // Uint32 implements NumberCheckerProvider[uint32]. - Uint32 NumberCheckerProvider[uint32] = numberCheckerProvider[uint32]{} - // Uint64 implements NumberCheckerProvider[uint64]. - Uint64 NumberCheckerProvider[uint64] = numberCheckerProvider[uint64]{} - // Float32 implements NumberCheckerProvider[float32]. - Float32 NumberCheckerProvider[float32] = numberCheckerProvider[float32]{} - // Float64 implements NumberCheckerProvider[float64]. - Float64 NumberCheckerProvider[float64] = numberCheckerProvider[float64]{} - // Bool implements BoolCheckerProvider. - Bool BoolCheckerProvider = boolCheckerProvider{} - // Bytes implements BytesCheckerProvider. - Bytes BytesCheckerProvider = bytesCheckerProvider{} - // Context implements ContextCheckerProvider. - Context ContextCheckerProvider = contextCheckerProvider{} - // Duration implements DurationCheckerProvider. - Duration DurationCheckerProvider = durationCheckerProvider{} - // HTTPHeader implements HTTPHeaderCheckerProvider. - HTTPHeader HTTPHeaderCheckerProvider = httpHeaderCheckerProvider{} - // HTTPRequest implements HTTPRequestCheckerProvider. - HTTPRequest HTTPRequestCheckerProvider = httpRequestCheckerProvider{} - // HTTPResponse implements HTTPResponseCheckerProvider. - HTTPResponse HTTPResponseCheckerProvider = httpResponseCheckerProvider{} - // Map implements MapCheckerProvider. - Map MapCheckerProvider = mapCheckerProvider{} - // Slice implements SliceCheckerProvider. - Slice SliceCheckerProvider = sliceCheckerProvider{} - // String implements StringCheckerProvider. - String StringCheckerProvider = stringCheckerProvider{} - // Struct implements StructCheckerProvider. - Struct StructCheckerProvider = structCheckerProvider{} - // Value implements ValueCheckerProvider. - Value ValueCheckerProvider = valueCheckerProvider{} - + // Int provides checks on type int. + Int = NumberCheckerProvider[int]{} + // Int8 provides checks on type int8. + Int8 = NumberCheckerProvider[int8]{} + // Int16 provides checks on type int16. + Int16 = NumberCheckerProvider[int16]{} + // Int32 provides checks on type int32. + Int32 = NumberCheckerProvider[int32]{} + // Int64 provides checks on type int64. + Int64 = NumberCheckerProvider[int64]{} + // Uint provides checks on type uint. + Uint = NumberCheckerProvider[uint]{} + // Uint8 provides checks on type uint8. + Uint8 = NumberCheckerProvider[uint8]{} + // Uint16 provides checks on type uint16. + Uint16 = NumberCheckerProvider[uint16]{} + // Uint32 provides checks on type uint32. + Uint32 = NumberCheckerProvider[uint32]{} + // Uint64 provides checks on type uint64. + Uint64 = NumberCheckerProvider[uint64]{} + // Float32 provides checks on type float32. + Float32 = NumberCheckerProvider[float32]{} + // Float64 provides checks on type float64. + Float64 = NumberCheckerProvider[float64]{} + // Bool provides checks on type bool. + Bool = BoolCheckerProvider{} + // Bytes provides checks on type []byte. + Bytes = BytesCheckerProvider{} + // Context provides checks on type context.Context. + Context = ContextCheckerProvider{} + // Duration provides checks on type time.Duration. + Duration = DurationCheckerProvider{} + // HTTPHeader provides checks on type http.Header. + HTTPHeader = HTTPHeaderCheckerProvider{} + // HTTPRequest provides checks on type *http.Request. + HTTPRequest = HTTPRequestCheckerProvider{} + // HTTPResponse provides checks on type *http.Response. + HTTPResponse = HTTPResponseCheckerProvider{} + // Map provides checks on kind map. + Map = MapCheckerProvider{} + // Slice provides checks on kind slice. + Slice = SliceCheckerProvider{} + // String provides checks on type string. + String = StringCheckerProvider{} + // Struct provides checks on kind struct. + Struct = StructCheckerProvider{} + // Value provides checks on type any. + Value = ValueCheckerProvider{} ) diff --git a/check/providers_bool.go b/check/providers_bool.go index 67f0673..193586e 100644 --- a/check/providers_bool.go +++ b/check/providers_bool.go @@ -1,10 +1,10 @@ package check -// boolCheckerProvider provides checks on type bool. -type boolCheckerProvider struct{ baseCheckerProvider } +// BoolCheckerProvider provides checks on type bool. +type BoolCheckerProvider struct{ baseCheckerProvider } // Is checks the gotten bool is equal to the target. -func (p boolCheckerProvider) Is(tar bool) Checker[bool] { +func (p BoolCheckerProvider) Is(tar bool) Checker[bool] { pass := func(got bool) bool { return got == tar } expl := func(label string, got any) string { return p.explain(label, tar, got) diff --git a/check/providers_bytes.go b/check/providers_bytes.go index c29b184..9088613 100644 --- a/check/providers_bytes.go +++ b/check/providers_bytes.go @@ -6,11 +6,11 @@ import ( "fmt" ) -// bytesCheckerProvider provides checks on type []byte. -type bytesCheckerProvider struct{ baseCheckerProvider } +// BytesCheckerProvider provides checks on type []byte. +type BytesCheckerProvider struct{ baseCheckerProvider } // Is checks the gotten []byte is equal to the target. -func (p bytesCheckerProvider) Is(tar []byte) Checker[[]byte] { +func (p BytesCheckerProvider) Is(tar []byte) Checker[[]byte] { pass := func(got []byte) bool { return p.eq(got, tar) } expl := func(label string, got any) string { return p.explain(label, tar, got) @@ -19,7 +19,7 @@ func (p bytesCheckerProvider) Is(tar []byte) Checker[[]byte] { } // Not checks the gotten []byte is not equal to the target. -func (p bytesCheckerProvider) Not(values ...[]byte) Checker[[]byte] { +func (p BytesCheckerProvider) Not(values ...[]byte) Checker[[]byte] { match := []byte{} pass := func(got []byte) bool { for _, v := range values { @@ -38,7 +38,7 @@ func (p bytesCheckerProvider) Not(values ...[]byte) Checker[[]byte] { // SameJSON checks the gotten []byte and the target read as the same // JSON value, ignoring formatting and keys order. -func (p bytesCheckerProvider) SameJSON(tar []byte) Checker[[]byte] { +func (p BytesCheckerProvider) SameJSON(tar []byte) Checker[[]byte] { var decGot, decTar any pass := func(got []byte) bool { return p.sameJSON(got, tar, &decGot, &decTar) @@ -54,7 +54,7 @@ func (p bytesCheckerProvider) SameJSON(tar []byte) Checker[[]byte] { // Len checks the gotten []byte's length passes the provided // Checker[int]. -func (p bytesCheckerProvider) Len(c Checker[int]) Checker[[]byte] { +func (p BytesCheckerProvider) Len(c Checker[int]) Checker[[]byte] { pass := func(got []byte) bool { return c.Pass(len(got)) } expl := func(label string, got any) string { return p.explainCheck(label, @@ -66,7 +66,7 @@ func (p bytesCheckerProvider) Len(c Checker[int]) Checker[[]byte] { } // Contains checks the gotten []byte contains a specific subslice. -func (p bytesCheckerProvider) Contains(subslice []byte) Checker[[]byte] { +func (p BytesCheckerProvider) Contains(subslice []byte) Checker[[]byte] { pass := func(got []byte) bool { return bytes.Contains(got, subslice) } expl := func(label string, got any) string { return p.explain(label, @@ -78,7 +78,7 @@ func (p bytesCheckerProvider) Contains(subslice []byte) Checker[[]byte] { } // NotContains checks the gotten []byte contains a specific subslice. -func (p bytesCheckerProvider) NotContains(subslice []byte) Checker[[]byte] { +func (p BytesCheckerProvider) NotContains(subslice []byte) Checker[[]byte] { pass := func(got []byte) bool { return !bytes.Contains(got, subslice) } expl := func(label string, got any) string { return p.explainNot(label, @@ -92,7 +92,7 @@ func (p bytesCheckerProvider) NotContains(subslice []byte) Checker[[]byte] { // AsMap checks the gotten []byte passes the given mapChecker // once json-unmarshaled to a map[string]any. // It fails if it is not a valid JSON. -func (p bytesCheckerProvider) AsMap(mapChecker Checker[any]) Checker[[]byte] { +func (p BytesCheckerProvider) AsMap(mapChecker Checker[any]) Checker[[]byte] { var m map[string]any var goterr error pass := func(got []byte) bool { @@ -116,7 +116,7 @@ func (p bytesCheckerProvider) AsMap(mapChecker Checker[any]) Checker[[]byte] { // AsString checks the gotten []byte passes the given Checker[string] // once converted to a string. -func (p bytesCheckerProvider) AsString(c Checker[string]) Checker[[]byte] { +func (p BytesCheckerProvider) AsString(c Checker[string]) Checker[[]byte] { var s string pass := func(got []byte) bool { s = string(got) @@ -131,6 +131,6 @@ func (p bytesCheckerProvider) AsString(c Checker[string]) Checker[[]byte] { return NewChecker(pass, expl) } -func (bytesCheckerProvider) eq(a, b []byte) bool { +func (BytesCheckerProvider) eq(a, b []byte) bool { return bytes.Equal(a, b) } diff --git a/check/providers_context.go b/check/providers_context.go index a327826..a4ce6d4 100644 --- a/check/providers_context.go +++ b/check/providers_context.go @@ -7,11 +7,11 @@ import ( "github.com/drykit-go/cond" ) -// contextCheckerProvider provides checks on type context.Context. -type contextCheckerProvider struct{ baseCheckerProvider } +// ContextCheckerProvider provides checks on type context.Context. +type ContextCheckerProvider struct{ baseCheckerProvider } // Done checks the gotten context is done. -func (p contextCheckerProvider) Done(expectDone bool) Checker[context.Context] { +func (p ContextCheckerProvider) Done(expectDone bool) Checker[context.Context] { var err error done := func() bool { return err != nil } pass := func(got context.Context) bool { @@ -28,7 +28,7 @@ func (p contextCheckerProvider) Done(expectDone bool) Checker[context.Context] { } // HasKeys checks the gotten context has the given keys set. -func (p contextCheckerProvider) HasKeys(keys ...any) Checker[context.Context] { +func (p ContextCheckerProvider) HasKeys(keys ...any) Checker[context.Context] { var missing []string pass := func(got context.Context) bool { for _, expk := range keys { @@ -54,7 +54,7 @@ func (p contextCheckerProvider) HasKeys(keys ...any) Checker[context.Context] { // Examples: // Context.Value("userID", Value.Is("abcde")) // Context.Value("userID", Wrap(String.Contains("abc"))) -func (p contextCheckerProvider) Value(key any, c Checker[any]) Checker[context.Context] { +func (p ContextCheckerProvider) Value(key any, c Checker[any]) Checker[context.Context] { var v any pass := func(got context.Context) bool { v = got.Value(key) diff --git a/check/providers_duration.go b/check/providers_duration.go index 594d447..bc53cd5 100644 --- a/check/providers_duration.go +++ b/check/providers_duration.go @@ -5,11 +5,11 @@ import ( "time" ) -// durationCheckerProvider provides checks on type time.Duration. -type durationCheckerProvider struct{ baseCheckerProvider } +// DurationCheckerProvider provides checks on type time.Duration. +type DurationCheckerProvider struct{ baseCheckerProvider } // Over checks the gotten time.Duration is over the target duration. -func (p durationCheckerProvider) Over(tar time.Duration) Checker[time.Duration] { +func (p DurationCheckerProvider) Over(tar time.Duration) Checker[time.Duration] { pass := func(got time.Duration) bool { return p.ns(got) > p.ns(tar) } expl := func(label string, got any) string { return p.explain(label, @@ -21,7 +21,7 @@ func (p durationCheckerProvider) Over(tar time.Duration) Checker[time.Duration] } // Under checks the gotten time.Duration is under the target duration. -func (p durationCheckerProvider) Under(tar time.Duration) Checker[time.Duration] { +func (p DurationCheckerProvider) Under(tar time.Duration) Checker[time.Duration] { pass := func(got time.Duration) bool { return p.ns(got) < p.ns(tar) } expl := func(label string, got any) string { return p.explain(label, @@ -33,7 +33,7 @@ func (p durationCheckerProvider) Under(tar time.Duration) Checker[time.Duration] } // InRange checks the gotten time.Duration is in range [lo:hi] -func (p durationCheckerProvider) InRange(lo, hi time.Duration) Checker[time.Duration] { +func (p DurationCheckerProvider) InRange(lo, hi time.Duration) Checker[time.Duration] { pass := func(got time.Duration) bool { return p.inrange(got, lo, hi) } expl := func(label string, got any) string { return p.explain(label, @@ -45,7 +45,7 @@ func (p durationCheckerProvider) InRange(lo, hi time.Duration) Checker[time.Dura } // OutRange checks the gotten time.Duration is not in range [lo:hi] -func (p durationCheckerProvider) OutRange(lo, hi time.Duration) Checker[time.Duration] { +func (p DurationCheckerProvider) OutRange(lo, hi time.Duration) Checker[time.Duration] { pass := func(got time.Duration) bool { return !p.inrange(got, lo, hi) } expl := func(label string, got any) string { return p.explain(label, @@ -58,15 +58,15 @@ func (p durationCheckerProvider) OutRange(lo, hi time.Duration) Checker[time.Dur // Helpers -func (p durationCheckerProvider) inrange(d, lo, hi time.Duration) bool { +func (p DurationCheckerProvider) inrange(d, lo, hi time.Duration) bool { nsd, nslo, nshi := p.ns(d), p.ns(lo), p.ns(hi) return nslo <= nsd && nsd <= nshi } -func (durationCheckerProvider) ms(d time.Duration) int64 { +func (DurationCheckerProvider) ms(d time.Duration) int64 { return d.Milliseconds() } -func (durationCheckerProvider) ns(d time.Duration) int64 { +func (DurationCheckerProvider) ns(d time.Duration) int64 { return d.Nanoseconds() } diff --git a/check/providers_httpheader.go b/check/providers_httpheader.go index eb8004b..f6c148f 100644 --- a/check/providers_httpheader.go +++ b/check/providers_httpheader.go @@ -5,13 +5,13 @@ import ( "net/http" ) -// httpHeaderCheckerProvider provides checks on type http.Header. -type httpHeaderCheckerProvider struct{ baseCheckerProvider } +// HTTPHeaderCheckerProvider provides checks on type http.Header. +type HTTPHeaderCheckerProvider struct{ baseCheckerProvider } // HasKey checks the gotten http.Header has a specific key set. // The corresponding value is ignored, meaning an empty value // for that key passes the check. -func (p httpHeaderCheckerProvider) HasKey(key string) Checker[http.Header] { +func (p HTTPHeaderCheckerProvider) HasKey(key string) Checker[http.Header] { pass := func(got http.Header) bool { return p.hasKey(got, key) } expl := func(label string, got any) string { return p.explain(label, `to have key "`+key+`"`, got) @@ -21,7 +21,7 @@ func (p httpHeaderCheckerProvider) HasKey(key string) Checker[http.Header] { // HasNotKey checks the gotten http.Header does not have // a specific key set. -func (p httpHeaderCheckerProvider) HasNotKey(key string) Checker[http.Header] { +func (p HTTPHeaderCheckerProvider) HasNotKey(key string) Checker[http.Header] { pass := func(got http.Header) bool { return !p.hasKey(got, key) } expl := func(label string, got any) string { return p.explainNot(label, `to have key "`+key+`"`, got) @@ -31,7 +31,7 @@ func (p httpHeaderCheckerProvider) HasNotKey(key string) Checker[http.Header] { // HasValue checks the gotten http.Header has any value equal to val. // It only compares the first result for each key. -func (p httpHeaderCheckerProvider) HasValue(val string) Checker[http.Header] { +func (p HTTPHeaderCheckerProvider) HasValue(val string) Checker[http.Header] { pass := func(got http.Header) bool { return p.hasValue(got, val) } expl := func(label string, got any) string { return p.explain(label, "to have value "+val, got) @@ -41,7 +41,7 @@ func (p httpHeaderCheckerProvider) HasValue(val string) Checker[http.Header] { // HasNotValue checks the gotten http.Header does not have a value equal to val. // It only compares the first result for each key. -func (p httpHeaderCheckerProvider) HasNotValue(val string) Checker[http.Header] { +func (p HTTPHeaderCheckerProvider) HasNotValue(val string) Checker[http.Header] { pass := func(got http.Header) bool { return !p.hasValue(got, val) } expl := func(label string, got any) string { return p.explainNot(label, "to have value "+val, got) @@ -52,7 +52,7 @@ func (p httpHeaderCheckerProvider) HasNotValue(val string) Checker[http.Header] // CheckValue checks the gotten http.Header has a value for the matching key // that passes the given Checker[string]. // It only checks the first result for the given key. -func (p httpHeaderCheckerProvider) CheckValue(key string, c Checker[string]) Checker[http.Header] { +func (p HTTPHeaderCheckerProvider) CheckValue(key string, c Checker[string]) Checker[http.Header] { var val string pass := func(got http.Header) bool { v, ok := p.get(got, key) @@ -73,7 +73,7 @@ func (p httpHeaderCheckerProvider) CheckValue(key string, c Checker[string]) Che // Helpers -func (httpHeaderCheckerProvider) get(h http.Header, key string) (string, bool) { +func (HTTPHeaderCheckerProvider) get(h http.Header, key string) (string, bool) { values, ok := h[key] if !ok || len(values) == 0 { return "", false @@ -81,12 +81,12 @@ func (httpHeaderCheckerProvider) get(h http.Header, key string) (string, bool) { return values[0], true } -func (httpHeaderCheckerProvider) hasKey(h http.Header, key string) bool { +func (HTTPHeaderCheckerProvider) hasKey(h http.Header, key string) bool { _, ok := h[key] return ok } -func (httpHeaderCheckerProvider) hasValue(h http.Header, val string) bool { +func (HTTPHeaderCheckerProvider) hasValue(h http.Header, val string) bool { for _, values := range h { if len(values) == 0 { continue diff --git a/check/providers_httprequest.go b/check/providers_httprequest.go index f9a20ce..e3d5b58 100644 --- a/check/providers_httprequest.go +++ b/check/providers_httprequest.go @@ -7,12 +7,12 @@ import ( "github.com/drykit-go/testx/internal/ioutil" ) -// httpRequestCheckerProvider provides checks on type *http.Request. -type httpRequestCheckerProvider struct{ baseHTTPCheckerProvider } +// HTTPRequestCheckerProvider provides checks on type *http.Request. +type HTTPRequestCheckerProvider struct{ baseHTTPCheckerProvider } // ContentLength checks the gotten *http.Request ContentLength passes // the input Checker[int]. -func (p httpRequestCheckerProvider) ContentLength(c Checker[int]) Checker[*http.Request] { +func (p HTTPRequestCheckerProvider) ContentLength(c Checker[int]) Checker[*http.Request] { var clen int pass := func(got *http.Request) bool { clen = int(got.ContentLength) @@ -26,7 +26,7 @@ func (p httpRequestCheckerProvider) ContentLength(c Checker[int]) Checker[*http. // Header checks the gotten *http.Request Header passes // the input Checker[http.Header]. -func (p httpRequestCheckerProvider) Header(c Checker[http.Header]) Checker[*http.Request] { +func (p HTTPRequestCheckerProvider) Header(c Checker[http.Header]) Checker[*http.Request] { var header http.Header pass := func(got *http.Request) bool { header = got.Header @@ -41,7 +41,7 @@ func (p httpRequestCheckerProvider) Header(c Checker[http.Header]) Checker[*http // Body checks the gotten *http.Request Body passes the input Checker[[]byte]. // It should be used only once on a same *http.Request as it closes its body // after reading it. -func (p httpRequestCheckerProvider) Body(c Checker[[]byte]) Checker[*http.Request] { +func (p HTTPRequestCheckerProvider) Body(c Checker[[]byte]) Checker[*http.Request] { var body []byte pass := func(got *http.Request) bool { body = ioutil.NopRead(&got.Body) @@ -55,7 +55,7 @@ func (p httpRequestCheckerProvider) Body(c Checker[[]byte]) Checker[*http.Reques // Context checks the gotten *http.Request Context passes // the input Checker[context.Context]. -func (p httpRequestCheckerProvider) Context(c Checker[context.Context]) Checker[*http.Request] { +func (p HTTPRequestCheckerProvider) Context(c Checker[context.Context]) Checker[*http.Request] { var ctx context.Context pass := func(got *http.Request) bool { ctx = got.Context() diff --git a/check/providers_httpresponse.go b/check/providers_httpresponse.go index 5b37729..65f39c0 100644 --- a/check/providers_httpresponse.go +++ b/check/providers_httpresponse.go @@ -6,12 +6,12 @@ import ( "github.com/drykit-go/testx/internal/ioutil" ) -// httpResponseCheckerProvider provides checks on type *http.Response. -type httpResponseCheckerProvider struct{ baseHTTPCheckerProvider } +// HTTPResponseCheckerProvider provides checks on type *http.Response. +type HTTPResponseCheckerProvider struct{ baseHTTPCheckerProvider } // StatusCode checks the gotten *http.Response StatusCode passes // the input Checker[int]. -func (p httpResponseCheckerProvider) StatusCode(c Checker[int]) Checker[*http.Response] { +func (p HTTPResponseCheckerProvider) StatusCode(c Checker[int]) Checker[*http.Response] { var code int pass := func(got *http.Response) bool { code = got.StatusCode @@ -28,7 +28,7 @@ func (p httpResponseCheckerProvider) StatusCode(c Checker[int]) Checker[*http.Re // Status checks the gotten *http.Response Status passes // the input Checker[string]. -func (p httpResponseCheckerProvider) Status(c Checker[string]) Checker[*http.Response] { +func (p HTTPResponseCheckerProvider) Status(c Checker[string]) Checker[*http.Response] { var status string pass := func(got *http.Response) bool { status = got.Status @@ -45,7 +45,7 @@ func (p httpResponseCheckerProvider) Status(c Checker[string]) Checker[*http.Res // ContentLength checks the gotten *http.Response ContentLength passes // the input Checker[int]. -func (p httpResponseCheckerProvider) ContentLength(c Checker[int]) Checker[*http.Response] { +func (p HTTPResponseCheckerProvider) ContentLength(c Checker[int]) Checker[*http.Response] { var clen int pass := func(got *http.Response) bool { clen = int(got.ContentLength) @@ -59,7 +59,7 @@ func (p httpResponseCheckerProvider) ContentLength(c Checker[int]) Checker[*http // Header checks the gotten *http.Response Header passes // the input Checker[http.Header]. -func (p httpResponseCheckerProvider) Header(c Checker[http.Header]) Checker[*http.Response] { +func (p HTTPResponseCheckerProvider) Header(c Checker[http.Header]) Checker[*http.Response] { var header http.Header pass := func(got *http.Response) bool { header = got.Header @@ -74,7 +74,7 @@ func (p httpResponseCheckerProvider) Header(c Checker[http.Header]) Checker[*htt // Body checks the gotten *http.Response Body passes the input Checker[[]byte]. // It should be used only once on a same *http.Response as it closes its body // after reading it. -func (p httpResponseCheckerProvider) Body(c Checker[[]byte]) Checker[*http.Response] { +func (p HTTPResponseCheckerProvider) Body(c Checker[[]byte]) Checker[*http.Response] { var body []byte pass := func(got *http.Response) bool { body = ioutil.NopRead(&got.Body) diff --git a/check/providers_map.go b/check/providers_map.go index 86b274e..94902fc 100644 --- a/check/providers_map.go +++ b/check/providers_map.go @@ -10,11 +10,11 @@ import ( "github.com/drykit-go/testx/internal/reflectutil" ) -// mapCheckerProvider provides checks on kind map. -type mapCheckerProvider struct{ valueCheckerProvider } +// MapCheckerProvider provides checks on kind map. +type MapCheckerProvider struct{ ValueCheckerProvider } // Len checks the gotten map passes the given Checker[int]. -func (p mapCheckerProvider) Len(c Checker[int]) Checker[any] { +func (p MapCheckerProvider) Len(c Checker[int]) Checker[any] { var gotlen int pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Map) @@ -31,7 +31,7 @@ func (p mapCheckerProvider) Len(c Checker[int]) Checker[any] { } // HasKeys checks the gotten map has the given keys set. -func (p mapCheckerProvider) HasKeys(keys ...any) Checker[any] { +func (p MapCheckerProvider) HasKeys(keys ...any) Checker[any] { var missing []string pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Map) @@ -49,7 +49,7 @@ func (p mapCheckerProvider) HasKeys(keys ...any) Checker[any] { } // HasNotKeys checks the gotten map has the given keys set. -func (p mapCheckerProvider) HasNotKeys(keys ...any) Checker[any] { +func (p MapCheckerProvider) HasNotKeys(keys ...any) Checker[any] { var badkeys []string pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Map) @@ -67,7 +67,7 @@ func (p mapCheckerProvider) HasNotKeys(keys ...any) Checker[any] { } // HasValues checks the gotten map has the given values set. -func (p mapCheckerProvider) HasValues(values ...any) Checker[any] { +func (p MapCheckerProvider) HasValues(values ...any) Checker[any] { var missing []string pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Map) @@ -85,7 +85,7 @@ func (p mapCheckerProvider) HasValues(values ...any) Checker[any] { } // HasNotValues checks the gotten map has not the given values set. -func (p mapCheckerProvider) HasNotValues(values ...any) Checker[any] { +func (p MapCheckerProvider) HasNotValues(values ...any) Checker[any] { var badvalues []string pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Map) @@ -105,7 +105,7 @@ func (p mapCheckerProvider) HasNotValues(values ...any) Checker[any] { // CheckValues checks the gotten map's values corresponding to the given keys // pass the given checker. A key not found is considered a fail. // If len(keys) == 0, the check is made on all map values. -func (p mapCheckerProvider) CheckValues(c Checker[any], keys ...any) Checker[any] { //nolint: gocognit // TODO: refactor +func (p MapCheckerProvider) CheckValues(c Checker[any], keys ...any) Checker[any] { //nolint: gocognit // TODO: refactor var badentries []string allKeys := len(keys) == 0 pass := func(got any) bool { @@ -138,7 +138,7 @@ func (p mapCheckerProvider) CheckValues(c Checker[any], keys ...any) Checker[any } // get returns gotmap[key] and a bool representing whether a match is found. -func (p mapCheckerProvider) get(gotmap, key any) (any, bool) { +func (p MapCheckerProvider) get(gotmap, key any) (any, bool) { iter := reflect.ValueOf(gotmap).MapRange() for iter.Next() { if k := iter.Key().Interface(); p.deq(k, key) { @@ -149,7 +149,7 @@ func (p mapCheckerProvider) get(gotmap, key any) (any, bool) { } // hasValue returns true if gotmap matches the specified value. -func (p mapCheckerProvider) hasValue(gotmap, value any) bool { +func (p MapCheckerProvider) hasValue(gotmap, value any) bool { iter := reflect.ValueOf(gotmap).MapRange() for iter.Next() { if gotv := iter.Value().Interface(); p.deq(gotv, value) { @@ -159,7 +159,7 @@ func (p mapCheckerProvider) hasValue(gotmap, value any) bool { return false } -func (mapCheckerProvider) walk(gotmap any, f func(k, v any)) { +func (MapCheckerProvider) walk(gotmap any, f func(k, v any)) { vmap := reflect.ValueOf(gotmap) iter := vmap.MapRange() for iter.Next() { diff --git a/check/providers_number.go b/check/providers_number.go index f18086f..8e8ea4f 100644 --- a/check/providers_number.go +++ b/check/providers_number.go @@ -11,11 +11,11 @@ import ( // interface automatically: the template mentionned above must be updated // manually. -// numberCheckerProvider provides checks on numeric types. -type numberCheckerProvider[T Numeric] struct{ baseCheckerProvider } +// NumberCheckerProvider provides checks on numeric types. +type NumberCheckerProvider[T Numeric] struct{ baseCheckerProvider } // Is checks the gotten Number is equal to the target. -func (p numberCheckerProvider[T]) Is(tar T) Checker[T] { +func (p NumberCheckerProvider[T]) Is(tar T) Checker[T] { pass := func(got T) bool { return got == tar } expl := func(label string, got any) string { return p.explain(label, tar, got) @@ -24,7 +24,7 @@ func (p numberCheckerProvider[T]) Is(tar T) Checker[T] { } // Not checks the gotten Number is not equal to the target. -func (p numberCheckerProvider[T]) Not(values ...T) Checker[T] { +func (p NumberCheckerProvider[T]) Not(values ...T) Checker[T] { var match T pass := func(got T) bool { for _, v := range values { @@ -42,7 +42,7 @@ func (p numberCheckerProvider[T]) Not(values ...T) Checker[T] { } // InRange checks the gotten Number is in the closed interval [lo:hi]. -func (p numberCheckerProvider[T]) InRange(lo, hi T) Checker[T] { +func (p NumberCheckerProvider[T]) InRange(lo, hi T) Checker[T] { pass := func(got T) bool { return p.inrange(got, lo, hi) } expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("in range [%v:%v]", lo, hi), got) @@ -51,7 +51,7 @@ func (p numberCheckerProvider[T]) InRange(lo, hi T) Checker[T] { } // OutRange checks the gotten Number is not in the closed interval [lo:hi]. -func (p numberCheckerProvider[T]) OutRange(lo, hi T) Checker[T] { +func (p NumberCheckerProvider[T]) OutRange(lo, hi T) Checker[T] { pass := func(got T) bool { return !p.inrange(got, lo, hi) } expl := func(label string, got any) string { return p.explainNot(label, fmt.Sprintf("in range [%v:%v]", lo, hi), got) @@ -60,7 +60,7 @@ func (p numberCheckerProvider[T]) OutRange(lo, hi T) Checker[T] { } // GT checks the gotten Number is greater than the target. -func (p numberCheckerProvider[T]) GT(tar T) Checker[T] { +func (p NumberCheckerProvider[T]) GT(tar T) Checker[T] { pass := func(got T) bool { return !p.lte(got, tar) } expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("> %v", tar), got) @@ -69,7 +69,7 @@ func (p numberCheckerProvider[T]) GT(tar T) Checker[T] { } // GTE checks the gotten Number is greater or equal to the target. -func (p numberCheckerProvider[T]) GTE(tar T) Checker[T] { +func (p NumberCheckerProvider[T]) GTE(tar T) Checker[T] { pass := func(got T) bool { return !p.lt(got, tar) } expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf(">= %v", tar), got) @@ -78,7 +78,7 @@ func (p numberCheckerProvider[T]) GTE(tar T) Checker[T] { } // LT checks the gotten Number is lesser than the target. -func (p numberCheckerProvider[T]) LT(tar T) Checker[T] { +func (p NumberCheckerProvider[T]) LT(tar T) Checker[T] { pass := func(got T) bool { return p.lt(got, tar) } expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("< %v", tar), got) @@ -87,7 +87,7 @@ func (p numberCheckerProvider[T]) LT(tar T) Checker[T] { } // LTE checks the gotten Number is lesser or equal to the target. -func (p numberCheckerProvider[T]) LTE(tar T) Checker[T] { +func (p NumberCheckerProvider[T]) LTE(tar T) Checker[T] { pass := func(got T) bool { return p.lte(got, tar) } expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("<= %v", tar), got) @@ -97,8 +97,8 @@ func (p numberCheckerProvider[T]) LTE(tar T) Checker[T] { // Helpers -func (numberCheckerProvider[T]) lt(a, b T) bool { return a < b } -func (numberCheckerProvider[T]) lte(a, b T) bool { return a <= b } -func (p numberCheckerProvider[T]) inrange(n, lo, hi T) bool { +func (NumberCheckerProvider[T]) lt(a, b T) bool { return a < b } +func (NumberCheckerProvider[T]) lte(a, b T) bool { return a <= b } +func (p NumberCheckerProvider[T]) inrange(n, lo, hi T) bool { return !p.lt(n, lo) && p.lte(n, hi) } diff --git a/check/providers_slice.go b/check/providers_slice.go index 51a1a41..3cc196d 100644 --- a/check/providers_slice.go +++ b/check/providers_slice.go @@ -7,11 +7,11 @@ import ( "github.com/drykit-go/testx/internal/reflectutil" ) -// sliceCheckerProvider provides checks on kind slice. -type sliceCheckerProvider struct{ valueCheckerProvider } +// SliceCheckerProvider provides checks on kind slice. +type SliceCheckerProvider struct{ ValueCheckerProvider } // Len checks the length of the gotten slice passes the given Checker[int]. -func (p sliceCheckerProvider) Len(c Checker[int]) Checker[any] { +func (p SliceCheckerProvider) Len(c Checker[int]) Checker[any] { var gotlen int pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Slice) @@ -28,7 +28,7 @@ func (p sliceCheckerProvider) Len(c Checker[int]) Checker[any] { } // Cap checks the capacity of the gotten slice passes the given Checker[int]. -func (p sliceCheckerProvider) Cap(c Checker[int]) Checker[any] { +func (p SliceCheckerProvider) Cap(c Checker[int]) Checker[any] { var gotcap int pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Slice) @@ -45,7 +45,7 @@ func (p sliceCheckerProvider) Cap(c Checker[int]) Checker[any] { } // HasValues checks the gotten slice has the given values set. -func (p sliceCheckerProvider) HasValues(values ...any) Checker[any] { +func (p SliceCheckerProvider) HasValues(values ...any) Checker[any] { var missing []string pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Slice) @@ -66,7 +66,7 @@ func (p sliceCheckerProvider) HasValues(values ...any) Checker[any] { } // HasNotValues checks the gotten slice has not the given values set. -func (p sliceCheckerProvider) HasNotValues(values ...any) Checker[any] { +func (p SliceCheckerProvider) HasNotValues(values ...any) Checker[any] { var badvalues []string pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Slice) @@ -89,7 +89,7 @@ func (p sliceCheckerProvider) HasNotValues(values ...any) Checker[any] { // CheckValues checks the values of the gotten slice passes // the given Checker[any]. // If a filterFunc is provided, the values not passing it are ignored. -func (p sliceCheckerProvider) CheckValues( +func (p SliceCheckerProvider) CheckValues( c Checker[any], filters ...func(i int, v any) bool, ) Checker[any] { @@ -115,7 +115,7 @@ func (p sliceCheckerProvider) CheckValues( // Helpers // hasValue returns true if slice has a value equal to expv. -func (p sliceCheckerProvider) hasValue(slice, expv any) bool { +func (p SliceCheckerProvider) hasValue(slice, expv any) bool { return p.walkUntil(slice, nil, func(_ int, v any) bool { return p.deq(v, expv) }) @@ -123,7 +123,7 @@ func (p sliceCheckerProvider) hasValue(slice, expv any) bool { // walk iterates over a slice until the end is reached. // It calls f(i, v) each iteration if (i, v) pass the given filters. -func (p sliceCheckerProvider) walk( +func (p SliceCheckerProvider) walk( slice any, filters []func(int, any) bool, f func(i int, v any), @@ -137,7 +137,7 @@ func (p sliceCheckerProvider) walk( // walksUntil behaves like walk excepts it returns early if the stop func // returns true for the current iteration. In returns true if it was stopped // early, false otherwise. -func (p sliceCheckerProvider) walkUntil( +func (p SliceCheckerProvider) walkUntil( slice any, filters []func(int, any) bool, stop func(int, any) bool, @@ -156,7 +156,7 @@ func (p sliceCheckerProvider) walkUntil( } // mergeFilters combinates several filtering funcs into one. -func (p sliceCheckerProvider) mergeFilters( +func (p SliceCheckerProvider) mergeFilters( filters ...func(int, any) bool, ) func(int, any) bool { if len(filters) == 0 { diff --git a/check/providers_string.go b/check/providers_string.go index c86ec59..5e4748f 100644 --- a/check/providers_string.go +++ b/check/providers_string.go @@ -6,11 +6,11 @@ import ( "strings" ) -// stringCheckerProvider provides checks on type string. -type stringCheckerProvider struct{ baseCheckerProvider } +// StringCheckerProvider provides checks on type string. +type StringCheckerProvider struct{ baseCheckerProvider } // Is checks the gotten string is equal to the target. -func (p stringCheckerProvider) Is(tar string) Checker[string] { +func (p StringCheckerProvider) Is(tar string) Checker[string] { pass := func(got string) bool { return got == tar } expl := func(label string, got any) string { return p.explain(label, tar, got) @@ -19,7 +19,7 @@ func (p stringCheckerProvider) Is(tar string) Checker[string] { } // Not checks the gotten string is not equal to the target. -func (p stringCheckerProvider) Not(values ...string) Checker[string] { +func (p StringCheckerProvider) Not(values ...string) Checker[string] { var match string pass := func(got string) bool { for _, v := range values { @@ -37,7 +37,7 @@ func (p stringCheckerProvider) Not(values ...string) Checker[string] { } // Len checks the gotten string's length passes the given Checker[int]. -func (p stringCheckerProvider) Len(c Checker[int]) Checker[string] { +func (p StringCheckerProvider) Len(c Checker[int]) Checker[string] { pass := func(got string) bool { return c.Pass(len(got)) } expl := func(label string, got any) string { return p.explainCheck(label, @@ -49,7 +49,7 @@ func (p stringCheckerProvider) Len(c Checker[int]) Checker[string] { } // Match checks the gotten string matches the given regexp. -func (p stringCheckerProvider) Match(rgx *regexp.Regexp) Checker[string] { +func (p StringCheckerProvider) Match(rgx *regexp.Regexp) Checker[string] { pass := func(got string) bool { return rgx.MatchString(got) } expl := func(label string, got any) string { return p.explain(label, @@ -61,7 +61,7 @@ func (p stringCheckerProvider) Match(rgx *regexp.Regexp) Checker[string] { } // NotMatch checks the gotten string do not match the given regexp. -func (p stringCheckerProvider) NotMatch(rgx *regexp.Regexp) Checker[string] { +func (p StringCheckerProvider) NotMatch(rgx *regexp.Regexp) Checker[string] { pass := func(got string) bool { return !rgx.MatchString(got) } expl := func(label string, got any) string { return p.explainNot(label, @@ -73,7 +73,7 @@ func (p stringCheckerProvider) NotMatch(rgx *regexp.Regexp) Checker[string] { } // Contains checks the gotten string contains the target substring. -func (p stringCheckerProvider) Contains(sub string) Checker[string] { +func (p StringCheckerProvider) Contains(sub string) Checker[string] { pass := func(got string) bool { return strings.Contains(got, sub) } expl := func(label string, got any) string { return p.explain(label, "to contain substring "+sub, got) @@ -83,7 +83,7 @@ func (p stringCheckerProvider) Contains(sub string) Checker[string] { // NotContains checks the gotten string do not contain the target // substring. -func (p stringCheckerProvider) NotContains(sub string) Checker[string] { +func (p StringCheckerProvider) NotContains(sub string) Checker[string] { pass := func(got string) bool { return !strings.Contains(got, sub) } expl := func(label string, got any) string { return p.explainNot(label, "to contain substring "+sub, got) diff --git a/check/providers_struct.go b/check/providers_struct.go index f397c03..43d5870 100644 --- a/check/providers_struct.go +++ b/check/providers_struct.go @@ -8,13 +8,13 @@ import ( "github.com/drykit-go/testx/internal/reflectutil" ) -// structCheckerProvider provides checks on kind struct. -type structCheckerProvider struct{ valueCheckerProvider } +// StructCheckerProvider provides checks on kind struct. +type StructCheckerProvider struct{ ValueCheckerProvider } // FieldsEqual checks all given fields equal the exp value. // It panics if the fields do not exist or are not exported, // or if the tested value is not a struct. -func (p structCheckerProvider) FieldsEqual(exp any, fields []string) Checker[any] { +func (p StructCheckerProvider) FieldsEqual(exp any, fields []string) Checker[any] { var bads []string pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Struct) @@ -35,7 +35,7 @@ func (p structCheckerProvider) FieldsEqual(exp any, fields []string) Checker[any // CheckFields checks all given fields pass the Checker[any]. // It panics if the fields do not exist or are not exported, // or if the tested value is not a struct. -func (p structCheckerProvider) CheckFields(c Checker[any], fields []string) Checker[any] { +func (p StructCheckerProvider) CheckFields(c Checker[any], fields []string) Checker[any] { var bads []string pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Struct) @@ -53,7 +53,7 @@ func (p structCheckerProvider) CheckFields(c Checker[any], fields []string) Chec return NewChecker(pass, expl) } -func (p structCheckerProvider) badFields( +func (p StructCheckerProvider) badFields( gotstruct any, fields []string, pass func(k string, v any) bool, @@ -69,7 +69,7 @@ func (p structCheckerProvider) badFields( return } -func (structCheckerProvider) formatFields(fields []string) string { +func (StructCheckerProvider) formatFields(fields []string) string { n := len(fields) var b strings.Builder for i, f := range fields { diff --git a/check/providers_value.go b/check/providers_value.go index 8356022..911b539 100644 --- a/check/providers_value.go +++ b/check/providers_value.go @@ -6,13 +6,13 @@ import ( "github.com/drykit-go/testx/internal/reflectutil" ) -// valueCheckerProvider provides checks on type any. -type valueCheckerProvider struct{ baseCheckerProvider } +// ValueCheckerProvider provides checks on type any. +type ValueCheckerProvider struct{ baseCheckerProvider } // Custom checks the gotten value passes the given PassFunc[any]. // The description should give information about the expected value, // as it outputs in format "exp " in case of failure. -func (p valueCheckerProvider) Custom(desc string, f PassFunc[any]) Checker[any] { +func (p ValueCheckerProvider) Custom(desc string, f PassFunc[any]) Checker[any] { expl := func(label string, got any) string { return p.explain(label, desc, got) } @@ -20,7 +20,7 @@ func (p valueCheckerProvider) Custom(desc string, f PassFunc[any]) Checker[any] } // Is checks the gotten value is equal to the target. -func (p valueCheckerProvider) Is(tar any) Checker[any] { +func (p ValueCheckerProvider) Is(tar any) Checker[any] { pass := func(got any) bool { return p.deq(got, tar) } expl := func(label string, got any) string { return p.explain(label, tar, got) @@ -29,7 +29,7 @@ func (p valueCheckerProvider) Is(tar any) Checker[any] { } // Not checks the gotten value is not equal to the target. -func (p valueCheckerProvider) Not(values ...any) Checker[any] { +func (p ValueCheckerProvider) Not(values ...any) Checker[any] { var match any pass := func(got any) bool { for _, v := range values { @@ -48,7 +48,7 @@ func (p valueCheckerProvider) Not(values ...any) Checker[any] { // IsZero checks the gotten value is a zero value, indicating it might not // have been initialized. -func (p valueCheckerProvider) IsZero() Checker[any] { +func (p ValueCheckerProvider) IsZero() Checker[any] { expl := func(label string, got any) string { return p.explain(label, "to be a zero value", got) } @@ -57,7 +57,7 @@ func (p valueCheckerProvider) IsZero() Checker[any] { // NotZero checks the gotten struct contains at least 1 non-zero value, // meaning it has been initialized. -func (p valueCheckerProvider) NotZero() Checker[any] { +func (p ValueCheckerProvider) NotZero() Checker[any] { pass := func(got any) bool { return !reflectutil.IsZero(got) } expl := func(label string, got any) string { return p.explainNot(label, "to be a zero value", got) @@ -68,7 +68,7 @@ func (p valueCheckerProvider) NotZero() Checker[any] { // SameJSON checks the gotten value and the target value // produce the same JSON, ignoring formatting and keys order. // It panics if any error occurs in the marshaling process. -func (p valueCheckerProvider) SameJSON(tar any) Checker[any] { +func (p ValueCheckerProvider) SameJSON(tar any) Checker[any] { var gotDec, tarDec any pass := func(got any) bool { return p.sameJSONProduced(got, tar, &gotDec, &tarDec) diff --git a/cmd/gen/main.go b/cmd/gen/main.go index 6c069c1..a2cc1ee 100644 --- a/cmd/gen/main.go +++ b/cmd/gen/main.go @@ -26,11 +26,11 @@ const ( var ( name = flag.String("name", "", "template name in internal/gen (without extension)") - kind = flag.String("kind", "", "data to be generated (types or interfaces)") + kind = flag.String("kind", "", "data to be generated (only 'types' currently") ) var kindsFuncs = map[string]func(tpl, out string) error{ - "interfaces": gen.Interfaces, + "interfaces": gen.ProvidersVars, } func main() { diff --git a/internal/gen/docparser/docparser.go b/internal/gen/docparser/docparser.go index 68ddd9f..69e8e3d 100644 --- a/internal/gen/docparser/docparser.go +++ b/internal/gen/docparser/docparser.go @@ -5,8 +5,8 @@ import ( ) // ParseDocLines reads the rawdoc got from go/doc parsing, -// adds a leading "// " to each line, applies the replacements, -// and returns a slice of strings for each line. +// applies the replacements, and returns a slice of strings +// for each line. // The result can then be conveniently used in a go template. // // Example: @@ -17,8 +17,8 @@ import ( // // // Output // []string{ -// "// MyFunc does this.", -// "// It is nice.", +// "MyFunc does this.", +// "It is nice.", // } func ParseDocLines(rawdoc string, repl map[string]string) []string { lines := []string{} diff --git a/internal/gen/gen.go b/internal/gen/gen.go index e8f6f11..0bc0670 100644 --- a/internal/gen/gen.go +++ b/internal/gen/gen.go @@ -15,12 +15,10 @@ type config struct { tplFuncs template.FuncMap } -// Interfaces generates the public interfaces for checker providers -// implementations in package check (provider_*.go files). It should be run -// every time their API is modified (method signature change, doc comment, -// new method, method removal, ...) -func Interfaces(tpl, out string) error { - src, err := computeInterfaces() +// ProvidersVars generates the public instances of checker providers +// implementations in package check (providers_*.go files). +func ProvidersVars(tpl, out string) error { + src, err := computeProvidersMetaData() if err != nil { return err } diff --git a/internal/gen/interfaces.go b/internal/gen/gen_providers.go similarity index 58% rename from internal/gen/interfaces.go rename to internal/gen/gen_providers.go index 4f02820..7cf6860 100644 --- a/internal/gen/interfaces.go +++ b/internal/gen/gen_providers.go @@ -5,7 +5,6 @@ import ( "go/doc" "go/parser" "go/token" - "go/types" "io/fs" "strings" @@ -19,11 +18,10 @@ import ( // in package check. // It is meant to be used as a data source for template providers.gotmpl. type ProvidersMetaData struct { - Interfaces []metatype.Interface - Vars []metatype.Var + Vars []metatype.Var } -func computeInterfaces() (ProvidersMetaData, error) { +func computeProvidersMetaData() (ProvidersMetaData, error) { docp, err := newDocPackage("check", isProviderFile) // TODO: package-agnostic if err != nil { return ProvidersMetaData{}, err @@ -32,57 +30,20 @@ func computeInterfaces() (ProvidersMetaData, error) { data := ProvidersMetaData{Vars: numericMetaVars()} for _, t := range docp.Types { data.Vars = append(data.Vars, computeMetaVar(t)) - - // FIXME: t.Method is empty for numberCheckerProvider, - // probably a bug due to it being type-parameterized. - // Thus we hardcode its interface in the template file. - if t.Name != "numberCheckerProvider" { - data.Interfaces = append(data.Interfaces, computeMetaInterface(t)) - } } return data, nil } -// computeMetaInterface returns a MetaInterface after the given *doc.Type. -// It reads and attaches the type's name and docs and iterates over its methods. -// If a method is inherited, it embeds the computed interface name of the parent -// rather than adding it to the interface. -func computeMetaInterface(t *doc.Type) metatype.Interface { - name := structToInterfaceName(t.Name) - mitf := metatype.Interface{ - Name: name, - DocLines: docparser.ParseDocLines(t.Doc, map[string]string{ - t.Name: name, - }), - } - - for _, m := range t.Methods { - // ignore private methods - if !m.Decl.Name.IsExported() { - continue - } - // If a method is inherited, embed the interface of the parent - // rather than including all its methods - if m.Level != 0 { - orig := strings.TrimPrefix(m.Orig, "*") // m.Orig might have a leading "*" - mitf.EmbedInterface(structToInterfaceName(orig)) - continue - } - mitf.AddFunc(metatype.Func{ - Sign: m.Name + strings.TrimPrefix(types.ExprString(m.Decl.Type), "func"), - DocLines: docparser.ParseDocLines(m.Doc, nil), - }) - } - return mitf -} - // computeMetaVar returns a MetaVar after the given *doc.Type. func computeMetaVar(t *doc.Type) metatype.Var { - return metatype.Var{ // var T TCheckerProvider = tCheckerProvider{} - Name: structToVarName(t.Name), - Type: structToInterfaceName(t.Name), - Value: t.Name + "{}", + varname := structToVarName(t.Name) + return metatype.Var{ + Name: varname, + Value: structInstanceString(t.Name), + DocLines: docparser.ParseDocLines(t.Doc, map[string]string{ + t.Name: varname, + }), } } @@ -123,12 +84,16 @@ func numericMetaVars() []metatype.Var { return slicex.Map(numerics, func(nt namedType) metatype.Var { return metatype.Var{ Name: nt.N, - Type: "NumberCheckerProvider[" + nt.T + "]", - Value: "numberCheckerProvider[" + nt.T + "]{}", + Value: structInstanceString(addProviderSuffix("Number"), nt.T), + DocLines: []string{ + fmt.Sprintf("%s provides checks on type %s.", nt.N, nt.T), + }, } }) } +// File filters + func isProviderFile(file fs.FileInfo) bool { return strings.HasPrefix(file.Name(), "providers_") && !isTestFile(file) && @@ -142,3 +107,23 @@ func isTestFile(file fs.FileInfo) bool { func isBaseFile(file fs.FileInfo) bool { return strings.HasSuffix(file.Name(), "_base.go") } + +// String helpers + +const providerSuffix = "CheckerProvider" + +func addProviderSuffix(name string) string { + return name + providerSuffix +} + +func structToVarName(structName string) string { + return strings.TrimSuffix(structName, providerSuffix) +} + +func structInstanceString(name string, typeparams ...string) string { + tpstr := "" + if len(typeparams) != 0 { + tpstr = "[" + strings.Join(typeparams, ", ") + "]" + } + return name + tpstr + "{}" +} diff --git a/internal/gen/metatype/metatype.go b/internal/gen/metatype/metatype.go index 92c3f0a..3cd62d2 100644 --- a/internal/gen/metatype/metatype.go +++ b/internal/gen/metatype/metatype.go @@ -1,40 +1,8 @@ package metatype -// Interface gathers an interface informations so it can be generated -// via a go template. -type Interface struct { - Name string - DocLines []string - Embedded []string - Funcs []Func -} - -// EmbedInterface appends interfaceName to MetaInterface.Embedded -// if not already exists, else it is ignored. -func (mi *Interface) EmbedInterface(interfaceName string) { - for _, itf := range mi.Embedded { - if itf == interfaceName { - return - } - } - mi.Embedded = append(mi.Embedded, interfaceName) -} - -// AddFunc creates a MetaFunc from *doc.Func and appends it -// to MetaInterface.Funcs. -func (mi *Interface) AddFunc(f Func) { - mi.Funcs = append(mi.Funcs, f) -} - -// Func gathers a func informations so it can be generated -// via a go template. -type Func struct { - Sign string - DocLines []string -} - // Var gathers a variable informations so it can be generated // via a go template. type Var struct { - Name, Type, Value string + Name, Value string + DocLines []string } diff --git a/internal/gen/providers.go b/internal/gen/providers.go deleted file mode 100644 index 646f88c..0000000 --- a/internal/gen/providers.go +++ /dev/null @@ -1,35 +0,0 @@ -package gen - -import ( - "strings" - - "github.com/drykit-go/strcase" -) - -const ( - providerStructSuffix = "CheckerProvider" - providerInterfaceSuffix = "CheckerProvider" -) - -func structToInterfaceName(structName string) string { - baseName := trimProviderStructSuffix(structName) - interfaceName := appendProviderInterfaceSuffix(baseName) - return exportName(interfaceName) -} - -func structToVarName(structName string) string { - baseName := trimProviderStructSuffix(structName) - return exportName(baseName) -} - -func trimProviderStructSuffix(structName string) string { - return strings.TrimSuffix(structName, providerStructSuffix) -} - -func appendProviderInterfaceSuffix(baseName string) string { - return baseName + providerInterfaceSuffix -} - -func exportName(unexported string) string { - return strcase.Pascal(unexported) -} diff --git a/internal/gen/templates/providers.gotmpl b/internal/gen/templates/providers.gotmpl index 02d88b5..bc10d4d 100644 --- a/internal/gen/templates/providers.gotmpl +++ b/internal/gen/templates/providers.gotmpl @@ -1,57 +1,12 @@ package check -import ( - "context" - "net/http" - "regexp" - "time" -) - -type ( - // NumberCheckerProvider provides checks on numeric types: - // int, uint, float and their variants. - NumberCheckerProvider[T Numeric] interface { - // GT checks the gotten Number is greater than the target. - GT(tar T) Checker[T] - // GTE checks the gotten Number is greater or equal to the target. - GTE(tar T) Checker[T] - // InRange checks the gotten Number is in the closed interval [lo:hi]. - InRange(lo, hi T) Checker[T] - // Is checks the gotten Number is equal to the target. - Is(tar T) Checker[T] - // LT checks the gotten Number is lesser than the target. - LT(tar T) Checker[T] - // LTE checks the gotten Number is lesser or equal to the target. - LTE(tar T) Checker[T] - // Not checks the gotten Number is not equal to the target. - Not(values ...T) Checker[T] - // OutRange checks the gotten Number is not in the closed interval [lo:hi]. - OutRange(lo, hi T) Checker[T] - } - {{range .Interfaces -}} +var ( + {{- range .Vars -}} + {{if ne .Name "Number" -}} {{range .DocLines}} // {{.}} {{- end}} - {{.Name}} interface { - {{range .Embedded -}} - {{.}} - - {{end -}} - {{range .Funcs -}} - {{range .DocLines -}} - // {{.}} - {{end -}} - {{.Sign}} - {{end}} - } - {{end}} -) - -var ( - {{range .Vars -}} - {{if ne .Name "Number" -}} - // {{.Name}} implements {{.Type}}. - {{.Name}} {{.Type}} = {{.Value}} - {{end -}} - {{end}} + {{.Name}} = {{.Value}} + {{- end}} + {{- end}} ) From 354609c582a54711fbc3831560f49d58b1dd4d65 Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Wed, 17 Nov 2021 17:17:16 +0100 Subject: [PATCH 15/19] ops(generics): use determined gotip version (#82) * Use fixed gotip version * Remove duplicate gotip download command * Fix CI: add confirmation user prompt * Add '|| true' to gotip download command --- .circleci/config.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7e83bb5..a7b8fa2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,15 +31,14 @@ commands: command: | command -v /go/bin/gotip && echo "Skipping gotip installation" && exit go install golang.org/dl/gotip@latest - gotip download - save_cache: key: gotip-{{ .Environment.CACHE_VERSION }} paths: - /go/bin/gotip - run: name: Run gotip download - command: | - gotip download + command: | # Nov 12 (https://go-review.googlesource.com/c/go/+/359478) + yes | gotip download 359478 || true setup-lint: description: Set up lint From 469485e215a3754bbc2dcac85a22cce8dfac1d08 Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Wed, 17 Nov 2021 22:03:07 +0100 Subject: [PATCH 16/19] feat(generics): type MapCheckerProvider with generics (#78) * Use generics in MapCheckerProvider implementation * Update code gen and template manually * Run make gen * Update Map unit tests * Update Bytes.AsMap - Change parameter type Checker[any] -> Cheker[map[string]any] - Update unit tests for Bytes - Run make gen --- check/providers.go | 8 ++-- check/providers_bytes.go | 2 +- check/providers_bytes_test.go | 6 +-- check/providers_map.go | 62 ++++++++++++++---------- check/providers_map_test.go | 64 ++++++++++++------------- internal/gen/gen_providers.go | 12 ++++- internal/gen/templates/providers.gotmpl | 5 ++ 7 files changed, 93 insertions(+), 66 deletions(-) diff --git a/check/providers.go b/check/providers.go index 5d931ff..c5977a4 100644 --- a/check/providers.go +++ b/check/providers.go @@ -1,5 +1,5 @@ // Code generated by go generate ./...; DO NOT EDIT -// Last generated on 11 Nov 21 18:00 UTC +// Last generated on 17 Nov 21 20:01 UTC package check @@ -42,8 +42,6 @@ var ( HTTPRequest = HTTPRequestCheckerProvider{} // HTTPResponse provides checks on type *http.Response. HTTPResponse = HTTPResponseCheckerProvider{} - // Map provides checks on kind map. - Map = MapCheckerProvider{} // Slice provides checks on kind slice. Slice = SliceCheckerProvider{} // String provides checks on type string. @@ -53,3 +51,7 @@ var ( // Value provides checks on type any. Value = ValueCheckerProvider{} ) + +func Map[Key comparable, Val any]() MapCheckerProvider[Key, Val] { + return MapCheckerProvider[Key, Val]{} +} diff --git a/check/providers_bytes.go b/check/providers_bytes.go index 9088613..cbee105 100644 --- a/check/providers_bytes.go +++ b/check/providers_bytes.go @@ -92,7 +92,7 @@ func (p BytesCheckerProvider) NotContains(subslice []byte) Checker[[]byte] { // AsMap checks the gotten []byte passes the given mapChecker // once json-unmarshaled to a map[string]any. // It fails if it is not a valid JSON. -func (p BytesCheckerProvider) AsMap(mapChecker Checker[any]) Checker[[]byte] { +func (p BytesCheckerProvider) AsMap(mapChecker Checker[map[string]any]) Checker[[]byte] { var m map[string]any var goterr error pass := func(got []byte) bool { diff --git a/check/providers_bytes_test.go b/check/providers_bytes_test.go index b80c035..29f5eca 100644 --- a/check/providers_bytes_test.go +++ b/check/providers_bytes_test.go @@ -127,13 +127,13 @@ func TestBytesCheckerProvider(t *testing.T) { }) t.Run("AsMap pass", func(t *testing.T) { - c := check.Bytes.AsMap(check.Map.HasKeys("id")) + c := check.Bytes.AsMap(check.Map[string, any]().HasKeys("id")) assertPassChecker(t, "Bytes.AsMap", c, b) assertPassChecker(t, "Bytes.AsMap", c, eqJSON) }) t.Run("AsMap fail", func(t *testing.T) { - c := check.Bytes.AsMap(check.Map.HasKeys("id", "nomatch")) + c := check.Bytes.AsMap(check.Map[string, any]().HasKeys("id", "nomatch")) assertFailChecker(t, "Bytes.AsMap", c, b, makeExpl( "to pass MapChecker", "explanation: json map:\n"+makeExpl( @@ -142,7 +142,7 @@ func TestBytesCheckerProvider(t *testing.T) { ), )) - c = check.Bytes.AsMap(check.Map.HasKeys("id")) + c = check.Bytes.AsMap(check.Map[string, any]().HasKeys("id")) assertFailChecker(t, "Bytes.AsMap", c, sub, makeExpl( "to pass MapChecker", "error: json: cannot unmarshal string into Go value of type map[string]interface {}", diff --git a/check/providers_map.go b/check/providers_map.go index 94902fc..f4b7122 100644 --- a/check/providers_map.go +++ b/check/providers_map.go @@ -11,12 +11,12 @@ import ( ) // MapCheckerProvider provides checks on kind map. -type MapCheckerProvider struct{ ValueCheckerProvider } +type MapCheckerProvider[K comparable, V any] struct{ ValueCheckerProvider } // Len checks the gotten map passes the given Checker[int]. -func (p MapCheckerProvider) Len(c Checker[int]) Checker[any] { +func (p MapCheckerProvider[K, V]) Len(c Checker[int]) Checker[map[K]V] { var gotlen int - pass := func(got any) bool { + pass := func(got map[K]V) bool { reflectutil.MustBeOfKind(got, reflect.Map) gotlen = reflect.ValueOf(got).Len() return c.Pass(gotlen) @@ -31,9 +31,9 @@ func (p MapCheckerProvider) Len(c Checker[int]) Checker[any] { } // HasKeys checks the gotten map has the given keys set. -func (p MapCheckerProvider) HasKeys(keys ...any) Checker[any] { +func (p MapCheckerProvider[K, V]) HasKeys(keys ...K) Checker[map[K]V] { var missing []string - pass := func(got any) bool { + pass := func(got map[K]V) bool { reflectutil.MustBeOfKind(got, reflect.Map) for _, expk := range keys { if _, found := p.get(got, expk); !found { @@ -49,9 +49,9 @@ func (p MapCheckerProvider) HasKeys(keys ...any) Checker[any] { } // HasNotKeys checks the gotten map has the given keys set. -func (p MapCheckerProvider) HasNotKeys(keys ...any) Checker[any] { +func (p MapCheckerProvider[K, V]) HasNotKeys(keys ...K) Checker[map[K]V] { var badkeys []string - pass := func(got any) bool { + pass := func(got map[K]V) bool { reflectutil.MustBeOfKind(got, reflect.Map) for _, expk := range keys { if _, found := p.get(got, expk); found { @@ -67,9 +67,9 @@ func (p MapCheckerProvider) HasNotKeys(keys ...any) Checker[any] { } // HasValues checks the gotten map has the given values set. -func (p MapCheckerProvider) HasValues(values ...any) Checker[any] { +func (p MapCheckerProvider[K, V]) HasValues(values ...V) Checker[map[K]V] { var missing []string - pass := func(got any) bool { + pass := func(got map[K]V) bool { reflectutil.MustBeOfKind(got, reflect.Map) for _, expv := range values { if !p.hasValue(got, expv) { @@ -85,9 +85,9 @@ func (p MapCheckerProvider) HasValues(values ...any) Checker[any] { } // HasNotValues checks the gotten map has not the given values set. -func (p MapCheckerProvider) HasNotValues(values ...any) Checker[any] { +func (p MapCheckerProvider[K, V]) HasNotValues(values ...V) Checker[map[K]V] { var badvalues []string - pass := func(got any) bool { + pass := func(got map[K]V) bool { reflectutil.MustBeOfKind(got, reflect.Map) for _, badv := range values { if p.hasValue(got, badv) { @@ -105,15 +105,15 @@ func (p MapCheckerProvider) HasNotValues(values ...any) Checker[any] { // CheckValues checks the gotten map's values corresponding to the given keys // pass the given checker. A key not found is considered a fail. // If len(keys) == 0, the check is made on all map values. -func (p MapCheckerProvider) CheckValues(c Checker[any], keys ...any) Checker[any] { //nolint: gocognit // TODO: refactor +func (p MapCheckerProvider[K, V]) CheckValues(c Checker[V], keys ...K) Checker[map[K]V] { //nolint: gocognit // TODO: refactor var badentries []string allKeys := len(keys) == 0 - pass := func(got any) bool { + pass := func(got map[K]V) bool { reflectutil.MustBeOfKind(got, reflect.Map) if allKeys { - p.walk(got, func(gotk, gotv any) { + p.walk(got, func(gotk K, gotv V) { if !c.Pass(gotv) { - badentries = append(badentries, fmt.Sprintf("%s:%v", gotk, gotv)) + badentries = append(badentries, fmt.Sprintf("%v:%v", gotk, gotv)) } }) sort.Strings(badentries) @@ -121,7 +121,7 @@ func (p MapCheckerProvider) CheckValues(c Checker[any], keys ...any) Checker[any for _, expk := range keys { gotv, ok := p.get(got, expk) if !ok || !c.Pass(gotv) { - badentries = append(badentries, fmt.Sprintf("%s:%v", expk, gotv)) + badentries = append(badentries, fmt.Sprintf("%v:%v", expk, gotv)) } } } @@ -130,7 +130,7 @@ func (p MapCheckerProvider) CheckValues(c Checker[any], keys ...any) Checker[any expl := func(label string, _ any) string { checkedKeys := cond.String("all keys", fmt.Sprintf("keys %v", keys), allKeys) return p.explainCheck(label, - fmt.Sprintf("values for %s to pass Checker[any]", checkedKeys), + fmt.Sprintf("values for %s to pass Checker[map[K]V]", checkedKeys), c.Explain("values", p.formatList(badentries)), ) } @@ -138,18 +138,24 @@ func (p MapCheckerProvider) CheckValues(c Checker[any], keys ...any) Checker[any } // get returns gotmap[key] and a bool representing whether a match is found. -func (p MapCheckerProvider) get(gotmap, key any) (any, bool) { +func (p MapCheckerProvider[K, V]) get(gotmap map[K]V, key K) (V, bool) { iter := reflect.ValueOf(gotmap).MapRange() for iter.Next() { if k := iter.Key().Interface(); p.deq(k, key) { - return iter.Value().Interface(), true + v, ok := iter.Value().Interface().(V) + if !ok { + var vnil V + return vnil, true + } + return v, true } } - return nil, false + var vnil V + return vnil, false } // hasValue returns true if gotmap matches the specified value. -func (p MapCheckerProvider) hasValue(gotmap, value any) bool { +func (p MapCheckerProvider[K, V]) hasValue(gotmap map[K]V, value V) bool { iter := reflect.ValueOf(gotmap).MapRange() for iter.Next() { if gotv := iter.Value().Interface(); p.deq(gotv, value) { @@ -159,12 +165,20 @@ func (p MapCheckerProvider) hasValue(gotmap, value any) bool { return false } -func (MapCheckerProvider) walk(gotmap any, f func(k, v any)) { +func (MapCheckerProvider[K, V]) walk(gotmap map[K]V, f func(k K, v V)) { vmap := reflect.ValueOf(gotmap) iter := vmap.MapRange() for iter.Next() { - k := iter.Key().Interface() - v := iter.Value().Interface() + var knil K + k, ok := iter.Key().Interface().(K) + if !ok { + k = knil + } + var vnil V + v, ok := iter.Value().Interface().(V) + if !ok { + v = vnil + } f(k, v) } } diff --git a/check/providers_map_test.go b/check/providers_map_test.go index 291c6b0..8ca97bd 100644 --- a/check/providers_map_test.go +++ b/check/providers_map_test.go @@ -13,71 +13,67 @@ func TestMapCheckerProvider(t *testing.T) { "age": 42, "friends": []string{"Robert Robichet", "Jean-Pierre Avidol"}, } - // FIXME: remove forced conversion - itf := func(m map[string]any) any { - return m - } t.Run("Len pass", func(t *testing.T) { - c := check.Map.Len(check.Int.Is(3)) - assertPassChecker(t, "Map.Len", c, itf(m)) + c := check.Map[string, any]().Len(check.Int.Is(3)) + assertPassChecker(t, "Map.Len", c, m) }) t.Run("Len fail", func(t *testing.T) { - c := check.Map.Len(check.Int.Not(3)) - assertFailChecker(t, "Map.Len", c, itf(m), makeExpl( + c := check.Map[string, any]().Len(check.Int.Not(3)) + assertFailChecker(t, "Map.Len", c, m, makeExpl( "length to pass Checker[int]", "explanation: length:\n"+makeExpl("not 3", "3"), )) }) t.Run("HasKeys pass", func(t *testing.T) { - c := check.Map.HasKeys("name", "friends") - assertPassChecker(t, "Map.HasKeys", c, itf(m)) + c := check.Map[string, any]().HasKeys("name", "friends") + assertPassChecker(t, "Map.HasKeys", c, m) }) t.Run("HasKeys fail", func(t *testing.T) { - c := check.Map.HasKeys("name", "hello", "bad") - assertFailChecker(t, "Map.HasKeys", c, itf(m), makeExpl( + c := check.Map[string, any]().HasKeys("name", "hello", "bad") + assertFailChecker(t, "Map.HasKeys", c, m, makeExpl( "to have keys [hello, bad]", fmt.Sprint(m), )) }) t.Run("HasNotKeys pass", func(t *testing.T) { - c := check.Map.HasNotKeys("hello", 42) - assertPassChecker(t, "Map.HasNotKeys", c, itf(m)) + c := check.Map[string, any]().HasNotKeys("hello", "42") + assertPassChecker(t, "Map.HasNotKeys", c, m) }) t.Run("HasNotKeys fail", func(t *testing.T) { - c := check.Map.HasNotKeys("name", "hello", "age") - assertFailChecker(t, "Map.HasNotKeys", c, itf(m), makeExpl( + c := check.Map[string, any]().HasNotKeys("name", "hello", "age") + assertFailChecker(t, "Map.HasNotKeys", c, m, makeExpl( "not to have keys [name, age]", fmt.Sprint(m), )) }) t.Run("HasValues pass", func(t *testing.T) { - c := check.Map.HasValues(42, []string{"Robert Robichet", "Jean-Pierre Avidol"}) - assertPassChecker(t, "Map.HasValues", c, itf(m)) + c := check.Map[string, any]().HasValues(42, []string{"Robert Robichet", "Jean-Pierre Avidol"}) + assertPassChecker(t, "Map.HasValues", c, m) }) t.Run("HasValues fail", func(t *testing.T) { - c := check.Map.HasValues(42, "hello", true) - assertFailChecker(t, "Map.HasValues", c, itf(m), makeExpl( + c := check.Map[string, any]().HasValues(42, "hello", true) + assertFailChecker(t, "Map.HasValues", c, m, makeExpl( "to have values [hello, true]", fmt.Sprint(m), )) }) t.Run("HasNotValues pass", func(t *testing.T) { - c := check.Map.HasNotValues("hello", -1) - assertPassChecker(t, "Map.HasNotValues", c, itf(m)) + c := check.Map[string, any]().HasNotValues("hello", -1) + assertPassChecker(t, "Map.HasNotValues", c, m) }) t.Run("HasNotValues fail", func(t *testing.T) { - c := check.Map.HasNotValues(42, "hi", []string{"Robert Robichet", "Jean-Pierre Avidol"}) - assertFailChecker(t, "Map.HasNotValues", c, itf(m), makeExpl( + c := check.Map[string, any]().HasNotValues(42, "hi", []string{"Robert Robichet", "Jean-Pierre Avidol"}) + assertFailChecker(t, "Map.HasNotValues", c, m, makeExpl( "not to have values [42, [Robert Robichet Jean-Pierre Avidol]]", fmt.Sprint(m), )) @@ -85,25 +81,25 @@ func TestMapCheckerProvider(t *testing.T) { t.Run("CheckValues pass", func(t *testing.T) { // keys subset - c := check.Map.CheckValues( + c := check.Map[string, any]().CheckValues( check.Wrap(check.Int.InRange(41, 43)), "age", ) - assertPassChecker(t, "Map.CheckValues", c, itf(m)) + assertPassChecker(t, "Map.CheckValues", c, m) // all keys - c = check.Map.CheckValues(check.Value.Not(0)) - assertPassChecker(t, "Map.CheckValues", c, itf(m)) + c = check.Map[string, any]().CheckValues(check.Value.Not(0)) + assertPassChecker(t, "Map.CheckValues", c, m) }) t.Run("CheckValues fail", func(t *testing.T) { // keys subset - c := check.Map.CheckValues( + c := check.Map[string, any]().CheckValues( check.Wrap(check.Int.OutRange(41, 43)), "age", "badkey", ) - assertFailChecker(t, "Map.CheckValues", c, itf(m), makeExpl( - "values for keys [age badkey] to pass Checker[any]", + assertFailChecker(t, "Map.CheckValues", c, m, makeExpl( + "values for keys [age badkey] to pass Checker[map[K]V]", "explanation: values:\n"+makeExpl( "not in range [41:43]", "[age:42, badkey:]", @@ -111,9 +107,9 @@ func TestMapCheckerProvider(t *testing.T) { )) // all keys - c = check.Map.CheckValues(check.Value.Is("Marcel Patulacci")) - assertFailChecker(t, "Map.CheckValues", c, itf(m), makeExpl( - "values for all keys to pass Checker[any]", + c = check.Map[string, any]().CheckValues(check.Value.Is("Marcel Patulacci")) + assertFailChecker(t, "Map.CheckValues", c, m, makeExpl( + "values for all keys to pass Checker[map[K]V]", "explanation: values:\n"+makeExpl( "Marcel Patulacci", "[age:42, friends:[Robert Robichet Jean-Pierre Avidol]]", diff --git a/internal/gen/gen_providers.go b/internal/gen/gen_providers.go index 7cf6860..2845572 100644 --- a/internal/gen/gen_providers.go +++ b/internal/gen/gen_providers.go @@ -97,7 +97,8 @@ func numericMetaVars() []metatype.Var { func isProviderFile(file fs.FileInfo) bool { return strings.HasPrefix(file.Name(), "providers_") && !isTestFile(file) && - !isBaseFile(file) + !isBaseFile(file) && + !isBlacklisted(file) } func isTestFile(file fs.FileInfo) bool { @@ -108,6 +109,15 @@ func isBaseFile(file fs.FileInfo) bool { return strings.HasSuffix(file.Name(), "_base.go") } +func isBlacklisted(file fs.FileInfo) bool { + blacklist := map[string]struct{}{ + "providers_number.go": {}, + "providers_map.go": {}, + } + _, inBlacklist := blacklist[file.Name()] + return inBlacklist +} + // String helpers const providerSuffix = "CheckerProvider" diff --git a/internal/gen/templates/providers.gotmpl b/internal/gen/templates/providers.gotmpl index bc10d4d..163888c 100644 --- a/internal/gen/templates/providers.gotmpl +++ b/internal/gen/templates/providers.gotmpl @@ -10,3 +10,8 @@ var ( {{- end}} {{- end}} ) + +// Map provides checks on type map[Key]Val. +func Map[Key comparable, Val any]() MapCheckerProvider[Key, Val] { + return MapCheckerProvider[Key, Val]{} +} From bb7f7cc60236104f345535f761cae1660a156a7b Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Wed, 17 Nov 2021 22:55:34 +0100 Subject: [PATCH 17/19] feat(generics): type SliceCheckerProvider with generics (#83) * Type SliceCheckerProvider with generics * Update var gen * Run make gen * Update unit tests * Fix Map().CheckValues explain output --- check/providers.go | 10 ++-- check/providers_map.go | 2 +- check/providers_map_test.go | 4 +- check/providers_slice.go | 68 ++++++++++++------------- check/providers_slice_test.go | 46 ++++++++--------- internal/gen/gen_providers.go | 1 + internal/gen/templates/providers.gotmpl | 5 ++ 7 files changed, 71 insertions(+), 65 deletions(-) diff --git a/check/providers.go b/check/providers.go index c5977a4..4bc0734 100644 --- a/check/providers.go +++ b/check/providers.go @@ -1,5 +1,5 @@ // Code generated by go generate ./...; DO NOT EDIT -// Last generated on 17 Nov 21 20:01 UTC +// Last generated on 17 Nov 21 21:30 UTC package check @@ -42,8 +42,6 @@ var ( HTTPRequest = HTTPRequestCheckerProvider{} // HTTPResponse provides checks on type *http.Response. HTTPResponse = HTTPResponseCheckerProvider{} - // Slice provides checks on kind slice. - Slice = SliceCheckerProvider{} // String provides checks on type string. String = StringCheckerProvider{} // Struct provides checks on kind struct. @@ -52,6 +50,12 @@ var ( Value = ValueCheckerProvider{} ) +// Map provides checks on type map[Key]Val. func Map[Key comparable, Val any]() MapCheckerProvider[Key, Val] { return MapCheckerProvider[Key, Val]{} } + +// Slice provides checks on type []Elem. +func Slice[Elem any]() SliceCheckerProvider[Elem] { + return SliceCheckerProvider[Elem]{} +} diff --git a/check/providers_map.go b/check/providers_map.go index f4b7122..c880a4c 100644 --- a/check/providers_map.go +++ b/check/providers_map.go @@ -130,7 +130,7 @@ func (p MapCheckerProvider[K, V]) CheckValues(c Checker[V], keys ...K) Checker[m expl := func(label string, _ any) string { checkedKeys := cond.String("all keys", fmt.Sprintf("keys %v", keys), allKeys) return p.explainCheck(label, - fmt.Sprintf("values for %s to pass Checker[map[K]V]", checkedKeys), + fmt.Sprintf("values for %s to pass Checker[V]", checkedKeys), c.Explain("values", p.formatList(badentries)), ) } diff --git a/check/providers_map_test.go b/check/providers_map_test.go index 8ca97bd..d96357b 100644 --- a/check/providers_map_test.go +++ b/check/providers_map_test.go @@ -99,7 +99,7 @@ func TestMapCheckerProvider(t *testing.T) { "age", "badkey", ) assertFailChecker(t, "Map.CheckValues", c, m, makeExpl( - "values for keys [age badkey] to pass Checker[map[K]V]", + "values for keys [age badkey] to pass Checker[V]", "explanation: values:\n"+makeExpl( "not in range [41:43]", "[age:42, badkey:]", @@ -109,7 +109,7 @@ func TestMapCheckerProvider(t *testing.T) { // all keys c = check.Map[string, any]().CheckValues(check.Value.Is("Marcel Patulacci")) assertFailChecker(t, "Map.CheckValues", c, m, makeExpl( - "values for all keys to pass Checker[map[K]V]", + "values for all keys to pass Checker[V]", "explanation: values:\n"+makeExpl( "Marcel Patulacci", "[age:42, friends:[Robert Robichet Jean-Pierre Avidol]]", diff --git a/check/providers_slice.go b/check/providers_slice.go index 3cc196d..884c41c 100644 --- a/check/providers_slice.go +++ b/check/providers_slice.go @@ -8,12 +8,12 @@ import ( ) // SliceCheckerProvider provides checks on kind slice. -type SliceCheckerProvider struct{ ValueCheckerProvider } +type SliceCheckerProvider[Elem any] struct{ ValueCheckerProvider } // Len checks the length of the gotten slice passes the given Checker[int]. -func (p SliceCheckerProvider) Len(c Checker[int]) Checker[any] { +func (p SliceCheckerProvider[Elem]) Len(c Checker[int]) Checker[[]Elem] { var gotlen int - pass := func(got any) bool { + pass := func(got []Elem) bool { reflectutil.MustBeOfKind(got, reflect.Slice) gotlen = reflect.ValueOf(got).Len() return c.Pass(gotlen) @@ -28,9 +28,9 @@ func (p SliceCheckerProvider) Len(c Checker[int]) Checker[any] { } // Cap checks the capacity of the gotten slice passes the given Checker[int]. -func (p SliceCheckerProvider) Cap(c Checker[int]) Checker[any] { +func (p SliceCheckerProvider[Elem]) Cap(c Checker[int]) Checker[[]Elem] { var gotcap int - pass := func(got any) bool { + pass := func(got []Elem) bool { reflectutil.MustBeOfKind(got, reflect.Slice) gotcap = reflect.ValueOf(got).Cap() return c.Pass(gotcap) @@ -45,9 +45,9 @@ func (p SliceCheckerProvider) Cap(c Checker[int]) Checker[any] { } // HasValues checks the gotten slice has the given values set. -func (p SliceCheckerProvider) HasValues(values ...any) Checker[any] { +func (p SliceCheckerProvider[Elem]) HasValues(values ...Elem) Checker[[]Elem] { var missing []string - pass := func(got any) bool { + pass := func(got []Elem) bool { reflectutil.MustBeOfKind(got, reflect.Slice) for _, expv := range values { if !p.hasValue(got, expv) { @@ -66,9 +66,9 @@ func (p SliceCheckerProvider) HasValues(values ...any) Checker[any] { } // HasNotValues checks the gotten slice has not the given values set. -func (p SliceCheckerProvider) HasNotValues(values ...any) Checker[any] { +func (p SliceCheckerProvider[Elem]) HasNotValues(values ...Elem) Checker[[]Elem] { var badvalues []string - pass := func(got any) bool { + pass := func(got []Elem) bool { reflectutil.MustBeOfKind(got, reflect.Slice) for _, badv := range values { if p.hasValue(got, badv) { @@ -87,16 +87,16 @@ func (p SliceCheckerProvider) HasNotValues(values ...any) Checker[any] { } // CheckValues checks the values of the gotten slice passes -// the given Checker[any]. +// the given Checker[Elem]. // If a filterFunc is provided, the values not passing it are ignored. -func (p SliceCheckerProvider) CheckValues( - c Checker[any], - filters ...func(i int, v any) bool, -) Checker[any] { +func (p SliceCheckerProvider[Elem]) CheckValues( + c Checker[Elem], + filters ...func(i int, v Elem) bool, +) Checker[[]Elem] { var badvalues []string - pass := func(got any) bool { + pass := func(got []Elem) bool { reflectutil.MustBeOfKind(got, reflect.Slice) - p.walk(got, filters, func(i int, v any) { + p.walk(got, filters, func(i int, v Elem) { if !c.Pass(v) { badvalues = append(badvalues, fmt.Sprintf("%d:%v", i, v)) } @@ -105,7 +105,7 @@ func (p SliceCheckerProvider) CheckValues( } expl := func(label string, _ any) string { return p.explainCheck(label, - "values to pass Checker[any]", + "values to pass Checker[Elem]", c.Explain("values", p.formatList(badvalues)), ) } @@ -115,20 +115,20 @@ func (p SliceCheckerProvider) CheckValues( // Helpers // hasValue returns true if slice has a value equal to expv. -func (p SliceCheckerProvider) hasValue(slice, expv any) bool { - return p.walkUntil(slice, nil, func(_ int, v any) bool { +func (p SliceCheckerProvider[Elem]) hasValue(slice []Elem, expv Elem) bool { + return p.walkUntil(slice, nil, func(_ int, v Elem) bool { return p.deq(v, expv) }) } // walk iterates over a slice until the end is reached. // It calls f(i, v) each iteration if (i, v) pass the given filters. -func (p SliceCheckerProvider) walk( - slice any, - filters []func(int, any) bool, - f func(i int, v any), +func (p SliceCheckerProvider[Elem]) walk( + slice []Elem, + filters []func(int, Elem) bool, + f func(i int, v Elem), ) { - p.walkUntil(slice, filters, func(i int, v any) bool { + p.walkUntil(slice, filters, func(i int, v Elem) bool { f(i, v) return false }) @@ -137,15 +137,15 @@ func (p SliceCheckerProvider) walk( // walksUntil behaves like walk excepts it returns early if the stop func // returns true for the current iteration. In returns true if it was stopped // early, false otherwise. -func (p SliceCheckerProvider) walkUntil( - slice any, - filters []func(int, any) bool, - stop func(int, any) bool, +func (p SliceCheckerProvider[Elem]) walkUntil( + slice []Elem, + filters []func(int, Elem) bool, + stop func(int, Elem) bool, ) bool { vslice := reflect.ValueOf(slice) l := vslice.Len() for i := 0; i < l; i++ { - v := vslice.Index(i).Interface() + v := vslice.Index(i).Interface().(Elem) filter := p.mergeFilters(filters...) passed := filter(i, v) if passed && stop(i, v) { @@ -156,13 +156,13 @@ func (p SliceCheckerProvider) walkUntil( } // mergeFilters combinates several filtering funcs into one. -func (p SliceCheckerProvider) mergeFilters( - filters ...func(int, any) bool, -) func(int, any) bool { +func (p SliceCheckerProvider[Elem]) mergeFilters( + filters ...func(int, Elem) bool, +) func(int, Elem) bool { if len(filters) == 0 { - return func(int, any) bool { return true } + return func(int, Elem) bool { return true } } - return func(i int, v any) bool { + return func(i int, v Elem) bool { curr := filters[0] next := p.mergeFilters(filters[1:]...) return curr(i, v) && next(i, v) diff --git a/check/providers_slice_test.go b/check/providers_slice_test.go index 4238b5d..26f3ec4 100644 --- a/check/providers_slice_test.go +++ b/check/providers_slice_test.go @@ -9,78 +9,74 @@ import ( func TestSliceCheckerProvider(t *testing.T) { s := []any{"hello", 42, "Marcel Patulacci", []float32{3.14}} - // FIXME: remove forced conversion - itf := func(v []any) any { - return v - } t.Run("Len pass", func(t *testing.T) { - c := check.Slice.Len(check.Int.Is(4)) - assertPassChecker(t, "Slice.Len", c, itf(s)) + c := check.Slice[any]().Len(check.Int.Is(4)) + assertPassChecker(t, "Slice.Len", c, s) }) t.Run("Len fail", func(t *testing.T) { - c := check.Slice.Len(check.Int.Not(4)) - assertFailChecker(t, "Slice.Len", c, itf(s), makeExpl( + c := check.Slice[any]().Len(check.Int.Not(4)) + assertFailChecker(t, "Slice.Len", c, s, makeExpl( "length to pass Checker[int]", "explanation: length:\n"+makeExpl("not 4", "4"), )) }) t.Run("Cap pass", func(t *testing.T) { - c := check.Slice.Cap(check.Int.Is(4)) - assertPassChecker(t, "Slice.Cap", c, itf(s)) + c := check.Slice[any]().Cap(check.Int.Is(4)) + assertPassChecker(t, "Slice.Cap", c, s) }) t.Run("Cap fail", func(t *testing.T) { - c := check.Slice.Cap(check.Int.Not(4)) - assertFailChecker(t, "Slice.Cap", c, itf(s), makeExpl( + c := check.Slice[any]().Cap(check.Int.Not(4)) + assertFailChecker(t, "Slice.Cap", c, s, makeExpl( "capacity to pass Checker[int]", "explanation: capacity:\n"+makeExpl("not 4", "4"), )) }) t.Run("HasValues pass", func(t *testing.T) { - c := check.Slice.HasValues("hello", 42, []float32{3.14}) - assertPassChecker(t, "Slice.HasValues", c, itf(s)) + c := check.Slice[any]().HasValues("hello", 42, []float32{3.14}) + assertPassChecker(t, "Slice.HasValues", c, s) }) t.Run("HasValues fail", func(t *testing.T) { - c := check.Slice.HasValues([]float64{3.14}) - assertFailChecker(t, "Slice.HasValues", c, itf(s), makeExpl( + c := check.Slice[any]().HasValues([]float64{3.14}) + assertFailChecker(t, "Slice.HasValues", c, s, makeExpl( "to have values [[3.14]]", fmt.Sprint(s), )) }) t.Run("HasNotValues pass", func(t *testing.T) { - c := check.Slice.HasNotValues("hi", -1, []float64{3.14}) - assertPassChecker(t, "Slice.HasNotValues", c, itf(s)) + c := check.Slice[any]().HasNotValues("hi", -1, []float64{3.14}) + assertPassChecker(t, "Slice.HasNotValues", c, s) }) t.Run("HasNotValues fail", func(t *testing.T) { - c := check.Slice.HasNotValues("hi", -1, []float32{3.14}) - assertFailChecker(t, "Slice.HasNotValues", c, itf(s), makeExpl( + c := check.Slice[any]().HasNotValues("hi", -1, []float32{3.14}) + assertFailChecker(t, "Slice.HasNotValues", c, s, makeExpl( "not to have values [[3.14]]", fmt.Sprint(s), )) }) t.Run("CheckValues pass", func(t *testing.T) { - c := check.Slice.CheckValues( + c := check.Slice[any]().CheckValues( check.Wrap(check.Int.InRange(41, 43)), func(_ int, v any) bool { _, ok := v.(int); return ok }, ) - assertPassChecker(t, "Slice.CheckValues", c, itf(s)) + assertPassChecker(t, "Slice.CheckValues", c, s) }) t.Run("CheckValues fail", func(t *testing.T) { - c := check.Slice.CheckValues( + c := check.Slice[any]().CheckValues( check.Wrap(check.Int.OutRange(41, 43)), func(_ int, v any) bool { _, ok := v.(int); return ok }, ) - assertFailChecker(t, "Slice.CheckValues", c, itf(s), makeExpl( - "values to pass Checker[any]", + assertFailChecker(t, "Slice.CheckValues", c, s, makeExpl( + "values to pass Checker[Elem]", "explanation: values:\n"+makeExpl("not in range [41:43]", "[1:42]"), )) }) diff --git a/internal/gen/gen_providers.go b/internal/gen/gen_providers.go index 2845572..6e1875c 100644 --- a/internal/gen/gen_providers.go +++ b/internal/gen/gen_providers.go @@ -113,6 +113,7 @@ func isBlacklisted(file fs.FileInfo) bool { blacklist := map[string]struct{}{ "providers_number.go": {}, "providers_map.go": {}, + "providers_slice.go": {}, } _, inBlacklist := blacklist[file.Name()] return inBlacklist diff --git a/internal/gen/templates/providers.gotmpl b/internal/gen/templates/providers.gotmpl index 163888c..d5fb47a 100644 --- a/internal/gen/templates/providers.gotmpl +++ b/internal/gen/templates/providers.gotmpl @@ -15,3 +15,8 @@ var ( func Map[Key comparable, Val any]() MapCheckerProvider[Key, Val] { return MapCheckerProvider[Key, Val]{} } + +// Slice provides checks on type []Elem. +func Slice[Elem any]() SliceCheckerProvider[Elem] { + return SliceCheckerProvider[Elem]{} +} From 36ed99948c1c4576a1b9adeea91cb82fe8de8862 Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Wed, 17 Nov 2021 23:33:44 +0100 Subject: [PATCH 18/19] feat(generics): type ValueCheckerProvider with generics (#84) * Update var gen * Run make gen * Type ValueCheckerProvider with generics * Update unit tests * Exclude lint rule with false positive --- .golangci.yml | 1 + check/providers.go | 9 ++++--- check/providers_context_test.go | 2 +- check/providers_httprequest_test.go | 4 +-- check/providers_map.go | 2 +- check/providers_map_test.go | 4 +-- check/providers_slice.go | 2 +- check/providers_struct.go | 2 +- check/providers_value.go | 36 ++++++++++++++----------- check/providers_value_test.go | 24 ++++++++--------- check/wrap_test.go | 3 ++- internal/gen/gen_providers.go | 1 + internal/gen/templates/providers.gotmpl | 5 ++++ runner_table.go | 4 +-- runner_value.go | 4 +-- 15 files changed, 59 insertions(+), 44 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 6b44d82..bc76426 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -164,4 +164,5 @@ issues: - "expected expression" - "undeclared name: `any`" - "missing ',' in parameter list" + - "missing element type in array type expression" - "mixed named and unnamed parameters" diff --git a/check/providers.go b/check/providers.go index 4bc0734..9e7717c 100644 --- a/check/providers.go +++ b/check/providers.go @@ -1,5 +1,5 @@ // Code generated by go generate ./...; DO NOT EDIT -// Last generated on 17 Nov 21 21:30 UTC +// Last generated on 17 Nov 21 22:02 UTC package check @@ -46,8 +46,6 @@ var ( String = StringCheckerProvider{} // Struct provides checks on kind struct. Struct = StructCheckerProvider{} - // Value provides checks on type any. - Value = ValueCheckerProvider{} ) // Map provides checks on type map[Key]Val. @@ -59,3 +57,8 @@ func Map[Key comparable, Val any]() MapCheckerProvider[Key, Val] { func Slice[Elem any]() SliceCheckerProvider[Elem] { return SliceCheckerProvider[Elem]{} } + +// ValueCheckerProvider provides generic checks on any type. +func Value[T any]() ValueCheckerProvider[T] { + return ValueCheckerProvider[T]{} +} diff --git a/check/providers_context_test.go b/check/providers_context_test.go index 37a0e45..06cb64d 100644 --- a/check/providers_context_test.go +++ b/check/providers_context_test.go @@ -70,7 +70,7 @@ func TestContextCheckerProvider(t *testing.T) { }) t.Run("Value fail", func(t *testing.T) { - c := check.Context.Value("userID", check.Value.Is(0)) + c := check.Context.Value("userID", check.Value[any]().Is(0)) ctxMissingKey := context.Background() assertFailChecker(t, "Context.Value", c, ctxMissingKey, makeExpl( diff --git a/check/providers_httprequest_test.go b/check/providers_httprequest_test.go index 4b12675..2ceac27 100644 --- a/check/providers_httprequest_test.go +++ b/check/providers_httprequest_test.go @@ -80,12 +80,12 @@ func TestHTTPRequestCheckerProvider(t *testing.T) { }) t.Run("Context pass", func(t *testing.T) { - c := check.HTTPRequest.Context(check.Context.Value(expCtxKey, check.Value.Is(expCtxVal))) + c := check.HTTPRequest.Context(check.Context.Value(expCtxKey, check.Value[any]().Is(expCtxVal))) assertPassChecker(t, "HTTPRequest.Context", c, newReq()) }) t.Run("Context fail", func(t *testing.T) { - c := check.HTTPRequest.Context(check.Context.Value(expCtxKey, check.Value.Not(expCtxVal))) + c := check.HTTPRequest.Context(check.Context.Value(expCtxKey, check.Value[any]().Not(expCtxVal))) assertFailChecker(t, "HTTPRequest.Context", c, newReq(), makeExpl( "context to pass Checker[context.Context]", "explanation: context:\n"+makeExpl( diff --git a/check/providers_map.go b/check/providers_map.go index c880a4c..bdb83d7 100644 --- a/check/providers_map.go +++ b/check/providers_map.go @@ -11,7 +11,7 @@ import ( ) // MapCheckerProvider provides checks on kind map. -type MapCheckerProvider[K comparable, V any] struct{ ValueCheckerProvider } +type MapCheckerProvider[K comparable, V any] struct{ ValueCheckerProvider[map[K]V] } // Len checks the gotten map passes the given Checker[int]. func (p MapCheckerProvider[K, V]) Len(c Checker[int]) Checker[map[K]V] { diff --git a/check/providers_map_test.go b/check/providers_map_test.go index d96357b..e1a47c2 100644 --- a/check/providers_map_test.go +++ b/check/providers_map_test.go @@ -88,7 +88,7 @@ func TestMapCheckerProvider(t *testing.T) { assertPassChecker(t, "Map.CheckValues", c, m) // all keys - c = check.Map[string, any]().CheckValues(check.Value.Not(0)) + c = check.Map[string, any]().CheckValues(check.Value[any]().Not(0)) assertPassChecker(t, "Map.CheckValues", c, m) }) @@ -107,7 +107,7 @@ func TestMapCheckerProvider(t *testing.T) { )) // all keys - c = check.Map[string, any]().CheckValues(check.Value.Is("Marcel Patulacci")) + c = check.Map[string, any]().CheckValues(check.Value[any]().Is("Marcel Patulacci")) assertFailChecker(t, "Map.CheckValues", c, m, makeExpl( "values for all keys to pass Checker[V]", "explanation: values:\n"+makeExpl( diff --git a/check/providers_slice.go b/check/providers_slice.go index 884c41c..e7a4c10 100644 --- a/check/providers_slice.go +++ b/check/providers_slice.go @@ -8,7 +8,7 @@ import ( ) // SliceCheckerProvider provides checks on kind slice. -type SliceCheckerProvider[Elem any] struct{ ValueCheckerProvider } +type SliceCheckerProvider[Elem any] struct{ ValueCheckerProvider[[]Elem] } // Len checks the length of the gotten slice passes the given Checker[int]. func (p SliceCheckerProvider[Elem]) Len(c Checker[int]) Checker[[]Elem] { diff --git a/check/providers_struct.go b/check/providers_struct.go index 43d5870..1dfc289 100644 --- a/check/providers_struct.go +++ b/check/providers_struct.go @@ -9,7 +9,7 @@ import ( ) // StructCheckerProvider provides checks on kind struct. -type StructCheckerProvider struct{ ValueCheckerProvider } +type StructCheckerProvider struct{ ValueCheckerProvider[any] } // FieldsEqual checks all given fields equal the exp value. // It panics if the fields do not exist or are not exported, diff --git a/check/providers_value.go b/check/providers_value.go index 911b539..7952b71 100644 --- a/check/providers_value.go +++ b/check/providers_value.go @@ -6,13 +6,13 @@ import ( "github.com/drykit-go/testx/internal/reflectutil" ) -// ValueCheckerProvider provides checks on type any. -type ValueCheckerProvider struct{ baseCheckerProvider } +// ValueCheckerProvider provides generic checks on any type. +type ValueCheckerProvider[T any] struct{ baseCheckerProvider } -// Custom checks the gotten value passes the given PassFunc[any]. +// Custom checks the gotten value passes the given PassFunc. // The description should give information about the expected value, // as it outputs in format "exp " in case of failure. -func (p ValueCheckerProvider) Custom(desc string, f PassFunc[any]) Checker[any] { +func (p ValueCheckerProvider[T]) Custom(desc string, f PassFunc[T]) Checker[T] { expl := func(label string, got any) string { return p.explain(label, desc, got) } @@ -20,8 +20,8 @@ func (p ValueCheckerProvider) Custom(desc string, f PassFunc[any]) Checker[any] } // Is checks the gotten value is equal to the target. -func (p ValueCheckerProvider) Is(tar any) Checker[any] { - pass := func(got any) bool { return p.deq(got, tar) } +func (p ValueCheckerProvider[T]) Is(tar T) Checker[T] { + pass := func(got T) bool { return p.deq(got, tar) } expl := func(label string, got any) string { return p.explain(label, tar, got) } @@ -29,9 +29,9 @@ func (p ValueCheckerProvider) Is(tar any) Checker[any] { } // Not checks the gotten value is not equal to the target. -func (p ValueCheckerProvider) Not(values ...any) Checker[any] { - var match any - pass := func(got any) bool { +func (p ValueCheckerProvider[T]) Not(values ...T) Checker[T] { + var match T + pass := func(got T) bool { for _, v := range values { if p.deq(got, v) { match = v @@ -48,17 +48,20 @@ func (p ValueCheckerProvider) Not(values ...any) Checker[any] { // IsZero checks the gotten value is a zero value, indicating it might not // have been initialized. -func (p ValueCheckerProvider) IsZero() Checker[any] { +func (p ValueCheckerProvider[T]) IsZero() Checker[T] { + pass := func(got T) bool { + return reflectutil.IsZero(got) + } expl := func(label string, got any) string { return p.explain(label, "to be a zero value", got) } - return NewChecker(reflectutil.IsZero, expl) + return NewChecker(pass, expl) } // NotZero checks the gotten struct contains at least 1 non-zero value, // meaning it has been initialized. -func (p ValueCheckerProvider) NotZero() Checker[any] { - pass := func(got any) bool { return !reflectutil.IsZero(got) } +func (p ValueCheckerProvider[T]) NotZero() Checker[T] { + pass := func(got T) bool { return !reflectutil.IsZero(got) } expl := func(label string, got any) string { return p.explainNot(label, "to be a zero value", got) } @@ -68,9 +71,10 @@ func (p ValueCheckerProvider) NotZero() Checker[any] { // SameJSON checks the gotten value and the target value // produce the same JSON, ignoring formatting and keys order. // It panics if any error occurs in the marshaling process. -func (p ValueCheckerProvider) SameJSON(tar any) Checker[any] { - var gotDec, tarDec any - pass := func(got any) bool { +func (p ValueCheckerProvider[T]) SameJSON(tar any) Checker[T] { + var gotDec T + var tarDec any + pass := func(got T) bool { return p.sameJSONProduced(got, tar, &gotDec, &tarDec) } expl := func(label string, got any) string { diff --git a/check/providers_value_test.go b/check/providers_value_test.go index 253a30d..26b7a35 100644 --- a/check/providers_value_test.go +++ b/check/providers_value_test.go @@ -31,34 +31,34 @@ func TestValueCheckerProvider(t *testing.T) { } t.Run("Is pass", func(t *testing.T) { - c := check.Value.Is(vsame) + c := check.Value[any]().Is(vsame) assertPassChecker(t, "Value.Is", c, itf(vorig)) }) t.Run("Is fail", func(t *testing.T) { - c := check.Value.Is(badval) + c := check.Value[any]().Is(badval) assertFailChecker(t, "Value.Is", c, itf(vorig), makeExpl("{hello}", "{hi}")) }) t.Run("Not pass", func(t *testing.T) { - c := check.Value.Not(badval, badtyp) + c := check.Value[any]().Not(badval, badtyp) assertPassChecker(t, "Value.Not", c, itf(vorig)) }) t.Run("Not fail", func(t *testing.T) { - c := check.Value.Not(badval, vsame, badtyp) + c := check.Value[any]().Not(badval, vsame, badtyp) assertFailChecker(t, "Value.Not", c, itf(vorig), makeExpl("not {hi}", "{hi}")) }) t.Run("IsZero pass", func(t *testing.T) { - c := check.Value.IsZero() + c := check.Value[any]().IsZero() for _, z := range zeros { assertPassChecker(t, "Value.IsZero", c, z) } }) t.Run("IsZero fail", func(t *testing.T) { - c := check.Value.IsZero() + c := check.Value[any]().IsZero() for _, nz := range nozeros { assertFailChecker(t, "Value.IsZero", c, nz, makeExpl( "to be a zero value", @@ -68,14 +68,14 @@ func TestValueCheckerProvider(t *testing.T) { }) t.Run("NotZero pass", func(t *testing.T) { - c := check.Value.NotZero() + c := check.Value[any]().NotZero() for _, nz := range nozeros { assertPassChecker(t, "Value.NotZero", c, nz) } }) t.Run("NotZero fail", func(t *testing.T) { - c := check.Value.NotZero() + c := check.Value[any]().NotZero() for _, z := range zeros { assertFailChecker(t, "Value.NotZero", c, z, makeExpl( "not to be a zero value", @@ -90,12 +90,12 @@ func TestValueCheckerProvider(t *testing.T) { } t.Run("Custom pass", func(t *testing.T) { - c := check.Value.Custom("", isEvenInt) + c := check.Value[any]().Custom("", isEvenInt) assertPassChecker(t, "Value.Custom", c, 42) }) t.Run("Custom fail", func(t *testing.T) { - c := check.Value.Custom("even int", isEvenInt) + c := check.Value[any]().Custom("even int", isEvenInt) assertFailChecker(t, "Value.Custom", c, -1, makeExpl("even int", "-1")) }) @@ -103,7 +103,7 @@ func TestValueCheckerProvider(t *testing.T) { mapequiv := map[string]any{ "Name": "hi", } - c := check.Value.SameJSON(mapequiv) + c := check.Value[any]().SameJSON(mapequiv) assertPassChecker(t, "Value.SameJSON", c, itf(vorig)) }) @@ -111,7 +111,7 @@ func TestValueCheckerProvider(t *testing.T) { mapdiff := map[string]any{ "Name": "bad", } - c := check.Value.SameJSON(mapdiff) + c := check.Value[any]().SameJSON(mapdiff) assertFailChecker(t, "Value.SameJSON", c, itf(vorig), makeExpl( "json data: map[Name:bad]", "json data: map[Name:hi]", diff --git a/check/wrap_test.go b/check/wrap_test.go index cc230fc..7daf4d3 100644 --- a/check/wrap_test.go +++ b/check/wrap_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/drykit-go/slicex" + "github.com/drykit-go/testx/check" ) @@ -30,7 +31,7 @@ func TestWrap(t *testing.T) { expexpl: "value:\nexp in range [41:43]\ngot -1", }, { - checker: check.Wrap(check.Value.Custom("", func(got any) bool { return true })), + checker: check.Value[any]().Custom("", func(got any) bool { return true }), in: "", exppass: true, expexpl: "", diff --git a/internal/gen/gen_providers.go b/internal/gen/gen_providers.go index 6e1875c..7cde468 100644 --- a/internal/gen/gen_providers.go +++ b/internal/gen/gen_providers.go @@ -114,6 +114,7 @@ func isBlacklisted(file fs.FileInfo) bool { "providers_number.go": {}, "providers_map.go": {}, "providers_slice.go": {}, + "providers_value.go": {}, } _, inBlacklist := blacklist[file.Name()] return inBlacklist diff --git a/internal/gen/templates/providers.gotmpl b/internal/gen/templates/providers.gotmpl index d5fb47a..3d47667 100644 --- a/internal/gen/templates/providers.gotmpl +++ b/internal/gen/templates/providers.gotmpl @@ -20,3 +20,8 @@ func Map[Key comparable, Val any]() MapCheckerProvider[Key, Val] { func Slice[Elem any]() SliceCheckerProvider[Elem] { return SliceCheckerProvider[Elem]{} } + +// ValueCheckerProvider provides generic checks on any type. +func Value[T any]() ValueCheckerProvider[T] { + return ValueCheckerProvider[T]{} +} diff --git a/runner_table.go b/runner_table.go index 261145b..efabc84 100644 --- a/runner_table.go +++ b/runner_table.go @@ -169,7 +169,7 @@ func (r *tableRunner[In, Exp]) Cases(cases []Case[In, Exp]) TableRunner[In, Exp] // add Case.Not checks if hasNotChecks { - addCaseCheck(check.Value.Not(slices.AsAny(tc.Not)...)) + addCaseCheck(check.Value[any]().Not(slices.AsAny(tc.Not)...)) } // add Case.Pass checks @@ -179,7 +179,7 @@ func (r *tableRunner[In, Exp]) Cases(cases []Case[In, Exp]) TableRunner[In, Exp] // add Case.Exp checks if hasExpCheck { - addCaseCheck(check.Value.Is(tc.Exp)) + addCaseCheck(check.Value[any]().Is(tc.Exp)) } } return r diff --git a/runner_value.go b/runner_value.go index bca230b..f8dba03 100644 --- a/runner_value.go +++ b/runner_value.go @@ -23,7 +23,7 @@ func (r *valueRunner[T]) DryRun() Resulter { } func (r *valueRunner[T]) Exp(value T) ValueRunner[T] { - r.addValueCheck(check.Value.Is(value)) + r.addValueCheck(check.Value[any]().Is(value)) return r } @@ -33,7 +33,7 @@ func (r *valueRunner[T]) Not(values ...T) ValueRunner[T] { for _, v := range values { valuesitf = append(valuesitf, v) } - r.addValueCheck(check.Value.Not(valuesitf...)) + r.addValueCheck(check.Value[any]().Not(valuesitf...)) return r } From b8017055e4429ff79c307da3d5405a5818e5adae Mon Sep 17 00:00:00 2001 From: Gregory Albouy <60700958+GregoryAlbouy@users.noreply.github.com> Date: Sun, 21 Nov 2021 17:15:52 +0100 Subject: [PATCH 19/19] dev(generics): move providers to dedicated internal package (#91) * Create internal package checktypes - Avoid cyclic deps * Create package internal/providers - Copy providers impl and tests into internal/providers * Update code gen - Move check/gen.go -> internal/providers/gen.go - Require output filepath argument (-o) - Adapt providers vars generation logics * Run make gen * Remove providers files in package check * Fix check.Wrap inference issues --- check/check.go | 27 ++++----- check/gen.go | 3 - check/providers.go | 60 ++++++++++--------- check/wrap_test.go | 4 +- cmd/gen/main.go | 6 +- internal/checktypes/checktypes.go | 59 ++++++++++++++++++ internal/gen/gen_providers.go | 13 ++-- internal/gen/templates/providers.gotmpl | 16 +++-- .../providers/bool.go | 8 ++- .../providers/bool_test.go | 13 ++-- internal/providers/gen.go | 3 + .../providers}/providers_base.go | 15 ++--- .../providers_base_internal_test.go | 2 +- .../providers}/providers_bytes.go | 36 +++++------ .../providers}/providers_bytes_test.go | 49 ++++++++------- .../providers}/providers_context.go | 16 ++--- .../providers}/providers_context_test.go | 21 ++++--- .../providers}/providers_duration.go | 20 ++++--- .../providers}/providers_duration_test.go | 34 ++++++----- .../providers}/providers_httpheader.go | 24 ++++---- .../providers}/providers_httpheader_test.go | 31 +++++----- .../providers}/providers_httprequest.go | 19 +++--- .../providers}/providers_httprequest_test.go | 21 ++++--- .../providers}/providers_httpresponse.go | 23 +++---- .../providers}/providers_httpresponse_test.go | 25 ++++---- .../providers}/providers_map.go | 27 +++++---- .../providers}/providers_map_test.go | 37 ++++++------ .../providers}/providers_number.go | 38 ++++++------ .../providers}/providers_number_test.go | 54 +++++++++-------- .../providers}/providers_slice.go | 25 ++++---- .../providers}/providers_slice_test.go | 29 +++++---- .../providers}/providers_string.go | 32 +++++----- .../providers}/providers_string_test.go | 37 ++++++------ .../providers}/providers_struct.go | 11 ++-- .../providers}/providers_struct_test.go | 17 +++--- .../providers}/providers_test.go | 4 +- .../providers}/providers_value.go | 27 +++++---- .../providers}/providers_value_test.go | 30 +++++----- 38 files changed, 519 insertions(+), 397 deletions(-) delete mode 100644 check/gen.go create mode 100644 internal/checktypes/checktypes.go rename check/providers_bool.go => internal/providers/bool.go (62%) rename check/providers_bool_test.go => internal/providers/bool_test.go (66%) create mode 100644 internal/providers/gen.go rename {check => internal/providers}/providers_base.go (92%) rename {check => internal/providers}/providers_base_internal_test.go (99%) rename {check => internal/providers}/providers_bytes.go (74%) rename {check => internal/providers}/providers_bytes_test.go (77%) rename {check => internal/providers}/providers_context.go (77%) rename {check => internal/providers}/providers_context_test.go (79%) rename {check => internal/providers}/providers_duration.go (80%) rename {check => internal/providers}/providers_duration_test.go (76%) rename {check => internal/providers}/providers_httpheader.go (76%) rename {check => internal/providers}/providers_httpheader_test.go (72%) rename {check => internal/providers}/providers_httprequest.go (72%) rename {check => internal/providers}/providers_httprequest_test.go (76%) rename {check => internal/providers}/providers_httpresponse.go (72%) rename {check => internal/providers}/providers_httpresponse_test.go (77%) rename {check => internal/providers}/providers_map.go (84%) rename {check => internal/providers}/providers_map_test.go (69%) rename {check => internal/providers}/providers_number.go (72%) rename {check => internal/providers}/providers_number_test.go (73%) rename {check => internal/providers}/providers_slice.go (87%) rename {check => internal/providers}/providers_slice_test.go (72%) rename {check => internal/providers}/providers_string.go (69%) rename {check => internal/providers}/providers_string_test.go (74%) rename {check => internal/providers}/providers_struct.go (88%) rename {check => internal/providers}/providers_struct_test.go (73%) rename {check => internal/providers}/providers_test.go (93%) rename {check => internal/providers}/providers_value.go (73%) rename {check => internal/providers}/providers_value_test.go (80%) diff --git a/check/check.go b/check/check.go index 5b521c6..eb60b25 100644 --- a/check/check.go +++ b/check/check.go @@ -1,39 +1,34 @@ package check -import "constraints" +import ( + "github.com/drykit-go/testx/internal/checktypes" +) -type Numeric interface { - constraints.Integer | constraints.Float -} +type Numeric interface{ checktypes.Numeric } type ( // PassFunc is the required method to implement Passer. // It returns a boolean that indicates whether the got value // passes the current check. - PassFunc[T any] func(got T) bool + PassFunc[T any] checktypes.PassFunc[T] // ExplainFunc is the required method to implement Explainer. // It returns a string explaining why the gotten value failed the check. // The label provides some context, such as "response code". - ExplainFunc func(label string, got any) string + ExplainFunc = checktypes.ExplainFunc ) type ( // Passer provides a method Pass that returns a bool that indicates // whether the got value passes the current check. - Passer[T any] interface{ Pass(got T) bool } + Passer[T any] interface{ checktypes.Passer[T] } // Explainer provides a method Explain describing the reason of a failed check. - Explainer interface { - Explain(label string, got any) string - } -) + Explainer interface{ checktypes.Explainer } -// Checker satisfies both Passer and Explainer interfaces. -type Checker[T any] interface { - Passer[T] - Explainer -} + // Checker satisfies both Passer and Explainer interfaces. + Checker[T any] interface{ checktypes.Checker[T] } +) type checker[T any] struct { pass PassFunc[T] diff --git a/check/gen.go b/check/gen.go deleted file mode 100644 index feced00..0000000 --- a/check/gen.go +++ /dev/null @@ -1,3 +0,0 @@ -package check - -//go:generate ../bin/gen -kind interfaces -name providers diff --git a/check/providers.go b/check/providers.go index 9e7717c..efe7a22 100644 --- a/check/providers.go +++ b/check/providers.go @@ -1,64 +1,66 @@ // Code generated by go generate ./...; DO NOT EDIT -// Last generated on 17 Nov 21 22:02 UTC +// Last generated on 21 Nov 21 13:15 UTC package check +import ( + "github.com/drykit-go/testx/internal/providers" +) + var ( // Int provides checks on type int. - Int = NumberCheckerProvider[int]{} + Int = providers.NumberCheckerProvider[int]{} // Int8 provides checks on type int8. - Int8 = NumberCheckerProvider[int8]{} + Int8 = providers.NumberCheckerProvider[int8]{} // Int16 provides checks on type int16. - Int16 = NumberCheckerProvider[int16]{} + Int16 = providers.NumberCheckerProvider[int16]{} // Int32 provides checks on type int32. - Int32 = NumberCheckerProvider[int32]{} + Int32 = providers.NumberCheckerProvider[int32]{} // Int64 provides checks on type int64. - Int64 = NumberCheckerProvider[int64]{} + Int64 = providers.NumberCheckerProvider[int64]{} // Uint provides checks on type uint. - Uint = NumberCheckerProvider[uint]{} + Uint = providers.NumberCheckerProvider[uint]{} // Uint8 provides checks on type uint8. - Uint8 = NumberCheckerProvider[uint8]{} + Uint8 = providers.NumberCheckerProvider[uint8]{} // Uint16 provides checks on type uint16. - Uint16 = NumberCheckerProvider[uint16]{} + Uint16 = providers.NumberCheckerProvider[uint16]{} // Uint32 provides checks on type uint32. - Uint32 = NumberCheckerProvider[uint32]{} + Uint32 = providers.NumberCheckerProvider[uint32]{} // Uint64 provides checks on type uint64. - Uint64 = NumberCheckerProvider[uint64]{} + Uint64 = providers.NumberCheckerProvider[uint64]{} // Float32 provides checks on type float32. - Float32 = NumberCheckerProvider[float32]{} + Float32 = providers.NumberCheckerProvider[float32]{} // Float64 provides checks on type float64. - Float64 = NumberCheckerProvider[float64]{} - // Bool provides checks on type bool. - Bool = BoolCheckerProvider{} + Float64 = providers.NumberCheckerProvider[float64]{} // Bytes provides checks on type []byte. - Bytes = BytesCheckerProvider{} + Bytes = providers.BytesCheckerProvider{} // Context provides checks on type context.Context. - Context = ContextCheckerProvider{} + Context = providers.ContextCheckerProvider{} // Duration provides checks on type time.Duration. - Duration = DurationCheckerProvider{} + Duration = providers.DurationCheckerProvider{} // HTTPHeader provides checks on type http.Header. - HTTPHeader = HTTPHeaderCheckerProvider{} + HTTPHeader = providers.HTTPHeaderCheckerProvider{} // HTTPRequest provides checks on type *http.Request. - HTTPRequest = HTTPRequestCheckerProvider{} + HTTPRequest = providers.HTTPRequestCheckerProvider{} // HTTPResponse provides checks on type *http.Response. - HTTPResponse = HTTPResponseCheckerProvider{} + HTTPResponse = providers.HTTPResponseCheckerProvider{} // String provides checks on type string. - String = StringCheckerProvider{} + String = providers.StringCheckerProvider{} // Struct provides checks on kind struct. - Struct = StructCheckerProvider{} + Struct = providers.StructCheckerProvider{} ) // Map provides checks on type map[Key]Val. -func Map[Key comparable, Val any]() MapCheckerProvider[Key, Val] { - return MapCheckerProvider[Key, Val]{} +func Map[Key comparable, Val any]() providers.MapCheckerProvider[Key, Val] { + return providers.MapCheckerProvider[Key, Val]{} } // Slice provides checks on type []Elem. -func Slice[Elem any]() SliceCheckerProvider[Elem] { - return SliceCheckerProvider[Elem]{} +func Slice[Elem any]() providers.SliceCheckerProvider[Elem] { + return providers.SliceCheckerProvider[Elem]{} } // ValueCheckerProvider provides generic checks on any type. -func Value[T any]() ValueCheckerProvider[T] { - return ValueCheckerProvider[T]{} +func Value[T any]() providers.ValueCheckerProvider[T] { + return providers.ValueCheckerProvider[T]{} } diff --git a/check/wrap_test.go b/check/wrap_test.go index 7daf4d3..769c499 100644 --- a/check/wrap_test.go +++ b/check/wrap_test.go @@ -19,13 +19,13 @@ func TestWrap(t *testing.T) { t.Run("native checkers", func(t *testing.T) { testcases := []checkerTestcase[any]{ { - checker: check.Wrap(check.Bytes.Is([]byte{42})), + checker: check.Wrap[[]byte](check.Bytes.Is([]byte{42})), in: []byte{42}, exppass: true, expexpl: "", }, { - checker: check.Wrap(check.Int.InRange(41, 43)), + checker: check.Wrap[int](check.Int.InRange(41, 43)), in: -1, exppass: false, expexpl: "value:\nexp in range [41:43]\ngot -1", diff --git a/cmd/gen/main.go b/cmd/gen/main.go index a2cc1ee..d93d775 100644 --- a/cmd/gen/main.go +++ b/cmd/gen/main.go @@ -27,6 +27,7 @@ const ( var ( name = flag.String("name", "", "template name in internal/gen (without extension)") kind = flag.String("kind", "", "data to be generated (only 'types' currently") + out = flag.String("o", "", "output file path") ) var kindsFuncs = map[string]func(tpl, out string) error{ @@ -68,6 +69,9 @@ func parseFlags() error { if *name == "" { return errors.New("missing template name (-name)") } + if *out == "" { + return errors.New("missing output path (-o)") + } return nil } @@ -89,7 +93,7 @@ func getFilesPaths() (tplPath, outPath string, err error) { } tplPath = filepath.Join(currDir, tplDirPath, filename(*name, tplExt)) - outPath = filepath.Join(workDir, filename(*name, outExt)) + outPath = filepath.Join(workDir, *out) return } diff --git a/internal/checktypes/checktypes.go b/internal/checktypes/checktypes.go new file mode 100644 index 0000000..8e59303 --- /dev/null +++ b/internal/checktypes/checktypes.go @@ -0,0 +1,59 @@ +package checktypes + +import "constraints" + +type Numeric interface { + constraints.Integer | constraints.Float +} + +type ( + // PassFunc is the required method to implement Passer. + // It returns a boolean that indicates whether the got value + // passes the current check. + PassFunc[T any] func(got T) bool + + // ExplainFunc is the required method to implement Explainer. + // It returns a string explaining why the gotten value failed the check. + // The label provides some context, such as "response code". + ExplainFunc func(label string, got any) string +) + +type ( + // Passer provides a method Pass that returns a bool that indicates + // whether the got value passes the current check. + Passer[T any] interface{ Pass(got T) bool } + + // Explainer provides a method Explain describing the reason of a failed check. + Explainer interface { + Explain(label string, got any) string + } +) + +// Checker satisfies both Passer and Explainer interfaces. +type Checker[T any] interface { + Passer[T] + Explainer +} + +type checker[T any] struct { + pass PassFunc[T] + expl ExplainFunc +} + +func (c checker[T]) Pass(got T) bool { + return c.pass(got) +} + +func (c checker[T]) Explain(label string, got any) string { + return c.expl(label, got) +} + +func NewChecker[T any]( + passFunc PassFunc[T], + explainFunc ExplainFunc, +) Checker[T] { + return checker[T]{ + pass: passFunc, + expl: explainFunc, + } +} diff --git a/internal/gen/gen_providers.go b/internal/gen/gen_providers.go index 7cde468..8d22abc 100644 --- a/internal/gen/gen_providers.go +++ b/internal/gen/gen_providers.go @@ -15,14 +15,14 @@ import ( ) // ProvidersMetaData is a representation of the parsed doc for providers files -// in package check. +// in package internal/providers. // It is meant to be used as a data source for template providers.gotmpl. type ProvidersMetaData struct { Vars []metatype.Var } func computeProvidersMetaData() (ProvidersMetaData, error) { - docp, err := newDocPackage("check", isProviderFile) // TODO: package-agnostic + docp, err := newDocPackage("providers", isProviderFile) // TODO: package-agnostic if err != nil { return ProvidersMetaData{}, err } @@ -51,7 +51,7 @@ func computeMetaVar(t *doc.Type) metatype.Var { // the given filter, or the first non-nil error occurring in the process. func newDocPackage(packageName string, filter func(fs.FileInfo) bool) (*doc.Package, error) { fset := token.NewFileSet() - pkgs, err := parser.ParseDir(fset, "./", filter, parser.ParseComments) + pkgs, err := parser.ParseDir(fset, ".", filter, parser.ParseComments) if err != nil { return nil, err } @@ -122,7 +122,10 @@ func isBlacklisted(file fs.FileInfo) bool { // String helpers -const providerSuffix = "CheckerProvider" +const ( + providerSuffix = "CheckerProvider" + packagePrefix = "providers." +) func addProviderSuffix(name string) string { return name + providerSuffix @@ -137,5 +140,5 @@ func structInstanceString(name string, typeparams ...string) string { if len(typeparams) != 0 { tpstr = "[" + strings.Join(typeparams, ", ") + "]" } - return name + tpstr + "{}" + return packagePrefix + name + tpstr + "{}" } diff --git a/internal/gen/templates/providers.gotmpl b/internal/gen/templates/providers.gotmpl index 3d47667..3ea9f69 100644 --- a/internal/gen/templates/providers.gotmpl +++ b/internal/gen/templates/providers.gotmpl @@ -1,5 +1,9 @@ package check +import ( + "github.com/drykit-go/testx/internal/providers" +) + var ( {{- range .Vars -}} {{if ne .Name "Number" -}} @@ -12,16 +16,16 @@ var ( ) // Map provides checks on type map[Key]Val. -func Map[Key comparable, Val any]() MapCheckerProvider[Key, Val] { - return MapCheckerProvider[Key, Val]{} +func Map[Key comparable, Val any]() providers.MapCheckerProvider[Key, Val] { + return providers.MapCheckerProvider[Key, Val]{} } // Slice provides checks on type []Elem. -func Slice[Elem any]() SliceCheckerProvider[Elem] { - return SliceCheckerProvider[Elem]{} +func Slice[Elem any]() providers.SliceCheckerProvider[Elem] { + return providers.SliceCheckerProvider[Elem]{} } // ValueCheckerProvider provides generic checks on any type. -func Value[T any]() ValueCheckerProvider[T] { - return ValueCheckerProvider[T]{} +func Value[T any]() providers.ValueCheckerProvider[T] { + return providers.ValueCheckerProvider[T]{} } diff --git a/check/providers_bool.go b/internal/providers/bool.go similarity index 62% rename from check/providers_bool.go rename to internal/providers/bool.go index 193586e..854ba16 100644 --- a/check/providers_bool.go +++ b/internal/providers/bool.go @@ -1,13 +1,15 @@ -package check +package providers + +import check "github.com/drykit-go/testx/internal/checktypes" // BoolCheckerProvider provides checks on type bool. type BoolCheckerProvider struct{ baseCheckerProvider } // Is checks the gotten bool is equal to the target. -func (p BoolCheckerProvider) Is(tar bool) Checker[bool] { +func (p BoolCheckerProvider) Is(tar bool) check.Checker[bool] { pass := func(got bool) bool { return got == tar } expl := func(label string, got any) string { return p.explain(label, tar, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } diff --git a/check/providers_bool_test.go b/internal/providers/bool_test.go similarity index 66% rename from check/providers_bool_test.go rename to internal/providers/bool_test.go index 08e14ab..e03fbd2 100644 --- a/check/providers_bool_test.go +++ b/internal/providers/bool_test.go @@ -1,27 +1,28 @@ -package check_test +package providers_test import ( "fmt" "testing" - "github.com/drykit-go/testx/check" + "github.com/drykit-go/testx/internal/providers" ) func TestBoolCheckerProvider(t *testing.T) { + checkBool := providers.BoolCheckerProvider{} const b = true t.Run("Is pass", func(t *testing.T) { - c := check.Bool.Is(b) + c := checkBool.Is(b) fmt.Print(c) assertPassChecker(t, "Bool.Is", c, b) - c = check.Bool.Is(!b) + c = checkBool.Is(!b) assertPassChecker(t, "Bool.Is", c, !b) }) t.Run("Is fail", func(t *testing.T) { - c := check.Bool.Is(!b) + c := checkBool.Is(!b) assertFailChecker(t, "Bool.Is", c, b, makeExpl("false", "true")) - c = check.Bool.Is(b) + c = checkBool.Is(b) assertFailChecker(t, "Bool.Is", c, !b, makeExpl("true", "false")) }) } diff --git a/internal/providers/gen.go b/internal/providers/gen.go new file mode 100644 index 0000000..c4d2f9e --- /dev/null +++ b/internal/providers/gen.go @@ -0,0 +1,3 @@ +package providers + +//go:generate ../../bin/gen -kind interfaces -name providers -o ../../check/providers.go diff --git a/check/providers_base.go b/internal/providers/providers_base.go similarity index 92% rename from check/providers_base.go rename to internal/providers/providers_base.go index f766fb7..711bb77 100644 --- a/check/providers_base.go +++ b/internal/providers/providers_base.go @@ -1,4 +1,4 @@ -package check +package providers import ( "encoding/json" @@ -7,6 +7,7 @@ import ( "reflect" "strings" + check "github.com/drykit-go/testx/internal/checktypes" "github.com/drykit-go/testx/internal/fmtexpl" ) @@ -75,9 +76,9 @@ func (p baseCheckerProvider) explainCheck(label, expStr, gotExpl string) string type baseHTTPCheckerProvider struct{ baseCheckerProvider } func (p baseHTTPCheckerProvider) explainContentLengthFunc( - c Checker[int], + c check.Checker[int], got func() int, -) ExplainFunc { +) check.ExplainFunc { return func(label string, _ any) string { return p.explainCheck(label, "content length to pass Checker[int]", @@ -87,9 +88,9 @@ func (p baseHTTPCheckerProvider) explainContentLengthFunc( } func (p baseHTTPCheckerProvider) explainHeaderFunc( - c Checker[http.Header], + c check.Checker[http.Header], got func() http.Header, -) ExplainFunc { +) check.ExplainFunc { return func(label string, _ any) string { return p.explainCheck(label, "header to pass Checker[http.Header]", @@ -99,9 +100,9 @@ func (p baseHTTPCheckerProvider) explainHeaderFunc( } func (p baseHTTPCheckerProvider) explainBodyFunc( - c Checker[[]byte], + c check.Checker[[]byte], got func() []byte, -) ExplainFunc { +) check.ExplainFunc { return func(label string, _ any) string { return p.explainCheck(label, "body to pass Checker[[]byte]", diff --git a/check/providers_base_internal_test.go b/internal/providers/providers_base_internal_test.go similarity index 99% rename from check/providers_base_internal_test.go rename to internal/providers/providers_base_internal_test.go index 73bceef..bd1f3b3 100644 --- a/check/providers_base_internal_test.go +++ b/internal/providers/providers_base_internal_test.go @@ -1,4 +1,4 @@ -package check +package providers import ( "testing" diff --git a/check/providers_bytes.go b/internal/providers/providers_bytes.go similarity index 74% rename from check/providers_bytes.go rename to internal/providers/providers_bytes.go index cbee105..783a725 100644 --- a/check/providers_bytes.go +++ b/internal/providers/providers_bytes.go @@ -1,25 +1,27 @@ -package check +package providers import ( "bytes" "encoding/json" "fmt" + + check "github.com/drykit-go/testx/internal/checktypes" ) // BytesCheckerProvider provides checks on type []byte. type BytesCheckerProvider struct{ baseCheckerProvider } // Is checks the gotten []byte is equal to the target. -func (p BytesCheckerProvider) Is(tar []byte) Checker[[]byte] { +func (p BytesCheckerProvider) Is(tar []byte) check.Checker[[]byte] { pass := func(got []byte) bool { return p.eq(got, tar) } expl := func(label string, got any) string { return p.explain(label, tar, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Not checks the gotten []byte is not equal to the target. -func (p BytesCheckerProvider) Not(values ...[]byte) Checker[[]byte] { +func (p BytesCheckerProvider) Not(values ...[]byte) check.Checker[[]byte] { match := []byte{} pass := func(got []byte) bool { for _, v := range values { @@ -33,12 +35,12 @@ func (p BytesCheckerProvider) Not(values ...[]byte) Checker[[]byte] { expl := func(label string, got any) string { return p.explainNot(label, match, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // SameJSON checks the gotten []byte and the target read as the same // JSON value, ignoring formatting and keys order. -func (p BytesCheckerProvider) SameJSON(tar []byte) Checker[[]byte] { +func (p BytesCheckerProvider) SameJSON(tar []byte) check.Checker[[]byte] { var decGot, decTar any pass := func(got []byte) bool { return p.sameJSON(got, tar, &decGot, &decTar) @@ -49,12 +51,12 @@ func (p BytesCheckerProvider) SameJSON(tar []byte) Checker[[]byte] { fmt.Sprintf("json data: %v", decGot), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Len checks the gotten []byte's length passes the provided // Checker[int]. -func (p BytesCheckerProvider) Len(c Checker[int]) Checker[[]byte] { +func (p BytesCheckerProvider) Len(c check.Checker[int]) check.Checker[[]byte] { pass := func(got []byte) bool { return c.Pass(len(got)) } expl := func(label string, got any) string { return p.explainCheck(label, @@ -62,11 +64,11 @@ func (p BytesCheckerProvider) Len(c Checker[int]) Checker[[]byte] { c.Explain("length", len(got.([]byte))), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Contains checks the gotten []byte contains a specific subslice. -func (p BytesCheckerProvider) Contains(subslice []byte) Checker[[]byte] { +func (p BytesCheckerProvider) Contains(subslice []byte) check.Checker[[]byte] { pass := func(got []byte) bool { return bytes.Contains(got, subslice) } expl := func(label string, got any) string { return p.explain(label, @@ -74,11 +76,11 @@ func (p BytesCheckerProvider) Contains(subslice []byte) Checker[[]byte] { got, ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // NotContains checks the gotten []byte contains a specific subslice. -func (p BytesCheckerProvider) NotContains(subslice []byte) Checker[[]byte] { +func (p BytesCheckerProvider) NotContains(subslice []byte) check.Checker[[]byte] { pass := func(got []byte) bool { return !bytes.Contains(got, subslice) } expl := func(label string, got any) string { return p.explainNot(label, @@ -86,13 +88,13 @@ func (p BytesCheckerProvider) NotContains(subslice []byte) Checker[[]byte] { got, ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // AsMap checks the gotten []byte passes the given mapChecker // once json-unmarshaled to a map[string]any. // It fails if it is not a valid JSON. -func (p BytesCheckerProvider) AsMap(mapChecker Checker[map[string]any]) Checker[[]byte] { +func (p BytesCheckerProvider) AsMap(mapChecker check.Checker[map[string]any]) check.Checker[[]byte] { var m map[string]any var goterr error pass := func(got []byte) bool { @@ -111,12 +113,12 @@ func (p BytesCheckerProvider) AsMap(mapChecker Checker[map[string]any]) Checker[ mapChecker.Explain("json map", m), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // AsString checks the gotten []byte passes the given Checker[string] // once converted to a string. -func (p BytesCheckerProvider) AsString(c Checker[string]) Checker[[]byte] { +func (p BytesCheckerProvider) AsString(c check.Checker[string]) check.Checker[[]byte] { var s string pass := func(got []byte) bool { s = string(got) @@ -128,7 +130,7 @@ func (p BytesCheckerProvider) AsString(c Checker[string]) Checker[[]byte] { c.Explain("converted bytes", s), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } func (BytesCheckerProvider) eq(a, b []byte) bool { diff --git a/check/providers_bytes_test.go b/internal/providers/providers_bytes_test.go similarity index 77% rename from check/providers_bytes_test.go rename to internal/providers/providers_bytes_test.go index 29f5eca..e0da366 100644 --- a/check/providers_bytes_test.go +++ b/internal/providers/providers_bytes_test.go @@ -1,4 +1,4 @@ -package check_test +package providers_test import ( "encoding/json" @@ -6,9 +6,12 @@ import ( "testing" "github.com/drykit-go/testx/check" + "github.com/drykit-go/testx/internal/providers" ) func TestBytesCheckerProvider(t *testing.T) { + checkBytes := providers.BytesCheckerProvider{} + var ( b = []byte(`{"id":42,"name":"Marcel Patulacci"}`) sub = []byte(`"id":42`) @@ -22,12 +25,12 @@ func TestBytesCheckerProvider(t *testing.T) { } t.Run("Is pass", func(t *testing.T) { - c := check.Bytes.Is(b) + c := checkBytes.Is(b) assertPassChecker(t, "Bytes.Is", c, b) }) t.Run("Is fail", func(t *testing.T) { - c := check.Bytes.Is(diff) + c := checkBytes.Is(diff) assertFailChecker(t, "Bytes.Is", c, b, makeExpl( fmt.Sprint(diff), fmt.Sprint(b), @@ -35,12 +38,12 @@ func TestBytesCheckerProvider(t *testing.T) { }) t.Run("Not pass", func(t *testing.T) { - c := check.Bytes.Not(diff, eqJSON) + c := checkBytes.Not(diff, eqJSON) assertPassChecker(t, "Bytes.Not", c, b) }) t.Run("Not fail", func(t *testing.T) { - c := check.Bytes.Not(diff, eqJSON, b) + c := checkBytes.Not(diff, eqJSON, b) assertFailChecker(t, "Bytes.Not", c, b, makeExpl( fmt.Sprintf("not %v", b), fmt.Sprint(b), @@ -48,14 +51,14 @@ func TestBytesCheckerProvider(t *testing.T) { }) t.Run("Len pass", func(t *testing.T) { - c := check.Bytes.Len(check.Int.Is(len(b))) + c := checkBytes.Len(check.Int.Is(len(b))) assertPassChecker(t, "Bytes.Len", c, b) }) t.Run("Len fail", func(t *testing.T) { gotlen := len(b) explen := gotlen + 1 - c := check.Bytes.Len(check.Int.Is(explen)) + c := checkBytes.Len(check.Int.Is(explen)) assertFailChecker(t, "Bytes.Len", c, b, makeExpl( "length to pass Checker[int]", "explanation: length:\n"+makeExpl( @@ -66,14 +69,14 @@ func TestBytesCheckerProvider(t *testing.T) { }) t.Run("SameJSON pass", func(t *testing.T) { - c := check.Bytes.SameJSON(eqJSON) + c := checkBytes.SameJSON(eqJSON) assertPassChecker(t, "Bytes.SameJSON", c, b) - c = check.Bytes.SameJSON(b) + c = checkBytes.SameJSON(b) assertPassChecker(t, "Bytes.SameJSON", c, b) }) t.Run("SameJSON fail", func(t *testing.T) { - c := check.Bytes.SameJSON(diff) + c := checkBytes.SameJSON(diff) assertFailChecker(t, "Bytes.SameJSON", c, b, makeExpl( fmt.Sprintf("json data: %v", mapof(diff)), fmt.Sprintf("json data: %v", mapof(b)), @@ -81,14 +84,14 @@ func TestBytesCheckerProvider(t *testing.T) { }) t.Run("Contains pass", func(t *testing.T) { - c := check.Bytes.Contains(sub) + c := checkBytes.Contains(sub) assertPassChecker(t, "Bytes.Contains", c, b) - c = check.Bytes.Contains(b) + c = checkBytes.Contains(b) assertPassChecker(t, "Bytes.Contains", c, b) }) t.Run("Contains fail", func(t *testing.T) { - c := check.Bytes.Contains(diff) + c := checkBytes.Contains(diff) assertFailChecker(t, "Bytes.Contains", c, b, makeExpl( fmt.Sprintf("to contain subslice %v", diff), @@ -96,7 +99,7 @@ func TestBytesCheckerProvider(t *testing.T) { ), ) - c = check.Bytes.Contains(eqJSON) + c = checkBytes.Contains(eqJSON) assertFailChecker(t, "Bytes.Contains", c, b, makeExpl( fmt.Sprintf("to contain subslice %v", eqJSON), @@ -106,20 +109,20 @@ func TestBytesCheckerProvider(t *testing.T) { }) t.Run("NotContains pass", func(t *testing.T) { - c := check.Bytes.NotContains(diff) + c := checkBytes.NotContains(diff) assertPassChecker(t, "Bytes.NotContains", c, b) - c = check.Bytes.NotContains(eqJSON) + c = checkBytes.NotContains(eqJSON) assertPassChecker(t, "Bytes.NotContains", c, b) }) t.Run("NotContains fail", func(t *testing.T) { - c := check.Bytes.NotContains(sub) + c := checkBytes.NotContains(sub) assertFailChecker(t, "Bytes.NotContains", c, b, makeExpl( fmt.Sprintf("not to contain subslice %v", sub), fmt.Sprint(b), )) - c = check.Bytes.NotContains(b) + c = checkBytes.NotContains(b) assertFailChecker(t, "Bytes.NotContains", c, b, makeExpl( fmt.Sprintf("not to contain subslice %v", b), fmt.Sprint(b), @@ -127,13 +130,13 @@ func TestBytesCheckerProvider(t *testing.T) { }) t.Run("AsMap pass", func(t *testing.T) { - c := check.Bytes.AsMap(check.Map[string, any]().HasKeys("id")) + c := checkBytes.AsMap(check.Map[string, any]().HasKeys("id")) assertPassChecker(t, "Bytes.AsMap", c, b) assertPassChecker(t, "Bytes.AsMap", c, eqJSON) }) t.Run("AsMap fail", func(t *testing.T) { - c := check.Bytes.AsMap(check.Map[string, any]().HasKeys("id", "nomatch")) + c := checkBytes.AsMap(check.Map[string, any]().HasKeys("id", "nomatch")) assertFailChecker(t, "Bytes.AsMap", c, b, makeExpl( "to pass MapChecker", "explanation: json map:\n"+makeExpl( @@ -142,7 +145,7 @@ func TestBytesCheckerProvider(t *testing.T) { ), )) - c = check.Bytes.AsMap(check.Map[string, any]().HasKeys("id")) + c = checkBytes.AsMap(check.Map[string, any]().HasKeys("id")) assertFailChecker(t, "Bytes.AsMap", c, sub, makeExpl( "to pass MapChecker", "error: json: cannot unmarshal string into Go value of type map[string]interface {}", @@ -150,12 +153,12 @@ func TestBytesCheckerProvider(t *testing.T) { }) t.Run("AsString pass", func(t *testing.T) { - c := check.Bytes.AsString(check.String.Is(string(b))) + c := checkBytes.AsString(check.String.Is(string(b))) assertPassChecker(t, "Bytes.AsString", c, b) }) t.Run("AsString fail", func(t *testing.T) { - c := check.Bytes.AsString(check.String.Is(string(diff))) + c := checkBytes.AsString(check.String.Is(string(diff))) assertFailChecker(t, "Bytes.AsString", c, b, makeExpl( "to pass Checker[string]", "explanation: converted bytes:\n"+makeExpl( diff --git a/check/providers_context.go b/internal/providers/providers_context.go similarity index 77% rename from check/providers_context.go rename to internal/providers/providers_context.go index a4ce6d4..26aa5cd 100644 --- a/check/providers_context.go +++ b/internal/providers/providers_context.go @@ -1,17 +1,19 @@ -package check +package providers import ( "context" "fmt" "github.com/drykit-go/cond" + + check "github.com/drykit-go/testx/internal/checktypes" ) // ContextCheckerProvider provides checks on type context.Context. type ContextCheckerProvider struct{ baseCheckerProvider } // Done checks the gotten context is done. -func (p ContextCheckerProvider) Done(expectDone bool) Checker[context.Context] { +func (p ContextCheckerProvider) Done(expectDone bool) check.Checker[context.Context] { var err error done := func() bool { return err != nil } pass := func(got context.Context) bool { @@ -24,11 +26,11 @@ func (p ContextCheckerProvider) Done(expectDone bool) Checker[context.Context] { gotString := cond.String(fmt.Sprint(err), "context not done", done()) return p.explain(label, expString, gotString) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // HasKeys checks the gotten context has the given keys set. -func (p ContextCheckerProvider) HasKeys(keys ...any) Checker[context.Context] { +func (p ContextCheckerProvider) HasKeys(keys ...any) check.Checker[context.Context] { var missing []string pass := func(got context.Context) bool { for _, expk := range keys { @@ -45,7 +47,7 @@ func (p ContextCheckerProvider) HasKeys(keys ...any) Checker[context.Context] { "keys not set", ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Value checks the gotten context's value for the given key passes @@ -54,7 +56,7 @@ func (p ContextCheckerProvider) HasKeys(keys ...any) Checker[context.Context] { // Examples: // Context.Value("userID", Value.Is("abcde")) // Context.Value("userID", Wrap(String.Contains("abc"))) -func (p ContextCheckerProvider) Value(key any, c Checker[any]) Checker[context.Context] { +func (p ContextCheckerProvider) Value(key any, c check.Checker[any]) check.Checker[context.Context] { var v any pass := func(got context.Context) bool { v = got.Value(key) @@ -66,5 +68,5 @@ func (p ContextCheckerProvider) Value(key any, c Checker[any]) Checker[context.C c.Explain("value", v), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } diff --git a/check/providers_context_test.go b/internal/providers/providers_context_test.go similarity index 79% rename from check/providers_context_test.go rename to internal/providers/providers_context_test.go index 06cb64d..c41d97c 100644 --- a/check/providers_context_test.go +++ b/internal/providers/providers_context_test.go @@ -1,13 +1,16 @@ -package check_test +package providers_test import ( "context" "testing" "github.com/drykit-go/testx/check" + "github.com/drykit-go/testx/internal/providers" ) func TestContextCheckerProvider(t *testing.T) { + checkContext := providers.ContextCheckerProvider{} + ctxNotDone := func() (context.Context, context.CancelFunc) { return context.WithCancel(context.Background()) } @@ -21,18 +24,18 @@ func TestContextCheckerProvider(t *testing.T) { } t.Run("Done pass", func(t *testing.T) { - checkDone := check.Context.Done(true) + checkDone := checkContext.Done(true) ctxDone := ctxDone() assertPassChecker(t, "Context.Done", checkDone, ctxDone) - checkNotDone := check.Context.Done(false) + checkNotDone := checkContext.Done(false) ctxNotDone, cancel := ctxNotDone() assertPassChecker(t, "Context.Done", checkNotDone, ctxNotDone) cancel() }) t.Run("Done fail", func(t *testing.T) { - checkDone := check.Context.Done(true) + checkDone := checkContext.Done(true) ctxNotDone, cancel := ctxNotDone() assertFailChecker(t, "Context.Done", checkDone, ctxNotDone, makeExpl( "context to be done", @@ -40,7 +43,7 @@ func TestContextCheckerProvider(t *testing.T) { )) cancel() - checkNotDone := check.Context.Done(false) + checkNotDone := checkContext.Done(false) ctxDone := ctxDone() assertFailChecker(t, "Context.Done", checkNotDone, ctxDone, makeExpl( "context not to be done", @@ -49,13 +52,13 @@ func TestContextCheckerProvider(t *testing.T) { }) t.Run("HasKeys pass", func(t *testing.T) { - c := check.Context.HasKeys("user") + c := checkContext.HasKeys("user") ctx := ctxVal("user", struct{}{}) assertPassChecker(t, "Context.HasKeys", c, ctx) }) t.Run("HasKeys fail", func(t *testing.T) { - c := check.Context.HasKeys("secret", "user", "token") + c := checkContext.HasKeys("secret", "user", "token") ctx := ctxVal("user", struct{}{}) assertFailChecker(t, "Context.HasKeys", c, ctx, makeExpl( "to have keys [secret, token]", @@ -64,13 +67,13 @@ func TestContextCheckerProvider(t *testing.T) { }) t.Run("Value pass", func(t *testing.T) { - c := check.Context.Value("userID", check.Wrap(check.Int.GT(0))) + c := checkContext.Value("userID", check.Wrap[int](check.Int.GT(0))) ctx := ctxVal("userID", 42) assertPassChecker(t, "Context.Value", c, ctx) }) t.Run("Value fail", func(t *testing.T) { - c := check.Context.Value("userID", check.Value[any]().Is(0)) + c := checkContext.Value("userID", check.Value[any]().Is(0)) ctxMissingKey := context.Background() assertFailChecker(t, "Context.Value", c, ctxMissingKey, makeExpl( diff --git a/check/providers_duration.go b/internal/providers/providers_duration.go similarity index 80% rename from check/providers_duration.go rename to internal/providers/providers_duration.go index bc53cd5..0bac3ad 100644 --- a/check/providers_duration.go +++ b/internal/providers/providers_duration.go @@ -1,15 +1,17 @@ -package check +package providers import ( "fmt" "time" + + check "github.com/drykit-go/testx/internal/checktypes" ) // DurationCheckerProvider provides checks on type time.Duration. type DurationCheckerProvider struct{ baseCheckerProvider } // Over checks the gotten time.Duration is over the target duration. -func (p DurationCheckerProvider) Over(tar time.Duration) Checker[time.Duration] { +func (p DurationCheckerProvider) Over(tar time.Duration) check.Checker[time.Duration] { pass := func(got time.Duration) bool { return p.ns(got) > p.ns(tar) } expl := func(label string, got any) string { return p.explain(label, @@ -17,11 +19,11 @@ func (p DurationCheckerProvider) Over(tar time.Duration) Checker[time.Duration] fmt.Sprintf("%vms", p.ms(got.(time.Duration))), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Under checks the gotten time.Duration is under the target duration. -func (p DurationCheckerProvider) Under(tar time.Duration) Checker[time.Duration] { +func (p DurationCheckerProvider) Under(tar time.Duration) check.Checker[time.Duration] { pass := func(got time.Duration) bool { return p.ns(got) < p.ns(tar) } expl := func(label string, got any) string { return p.explain(label, @@ -29,11 +31,11 @@ func (p DurationCheckerProvider) Under(tar time.Duration) Checker[time.Duration] fmt.Sprintf("%vms", p.ms(got.(time.Duration))), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // InRange checks the gotten time.Duration is in range [lo:hi] -func (p DurationCheckerProvider) InRange(lo, hi time.Duration) Checker[time.Duration] { +func (p DurationCheckerProvider) InRange(lo, hi time.Duration) check.Checker[time.Duration] { pass := func(got time.Duration) bool { return p.inrange(got, lo, hi) } expl := func(label string, got any) string { return p.explain(label, @@ -41,11 +43,11 @@ func (p DurationCheckerProvider) InRange(lo, hi time.Duration) Checker[time.Dura fmt.Sprintf("%vms", p.ms(got.(time.Duration))), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // OutRange checks the gotten time.Duration is not in range [lo:hi] -func (p DurationCheckerProvider) OutRange(lo, hi time.Duration) Checker[time.Duration] { +func (p DurationCheckerProvider) OutRange(lo, hi time.Duration) check.Checker[time.Duration] { pass := func(got time.Duration) bool { return !p.inrange(got, lo, hi) } expl := func(label string, got any) string { return p.explain(label, @@ -53,7 +55,7 @@ func (p DurationCheckerProvider) OutRange(lo, hi time.Duration) Checker[time.Dur fmt.Sprintf("%vms", p.ms(got.(time.Duration))), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Helpers diff --git a/check/providers_duration_test.go b/internal/providers/providers_duration_test.go similarity index 76% rename from check/providers_duration_test.go rename to internal/providers/providers_duration_test.go index 641cd78..d609f90 100644 --- a/check/providers_duration_test.go +++ b/internal/providers/providers_duration_test.go @@ -1,14 +1,16 @@ -package check_test +package providers_test import ( "fmt" "testing" "time" - "github.com/drykit-go/testx/check" + "github.com/drykit-go/testx/internal/providers" ) func TestDurationCheckerProvider(t *testing.T) { + checkDuration := providers.DurationCheckerProvider{} + const ( d = 1 * time.Second less = d - time.Millisecond @@ -21,18 +23,18 @@ func TestDurationCheckerProvider(t *testing.T) { } t.Run("Under pass", func(t *testing.T) { - c := check.Duration.Under(more) + c := checkDuration.Under(more) assertPassChecker(t, "Duration.Under", c, d) }) t.Run("Under fail", func(t *testing.T) { - c := check.Duration.Under(less) + c := checkDuration.Under(less) assertFailChecker(t, "Duration.Under", c, d, makeExpl( fmt.Sprintf("under %dms", ms(less)), fmt.Sprintf("%dms", ms(d)), )) - c = check.Duration.Under(d) + c = checkDuration.Under(d) assertFailChecker(t, "Duration.Under", c, d, makeExpl( fmt.Sprintf("under %dms", ms(d)), fmt.Sprintf("%dms", ms(d)), @@ -40,18 +42,18 @@ func TestDurationCheckerProvider(t *testing.T) { }) t.Run("Over pass", func(t *testing.T) { - c := check.Duration.Over(less) + c := checkDuration.Over(less) assertPassChecker(t, "Duration.Over", c, d) }) t.Run("Over fail", func(t *testing.T) { - c := check.Duration.Over(more) + c := checkDuration.Over(more) assertFailChecker(t, "Duration.Over", c, d, makeExpl( fmt.Sprintf("over %dms", ms(more)), fmt.Sprintf("%dms", ms(d)), )) - c = check.Duration.Over(d) + c = checkDuration.Over(d) assertFailChecker(t, "Duration.Over", c, d, makeExpl( fmt.Sprintf("over %dms", ms(d)), fmt.Sprintf("%dms", ms(d)), @@ -59,21 +61,21 @@ func TestDurationCheckerProvider(t *testing.T) { }) t.Run("InRange pass", func(t *testing.T) { - c := check.Duration.InRange(less, more) + c := checkDuration.InRange(less, more) assertPassChecker(t, "Duration.InRange", c, d) - c = check.Duration.InRange(d, d) + c = checkDuration.InRange(d, d) assertPassChecker(t, "Duration.InRange", c, d) }) t.Run("InRange fail", func(t *testing.T) { - c := check.Duration.InRange(more, moremore) + c := checkDuration.InRange(more, moremore) assertFailChecker(t, "Duration.InRange", c, d, makeExpl( fmt.Sprintf("in range [%dms:%dms]", ms(more), ms(moremore)), fmt.Sprintf("%dms", ms(d)), )) - c = check.Duration.InRange(more, less) + c = checkDuration.InRange(more, less) assertFailChecker(t, "Duration.InRange", c, d, makeExpl( fmt.Sprintf("in range [%dms:%dms]", ms(more), ms(less)), fmt.Sprintf("%dms", ms(d)), @@ -81,21 +83,21 @@ func TestDurationCheckerProvider(t *testing.T) { }) t.Run("OutRange pass", func(t *testing.T) { - c := check.Duration.OutRange(more, moremore) + c := checkDuration.OutRange(more, moremore) assertPassChecker(t, "Duration.OutRange", c, d) - c = check.Duration.OutRange(more, less) + c = checkDuration.OutRange(more, less) assertPassChecker(t, "Duration.OutRange", c, d) }) t.Run("OutRange fail", func(t *testing.T) { - c := check.Duration.OutRange(less, more) + c := checkDuration.OutRange(less, more) assertFailChecker(t, "Duration.OutRange", c, d, makeExpl( fmt.Sprintf("not in range [%dms:%dms]", ms(less), ms(more)), fmt.Sprintf("%dms", ms(d)), )) - c = check.Duration.OutRange(d, d) + c = checkDuration.OutRange(d, d) assertFailChecker(t, "Duration.OutRange", c, d, makeExpl( fmt.Sprintf("not in range [%dms:%dms]", ms(d), ms(d)), fmt.Sprintf("%dms", ms(d)), diff --git a/check/providers_httpheader.go b/internal/providers/providers_httpheader.go similarity index 76% rename from check/providers_httpheader.go rename to internal/providers/providers_httpheader.go index f6c148f..76c0510 100644 --- a/check/providers_httpheader.go +++ b/internal/providers/providers_httpheader.go @@ -1,8 +1,10 @@ -package check +package providers import ( "fmt" "net/http" + + check "github.com/drykit-go/testx/internal/checktypes" ) // HTTPHeaderCheckerProvider provides checks on type http.Header. @@ -11,48 +13,48 @@ type HTTPHeaderCheckerProvider struct{ baseCheckerProvider } // HasKey checks the gotten http.Header has a specific key set. // The corresponding value is ignored, meaning an empty value // for that key passes the check. -func (p HTTPHeaderCheckerProvider) HasKey(key string) Checker[http.Header] { +func (p HTTPHeaderCheckerProvider) HasKey(key string) check.Checker[http.Header] { pass := func(got http.Header) bool { return p.hasKey(got, key) } expl := func(label string, got any) string { return p.explain(label, `to have key "`+key+`"`, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // HasNotKey checks the gotten http.Header does not have // a specific key set. -func (p HTTPHeaderCheckerProvider) HasNotKey(key string) Checker[http.Header] { +func (p HTTPHeaderCheckerProvider) HasNotKey(key string) check.Checker[http.Header] { pass := func(got http.Header) bool { return !p.hasKey(got, key) } expl := func(label string, got any) string { return p.explainNot(label, `to have key "`+key+`"`, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // HasValue checks the gotten http.Header has any value equal to val. // It only compares the first result for each key. -func (p HTTPHeaderCheckerProvider) HasValue(val string) Checker[http.Header] { +func (p HTTPHeaderCheckerProvider) HasValue(val string) check.Checker[http.Header] { pass := func(got http.Header) bool { return p.hasValue(got, val) } expl := func(label string, got any) string { return p.explain(label, "to have value "+val, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // HasNotValue checks the gotten http.Header does not have a value equal to val. // It only compares the first result for each key. -func (p HTTPHeaderCheckerProvider) HasNotValue(val string) Checker[http.Header] { +func (p HTTPHeaderCheckerProvider) HasNotValue(val string) check.Checker[http.Header] { pass := func(got http.Header) bool { return !p.hasValue(got, val) } expl := func(label string, got any) string { return p.explainNot(label, "to have value "+val, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // CheckValue checks the gotten http.Header has a value for the matching key // that passes the given Checker[string]. // It only checks the first result for the given key. -func (p HTTPHeaderCheckerProvider) CheckValue(key string, c Checker[string]) Checker[http.Header] { +func (p HTTPHeaderCheckerProvider) CheckValue(key string, c check.Checker[string]) check.Checker[http.Header] { var val string pass := func(got http.Header) bool { v, ok := p.get(got, key) @@ -68,7 +70,7 @@ func (p HTTPHeaderCheckerProvider) CheckValue(key string, c Checker[string]) Che c.Explain(`http.Header["`+key+`"]`, val), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Helpers diff --git a/check/providers_httpheader_test.go b/internal/providers/providers_httpheader_test.go similarity index 72% rename from check/providers_httpheader_test.go rename to internal/providers/providers_httpheader_test.go index 9326213..21483c7 100644 --- a/check/providers_httpheader_test.go +++ b/internal/providers/providers_httpheader_test.go @@ -1,4 +1,4 @@ -package check_test +package providers_test import ( "fmt" @@ -6,9 +6,12 @@ import ( "testing" "github.com/drykit-go/testx/check" + "github.com/drykit-go/testx/internal/providers" ) func TestHTTPHeaderCheckerProvider(t *testing.T) { + checkHTTPHeader := providers.HTTPHeaderCheckerProvider{} + h := http.Header{ "Content-Length": []string{"42"}, "API_KEY": []string{"secret0", "secret1"}, @@ -16,72 +19,72 @@ func TestHTTPHeaderCheckerProvider(t *testing.T) { hstr := fmt.Sprint(h) t.Run("HasKey pass", func(t *testing.T) { - c := check.HTTPHeader.HasKey("API_KEY") + c := checkHTTPHeader.HasKey("API_KEY") assertPassChecker(t, "HTTPHeader.HasKey", c, h) }) t.Run("HasKey fail", func(t *testing.T) { - c := check.HTTPHeader.HasKey("password") + c := checkHTTPHeader.HasKey("password") assertFailChecker(t, "HTTPHeader.HasKey", c, h, makeExpl(`to have key "password"`, hstr), ) }) t.Run("HasNotKey pass", func(t *testing.T) { - c := check.HTTPHeader.HasNotKey("password") + c := checkHTTPHeader.HasNotKey("password") assertPassChecker(t, "HTTPHeader.HasNotKey", c, h) }) t.Run("HasNotKey fail", func(t *testing.T) { - c := check.HTTPHeader.HasNotKey("API_KEY") + c := checkHTTPHeader.HasNotKey("API_KEY") assertFailChecker(t, "HTTPHeader.HasNotKey", c, h, makeExpl(`not to have key "API_KEY"`, hstr), ) }) t.Run("HasValue pass", func(t *testing.T) { - c := check.HTTPHeader.HasValue("42") + c := checkHTTPHeader.HasValue("42") assertPassChecker(t, "HTTPHeader.HasValue", c, h) - c = check.HTTPHeader.HasValue("secret0") + c = checkHTTPHeader.HasValue("secret0") assertPassChecker(t, "HTTPHeader.HasValue", c, h) }) t.Run("HasValue fail", func(t *testing.T) { - c := check.HTTPHeader.HasValue("secret42") + c := checkHTTPHeader.HasValue("secret42") assertFailChecker(t, "HTTPHeader.HasValue", c, h, makeExpl(`to have value secret42`, hstr), ) - c = check.HTTPHeader.HasValue("secret1") + c = checkHTTPHeader.HasValue("secret1") assertFailChecker(t, "HTTPHeader.HasValue", c, h, makeExpl(`to have value secret1`, hstr), ) }) t.Run("HasNotValue pass", func(t *testing.T) { - c := check.HTTPHeader.HasNotValue("secret42") + c := checkHTTPHeader.HasNotValue("secret42") assertPassChecker(t, "HTTPHeader.HasNotValue", c, h) }) t.Run("HasNotValue fail", func(t *testing.T) { - c := check.HTTPHeader.HasNotValue("42") + c := checkHTTPHeader.HasNotValue("42") assertFailChecker(t, "HTTPHeader.HasNotValue", c, h, makeExpl(`not to have value 42`, hstr), ) - c = check.HTTPHeader.HasNotValue("secret0") + c = checkHTTPHeader.HasNotValue("secret0") assertFailChecker(t, "HTTPHeader.HasNotValue", c, h, makeExpl(`not to have value secret0`, hstr), ) }) t.Run("CheckValue pass", func(t *testing.T) { - c := check.HTTPHeader.CheckValue("API_KEY", check.String.Is("secret0")) + c := checkHTTPHeader.CheckValue("API_KEY", check.String.Is("secret0")) assertPassChecker(t, "HTTPHeader.CheckValue", c, h) }) t.Run("CheckValue fail", func(t *testing.T) { - c := check.HTTPHeader.CheckValue("API_KEY", check.String.Not("secret0")) + c := checkHTTPHeader.CheckValue("API_KEY", check.String.Not("secret0")) assertFailChecker(t, "HTTPHeader.CheckValue", c, h, makeExpl( `value for key "API_KEY" to pass Checker[string]`, `explanation: http.Header["API_KEY"]:`+"\n"+makeExpl( diff --git a/check/providers_httprequest.go b/internal/providers/providers_httprequest.go similarity index 72% rename from check/providers_httprequest.go rename to internal/providers/providers_httprequest.go index e3d5b58..ac38cf5 100644 --- a/check/providers_httprequest.go +++ b/internal/providers/providers_httprequest.go @@ -1,9 +1,10 @@ -package check +package providers import ( "context" "net/http" + check "github.com/drykit-go/testx/internal/checktypes" "github.com/drykit-go/testx/internal/ioutil" ) @@ -12,7 +13,7 @@ type HTTPRequestCheckerProvider struct{ baseHTTPCheckerProvider } // ContentLength checks the gotten *http.Request ContentLength passes // the input Checker[int]. -func (p HTTPRequestCheckerProvider) ContentLength(c Checker[int]) Checker[*http.Request] { +func (p HTTPRequestCheckerProvider) ContentLength(c check.Checker[int]) check.Checker[*http.Request] { var clen int pass := func(got *http.Request) bool { clen = int(got.ContentLength) @@ -21,12 +22,12 @@ func (p HTTPRequestCheckerProvider) ContentLength(c Checker[int]) Checker[*http. expl := func(label string, got any) string { return p.explainContentLengthFunc(c, func() int { return clen })(label, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Header checks the gotten *http.Request Header passes // the input Checker[http.Header]. -func (p HTTPRequestCheckerProvider) Header(c Checker[http.Header]) Checker[*http.Request] { +func (p HTTPRequestCheckerProvider) Header(c check.Checker[http.Header]) check.Checker[*http.Request] { var header http.Header pass := func(got *http.Request) bool { header = got.Header @@ -35,13 +36,13 @@ func (p HTTPRequestCheckerProvider) Header(c Checker[http.Header]) Checker[*http expl := func(label string, got any) string { return p.explainHeaderFunc(c, func() http.Header { return header })(label, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Body checks the gotten *http.Request Body passes the input Checker[[]byte]. // It should be used only once on a same *http.Request as it closes its body // after reading it. -func (p HTTPRequestCheckerProvider) Body(c Checker[[]byte]) Checker[*http.Request] { +func (p HTTPRequestCheckerProvider) Body(c check.Checker[[]byte]) check.Checker[*http.Request] { var body []byte pass := func(got *http.Request) bool { body = ioutil.NopRead(&got.Body) @@ -50,12 +51,12 @@ func (p HTTPRequestCheckerProvider) Body(c Checker[[]byte]) Checker[*http.Reques expl := func(label string, got any) string { return p.explainBodyFunc(c, func() []byte { return body })(label, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Context checks the gotten *http.Request Context passes // the input Checker[context.Context]. -func (p HTTPRequestCheckerProvider) Context(c Checker[context.Context]) Checker[*http.Request] { +func (p HTTPRequestCheckerProvider) Context(c check.Checker[context.Context]) check.Checker[*http.Request] { var ctx context.Context pass := func(got *http.Request) bool { ctx = got.Context() @@ -67,5 +68,5 @@ func (p HTTPRequestCheckerProvider) Context(c Checker[context.Context]) Checker[ c.Explain("context", ctx), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } diff --git a/check/providers_httprequest_test.go b/internal/providers/providers_httprequest_test.go similarity index 76% rename from check/providers_httprequest_test.go rename to internal/providers/providers_httprequest_test.go index 2ceac27..584aa80 100644 --- a/check/providers_httprequest_test.go +++ b/internal/providers/providers_httprequest_test.go @@ -1,4 +1,4 @@ -package check_test +package providers_test import ( "bytes" @@ -9,9 +9,12 @@ import ( "testing" "github.com/drykit-go/testx/check" + "github.com/drykit-go/testx/internal/providers" ) func TestHTTPRequestCheckerProvider(t *testing.T) { + checkHTTPRequest := providers.HTTPRequestCheckerProvider{} + newCtx := func(key, val any) context.Context { return context.WithValue(context.Background(), key, val) } @@ -31,12 +34,12 @@ func TestHTTPRequestCheckerProvider(t *testing.T) { ) t.Run("ContentLength pass", func(t *testing.T) { - c := check.HTTPRequest.ContentLength(check.Int.Is(expContentLength)) + c := checkHTTPRequest.ContentLength(check.Int.Is(expContentLength)) assertPassChecker(t, "HTTPRequest.ContentLength", c, newReq()) }) t.Run("ContentLength fail", func(t *testing.T) { - c := check.HTTPRequest.ContentLength(check.Int.Not(expContentLength)) + c := checkHTTPRequest.ContentLength(check.Int.Not(expContentLength)) assertFailChecker(t, "HTTPRequest.ContentLength", c, newReq(), makeExpl( "content length to pass Checker[int]", fmt.Sprintf( @@ -47,12 +50,12 @@ func TestHTTPRequestCheckerProvider(t *testing.T) { }) t.Run("Header pass", func(t *testing.T) { - c := check.HTTPRequest.Header(check.HTTPHeader.HasKey("Content-Type")) + c := checkHTTPRequest.Header(check.HTTPHeader.HasKey("Content-Type")) assertPassChecker(t, "HTTPRequest.Header", c, newReq()) }) t.Run("Header fail", func(t *testing.T) { - c := check.HTTPRequest.Header(check.HTTPHeader.HasNotKey("Content-Type")) + c := checkHTTPRequest.Header(check.HTTPHeader.HasNotKey("Content-Type")) r := newReq() assertFailChecker(t, "HTTPRequest.Header", c, r, makeExpl( "header to pass Checker[http.Header]", @@ -64,12 +67,12 @@ func TestHTTPRequestCheckerProvider(t *testing.T) { }) t.Run("Body pass", func(t *testing.T) { - c := check.HTTPRequest.Body(check.Bytes.Is(expBody)) + c := checkHTTPRequest.Body(check.Bytes.Is(expBody)) assertPassChecker(t, "HTTPRequest.Body", c, newReq()) }) t.Run("Body fail", func(t *testing.T) { - c := check.HTTPRequest.Body(check.Bytes.Not(expBody)) + c := checkHTTPRequest.Body(check.Bytes.Not(expBody)) assertFailChecker(t, "HTTPRequest.Body", c, newReq(), makeExpl( "body to pass Checker[[]byte]", "explanation: bytes:\n"+makeExpl( @@ -80,12 +83,12 @@ func TestHTTPRequestCheckerProvider(t *testing.T) { }) t.Run("Context pass", func(t *testing.T) { - c := check.HTTPRequest.Context(check.Context.Value(expCtxKey, check.Value[any]().Is(expCtxVal))) + c := checkHTTPRequest.Context(check.Context.Value(expCtxKey, check.Value[any]().Is(expCtxVal))) assertPassChecker(t, "HTTPRequest.Context", c, newReq()) }) t.Run("Context fail", func(t *testing.T) { - c := check.HTTPRequest.Context(check.Context.Value(expCtxKey, check.Value[any]().Not(expCtxVal))) + c := checkHTTPRequest.Context(check.Context.Value(expCtxKey, check.Value[any]().Not(expCtxVal))) assertFailChecker(t, "HTTPRequest.Context", c, newReq(), makeExpl( "context to pass Checker[context.Context]", "explanation: context:\n"+makeExpl( diff --git a/check/providers_httpresponse.go b/internal/providers/providers_httpresponse.go similarity index 72% rename from check/providers_httpresponse.go rename to internal/providers/providers_httpresponse.go index 65f39c0..9342767 100644 --- a/check/providers_httpresponse.go +++ b/internal/providers/providers_httpresponse.go @@ -1,8 +1,9 @@ -package check +package providers import ( "net/http" + check "github.com/drykit-go/testx/internal/checktypes" "github.com/drykit-go/testx/internal/ioutil" ) @@ -11,7 +12,7 @@ type HTTPResponseCheckerProvider struct{ baseHTTPCheckerProvider } // StatusCode checks the gotten *http.Response StatusCode passes // the input Checker[int]. -func (p HTTPResponseCheckerProvider) StatusCode(c Checker[int]) Checker[*http.Response] { +func (p HTTPResponseCheckerProvider) StatusCode(c check.Checker[int]) check.Checker[*http.Response] { var code int pass := func(got *http.Response) bool { code = got.StatusCode @@ -23,12 +24,12 @@ func (p HTTPResponseCheckerProvider) StatusCode(c Checker[int]) Checker[*http.Re c.Explain("status code", code), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Status checks the gotten *http.Response Status passes // the input Checker[string]. -func (p HTTPResponseCheckerProvider) Status(c Checker[string]) Checker[*http.Response] { +func (p HTTPResponseCheckerProvider) Status(c check.Checker[string]) check.Checker[*http.Response] { var status string pass := func(got *http.Response) bool { status = got.Status @@ -40,12 +41,12 @@ func (p HTTPResponseCheckerProvider) Status(c Checker[string]) Checker[*http.Res c.Explain("status", status), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // ContentLength checks the gotten *http.Response ContentLength passes // the input Checker[int]. -func (p HTTPResponseCheckerProvider) ContentLength(c Checker[int]) Checker[*http.Response] { +func (p HTTPResponseCheckerProvider) ContentLength(c check.Checker[int]) check.Checker[*http.Response] { var clen int pass := func(got *http.Response) bool { clen = int(got.ContentLength) @@ -54,12 +55,12 @@ func (p HTTPResponseCheckerProvider) ContentLength(c Checker[int]) Checker[*http expl := func(label string, got any) string { return p.explainContentLengthFunc(c, func() int { return clen })(label, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Header checks the gotten *http.Response Header passes // the input Checker[http.Header]. -func (p HTTPResponseCheckerProvider) Header(c Checker[http.Header]) Checker[*http.Response] { +func (p HTTPResponseCheckerProvider) Header(c check.Checker[http.Header]) check.Checker[*http.Response] { var header http.Header pass := func(got *http.Response) bool { header = got.Header @@ -68,13 +69,13 @@ func (p HTTPResponseCheckerProvider) Header(c Checker[http.Header]) Checker[*htt expl := func(label string, got any) string { return p.explainHeaderFunc(c, func() http.Header { return header })(label, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Body checks the gotten *http.Response Body passes the input Checker[[]byte]. // It should be used only once on a same *http.Response as it closes its body // after reading it. -func (p HTTPResponseCheckerProvider) Body(c Checker[[]byte]) Checker[*http.Response] { +func (p HTTPResponseCheckerProvider) Body(c check.Checker[[]byte]) check.Checker[*http.Response] { var body []byte pass := func(got *http.Response) bool { body = ioutil.NopRead(&got.Body) @@ -83,5 +84,5 @@ func (p HTTPResponseCheckerProvider) Body(c Checker[[]byte]) Checker[*http.Respo expl := func(label string, got any) string { return p.explainBodyFunc(c, func() []byte { return body })(label, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } diff --git a/check/providers_httpresponse_test.go b/internal/providers/providers_httpresponse_test.go similarity index 77% rename from check/providers_httpresponse_test.go rename to internal/providers/providers_httpresponse_test.go index 1da2689..bf3d43f 100644 --- a/check/providers_httpresponse_test.go +++ b/internal/providers/providers_httpresponse_test.go @@ -1,4 +1,4 @@ -package check_test +package providers_test import ( "encoding/json" @@ -8,10 +8,13 @@ import ( "testing" "github.com/drykit-go/testx/check" + "github.com/drykit-go/testx/internal/providers" ) //nolint:bodyclose func TestHTTPResponseCheckerProvider(t *testing.T) { + checkHTTPResponse := providers.HTTPResponseCheckerProvider{} + newResp := func() *http.Response { rr := httptest.NewRecorder() rq := httptest.NewRequest("GET", "/", nil) @@ -38,12 +41,12 @@ func TestHTTPResponseCheckerProvider(t *testing.T) { ) t.Run("StatusCode pass", func(t *testing.T) { - c := check.HTTPResponse.StatusCode(check.Int.Is(expStatusCode)) + c := checkHTTPResponse.StatusCode(check.Int.Is(expStatusCode)) assertPassChecker(t, "HTTPResponse.StatusCode", c, newResp()) }) t.Run("StatusCode fail", func(t *testing.T) { - c := check.HTTPResponse.StatusCode(check.Int.Not(expStatusCode)) + c := checkHTTPResponse.StatusCode(check.Int.Not(expStatusCode)) assertFailChecker(t, "HTTPResponse.StatusCode", c, newResp(), makeExpl( "status code to pass Checker[int]", "explanation: status code:\n"+makeExpl( @@ -54,12 +57,12 @@ func TestHTTPResponseCheckerProvider(t *testing.T) { }) t.Run("Status pass", func(t *testing.T) { - c := check.HTTPResponse.Status(check.String.Is(expStatus)) + c := checkHTTPResponse.Status(check.String.Is(expStatus)) assertPassChecker(t, "HTTPResponse.Status", c, newResp()) }) t.Run("Status fail", func(t *testing.T) { - c := check.HTTPResponse.Status(check.String.Not(expStatus)) + c := checkHTTPResponse.Status(check.String.Not(expStatus)) assertFailChecker(t, "HTTPResponse.Status", c, newResp(), makeExpl( "status to pass Checker[string]", "explanation: status:\n"+makeExpl( @@ -70,12 +73,12 @@ func TestHTTPResponseCheckerProvider(t *testing.T) { }) t.Run("ContentLength pass", func(t *testing.T) { - c := check.HTTPResponse.ContentLength(check.Int.Is(expContentLength)) + c := checkHTTPResponse.ContentLength(check.Int.Is(expContentLength)) assertPassChecker(t, "HTTPResponse.ContentLength", c, newResp()) }) t.Run("ContentLength fail", func(t *testing.T) { - c := check.HTTPResponse.ContentLength(check.Int.Not(expContentLength)) + c := checkHTTPResponse.ContentLength(check.Int.Not(expContentLength)) assertFailChecker(t, "HTTPResponse.ContentLength", c, newResp(), makeExpl( "content length to pass Checker[int]", "explanation: content length:\n"+makeExpl( @@ -86,12 +89,12 @@ func TestHTTPResponseCheckerProvider(t *testing.T) { }) t.Run("Header pass", func(t *testing.T) { - c := check.HTTPResponse.Header(check.HTTPHeader.HasKey("Content-Type")) + c := checkHTTPResponse.Header(check.HTTPHeader.HasKey("Content-Type")) assertPassChecker(t, "HTTPResponse.Header", c, newResp()) }) t.Run("Header fail", func(t *testing.T) { - c := check.HTTPResponse.Header(check.HTTPHeader.HasNotKey("Content-Type")) + c := checkHTTPResponse.Header(check.HTTPHeader.HasNotKey("Content-Type")) resp := newResp() assertFailChecker(t, "HTTPResponse.Header", c, resp, makeExpl( "header to pass Checker[http.Header]", @@ -103,12 +106,12 @@ func TestHTTPResponseCheckerProvider(t *testing.T) { }) t.Run("Body pass", func(t *testing.T) { - c := check.HTTPResponse.Body(check.Bytes.Is(expBody)) + c := checkHTTPResponse.Body(check.Bytes.Is(expBody)) assertPassChecker(t, "HTTPResponse.Body", c, newResp()) }) t.Run("Body fail", func(t *testing.T) { - c := check.HTTPResponse.Body(check.Bytes.Not(expBody)) + c := checkHTTPResponse.Body(check.Bytes.Not(expBody)) assertFailChecker(t, "HTTPResponse.Body", c, newResp(), makeExpl( "body to pass Checker[[]byte]", "explanation: bytes:\n"+makeExpl( diff --git a/check/providers_map.go b/internal/providers/providers_map.go similarity index 84% rename from check/providers_map.go rename to internal/providers/providers_map.go index bdb83d7..f39d9e7 100644 --- a/check/providers_map.go +++ b/internal/providers/providers_map.go @@ -1,4 +1,4 @@ -package check +package providers import ( "fmt" @@ -7,6 +7,7 @@ import ( "github.com/drykit-go/cond" + check "github.com/drykit-go/testx/internal/checktypes" "github.com/drykit-go/testx/internal/reflectutil" ) @@ -14,7 +15,7 @@ import ( type MapCheckerProvider[K comparable, V any] struct{ ValueCheckerProvider[map[K]V] } // Len checks the gotten map passes the given Checker[int]. -func (p MapCheckerProvider[K, V]) Len(c Checker[int]) Checker[map[K]V] { +func (p MapCheckerProvider[K, V]) Len(c check.Checker[int]) check.Checker[map[K]V] { var gotlen int pass := func(got map[K]V) bool { reflectutil.MustBeOfKind(got, reflect.Map) @@ -27,11 +28,11 @@ func (p MapCheckerProvider[K, V]) Len(c Checker[int]) Checker[map[K]V] { c.Explain("length", gotlen), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // HasKeys checks the gotten map has the given keys set. -func (p MapCheckerProvider[K, V]) HasKeys(keys ...K) Checker[map[K]V] { +func (p MapCheckerProvider[K, V]) HasKeys(keys ...K) check.Checker[map[K]V] { var missing []string pass := func(got map[K]V) bool { reflectutil.MustBeOfKind(got, reflect.Map) @@ -45,11 +46,11 @@ func (p MapCheckerProvider[K, V]) HasKeys(keys ...K) Checker[map[K]V] { expl := func(label string, got any) string { return p.explain(label, "to have keys "+p.formatList(missing), got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // HasNotKeys checks the gotten map has the given keys set. -func (p MapCheckerProvider[K, V]) HasNotKeys(keys ...K) Checker[map[K]V] { +func (p MapCheckerProvider[K, V]) HasNotKeys(keys ...K) check.Checker[map[K]V] { var badkeys []string pass := func(got map[K]V) bool { reflectutil.MustBeOfKind(got, reflect.Map) @@ -63,11 +64,11 @@ func (p MapCheckerProvider[K, V]) HasNotKeys(keys ...K) Checker[map[K]V] { expl := func(label string, got any) string { return p.explainNot(label, "to have keys "+p.formatList(badkeys), got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // HasValues checks the gotten map has the given values set. -func (p MapCheckerProvider[K, V]) HasValues(values ...V) Checker[map[K]V] { +func (p MapCheckerProvider[K, V]) HasValues(values ...V) check.Checker[map[K]V] { var missing []string pass := func(got map[K]V) bool { reflectutil.MustBeOfKind(got, reflect.Map) @@ -81,11 +82,11 @@ func (p MapCheckerProvider[K, V]) HasValues(values ...V) Checker[map[K]V] { expl := func(label string, got any) string { return p.explain(label, "to have values "+p.formatList(missing), got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // HasNotValues checks the gotten map has not the given values set. -func (p MapCheckerProvider[K, V]) HasNotValues(values ...V) Checker[map[K]V] { +func (p MapCheckerProvider[K, V]) HasNotValues(values ...V) check.Checker[map[K]V] { var badvalues []string pass := func(got map[K]V) bool { reflectutil.MustBeOfKind(got, reflect.Map) @@ -99,13 +100,13 @@ func (p MapCheckerProvider[K, V]) HasNotValues(values ...V) Checker[map[K]V] { expl := func(label string, got any) string { return p.explainNot(label, "to have values "+p.formatList(badvalues), got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // CheckValues checks the gotten map's values corresponding to the given keys // pass the given checker. A key not found is considered a fail. // If len(keys) == 0, the check is made on all map values. -func (p MapCheckerProvider[K, V]) CheckValues(c Checker[V], keys ...K) Checker[map[K]V] { //nolint: gocognit // TODO: refactor +func (p MapCheckerProvider[K, V]) CheckValues(c check.Checker[V], keys ...K) check.Checker[map[K]V] { //nolint: gocognit // TODO: refactor var badentries []string allKeys := len(keys) == 0 pass := func(got map[K]V) bool { @@ -134,7 +135,7 @@ func (p MapCheckerProvider[K, V]) CheckValues(c Checker[V], keys ...K) Checker[m c.Explain("values", p.formatList(badentries)), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // get returns gotmap[key] and a bool representing whether a match is found. diff --git a/check/providers_map_test.go b/internal/providers/providers_map_test.go similarity index 69% rename from check/providers_map_test.go rename to internal/providers/providers_map_test.go index e1a47c2..7b70cd5 100644 --- a/check/providers_map_test.go +++ b/internal/providers/providers_map_test.go @@ -1,13 +1,16 @@ -package check_test +package providers_test import ( "fmt" "testing" "github.com/drykit-go/testx/check" + "github.com/drykit-go/testx/internal/providers" ) func TestMapCheckerProvider(t *testing.T) { + checkMap := providers.MapCheckerProvider[string, any]{} + m := map[string]any{ "name": "Marcel Patulacci", "age": 42, @@ -15,12 +18,12 @@ func TestMapCheckerProvider(t *testing.T) { } t.Run("Len pass", func(t *testing.T) { - c := check.Map[string, any]().Len(check.Int.Is(3)) + c := checkMap.Len(check.Int.Is(3)) assertPassChecker(t, "Map.Len", c, m) }) t.Run("Len fail", func(t *testing.T) { - c := check.Map[string, any]().Len(check.Int.Not(3)) + c := checkMap.Len(check.Int.Not(3)) assertFailChecker(t, "Map.Len", c, m, makeExpl( "length to pass Checker[int]", "explanation: length:\n"+makeExpl("not 3", "3"), @@ -28,12 +31,12 @@ func TestMapCheckerProvider(t *testing.T) { }) t.Run("HasKeys pass", func(t *testing.T) { - c := check.Map[string, any]().HasKeys("name", "friends") + c := checkMap.HasKeys("name", "friends") assertPassChecker(t, "Map.HasKeys", c, m) }) t.Run("HasKeys fail", func(t *testing.T) { - c := check.Map[string, any]().HasKeys("name", "hello", "bad") + c := checkMap.HasKeys("name", "hello", "bad") assertFailChecker(t, "Map.HasKeys", c, m, makeExpl( "to have keys [hello, bad]", fmt.Sprint(m), @@ -41,12 +44,12 @@ func TestMapCheckerProvider(t *testing.T) { }) t.Run("HasNotKeys pass", func(t *testing.T) { - c := check.Map[string, any]().HasNotKeys("hello", "42") + c := checkMap.HasNotKeys("hello", "42") assertPassChecker(t, "Map.HasNotKeys", c, m) }) t.Run("HasNotKeys fail", func(t *testing.T) { - c := check.Map[string, any]().HasNotKeys("name", "hello", "age") + c := checkMap.HasNotKeys("name", "hello", "age") assertFailChecker(t, "Map.HasNotKeys", c, m, makeExpl( "not to have keys [name, age]", fmt.Sprint(m), @@ -54,12 +57,12 @@ func TestMapCheckerProvider(t *testing.T) { }) t.Run("HasValues pass", func(t *testing.T) { - c := check.Map[string, any]().HasValues(42, []string{"Robert Robichet", "Jean-Pierre Avidol"}) + c := checkMap.HasValues(42, []string{"Robert Robichet", "Jean-Pierre Avidol"}) assertPassChecker(t, "Map.HasValues", c, m) }) t.Run("HasValues fail", func(t *testing.T) { - c := check.Map[string, any]().HasValues(42, "hello", true) + c := checkMap.HasValues(42, "hello", true) assertFailChecker(t, "Map.HasValues", c, m, makeExpl( "to have values [hello, true]", fmt.Sprint(m), @@ -67,12 +70,12 @@ func TestMapCheckerProvider(t *testing.T) { }) t.Run("HasNotValues pass", func(t *testing.T) { - c := check.Map[string, any]().HasNotValues("hello", -1) + c := checkMap.HasNotValues("hello", -1) assertPassChecker(t, "Map.HasNotValues", c, m) }) t.Run("HasNotValues fail", func(t *testing.T) { - c := check.Map[string, any]().HasNotValues(42, "hi", []string{"Robert Robichet", "Jean-Pierre Avidol"}) + c := checkMap.HasNotValues(42, "hi", []string{"Robert Robichet", "Jean-Pierre Avidol"}) assertFailChecker(t, "Map.HasNotValues", c, m, makeExpl( "not to have values [42, [Robert Robichet Jean-Pierre Avidol]]", fmt.Sprint(m), @@ -81,21 +84,21 @@ func TestMapCheckerProvider(t *testing.T) { t.Run("CheckValues pass", func(t *testing.T) { // keys subset - c := check.Map[string, any]().CheckValues( - check.Wrap(check.Int.InRange(41, 43)), + c := checkMap.CheckValues( + check.Wrap[int](check.Int.InRange(41, 43)), "age", ) assertPassChecker(t, "Map.CheckValues", c, m) // all keys - c = check.Map[string, any]().CheckValues(check.Value[any]().Not(0)) + c = checkMap.CheckValues(check.Value[any]().Not(0)) assertPassChecker(t, "Map.CheckValues", c, m) }) t.Run("CheckValues fail", func(t *testing.T) { // keys subset - c := check.Map[string, any]().CheckValues( - check.Wrap(check.Int.OutRange(41, 43)), + c := checkMap.CheckValues( + check.Wrap[int](check.Int.OutRange(41, 43)), "age", "badkey", ) assertFailChecker(t, "Map.CheckValues", c, m, makeExpl( @@ -107,7 +110,7 @@ func TestMapCheckerProvider(t *testing.T) { )) // all keys - c = check.Map[string, any]().CheckValues(check.Value[any]().Is("Marcel Patulacci")) + c = checkMap.CheckValues(check.Value[any]().Is("Marcel Patulacci")) assertFailChecker(t, "Map.CheckValues", c, m, makeExpl( "values for all keys to pass Checker[V]", "explanation: values:\n"+makeExpl( diff --git a/check/providers_number.go b/internal/providers/providers_number.go similarity index 72% rename from check/providers_number.go rename to internal/providers/providers_number.go index 8e8ea4f..9244480 100644 --- a/check/providers_number.go +++ b/internal/providers/providers_number.go @@ -1,7 +1,9 @@ -package check +package providers import ( "fmt" + + check "github.com/drykit-go/testx/internal/checktypes" ) // FIXME: The interface for this type can't be properly generated yet. @@ -12,19 +14,19 @@ import ( // manually. // NumberCheckerProvider provides checks on numeric types. -type NumberCheckerProvider[T Numeric] struct{ baseCheckerProvider } +type NumberCheckerProvider[T check.Numeric] struct{ baseCheckerProvider } // Is checks the gotten Number is equal to the target. -func (p NumberCheckerProvider[T]) Is(tar T) Checker[T] { +func (p NumberCheckerProvider[T]) Is(tar T) check.Checker[T] { pass := func(got T) bool { return got == tar } expl := func(label string, got any) string { return p.explain(label, tar, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Not checks the gotten Number is not equal to the target. -func (p NumberCheckerProvider[T]) Not(values ...T) Checker[T] { +func (p NumberCheckerProvider[T]) Not(values ...T) check.Checker[T] { var match T pass := func(got T) bool { for _, v := range values { @@ -38,61 +40,61 @@ func (p NumberCheckerProvider[T]) Not(values ...T) Checker[T] { expl := func(label string, got any) string { return p.explainNot(label, match, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // InRange checks the gotten Number is in the closed interval [lo:hi]. -func (p NumberCheckerProvider[T]) InRange(lo, hi T) Checker[T] { +func (p NumberCheckerProvider[T]) InRange(lo, hi T) check.Checker[T] { pass := func(got T) bool { return p.inrange(got, lo, hi) } expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("in range [%v:%v]", lo, hi), got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // OutRange checks the gotten Number is not in the closed interval [lo:hi]. -func (p NumberCheckerProvider[T]) OutRange(lo, hi T) Checker[T] { +func (p NumberCheckerProvider[T]) OutRange(lo, hi T) check.Checker[T] { pass := func(got T) bool { return !p.inrange(got, lo, hi) } expl := func(label string, got any) string { return p.explainNot(label, fmt.Sprintf("in range [%v:%v]", lo, hi), got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // GT checks the gotten Number is greater than the target. -func (p NumberCheckerProvider[T]) GT(tar T) Checker[T] { +func (p NumberCheckerProvider[T]) GT(tar T) check.Checker[T] { pass := func(got T) bool { return !p.lte(got, tar) } expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("> %v", tar), got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // GTE checks the gotten Number is greater or equal to the target. -func (p NumberCheckerProvider[T]) GTE(tar T) Checker[T] { +func (p NumberCheckerProvider[T]) GTE(tar T) check.Checker[T] { pass := func(got T) bool { return !p.lt(got, tar) } expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf(">= %v", tar), got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // LT checks the gotten Number is lesser than the target. -func (p NumberCheckerProvider[T]) LT(tar T) Checker[T] { +func (p NumberCheckerProvider[T]) LT(tar T) check.Checker[T] { pass := func(got T) bool { return p.lt(got, tar) } expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("< %v", tar), got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // LTE checks the gotten Number is lesser or equal to the target. -func (p NumberCheckerProvider[T]) LTE(tar T) Checker[T] { +func (p NumberCheckerProvider[T]) LTE(tar T) check.Checker[T] { pass := func(got T) bool { return p.lte(got, tar) } expl := func(label string, got any) string { return p.explain(label, fmt.Sprintf("<= %v", tar), got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Helpers diff --git a/check/providers_number_test.go b/internal/providers/providers_number_test.go similarity index 73% rename from check/providers_number_test.go rename to internal/providers/providers_number_test.go index 542de54..351c6a3 100644 --- a/check/providers_number_test.go +++ b/internal/providers/providers_number_test.go @@ -1,13 +1,15 @@ -package check_test +package providers_test import ( "fmt" "testing" - "github.com/drykit-go/testx/check" + "github.com/drykit-go/testx/internal/providers" ) func TestNumberCheckerProvider(t *testing.T) { + checkFloat64 := providers.NumberCheckerProvider[float64]{} + const ( n = 42. inf = n - 1 @@ -20,89 +22,89 @@ func TestNumberCheckerProvider(t *testing.T) { ) t.Run("Is pass", func(t *testing.T) { - c := check.Float64.Is(n) + c := checkFloat64.Is(n) assertPassChecker(t, "Number.Is", c, n) }) t.Run("Is fail", func(t *testing.T) { - c := check.Float64.Is(inf) + c := checkFloat64.Is(inf) assertFailChecker(t, "Number.Is", c, n, makeExpl(infstr, nstr)) }) t.Run("Not pass", func(t *testing.T) { - c := check.Float64.Not(-1, 314, -n) + c := checkFloat64.Not(-1, 314, -n) assertPassChecker(t, "Number.Not", c, n) }) t.Run("Not fail", func(t *testing.T) { - c := check.Float64.Not(-1, 314, n, 1618) + c := checkFloat64.Not(-1, 314, n, 1618) assertFailChecker(t, "Number.Not", c, n, makeExpl("not "+nstr, nstr)) }) t.Run("LT pass", func(t *testing.T) { - c := check.Float64.LT(sup) + c := checkFloat64.LT(sup) assertPassChecker(t, "Number.LT", c, n) }) t.Run("LT fail", func(t *testing.T) { - c := check.Float64.LT(inf) + c := checkFloat64.LT(inf) assertFailChecker(t, "Number.LT", c, n, makeExpl("< "+infstr, nstr)) - c = check.Float64.LT(n) + c = checkFloat64.LT(n) assertFailChecker(t, "Number.LT", c, n, makeExpl("< "+nstr, nstr)) }) t.Run("LTE pass", func(t *testing.T) { - c := check.Float64.LTE(sup) + c := checkFloat64.LTE(sup) assertPassChecker(t, "Number.LTE", c, n) - c = check.Float64.LTE(n) + c = checkFloat64.LTE(n) assertPassChecker(t, "Number.LTE", c, n) }) t.Run("LTE fail", func(t *testing.T) { - c := check.Float64.LTE(inf) + c := checkFloat64.LTE(inf) assertFailChecker(t, "Number.LTE", c, n, makeExpl("<= "+infstr, nstr)) }) t.Run("GT pass", func(t *testing.T) { - c := check.Float64.GT(inf) + c := checkFloat64.GT(inf) assertPassChecker(t, "Number.GT", c, n) }) t.Run("GT fail", func(t *testing.T) { - c := check.Float64.GT(sup) + c := checkFloat64.GT(sup) assertFailChecker(t, "Number.GT", c, n, makeExpl("> "+supstr, nstr)) - c = check.Float64.GT(n) + c = checkFloat64.GT(n) assertFailChecker(t, "Number.GT", c, n, makeExpl("> "+nstr, nstr)) }) t.Run("GTE pass", func(t *testing.T) { - c := check.Float64.GTE(inf) + c := checkFloat64.GTE(inf) assertPassChecker(t, "Number.GTE", c, n) - c = check.Float64.GTE(n) + c = checkFloat64.GTE(n) assertPassChecker(t, "Number.GTE", c, n) }) t.Run("GTE fail", func(t *testing.T) { - c := check.Float64.GTE(sup) + c := checkFloat64.GTE(sup) assertFailChecker(t, "Number.GTE", c, n, makeExpl(">= "+supstr, nstr)) }) t.Run("InRange pass", func(t *testing.T) { - c := check.Float64.InRange(inf, sup) + c := checkFloat64.InRange(inf, sup) assertPassChecker(t, "Number.InRange", c, n) - c = check.Float64.InRange(n, n) + c = checkFloat64.InRange(n, n) assertPassChecker(t, "Number.InRange", c, n) }) t.Run("InRange fail", func(t *testing.T) { - c := check.Float64.InRange(sup, sup+1) + c := checkFloat64.InRange(sup, sup+1) assertFailChecker(t, "Number.InRange", c, n, makeExpl( fmt.Sprintf("in range [%v:%v]", sup, sup+1), nstr, )) - c = check.Float64.InRange(sup, inf) + c = checkFloat64.InRange(sup, inf) assertFailChecker(t, "Number.InRange", c, n, makeExpl( fmt.Sprintf("in range [%v:%v]", sup, inf), nstr, @@ -110,21 +112,21 @@ func TestNumberCheckerProvider(t *testing.T) { }) t.Run("OutRange pass", func(t *testing.T) { - c := check.Float64.OutRange(sup, sup+1) + c := checkFloat64.OutRange(sup, sup+1) assertPassChecker(t, "Number.OutRange", c, n) - c = check.Float64.OutRange(sup, inf) + c = checkFloat64.OutRange(sup, inf) assertPassChecker(t, "Number.OutRange", c, n) }) t.Run("OutRange fail", func(t *testing.T) { - c := check.Float64.OutRange(inf, sup) + c := checkFloat64.OutRange(inf, sup) assertFailChecker(t, "Number.OutRange", c, n, makeExpl( fmt.Sprintf("not in range [%v:%v]", inf, sup), nstr, )) - c = check.Float64.OutRange(n, n) + c = checkFloat64.OutRange(n, n) assertFailChecker(t, "Number.OutRange", c, n, makeExpl( fmt.Sprintf("not in range [%v:%v]", n, n), nstr, diff --git a/check/providers_slice.go b/internal/providers/providers_slice.go similarity index 87% rename from check/providers_slice.go rename to internal/providers/providers_slice.go index e7a4c10..3d56b47 100644 --- a/check/providers_slice.go +++ b/internal/providers/providers_slice.go @@ -1,9 +1,10 @@ -package check +package providers import ( "fmt" "reflect" + check "github.com/drykit-go/testx/internal/checktypes" "github.com/drykit-go/testx/internal/reflectutil" ) @@ -11,7 +12,7 @@ import ( type SliceCheckerProvider[Elem any] struct{ ValueCheckerProvider[[]Elem] } // Len checks the length of the gotten slice passes the given Checker[int]. -func (p SliceCheckerProvider[Elem]) Len(c Checker[int]) Checker[[]Elem] { +func (p SliceCheckerProvider[Elem]) Len(c check.Checker[int]) check.Checker[[]Elem] { var gotlen int pass := func(got []Elem) bool { reflectutil.MustBeOfKind(got, reflect.Slice) @@ -24,11 +25,11 @@ func (p SliceCheckerProvider[Elem]) Len(c Checker[int]) Checker[[]Elem] { c.Explain("length", gotlen), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Cap checks the capacity of the gotten slice passes the given Checker[int]. -func (p SliceCheckerProvider[Elem]) Cap(c Checker[int]) Checker[[]Elem] { +func (p SliceCheckerProvider[Elem]) Cap(c check.Checker[int]) check.Checker[[]Elem] { var gotcap int pass := func(got []Elem) bool { reflectutil.MustBeOfKind(got, reflect.Slice) @@ -41,11 +42,11 @@ func (p SliceCheckerProvider[Elem]) Cap(c Checker[int]) Checker[[]Elem] { c.Explain("capacity", gotcap), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // HasValues checks the gotten slice has the given values set. -func (p SliceCheckerProvider[Elem]) HasValues(values ...Elem) Checker[[]Elem] { +func (p SliceCheckerProvider[Elem]) HasValues(values ...Elem) check.Checker[[]Elem] { var missing []string pass := func(got []Elem) bool { reflectutil.MustBeOfKind(got, reflect.Slice) @@ -62,11 +63,11 @@ func (p SliceCheckerProvider[Elem]) HasValues(values ...Elem) Checker[[]Elem] { got, ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // HasNotValues checks the gotten slice has not the given values set. -func (p SliceCheckerProvider[Elem]) HasNotValues(values ...Elem) Checker[[]Elem] { +func (p SliceCheckerProvider[Elem]) HasNotValues(values ...Elem) check.Checker[[]Elem] { var badvalues []string pass := func(got []Elem) bool { reflectutil.MustBeOfKind(got, reflect.Slice) @@ -83,16 +84,16 @@ func (p SliceCheckerProvider[Elem]) HasNotValues(values ...Elem) Checker[[]Elem] got, ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // CheckValues checks the values of the gotten slice passes // the given Checker[Elem]. // If a filterFunc is provided, the values not passing it are ignored. func (p SliceCheckerProvider[Elem]) CheckValues( - c Checker[Elem], + c check.Checker[Elem], filters ...func(i int, v Elem) bool, -) Checker[[]Elem] { +) check.Checker[[]Elem] { var badvalues []string pass := func(got []Elem) bool { reflectutil.MustBeOfKind(got, reflect.Slice) @@ -109,7 +110,7 @@ func (p SliceCheckerProvider[Elem]) CheckValues( c.Explain("values", p.formatList(badvalues)), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Helpers diff --git a/check/providers_slice_test.go b/internal/providers/providers_slice_test.go similarity index 72% rename from check/providers_slice_test.go rename to internal/providers/providers_slice_test.go index 26f3ec4..d3efab4 100644 --- a/check/providers_slice_test.go +++ b/internal/providers/providers_slice_test.go @@ -1,22 +1,25 @@ -package check_test +package providers_test import ( "fmt" "testing" "github.com/drykit-go/testx/check" + "github.com/drykit-go/testx/internal/providers" ) func TestSliceCheckerProvider(t *testing.T) { + checkSlice := providers.SliceCheckerProvider[any]{} + s := []any{"hello", 42, "Marcel Patulacci", []float32{3.14}} t.Run("Len pass", func(t *testing.T) { - c := check.Slice[any]().Len(check.Int.Is(4)) + c := checkSlice.Len(check.Int.Is(4)) assertPassChecker(t, "Slice.Len", c, s) }) t.Run("Len fail", func(t *testing.T) { - c := check.Slice[any]().Len(check.Int.Not(4)) + c := checkSlice.Len(check.Int.Not(4)) assertFailChecker(t, "Slice.Len", c, s, makeExpl( "length to pass Checker[int]", "explanation: length:\n"+makeExpl("not 4", "4"), @@ -24,12 +27,12 @@ func TestSliceCheckerProvider(t *testing.T) { }) t.Run("Cap pass", func(t *testing.T) { - c := check.Slice[any]().Cap(check.Int.Is(4)) + c := checkSlice.Cap(check.Int.Is(4)) assertPassChecker(t, "Slice.Cap", c, s) }) t.Run("Cap fail", func(t *testing.T) { - c := check.Slice[any]().Cap(check.Int.Not(4)) + c := checkSlice.Cap(check.Int.Not(4)) assertFailChecker(t, "Slice.Cap", c, s, makeExpl( "capacity to pass Checker[int]", "explanation: capacity:\n"+makeExpl("not 4", "4"), @@ -37,12 +40,12 @@ func TestSliceCheckerProvider(t *testing.T) { }) t.Run("HasValues pass", func(t *testing.T) { - c := check.Slice[any]().HasValues("hello", 42, []float32{3.14}) + c := checkSlice.HasValues("hello", 42, []float32{3.14}) assertPassChecker(t, "Slice.HasValues", c, s) }) t.Run("HasValues fail", func(t *testing.T) { - c := check.Slice[any]().HasValues([]float64{3.14}) + c := checkSlice.HasValues([]float64{3.14}) assertFailChecker(t, "Slice.HasValues", c, s, makeExpl( "to have values [[3.14]]", fmt.Sprint(s), @@ -50,12 +53,12 @@ func TestSliceCheckerProvider(t *testing.T) { }) t.Run("HasNotValues pass", func(t *testing.T) { - c := check.Slice[any]().HasNotValues("hi", -1, []float64{3.14}) + c := checkSlice.HasNotValues("hi", -1, []float64{3.14}) assertPassChecker(t, "Slice.HasNotValues", c, s) }) t.Run("HasNotValues fail", func(t *testing.T) { - c := check.Slice[any]().HasNotValues("hi", -1, []float32{3.14}) + c := checkSlice.HasNotValues("hi", -1, []float32{3.14}) assertFailChecker(t, "Slice.HasNotValues", c, s, makeExpl( "not to have values [[3.14]]", fmt.Sprint(s), @@ -63,16 +66,16 @@ func TestSliceCheckerProvider(t *testing.T) { }) t.Run("CheckValues pass", func(t *testing.T) { - c := check.Slice[any]().CheckValues( - check.Wrap(check.Int.InRange(41, 43)), + c := checkSlice.CheckValues( + check.Wrap[int](check.Int.InRange(41, 43)), func(_ int, v any) bool { _, ok := v.(int); return ok }, ) assertPassChecker(t, "Slice.CheckValues", c, s) }) t.Run("CheckValues fail", func(t *testing.T) { - c := check.Slice[any]().CheckValues( - check.Wrap(check.Int.OutRange(41, 43)), + c := checkSlice.CheckValues( + check.Wrap[int](check.Int.OutRange(41, 43)), func(_ int, v any) bool { _, ok := v.(int); return ok }, ) assertFailChecker(t, "Slice.CheckValues", c, s, makeExpl( diff --git a/check/providers_string.go b/internal/providers/providers_string.go similarity index 69% rename from check/providers_string.go rename to internal/providers/providers_string.go index 5e4748f..8412b8b 100644 --- a/check/providers_string.go +++ b/internal/providers/providers_string.go @@ -1,25 +1,27 @@ -package check +package providers import ( "fmt" "regexp" "strings" + + check "github.com/drykit-go/testx/internal/checktypes" ) // StringCheckerProvider provides checks on type string. type StringCheckerProvider struct{ baseCheckerProvider } // Is checks the gotten string is equal to the target. -func (p StringCheckerProvider) Is(tar string) Checker[string] { +func (p StringCheckerProvider) Is(tar string) check.Checker[string] { pass := func(got string) bool { return got == tar } expl := func(label string, got any) string { return p.explain(label, tar, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Not checks the gotten string is not equal to the target. -func (p StringCheckerProvider) Not(values ...string) Checker[string] { +func (p StringCheckerProvider) Not(values ...string) check.Checker[string] { var match string pass := func(got string) bool { for _, v := range values { @@ -33,11 +35,11 @@ func (p StringCheckerProvider) Not(values ...string) Checker[string] { expl := func(label string, got any) string { return p.explainNot(label, match, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Len checks the gotten string's length passes the given Checker[int]. -func (p StringCheckerProvider) Len(c Checker[int]) Checker[string] { +func (p StringCheckerProvider) Len(c check.Checker[int]) check.Checker[string] { pass := func(got string) bool { return c.Pass(len(got)) } expl := func(label string, got any) string { return p.explainCheck(label, @@ -45,11 +47,11 @@ func (p StringCheckerProvider) Len(c Checker[int]) Checker[string] { c.Explain("length", len(got.(string))), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Match checks the gotten string matches the given regexp. -func (p StringCheckerProvider) Match(rgx *regexp.Regexp) Checker[string] { +func (p StringCheckerProvider) Match(rgx *regexp.Regexp) check.Checker[string] { pass := func(got string) bool { return rgx.MatchString(got) } expl := func(label string, got any) string { return p.explain(label, @@ -57,11 +59,11 @@ func (p StringCheckerProvider) Match(rgx *regexp.Regexp) Checker[string] { got, ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // NotMatch checks the gotten string do not match the given regexp. -func (p StringCheckerProvider) NotMatch(rgx *regexp.Regexp) Checker[string] { +func (p StringCheckerProvider) NotMatch(rgx *regexp.Regexp) check.Checker[string] { pass := func(got string) bool { return !rgx.MatchString(got) } expl := func(label string, got any) string { return p.explainNot(label, @@ -69,24 +71,24 @@ func (p StringCheckerProvider) NotMatch(rgx *regexp.Regexp) Checker[string] { got, ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Contains checks the gotten string contains the target substring. -func (p StringCheckerProvider) Contains(sub string) Checker[string] { +func (p StringCheckerProvider) Contains(sub string) check.Checker[string] { pass := func(got string) bool { return strings.Contains(got, sub) } expl := func(label string, got any) string { return p.explain(label, "to contain substring "+sub, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // NotContains checks the gotten string do not contain the target // substring. -func (p StringCheckerProvider) NotContains(sub string) Checker[string] { +func (p StringCheckerProvider) NotContains(sub string) check.Checker[string] { pass := func(got string) bool { return !strings.Contains(got, sub) } expl := func(label string, got any) string { return p.explainNot(label, "to contain substring "+sub, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } diff --git a/check/providers_string_test.go b/internal/providers/providers_string_test.go similarity index 74% rename from check/providers_string_test.go rename to internal/providers/providers_string_test.go index b2c42a2..5097d80 100644 --- a/check/providers_string_test.go +++ b/internal/providers/providers_string_test.go @@ -1,4 +1,4 @@ -package check_test +package providers_test import ( "fmt" @@ -6,9 +6,12 @@ import ( "testing" "github.com/drykit-go/testx/check" + "github.com/drykit-go/testx/internal/providers" ) func TestStringCheckerProvider(t *testing.T) { + checkString := providers.StringCheckerProvider{} + const ( s = "sator arepo tenet opera rotas" sub = "tenet" @@ -16,34 +19,34 @@ func TestStringCheckerProvider(t *testing.T) { ) t.Run("Is pass", func(t *testing.T) { - c := check.String.Is(s) + c := checkString.Is(s) assertPassChecker(t, "String.Is", c, s) }) t.Run("Is fail", func(t *testing.T) { - c := check.String.Is(exp) + c := checkString.Is(exp) assertFailChecker(t, "String.Is", c, s, makeExpl(exp, s)) }) t.Run("Not pass", func(t *testing.T) { - c := check.String.Not("hello", sub, exp) + c := checkString.Not("hello", sub, exp) assertPassChecker(t, "String.Not", c, s) }) t.Run("Not fail", func(t *testing.T) { - c := check.String.Not("hello", sub, s, exp) + c := checkString.Not("hello", sub, s, exp) assertFailChecker(t, "String.Not", c, s, makeExpl("not "+s, s)) }) t.Run("Len pass", func(t *testing.T) { - c := check.String.Len(check.Int.Is(len(s))) + c := checkString.Len(check.Int.Is(len(s))) assertPassChecker(t, "String.Len", c, s) }) t.Run("Len fail", func(t *testing.T) { gotlen := len(s) explen := gotlen + 1 - c := check.String.Len(check.Int.Is(explen)) + c := checkString.Len(check.Int.Is(explen)) assertFailChecker(t, "String.Len", c, s, makeExpl( "length to pass Checker[int]", "explanation: length:\n"+makeExpl( @@ -54,26 +57,26 @@ func TestStringCheckerProvider(t *testing.T) { }) t.Run("Match pass", func(t *testing.T) { - c := check.String.Match(regexp.MustCompile(`(?i)\sTENET\s`)) + c := checkString.Match(regexp.MustCompile(`(?i)\sTENET\s`)) assertPassChecker(t, "String.Match", c, s) }) t.Run("Match fail", func(t *testing.T) { r := regexp.MustCompile(`\sTENET\s`) - c := check.String.Match(r) + c := checkString.Match(r) assertFailChecker(t, "String.Match", c, s, makeExpl("to match regexp "+r.String(), s), ) }) t.Run("NotMatch pass", func(t *testing.T) { - c := check.String.NotMatch(regexp.MustCompile(`\sTENET\s`)) + c := checkString.NotMatch(regexp.MustCompile(`\sTENET\s`)) assertPassChecker(t, "String.NotMatch", c, s) }) t.Run("NotMatch fail", func(t *testing.T) { r := regexp.MustCompile(`(?i)\sTENET\s`) - c := check.String.NotMatch(r) + c := checkString.NotMatch(r) assertFailChecker(t, "String.NotMatch", c, s, makeExpl( "not to match regexp "+r.String(), s, @@ -81,14 +84,14 @@ func TestStringCheckerProvider(t *testing.T) { }) t.Run("Contains pass", func(t *testing.T) { - c := check.String.Contains(sub) + c := checkString.Contains(sub) assertPassChecker(t, "String.Contains", c, s) - c = check.String.Contains(s) + c = checkString.Contains(s) assertPassChecker(t, "String.Contains", c, s) }) t.Run("Contains fail", func(t *testing.T) { - c := check.String.Contains(exp) + c := checkString.Contains(exp) assertFailChecker(t, "String.Contains", c, s, makeExpl( "to contain substring "+exp, s, @@ -96,17 +99,17 @@ func TestStringCheckerProvider(t *testing.T) { }) t.Run("NotContains pass", func(t *testing.T) { - c := check.String.NotContains(exp) + c := checkString.NotContains(exp) assertPassChecker(t, "String.NotContains", c, s) }) t.Run("NotContains fail", func(t *testing.T) { - c := check.String.NotContains(sub) + c := checkString.NotContains(sub) assertFailChecker(t, "String.NotContains", c, s, makeExpl( "not to contain substring "+sub, s, )) - c = check.String.NotContains(s) + c = checkString.NotContains(s) assertFailChecker(t, "String.NotContains", c, s, makeExpl( "not to contain substring "+s, s, diff --git a/check/providers_struct.go b/internal/providers/providers_struct.go similarity index 88% rename from check/providers_struct.go rename to internal/providers/providers_struct.go index 1dfc289..da5c6f5 100644 --- a/check/providers_struct.go +++ b/internal/providers/providers_struct.go @@ -1,10 +1,11 @@ -package check +package providers import ( "fmt" "reflect" "strings" + check "github.com/drykit-go/testx/internal/checktypes" "github.com/drykit-go/testx/internal/reflectutil" ) @@ -14,7 +15,7 @@ type StructCheckerProvider struct{ ValueCheckerProvider[any] } // FieldsEqual checks all given fields equal the exp value. // It panics if the fields do not exist or are not exported, // or if the tested value is not a struct. -func (p StructCheckerProvider) FieldsEqual(exp any, fields []string) Checker[any] { +func (p StructCheckerProvider) FieldsEqual(exp any, fields []string) check.Checker[any] { var bads []string pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Struct) @@ -29,13 +30,13 @@ func (p StructCheckerProvider) FieldsEqual(exp any, fields []string) Checker[any strings.Join(bads, ", "), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // CheckFields checks all given fields pass the Checker[any]. // It panics if the fields do not exist or are not exported, // or if the tested value is not a struct. -func (p StructCheckerProvider) CheckFields(c Checker[any], fields []string) Checker[any] { +func (p StructCheckerProvider) CheckFields(c check.Checker[any], fields []string) check.Checker[any] { var bads []string pass := func(got any) bool { reflectutil.MustBeOfKind(got, reflect.Struct) @@ -50,7 +51,7 @@ func (p StructCheckerProvider) CheckFields(c Checker[any], fields []string) Chec c.Explain("fields", strings.Join(bads, ", ")), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } func (p StructCheckerProvider) badFields( diff --git a/check/providers_struct_test.go b/internal/providers/providers_struct_test.go similarity index 73% rename from check/providers_struct_test.go rename to internal/providers/providers_struct_test.go index 1288b56..ac079cf 100644 --- a/check/providers_struct_test.go +++ b/internal/providers/providers_struct_test.go @@ -1,10 +1,11 @@ -package check_test +package providers_test import ( "fmt" "testing" "github.com/drykit-go/testx/check" + "github.com/drykit-go/testx/internal/providers" ) type structTest struct { @@ -12,6 +13,8 @@ type structTest struct { } func TestStructCheckerProvider(t *testing.T) { + checkStruct := providers.StructCheckerProvider{} + const ( vAB = 10 vXY = 20 @@ -23,12 +26,12 @@ func TestStructCheckerProvider(t *testing.T) { } t.Run("FieldsEqual pass", func(t *testing.T) { - c := check.Struct.FieldsEqual(vAB, []string{"A", "B"}) + c := checkStruct.FieldsEqual(vAB, []string{"A", "B"}) assertPassChecker(t, "Struct.FieldsEqual", c, itf(s)) }) t.Run("FieldsEqual fail", func(t *testing.T) { - c := check.Struct.FieldsEqual(vAB, []string{"A", "B", "X", "Y"}) + c := checkStruct.FieldsEqual(vAB, []string{"A", "B", "X", "Y"}) assertFailChecker(t, "Struct.FieldsEqual", c, itf(s), makeExpl( fmt.Sprintf("fields [.A, .B, .X, .Y] to equal %v", vAB), fmt.Sprintf(".X=%v, .Y=%v", vXY, vXY), @@ -36,16 +39,16 @@ func TestStructCheckerProvider(t *testing.T) { }) t.Run("CheckFields pass", func(t *testing.T) { - c := check.Struct.CheckFields( - check.Wrap(check.Int.LT(vAB+1)), + c := checkStruct.CheckFields( + check.Wrap[int](check.Int.LT(vAB+1)), []string{"A", "B"}, ) assertPassChecker(t, "Struct.CheckFields", c, itf(s)) }) t.Run("CheckFields fail", func(t *testing.T) { - c := check.Struct.CheckFields( - check.Wrap(check.Int.LT(vAB+1)), + c := checkStruct.CheckFields( + check.Wrap[int](check.Int.LT(vAB+1)), []string{"A", "B", "X", "Y"}, ) assertFailChecker(t, "Struct.CheckFields", c, itf(s), makeExpl( diff --git a/check/providers_test.go b/internal/providers/providers_test.go similarity index 93% rename from check/providers_test.go rename to internal/providers/providers_test.go index 9e11b82..81d40a2 100644 --- a/check/providers_test.go +++ b/internal/providers/providers_test.go @@ -1,10 +1,10 @@ -package check_test +package providers_test import ( "fmt" "testing" - "github.com/drykit-go/testx/check" + check "github.com/drykit-go/testx/internal/checktypes" ) func assertPassChecker[T any]( diff --git a/check/providers_value.go b/internal/providers/providers_value.go similarity index 73% rename from check/providers_value.go rename to internal/providers/providers_value.go index 7952b71..f08b813 100644 --- a/check/providers_value.go +++ b/internal/providers/providers_value.go @@ -1,8 +1,9 @@ -package check +package providers import ( "fmt" + check "github.com/drykit-go/testx/internal/checktypes" "github.com/drykit-go/testx/internal/reflectutil" ) @@ -12,24 +13,24 @@ type ValueCheckerProvider[T any] struct{ baseCheckerProvider } // Custom checks the gotten value passes the given PassFunc. // The description should give information about the expected value, // as it outputs in format "exp " in case of failure. -func (p ValueCheckerProvider[T]) Custom(desc string, f PassFunc[T]) Checker[T] { +func (p ValueCheckerProvider[T]) Custom(desc string, f check.PassFunc[T]) check.Checker[T] { expl := func(label string, got any) string { return p.explain(label, desc, got) } - return NewChecker(f, expl) + return check.NewChecker(f, expl) } // Is checks the gotten value is equal to the target. -func (p ValueCheckerProvider[T]) Is(tar T) Checker[T] { +func (p ValueCheckerProvider[T]) Is(tar T) check.Checker[T] { pass := func(got T) bool { return p.deq(got, tar) } expl := func(label string, got any) string { return p.explain(label, tar, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // Not checks the gotten value is not equal to the target. -func (p ValueCheckerProvider[T]) Not(values ...T) Checker[T] { +func (p ValueCheckerProvider[T]) Not(values ...T) check.Checker[T] { var match T pass := func(got T) bool { for _, v := range values { @@ -43,35 +44,35 @@ func (p ValueCheckerProvider[T]) Not(values ...T) Checker[T] { expl := func(label string, got any) string { return p.explainNot(label, match, got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // IsZero checks the gotten value is a zero value, indicating it might not // have been initialized. -func (p ValueCheckerProvider[T]) IsZero() Checker[T] { +func (p ValueCheckerProvider[T]) IsZero() check.Checker[T] { pass := func(got T) bool { return reflectutil.IsZero(got) } expl := func(label string, got any) string { return p.explain(label, "to be a zero value", got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // NotZero checks the gotten struct contains at least 1 non-zero value, // meaning it has been initialized. -func (p ValueCheckerProvider[T]) NotZero() Checker[T] { +func (p ValueCheckerProvider[T]) NotZero() check.Checker[T] { pass := func(got T) bool { return !reflectutil.IsZero(got) } expl := func(label string, got any) string { return p.explainNot(label, "to be a zero value", got) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } // SameJSON checks the gotten value and the target value // produce the same JSON, ignoring formatting and keys order. // It panics if any error occurs in the marshaling process. -func (p ValueCheckerProvider[T]) SameJSON(tar any) Checker[T] { +func (p ValueCheckerProvider[T]) SameJSON(tar any) check.Checker[T] { var gotDec T var tarDec any pass := func(got T) bool { @@ -83,5 +84,5 @@ func (p ValueCheckerProvider[T]) SameJSON(tar any) Checker[T] { fmt.Sprintf("json data: %v", gotDec), ) } - return NewChecker(pass, expl) + return check.NewChecker(pass, expl) } diff --git a/check/providers_value_test.go b/internal/providers/providers_value_test.go similarity index 80% rename from check/providers_value_test.go rename to internal/providers/providers_value_test.go index 26b7a35..d5f451c 100644 --- a/check/providers_value_test.go +++ b/internal/providers/providers_value_test.go @@ -1,13 +1,15 @@ -package check_test +package providers_test import ( "fmt" "testing" - "github.com/drykit-go/testx/check" + "github.com/drykit-go/testx/internal/providers" ) func TestValueCheckerProvider(t *testing.T) { + checkValue := providers.ValueCheckerProvider[any]{} + type Thing struct { Name string } @@ -31,34 +33,34 @@ func TestValueCheckerProvider(t *testing.T) { } t.Run("Is pass", func(t *testing.T) { - c := check.Value[any]().Is(vsame) + c := checkValue.Is(vsame) assertPassChecker(t, "Value.Is", c, itf(vorig)) }) t.Run("Is fail", func(t *testing.T) { - c := check.Value[any]().Is(badval) + c := checkValue.Is(badval) assertFailChecker(t, "Value.Is", c, itf(vorig), makeExpl("{hello}", "{hi}")) }) t.Run("Not pass", func(t *testing.T) { - c := check.Value[any]().Not(badval, badtyp) + c := checkValue.Not(badval, badtyp) assertPassChecker(t, "Value.Not", c, itf(vorig)) }) t.Run("Not fail", func(t *testing.T) { - c := check.Value[any]().Not(badval, vsame, badtyp) + c := checkValue.Not(badval, vsame, badtyp) assertFailChecker(t, "Value.Not", c, itf(vorig), makeExpl("not {hi}", "{hi}")) }) t.Run("IsZero pass", func(t *testing.T) { - c := check.Value[any]().IsZero() + c := checkValue.IsZero() for _, z := range zeros { assertPassChecker(t, "Value.IsZero", c, z) } }) t.Run("IsZero fail", func(t *testing.T) { - c := check.Value[any]().IsZero() + c := checkValue.IsZero() for _, nz := range nozeros { assertFailChecker(t, "Value.IsZero", c, nz, makeExpl( "to be a zero value", @@ -68,14 +70,14 @@ func TestValueCheckerProvider(t *testing.T) { }) t.Run("NotZero pass", func(t *testing.T) { - c := check.Value[any]().NotZero() + c := checkValue.NotZero() for _, nz := range nozeros { assertPassChecker(t, "Value.NotZero", c, nz) } }) t.Run("NotZero fail", func(t *testing.T) { - c := check.Value[any]().NotZero() + c := checkValue.NotZero() for _, z := range zeros { assertFailChecker(t, "Value.NotZero", c, z, makeExpl( "not to be a zero value", @@ -90,12 +92,12 @@ func TestValueCheckerProvider(t *testing.T) { } t.Run("Custom pass", func(t *testing.T) { - c := check.Value[any]().Custom("", isEvenInt) + c := checkValue.Custom("", isEvenInt) assertPassChecker(t, "Value.Custom", c, 42) }) t.Run("Custom fail", func(t *testing.T) { - c := check.Value[any]().Custom("even int", isEvenInt) + c := checkValue.Custom("even int", isEvenInt) assertFailChecker(t, "Value.Custom", c, -1, makeExpl("even int", "-1")) }) @@ -103,7 +105,7 @@ func TestValueCheckerProvider(t *testing.T) { mapequiv := map[string]any{ "Name": "hi", } - c := check.Value[any]().SameJSON(mapequiv) + c := checkValue.SameJSON(mapequiv) assertPassChecker(t, "Value.SameJSON", c, itf(vorig)) }) @@ -111,7 +113,7 @@ func TestValueCheckerProvider(t *testing.T) { mapdiff := map[string]any{ "Name": "bad", } - c := check.Value[any]().SameJSON(mapdiff) + c := checkValue.SameJSON(mapdiff) assertFailChecker(t, "Value.SameJSON", c, itf(vorig), makeExpl( "json data: map[Name:bad]", "json data: map[Name:hi]",