├── go.sum ├── .gitignore ├── go.mod ├── assert ├── assertions │ ├── internal │ │ ├── oglematchers │ │ │ ├── .gitignore │ │ │ ├── .travis.yml │ │ │ ├── transform_description.go │ │ │ ├── greater_than.go │ │ │ ├── greater_or_equal.go │ │ │ ├── less_or_equal.go │ │ │ ├── not.go │ │ │ ├── contains.go │ │ │ ├── README.md │ │ │ ├── deep_equals.go │ │ │ ├── any_of.go │ │ │ ├── matcher.go │ │ │ └── less_than.go │ │ ├── go-diff │ │ │ ├── .gitignore │ │ │ ├── diffmatchpatch │ │ │ │ ├── operation_string.go │ │ │ │ ├── diffmatchpatch.go │ │ │ │ ├── stringutil.go │ │ │ │ └── match.go │ │ │ ├── .travis.yml │ │ │ ├── scripts │ │ │ │ └── lint.sh │ │ │ ├── AUTHORS │ │ │ ├── LICENSE │ │ │ ├── CONTRIBUTORS │ │ │ ├── Makefile │ │ │ └── README.md │ │ ├── go-render │ │ │ ├── render │ │ │ │ └── render_time.go │ │ │ ├── WATCHLISTS │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── pre-commit-go.yml │ │ │ ├── README.md │ │ │ └── PRESUBMIT.py │ │ └── unit │ │ │ └── fixture.go │ ├── doc_test.go │ ├── doc.go │ ├── filter.go │ ├── equality_diff.go │ ├── utilities_for_test.go │ ├── type.go │ ├── type_test.go │ ├── panic_test.go │ ├── panic.go │ ├── equality_specs.go │ ├── quantity.go │ ├── strings_test.go │ ├── messages.go │ ├── strings.go │ ├── time.go │ ├── equality.go │ └── collections.go ├── better │ ├── better_test.go │ └── better.go ├── so.go ├── so_test.go └── should │ └── should.go ├── .travis.yml ├── t.go ├── scan ├── fixture.go ├── 2_fixture_validator.go ├── 0_parser.go ├── 1_fixture_collector.go ├── scan.go ├── 3_method_finder.go ├── parser_test.go └── parser_test_inputs_test.go ├── Makefile ├── examples ├── environment_controller_hvac.go ├── long_running_fixture_test.go ├── sequential_fixture_test.go ├── skipped_fixture_test.go ├── sequential_test_cases_test.go ├── sequential_all_test.go ├── environment_controller.go ├── bowling_game.go ├── bowling_game_test.go ├── basic_examples_test.go ├── environment_controller_hvac_fake_test.go └── environment_controller_test.go ├── config.go ├── reports ├── panic_report.go ├── runtime.go ├── failure_report.go ├── failure_report_test.go └── panic_report_test.go ├── fixture_method_info.go ├── LICENSE.md ├── assertions └── assertions.go ├── runner.go ├── CONTRIBUTING.md ├── config_test.go ├── options.go ├── fixture_runner.go ├── test_case.go ├── fixture.go └── README.md /go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /ast.txt 3 | /coverage.* 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/smarty/gunit 2 | 3 | go 1.25 4 | -------------------------------------------------------------------------------- /assert/assertions/internal/oglematchers/.gitignore: -------------------------------------------------------------------------------- 1 | *.6 2 | 6.out 3 | _obj/ 4 | _test/ 5 | _testmain.go 6 | -------------------------------------------------------------------------------- /assert/assertions/internal/oglematchers/.travis.yml: -------------------------------------------------------------------------------- 1 | # Cf. http://docs.travis-ci.com/user/getting-started/ 2 | # Cf. http://docs.travis-ci.com/user/languages/go/ 3 | 4 | language: go 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: bionic 2 | 3 | language: go 4 | 5 | go: 6 | - 1.x 7 | 8 | env: 9 | - GO111MODULE=on 10 | 11 | script: 12 | - make build 13 | 14 | after_success: 15 | - bash <(curl -s https://codecov.io/bash) 16 | 17 | git: 18 | depth: 1 19 | 20 | cache: 21 | directories: 22 | - $HOME/.cache/go-build 23 | - $HOME/gopath/pkg/mod 24 | -------------------------------------------------------------------------------- /assert/assertions/doc_test.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import "github.com/smarty/gunit/assert" 4 | 5 | // so is like So, except that it only returns the string message, which is blank if the 6 | // assertion passed. Used to facilitate testing. 7 | func so(actual any, assertion assert.Func, expected ...any) string { 8 | return assertion(actual, expected...) 9 | } 10 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-diff/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /t.go: -------------------------------------------------------------------------------- 1 | package gunit 2 | 3 | import ( 4 | "context" 5 | "io" 6 | ) 7 | 8 | // TestingT represents the functional subset from *testing.T needed by Fixture. 9 | type TestingT interface { 10 | Helper() 11 | Name() string 12 | Log(args ...any) 13 | Fail() 14 | Failed() bool 15 | Fatalf(format string, args ...any) 16 | Errorf(format string, args ...any) 17 | Context() context.Context 18 | Output() io.Writer 19 | } 20 | -------------------------------------------------------------------------------- /assert/better/better_test.go: -------------------------------------------------------------------------------- 1 | package better 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestFatal(t *testing.T) { 9 | output := Equal(1, 1) 10 | if strings.HasPrefix(output, "<<>>\n") { 11 | t.Error("Unexpected 'fatal' prefix") 12 | } 13 | output = Equal(1, 2) 14 | if !strings.HasPrefix(output, "<<>>\n") { 15 | t.Error("Missing expected 'fatal' prefix from output:", output) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /assert/assertions/doc.go: -------------------------------------------------------------------------------- 1 | // Package assertions contains the implementations for all assertions 2 | // used with the So(...) method. 3 | // 4 | // A few of the assertions lean heavily on work done by Aaron Jacobs in his excellent oglematchers library. 5 | // (https://github.com/jacobsa/oglematchers) 6 | // The ShouldEqual assertion leans heavily on work done by Daniel Jacques in his very helpful go-render library. 7 | // (https://github.com/luci/go-render) 8 | package assertions 9 | -------------------------------------------------------------------------------- /assert/so.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import "strings" 4 | 5 | func So(t testingT, actual any, assertion Func, expected ...any) { 6 | t.Helper() 7 | result := assertion(actual, expected...) 8 | if strings.HasPrefix(result, "<<>>\n") { 9 | t.Fatal(result) 10 | } else if result != "" { 11 | t.Error(result) 12 | } 13 | } 14 | 15 | type testingT interface { 16 | Helper() 17 | Error(...any) 18 | Fatal(...any) 19 | } 20 | 21 | type Func func(actual any, expected ...any) string 22 | -------------------------------------------------------------------------------- /scan/fixture.go: -------------------------------------------------------------------------------- 1 | package scan 2 | 3 | import "fmt" 4 | 5 | type fixtureInfo struct { 6 | Filename string 7 | StructName string 8 | TestCases []*testCaseInfo 9 | } 10 | 11 | type testCaseInfo struct { 12 | CharacterPosition int 13 | LineNumber int 14 | Name string 15 | } 16 | 17 | func (this testCaseInfo) GoString() string { 18 | return fmt.Sprintf( 19 | `testCaseInfo{CharacterPosition: %d, LineNumber: %d, Name: %s}`, 20 | this.CharacterPosition, 21 | this.LineNumber, 22 | this.Name, 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | test-dev: fmt 4 | GORACE="atexit_sleep_ms=50" go test -timeout=1s -race -count=1 -covermode=atomic ./... && \ 5 | echo "---- Running tests a second time with -short ----" && \ 6 | GORACE="atexit_sleep_ms=50" go test -timeout=1s -race -count=1 -covermode=atomic -short ./... 7 | 8 | fmt: 9 | go fmt ./... 10 | 11 | test: 12 | GORACE="atexit_sleep_ms=50" go test -timeout=1s -race -covermode=atomic . 13 | 14 | compile: 15 | go build ./... 16 | 17 | build: test compile 18 | 19 | .PHONY: test-dev fmt test compile build 20 | -------------------------------------------------------------------------------- /examples/environment_controller_hvac.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | type HVAC interface { 4 | ActivateHeater() 5 | ActivateBlower() 6 | ActivateCooler() 7 | ActivateHighTemperatureAlarm() 8 | ActivateLowTemperatureAlarm() 9 | 10 | DeactivateHeater() 11 | DeactivateBlower() 12 | DeactivateCooler() 13 | DeactivateHighTemperatureAlarm() 14 | DeactivateLowTemperatureAlarm() 15 | 16 | IsHeating() bool 17 | IsBlowing() bool 18 | IsCooling() bool 19 | HighTemperatureAlarm() bool 20 | LowTemperatureAlarm() bool 21 | 22 | CurrentTemperature() int 23 | } 24 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-diff/diffmatchpatch/operation_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Operation -trimprefix=Diff"; DO NOT EDIT. 2 | 3 | package diffmatchpatch 4 | 5 | import "fmt" 6 | 7 | const _Operation_name = "DeleteEqualInsert" 8 | 9 | var _Operation_index = [...]uint8{0, 6, 11, 17} 10 | 11 | func (i Operation) String() string { 12 | i -= -1 13 | if i < 0 || i >= Operation(len(_Operation_index)-1) { 14 | return fmt.Sprintf("Operation(%d)", i+-1) 15 | } 16 | return _Operation_name[_Operation_index[i]:_Operation_index[i+1]] 17 | } 18 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package gunit 2 | 3 | type configuration struct { 4 | SequentialFixture bool 5 | SequentialTestCases bool 6 | SkippedTestCases bool 7 | LongRunningTestCases bool 8 | } 9 | 10 | func newConfig(options ...option) configuration { 11 | config := new(configuration) 12 | Options.composite(options...)(config) 13 | return *config 14 | } 15 | 16 | func (this configuration) ParallelFixture() bool { 17 | return !this.SequentialFixture 18 | } 19 | 20 | func (this configuration) ParallelTestCases() bool { 21 | return !this.SequentialTestCases 22 | } 23 | -------------------------------------------------------------------------------- /examples/long_running_fixture_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/smarty/gunit" 7 | ) 8 | 9 | func TestHowToMarkAnEntireFixtureAsLongRunning(t *testing.T) { 10 | gunit.Run(new(HowToMarkAnEntireFixtureAsLongRunning), t, 11 | gunit.Options.LongRunning(), // <-- Just pass the LongRunning option to gunit.Run(...)! 12 | ) 13 | } 14 | 15 | type HowToMarkAnEntireFixtureAsLongRunning struct { 16 | *gunit.Fixture 17 | } 18 | 19 | func (this *HowToMarkAnEntireFixtureAsLongRunning) TestTheseTestsWillBeSkippedIfTheShortFlagWasPassedAtTheCommandLine() { 20 | } 21 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-render/render/render_time.go: -------------------------------------------------------------------------------- 1 | package render 2 | 3 | import ( 4 | "reflect" 5 | "time" 6 | ) 7 | 8 | func renderTime(value reflect.Value) (string, bool) { 9 | if instant, ok := convertTime(value); !ok { 10 | return "", false 11 | } else if instant.IsZero() { 12 | return "0", true 13 | } else { 14 | return instant.String(), true 15 | } 16 | } 17 | 18 | func convertTime(value reflect.Value) (t time.Time, ok bool) { 19 | if value.Type() == timeType { 20 | defer func() { recover() }() 21 | t, ok = value.Interface().(time.Time) 22 | } 23 | return 24 | } 25 | 26 | var timeType = reflect.TypeOf(time.Time{}) 27 | -------------------------------------------------------------------------------- /assert/assertions/filter.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import "fmt" 4 | 5 | const ( 6 | success = "" 7 | needExactValues = "This assertion requires exactly %d comparison values (you provided %d)." 8 | needNonEmptyCollection = "This assertion requires at least 1 comparison value (you provided 0)." 9 | ) 10 | 11 | func need(needed int, expected []any) string { 12 | if len(expected) != needed { 13 | return fmt.Sprintf(needExactValues, needed, len(expected)) 14 | } 15 | return success 16 | } 17 | 18 | func atLeast(minimum int, expected []any) string { 19 | if len(expected) < minimum { 20 | return needNonEmptyCollection 21 | } 22 | return success 23 | } 24 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-render/WATCHLISTS: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The Chromium Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style license that can be 3 | # found in the LICENSE file. 4 | 5 | # Watchlist Rules 6 | # Refer: http://dev.chromium.org/developers/contributing-code/watchlists 7 | 8 | { 9 | 10 | 'WATCHLIST_DEFINITIONS': { 11 | 'all': { 12 | 'filepath': '.+', 13 | }, 14 | }, 15 | 16 | 'WATCHLISTS': { 17 | 'all': [ 18 | # Add yourself here to get explicitly spammed. 19 | 'maruel@chromium.org', 20 | 'tandrii+luci-go@chromium.org', 21 | 'todd@cloudera.com', 22 | 'andrew.wang@cloudera.com', 23 | ], 24 | }, 25 | 26 | } 27 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-render/.travis.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 The Chromium Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style license that can be 3 | # found in the LICENSE file. 4 | 5 | # {sudo: required, dist: trusty} is the magic incantation to pick the trusty 6 | # beta environment, which is the only environment we can get that has >4GB 7 | # memory. Currently the `go test -race` tests that we run will peak at just 8 | # over 4GB, which results in everything getting OOM-killed. 9 | sudo: required 10 | dist: trusty 11 | 12 | language: go 13 | 14 | go: 15 | - 1.4.2 16 | 17 | before_install: 18 | - go get github.com/maruel/pre-commit-go/cmd/pcg 19 | 20 | script: 21 | - pcg 22 | -------------------------------------------------------------------------------- /reports/panic_report.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func PanicReport(r any, stack []byte) string { 9 | var builder strings.Builder 10 | fmt.Fprintln(&builder, "PANIC:", r) 11 | fmt.Fprintln(&builder, "...") 12 | 13 | opened, closed := false, false 14 | for _, line := range strings.Split(string(stack), "\n") { 15 | if strings.Contains(line, "/runtime/panic.go:") { 16 | opened = true 17 | continue 18 | } 19 | if !opened || closed { 20 | continue 21 | } 22 | if strings.Contains(line, "reflect.Value.call(0x") { 23 | closed = true 24 | continue 25 | } 26 | fmt.Fprintln(&builder, line) 27 | } 28 | 29 | return strings.TrimSpace(builder.String()) 30 | 31 | } 32 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-diff/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | os: 4 | - linux 5 | - osx 6 | 7 | go: 8 | - 1.8.x 9 | - 1.9.x 10 | 11 | sudo: false 12 | 13 | env: 14 | global: 15 | # Coveralls.io 16 | - secure: OGYOsFNXNarEZ5yA4/M6ZdVguD0jL8vXgXrbLzjcpkKcq8ObHSCtNINoUlnNf6l6Z92kPnuV+LSm7jKTojBlov4IwgiY1ACbvg921SdjxYkg1AiwHTRTLR1g/esX8RdaBpJ0TOcXOFFsYMRVvl5sxxtb0tXSuUrT+Ch4SUCY7X8= 17 | 18 | install: 19 | - make install-dependencies 20 | - make install-tools 21 | - make install 22 | 23 | script: 24 | - make lint 25 | - make test-with-coverage 26 | - gover 27 | - if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then goveralls -coverprofile=gover.coverprofile -service=travis-ci -repotoken $COVERALLS_TOKEN; fi 28 | -------------------------------------------------------------------------------- /examples/sequential_fixture_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/smarty/gunit" 7 | ) 8 | 9 | func TestHowToRegisterASequentialFixture(t *testing.T) { 10 | gunit.Run(new(HowToRegisterASequentialFixture), t, 11 | gunit.Options.SequentialFixture(), // <-- Just pass the SequentialFixture option to gunit.Run(...)! 12 | ) 13 | } 14 | 15 | type HowToRegisterASequentialFixture struct { 16 | *gunit.Fixture 17 | } 18 | 19 | func (this *HowToRegisterASequentialFixture) TestTestsOnThisFixtureWillNOTBeRunInParallelAnyOtherTests() { 20 | } 21 | 22 | func (this *HowToRegisterASequentialFixture) TestA() {} 23 | func (this *HowToRegisterASequentialFixture) TestB() {} 24 | func (this *HowToRegisterASequentialFixture) TestC() {} 25 | -------------------------------------------------------------------------------- /examples/skipped_fixture_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/smarty/gunit" 7 | ) 8 | 9 | func TestHowToSkipAnEntireFixture(t *testing.T) { 10 | gunit.Run(new(HowToSkipAnEntireFixture), t, 11 | gunit.Options.SkipAll(), // <-- Just pass the SkipAll option to gunit.Run(...)! 12 | ) 13 | } 14 | 15 | type HowToSkipAnEntireFixture struct { 16 | *gunit.Fixture 17 | } 18 | 19 | func (this *HowToSkipAnEntireFixture) TestAllTestMethodsWillBeSkipped_ThankGoodness() { 20 | this.Assert(false) 21 | } 22 | 23 | func (this *HowToSkipAnEntireFixture) TestA() { this.Assert(false) } 24 | func (this *HowToSkipAnEntireFixture) TestB() { this.Assert(false) } 25 | func (this *HowToSkipAnEntireFixture) TestC() { this.Assert(false) } 26 | -------------------------------------------------------------------------------- /examples/sequential_test_cases_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/smarty/gunit" 7 | ) 8 | 9 | func TestHowToRegisterAllTestCasesAsSequential(t *testing.T) { 10 | gunit.Run(new(HowToRegisterAllTestCasesAsSequential), t, 11 | gunit.Options.SequentialTestCases(), // <-- Just pass the SequentialTestCases option to gunit.Run(...)! 12 | ) 13 | } 14 | 15 | type HowToRegisterAllTestCasesAsSequential struct { 16 | *gunit.Fixture 17 | } 18 | 19 | func (this *HowToRegisterAllTestCasesAsSequential) TestTestsOnThisFixtureWillNOTBeRunInParallelWithEachOther() { 20 | } 21 | 22 | func (this *HowToRegisterAllTestCasesAsSequential) TestA() {} 23 | func (this *HowToRegisterAllTestCasesAsSequential) TestB() {} 24 | func (this *HowToRegisterAllTestCasesAsSequential) TestC() {} 25 | -------------------------------------------------------------------------------- /scan/2_fixture_validator.go: -------------------------------------------------------------------------------- 1 | package scan 2 | 3 | import "go/ast" 4 | 5 | type fixtureValidator struct { 6 | Parent *fixtureCollector 7 | FixtureName string 8 | } 9 | 10 | func (this *fixtureValidator) Visit(node ast.Node) ast.Visitor { 11 | // We start at a TypeSpec and look for an embedded pointer field: `*gunit.Fixture`. 12 | field, isField := node.(*ast.Field) 13 | if !isField { 14 | return this 15 | } 16 | pointer, isPointer := field.Type.(*ast.StarExpr) 17 | if !isPointer { 18 | return this 19 | } 20 | 21 | selector, isSelector := pointer.X.(*ast.SelectorExpr) 22 | if !isSelector { 23 | return this 24 | } 25 | gunit, isGunit := selector.X.(*ast.Ident) 26 | if selector.Sel.Name != "Fixture" || !isGunit || gunit.Name != "gunit" { 27 | return this 28 | } 29 | this.Parent.Validate(this.FixtureName) 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /examples/sequential_all_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/smarty/gunit" 7 | ) 8 | 9 | func TestHowToRegisterTheFixtureAndAllItsCasesAsSequential(t *testing.T) { 10 | gunit.Run(new(HowToRegisterTheFixtureAndAllItsCasesAsSequential), t, 11 | gunit.Options.AllSequential(), // <-- Just pass the AllSequential option to gunit.Run(...)! 12 | ) 13 | } 14 | 15 | type HowToRegisterTheFixtureAndAllItsCasesAsSequential struct { 16 | *gunit.Fixture 17 | } 18 | 19 | func (this *HowToRegisterTheFixtureAndAllItsCasesAsSequential) TestThisFixtureAndItsTestsWillNotBeRunInParallelInAnyWay() { 20 | } 21 | 22 | func (this *HowToRegisterTheFixtureAndAllItsCasesAsSequential) TestA() {} 23 | func (this *HowToRegisterTheFixtureAndAllItsCasesAsSequential) TestB() {} 24 | func (this *HowToRegisterTheFixtureAndAllItsCasesAsSequential) TestC() {} 25 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-diff/scripts/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z ${PKG+x} ]; then echo "PKG is not set"; exit 1; fi 4 | if [ -z ${ROOT_DIR+x} ]; then echo "ROOT_DIR is not set"; exit 1; fi 5 | 6 | echo "gofmt:" 7 | OUT=$(gofmt -l $ROOT_DIR) 8 | if [ $(echo "$OUT\c" | wc -l) -ne 0 ]; then echo "$OUT"; PROBLEM=1; fi 9 | 10 | echo "errcheck:" 11 | OUT=$(errcheck $PKG/...) 12 | if [ $(echo "$OUT\c" | wc -l) -ne 0 ]; then echo "$OUT"; PROBLEM=1; fi 13 | 14 | echo "go vet:" 15 | OUT=$(go tool vet -all=true -v=true $ROOT_DIR 2>&1 | grep --invert-match -E "(Checking file|\%p of wrong type|can't check non-constant format)") 16 | if [ $(echo "$OUT\c" | wc -l) -ne 0 ]; then echo "$OUT"; PROBLEM=1; fi 17 | 18 | echo "golint:" 19 | OUT=$(golint $PKG/... | grep --invert-match -E "(method DiffPrettyHtml should be DiffPrettyHTML)") 20 | if [ $(echo "$OUT\c" | wc -l) -ne 0 ]; then echo "$OUT"; PROBLEM=1; fi 21 | 22 | if [ -n "$PROBLEM" ]; then exit 1; fi 23 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-diff/AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of go-diff authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS files. 3 | # See the latter for an explanation. 4 | 5 | # Names should be added to this file as 6 | # Name or Organization 7 | # The email address is not required for organizations. 8 | 9 | # Please keep the list sorted. 10 | 11 | Danny Yoo 12 | James Kolb 13 | Jonathan Amsterdam 14 | Markus Zimmermann 15 | Matt Kovars 16 | Örjan Persson 17 | Osman Masood 18 | Robert Carlsen 19 | Rory Flynn 20 | Sergi Mansilla 21 | Shatrugna Sadhu 22 | Shawn Smith 23 | Stas Maksimov 24 | Tor Arvid Lund 25 | Zac Bergquist 26 | -------------------------------------------------------------------------------- /scan/0_parser.go: -------------------------------------------------------------------------------- 1 | package scan 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "go/ast" 7 | "go/parser" 8 | "go/token" 9 | ) 10 | 11 | ////////////////////////////////////////////////////////////////////////////// 12 | 13 | func scanForFixtures(code string) ([]*fixtureInfo, error) { 14 | fileset := token.NewFileSet() 15 | file, err := parser.ParseFile(fileset, "", code, 0) 16 | if err != nil { 17 | return nil, err 18 | } 19 | // ast.Print(fileset, file) // helps with debugging... 20 | return findAndListFixtures(file) 21 | } 22 | 23 | func findAndListFixtures(file *ast.File) ([]*fixtureInfo, error) { 24 | collection := newFixtureCollector().Collect(file) 25 | collection = newFixtureMethodFinder(collection).Find(file) 26 | return listFixtures(collection) 27 | } 28 | 29 | func listFixtures(collection map[string]*fixtureInfo) ([]*fixtureInfo, error) { 30 | var fixtures []*fixtureInfo 31 | errorMessage := new(bytes.Buffer) 32 | 33 | for _, fixture := range collection { 34 | fixtures = append(fixtures, fixture) 35 | } 36 | if errorMessage.Len() > 0 { 37 | return nil, errors.New(errorMessage.String()) 38 | } 39 | return fixtures, nil 40 | } 41 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-diff/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2016 The go-diff Authors. All rights reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included 11 | in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 14 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /scan/1_fixture_collector.go: -------------------------------------------------------------------------------- 1 | package scan 2 | 3 | import "go/ast" 4 | 5 | type fixtureCollector struct { 6 | candidates map[string]*fixtureInfo 7 | fixtures map[string]*fixtureInfo 8 | } 9 | 10 | func newFixtureCollector() *fixtureCollector { 11 | return &fixtureCollector{ 12 | candidates: make(map[string]*fixtureInfo), 13 | fixtures: make(map[string]*fixtureInfo), 14 | } 15 | } 16 | 17 | func (this *fixtureCollector) Collect(file *ast.File) map[string]*fixtureInfo { 18 | ast.Walk(this, file) // Calls this.Visit(...) recursively which populates this.fixtures 19 | return this.fixtures 20 | } 21 | 22 | func (this *fixtureCollector) Visit(node ast.Node) ast.Visitor { 23 | switch t := node.(type) { 24 | case *ast.TypeSpec: 25 | name := t.Name.Name 26 | this.candidates[name] = &fixtureInfo{StructName: name} 27 | return &fixtureValidator{Parent: this, FixtureName: name} 28 | default: 29 | return this 30 | } 31 | } 32 | 33 | func (this *fixtureCollector) Validate(fixture string) { 34 | this.fixtures[fixture] = this.candidates[fixture] 35 | delete(this.candidates, fixture) 36 | } 37 | 38 | func (this *fixtureCollector) Invalidate(fixture string) { 39 | this.Validate(fixture) 40 | } 41 | -------------------------------------------------------------------------------- /examples/environment_controller.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | type EnvironmentController struct { 4 | hardware HVAC 5 | } 6 | 7 | func NewController(hardware HVAC) *EnvironmentController { 8 | hardware.DeactivateBlower() 9 | hardware.DeactivateHeater() 10 | hardware.DeactivateCooler() 11 | hardware.DeactivateHighTemperatureAlarm() 12 | hardware.DeactivateLowTemperatureAlarm() 13 | return &EnvironmentController{hardware: hardware} 14 | } 15 | 16 | func (this *EnvironmentController) Regulate() { 17 | temperature := this.hardware.CurrentTemperature() 18 | 19 | if temperature >= WAY_TOO_HOT { 20 | this.hardware.ActivateHighTemperatureAlarm() 21 | } else if temperature <= WAY_TOO_COLD { 22 | this.hardware.ActivateLowTemperatureAlarm() 23 | } 24 | 25 | if temperature >= TOO_HOT { 26 | this.hardware.DeactivateHeater() 27 | this.hardware.ActivateBlower() 28 | this.hardware.ActivateCooler() 29 | } else if temperature <= TOO_COLD { 30 | this.hardware.DeactivateCooler() 31 | this.hardware.ActivateBlower() 32 | this.hardware.ActivateHeater() 33 | } 34 | } 35 | 36 | const ( 37 | WAY_TOO_HOT = 80 38 | TOO_HOT = 70 39 | TOO_COLD = 60 40 | WAY_TOO_COLD = 50 41 | COMFORTABLE = (TOO_HOT + TOO_COLD) / 2 42 | ) 43 | -------------------------------------------------------------------------------- /assert/assertions/equality_diff.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/smarty/gunit/assert/assertions/internal/go-diff/diffmatchpatch" 7 | ) 8 | 9 | func composePrettyDiff(expected, actual string) string { 10 | diff := diffmatchpatch.New() 11 | diffs := diff.DiffMain(expected, actual, false) 12 | if prettyDiffIsLikelyToBeHelpful(diffs) { 13 | return fmt.Sprintf("\nDiff: '%s'", diff.DiffPrettyText(diffs)) 14 | } 15 | return "" 16 | } 17 | 18 | // prettyDiffIsLikelyToBeHelpful returns true if the diff listing contains 19 | // more 'equal' segments than 'deleted'/'inserted' segments. 20 | func prettyDiffIsLikelyToBeHelpful(diffs []diffmatchpatch.Diff) bool { 21 | equal, deleted, inserted := measureDiffTypeLengths(diffs) 22 | return equal > deleted && equal > inserted 23 | } 24 | 25 | func measureDiffTypeLengths(diffs []diffmatchpatch.Diff) (equal, deleted, inserted int) { 26 | for _, segment := range diffs { 27 | switch segment.Type { 28 | case diffmatchpatch.DiffEqual: 29 | equal += len(segment.Text) 30 | case diffmatchpatch.DiffDelete: 31 | deleted += len(segment.Text) 32 | case diffmatchpatch.DiffInsert: 33 | inserted += len(segment.Text) 34 | } 35 | } 36 | return equal, deleted, inserted 37 | } 38 | -------------------------------------------------------------------------------- /fixture_method_info.go: -------------------------------------------------------------------------------- 1 | package gunit 2 | 3 | import "strings" 4 | 5 | type fixtureMethodInfo struct { 6 | name string 7 | isSetup bool 8 | isTeardown bool 9 | isTest bool 10 | isFocusTest bool 11 | isLongTest bool 12 | isSkippedTest bool 13 | } 14 | 15 | func (this *fixtureRunner) newFixtureMethodInfo(name string) fixtureMethodInfo { 16 | var ( 17 | isSetup = strings.HasPrefix(name, "Setup") 18 | isTeardown = strings.HasPrefix(name, "Teardown") 19 | isTest = strings.HasPrefix(name, "Test") 20 | isLongTest = strings.HasPrefix(name, "LongTest") 21 | isFocusTest = strings.HasPrefix(name, "FocusTest") 22 | isFocusLongTest = strings.HasPrefix(name, "FocusLongTest") 23 | isSkippedTest = strings.HasPrefix(name, "SkipTest") 24 | isSkippedLongTest = strings.HasPrefix(name, "SkipLongTest") 25 | ) 26 | 27 | return fixtureMethodInfo{ 28 | name: name, 29 | isSetup: isSetup, 30 | isTeardown: isTeardown, 31 | isLongTest: isLongTest || isSkippedLongTest || isFocusLongTest, 32 | isFocusTest: isFocusTest || isFocusLongTest, 33 | isSkippedTest: isSkippedTest || isSkippedLongTest, 34 | isTest: isTest || isLongTest || isSkippedTest || isSkippedLongTest || isFocusTest || isFocusLongTest, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-diff/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This is the official list of people who can contribute 2 | # (and typically have contributed) code to the go-diff 3 | # repository. 4 | # 5 | # The AUTHORS file lists the copyright holders; this file 6 | # lists people. For example, ACME Inc. employees would be listed here 7 | # but not in AUTHORS, because ACME Inc. would hold the copyright. 8 | # 9 | # When adding J Random Contributor's name to this file, 10 | # either J's name or J's organization's name should be 11 | # added to the AUTHORS file. 12 | # 13 | # Names should be added to this file like so: 14 | # Name 15 | # 16 | # Please keep the list sorted. 17 | 18 | Danny Yoo 19 | James Kolb 20 | Jonathan Amsterdam 21 | Markus Zimmermann 22 | Matt Kovars 23 | Örjan Persson 24 | Osman Masood 25 | Robert Carlsen 26 | Rory Flynn 27 | Sergi Mansilla 28 | Shatrugna Sadhu 29 | Shawn Smith 30 | Stas Maksimov 31 | Tor Arvid Lund 32 | Zac Bergquist 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Smarty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | NOTE: Various optional and subordinate components carry their own licensing 24 | requirements and restrictions. Use of those components is subject to the terms 25 | and conditions outlined the respective license of each component. 26 | -------------------------------------------------------------------------------- /scan/scan.go: -------------------------------------------------------------------------------- 1 | package scan 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | type TestCasePositions map[string]string 10 | 11 | func LocateTestCases(filename string) TestCasePositions { 12 | return gatherTestCaseLineNumbers(parseFixtures(filename)) 13 | } 14 | 15 | func gatherTestCaseLineNumbers(fixtures []*fixtureInfo) TestCasePositions { 16 | positions := make(TestCasePositions) 17 | for _, fixture := range fixtures { 18 | for _, test := range fixture.TestCases { 19 | key := fmt.Sprintf("Test%s/%s", fixture.StructName, test.Name) 20 | value := fmt.Sprintf("%s:%d", fixture.Filename, test.LineNumber) 21 | positions[key] = value 22 | } 23 | } 24 | return positions 25 | } 26 | 27 | func parseFixtures(filename string) (fixtures []*fixtureInfo) { 28 | source, err := os.ReadFile(filename) 29 | if err != nil { 30 | return nil 31 | } 32 | batch, err := scanForFixtures(string(source)) 33 | if err != nil { 34 | return nil 35 | } 36 | for _, fixture := range batch { 37 | fixture.Filename = filename 38 | fixtures = append(fixtures, fixture) 39 | for _, test := range fixture.TestCases { 40 | test.LineNumber = lineNumber(string(source), test.CharacterPosition) 41 | } 42 | } 43 | return fixtures 44 | } 45 | 46 | func lineNumber(source string, position int) int { 47 | source = source[:position+1] 48 | return strings.Count(source, "\n") + 1 49 | } 50 | -------------------------------------------------------------------------------- /examples/bowling_game.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | type Game struct { 4 | rolls [maxThrowsPerGame]int 5 | current int 6 | } 7 | 8 | func NewGame() *Game { 9 | return new(Game) 10 | } 11 | 12 | func (this *Game) Roll(pins int) { 13 | this.rolls[this.current] = pins 14 | this.current++ 15 | } 16 | 17 | func (this *Game) Score() (score int) { 18 | for frameIndex, frame := 0, 0; frame < framesPerGame; frame++ { 19 | if this.isStrike(frameIndex) { 20 | score += allPins + this.nextTwoBallsForStrike(frameIndex) 21 | frameIndex += 1 22 | } else if this.isSpare(frameIndex) { 23 | score += allPins + this.nextBallForSpare(frameIndex) 24 | frameIndex += 2 25 | } else { 26 | score += this.twoBallsInFrame(frameIndex) 27 | frameIndex += 2 28 | } 29 | } 30 | return score 31 | } 32 | 33 | func (this *Game) isSpare(frame int) bool { return this.twoBallsInFrame(frame) == allPins } 34 | func (this *Game) isStrike(frame int) bool { return this.rolls[frame] == allPins } 35 | 36 | func (this *Game) nextTwoBallsForStrike(frame int) int { return this.twoBallsInFrame(frame + 1) } 37 | func (this *Game) nextBallForSpare(frame int) int { return this.rolls[frame+2] } 38 | func (this *Game) twoBallsInFrame(frame int) int { return this.rolls[frame] + this.rolls[frame+1] } 39 | 40 | const ( 41 | allPins = 10 42 | framesPerGame = 10 43 | maxThrowsPerGame = 21 44 | ) 45 | -------------------------------------------------------------------------------- /assertions/assertions.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type Assertion struct{ t *testing.T } 9 | 10 | func New(t *testing.T) *Assertion { 11 | return &Assertion{t: t} 12 | } 13 | 14 | func (this *Assertion) AssertEqual(expected, actual any) bool { 15 | this.t.Helper() 16 | 17 | equal := expected == actual 18 | if !equal { 19 | this.t.Errorf("\nExpected: [%v]\nActual: [%v]", expected, actual) 20 | } 21 | return equal 22 | } 23 | 24 | func (this *Assertion) AssertNil(value any) bool { 25 | this.t.Helper() 26 | 27 | isNil := value == nil || reflect.ValueOf(value).IsNil() 28 | if !isNil { 29 | this.t.Errorf("Expected [%v] to be nil, but it wasn't.", value) 30 | } 31 | return isNil 32 | } 33 | 34 | func (this *Assertion) AssertTrue(value bool) bool { 35 | this.t.Helper() 36 | 37 | if !value { 38 | this.t.Error("Expected true, got false instead") 39 | } 40 | return value 41 | } 42 | 43 | func (this *Assertion) AssertFalse(value bool) bool { 44 | this.t.Helper() 45 | 46 | if value { 47 | this.t.Error("Expected false, got true instead") 48 | } 49 | return !value 50 | } 51 | 52 | func (this *Assertion) AssertDeepEqual(expected, actual any) bool { 53 | this.t.Helper() 54 | 55 | equal := reflect.DeepEqual(expected, actual) 56 | if !equal { 57 | this.t.Errorf("\nExpected: [%v]\nActual: [%v]", expected, actual) 58 | } 59 | return equal 60 | } 61 | -------------------------------------------------------------------------------- /assert/assertions/internal/oglematchers/transform_description.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Aaron Jacobs. All Rights Reserved. 2 | // Author: aaronjjacobs@gmail.com (Aaron Jacobs) 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package oglematchers 17 | 18 | // transformDescription returns a matcher that is equivalent to the supplied 19 | // one, except that it has the supplied description instead of the one attached 20 | // to the existing matcher. 21 | func transformDescription(m Matcher, newDesc string) Matcher { 22 | return &transformDescriptionMatcher{newDesc, m} 23 | } 24 | 25 | type transformDescriptionMatcher struct { 26 | desc string 27 | wrappedMatcher Matcher 28 | } 29 | 30 | func (m *transformDescriptionMatcher) Description() string { 31 | return m.desc 32 | } 33 | 34 | func (m *transformDescriptionMatcher) Matches(c any) error { 35 | return m.wrappedMatcher.Matches(c) 36 | } 37 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-diff/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean clean-coverage install install-dependencies install-tools lint test test-verbose test-with-coverage 2 | 3 | export ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) 4 | export PKG := github.com/sergi/go-diff 5 | export ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) 6 | 7 | $(eval $(ARGS):;@:) # turn arguments into do-nothing targets 8 | export ARGS 9 | 10 | ifdef ARGS 11 | PKG_TEST := $(ARGS) 12 | else 13 | PKG_TEST := $(PKG)/... 14 | endif 15 | 16 | all: install-tools install-dependencies install lint test 17 | 18 | clean: 19 | go clean -i $(PKG)/... 20 | go clean -i -race $(PKG)/... 21 | clean-coverage: 22 | find $(ROOT_DIR) | grep .coverprofile | xargs rm 23 | install: 24 | go install -v $(PKG)/... 25 | install-dependencies: 26 | go get -t -v $(PKG)/... 27 | go build -v $(PKG)/... 28 | install-tools: 29 | # Install linting tools 30 | go get -u -v github.com/golang/lint/... 31 | go get -u -v github.com/kisielk/errcheck/... 32 | 33 | # Install code coverage tools 34 | go get -u -v github.com/onsi/ginkgo/ginkgo/... 35 | go get -u -v github.com/modocache/gover/... 36 | go get -u -v github.com/mattn/goveralls/... 37 | lint: 38 | $(ROOT_DIR)/scripts/lint.sh 39 | test: 40 | go test -race -test.timeout 120s $(PKG_TEST) 41 | test-verbose: 42 | go test -race -test.timeout 120s -v $(PKG_TEST) 43 | test-with-coverage: 44 | ginkgo -r -cover -race -skipPackage="testdata" 45 | -------------------------------------------------------------------------------- /assert/assertions/internal/oglematchers/greater_than.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Aaron Jacobs. All Rights Reserved. 2 | // Author: aaronjjacobs@gmail.com (Aaron Jacobs) 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package oglematchers 17 | 18 | import ( 19 | "fmt" 20 | "reflect" 21 | ) 22 | 23 | // GreaterThan returns a matcher that matches integer, floating point, or 24 | // strings values v such that v > x. Comparison is not defined between numeric 25 | // and string types, but is defined between all integer and floating point 26 | // types. 27 | // 28 | // x must itself be an integer, floating point, or string type; otherwise, 29 | // GreaterThan will panic. 30 | func GreaterThan(x any) Matcher { 31 | desc := fmt.Sprintf("greater than %v", x) 32 | 33 | // Special case: make it clear that strings are strings. 34 | if reflect.TypeOf(x).Kind() == reflect.String { 35 | desc = fmt.Sprintf("greater than \"%s\"", x) 36 | } 37 | 38 | return transformDescription(Not(LessOrEqual(x)), desc) 39 | } 40 | -------------------------------------------------------------------------------- /assert/assertions/internal/oglematchers/greater_or_equal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Aaron Jacobs. All Rights Reserved. 2 | // Author: aaronjjacobs@gmail.com (Aaron Jacobs) 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package oglematchers 17 | 18 | import ( 19 | "fmt" 20 | "reflect" 21 | ) 22 | 23 | // GreaterOrEqual returns a matcher that matches integer, floating point, or 24 | // strings values v such that v >= x. Comparison is not defined between numeric 25 | // and string types, but is defined between all integer and floating point 26 | // types. 27 | // 28 | // x must itself be an integer, floating point, or string type; otherwise, 29 | // GreaterOrEqual will panic. 30 | func GreaterOrEqual(x any) Matcher { 31 | desc := fmt.Sprintf("greater than or equal to %v", x) 32 | 33 | // Special case: make it clear that strings are strings. 34 | if reflect.TypeOf(x).Kind() == reflect.String { 35 | desc = fmt.Sprintf("greater than or equal to \"%s\"", x) 36 | } 37 | 38 | return transformDescription(Not(LessThan(x)), desc) 39 | } 40 | -------------------------------------------------------------------------------- /assert/assertions/internal/oglematchers/less_or_equal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Aaron Jacobs. All Rights Reserved. 2 | // Author: aaronjjacobs@gmail.com (Aaron Jacobs) 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package oglematchers 17 | 18 | import ( 19 | "fmt" 20 | "reflect" 21 | ) 22 | 23 | // LessOrEqual returns a matcher that matches integer, floating point, or 24 | // strings values v such that v <= x. Comparison is not defined between numeric 25 | // and string types, but is defined between all integer and floating point 26 | // types. 27 | // 28 | // x must itself be an integer, floating point, or string type; otherwise, 29 | // LessOrEqual will panic. 30 | func LessOrEqual(x any) Matcher { 31 | desc := fmt.Sprintf("less than or equal to %v", x) 32 | 33 | // Special case: make it clear that strings are strings. 34 | if reflect.TypeOf(x).Kind() == reflect.String { 35 | desc = fmt.Sprintf("less than or equal to \"%s\"", x) 36 | } 37 | 38 | // Put LessThan last so that its error messages will be used in the event of 39 | // failure. 40 | return transformDescription(AnyOf(Equals(x), LessThan(x)), desc) 41 | } 42 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-render/LICENSE: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 The Chromium Authors. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are 5 | // met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above 10 | // copyright notice, this list of conditions and the following disclaimer 11 | // in the documentation and/or other materials provided with the 12 | // distribution. 13 | // * Neither the name of Google Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /assert/assertions/internal/oglematchers/not.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Aaron Jacobs. All Rights Reserved. 2 | // Author: aaronjjacobs@gmail.com (Aaron Jacobs) 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package oglematchers 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | ) 22 | 23 | // Not returns a matcher that inverts the set of values matched by the wrapped 24 | // matcher. It does not transform the result for values for which the wrapped 25 | // matcher returns a fatal error. 26 | func Not(m Matcher) Matcher { 27 | return ¬Matcher{m} 28 | } 29 | 30 | type notMatcher struct { 31 | wrapped Matcher 32 | } 33 | 34 | func (m *notMatcher) Matches(c any) (err error) { 35 | err = m.wrapped.Matches(c) 36 | 37 | // Did the wrapped matcher say yes? 38 | if err == nil { 39 | return errors.New("") 40 | } 41 | 42 | // Did the wrapped matcher return a fatal error? 43 | if _, isFatal := err.(*FatalError); isFatal { 44 | return err 45 | } 46 | 47 | // The wrapped matcher returned a non-fatal error. 48 | return nil 49 | } 50 | 51 | func (m *notMatcher) Description() string { 52 | return fmt.Sprintf("not(%s)", m.wrapped.Description()) 53 | } 54 | -------------------------------------------------------------------------------- /assert/assertions/utilities_for_test.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/smarty/gunit/assert/assertions/internal/unit" 8 | ) 9 | 10 | /**************************************************************************/ 11 | 12 | func TestAssertionsFixture(t *testing.T) { 13 | unit.Run(new(AssertionsFixture), t) 14 | } 15 | 16 | type AssertionsFixture struct { 17 | *unit.Fixture 18 | } 19 | 20 | func (this *AssertionsFixture) pass(result string) { 21 | this.Assert(result == success, result) 22 | } 23 | 24 | func (this *AssertionsFixture) fail(actual string, expected string) { 25 | actual = format(actual) 26 | expected = format(expected) 27 | 28 | if expected == "[no-check]" && actual == "" { 29 | this.Errorf("Expected fail, but assertion passed.") 30 | } else if expected == "[no-check]" { 31 | return 32 | } 33 | if actual != expected { 34 | if actual == "" { 35 | actual = "(empty)" 36 | } 37 | this.Errorf("Expected: %s\nActual: %s\n", expected, actual) 38 | } 39 | } 40 | func format(message string) string { 41 | message = strings.ReplaceAll(message, "\n", " ") 42 | for strings.Contains(message, " ") { 43 | message = strings.ReplaceAll(message, " ", " ") 44 | } 45 | message = strings.ReplaceAll(message, "\x1b[32m", "") 46 | message = strings.ReplaceAll(message, "\x1b[31m", "") 47 | message = strings.ReplaceAll(message, "\x1b[0m", "") 48 | return message 49 | } 50 | 51 | /**************************************************************************/ 52 | 53 | type Thing1 struct { 54 | a string 55 | } 56 | type Thing2 struct { 57 | a string 58 | } 59 | 60 | type ThingInterface interface { 61 | Hi() 62 | } 63 | 64 | type ThingImplementation struct{} 65 | 66 | func (self *ThingImplementation) Hi() {} 67 | -------------------------------------------------------------------------------- /runner.go: -------------------------------------------------------------------------------- 1 | package gunit 2 | 3 | import ( 4 | "reflect" 5 | "runtime" 6 | "testing" 7 | 8 | "github.com/smarty/gunit/scan" 9 | ) 10 | 11 | // Run receives an instance of a struct that embeds *Fixture. 12 | // The struct definition may include Setup*, Teardown*, and Test* 13 | // methods which will be run as an xUnit-style test fixture. 14 | func Run(fixture any, t *testing.T, options ...option) { 15 | t.Helper() 16 | run(fixture, t, newConfig(options...)) 17 | } 18 | 19 | // RunSequential (like Run) receives an instance of a struct that embeds *Fixture. 20 | // The fixture is run in much the same way, except that it will not be run in 21 | // parallel with other fixtures in the same package, nor will test cases of the 22 | // corresponding fixture be run in parallel with each other. 23 | // 24 | // # Deprecated 25 | // 26 | // Use Run(fixture, t, Options.AllSequential()) instead. 27 | func RunSequential(fixture any, t *testing.T) { 28 | t.Helper() 29 | 30 | Run(fixture, t, Options.AllSequential()) 31 | } 32 | 33 | func run(fixture any, t *testing.T, config configuration) { 34 | t.Helper() 35 | 36 | ensureEmbeddedFixture(fixture, t) 37 | 38 | _, filename, _, _ := runtime.Caller(2) 39 | positions := scan.LocateTestCases(filename) 40 | 41 | runner := newFixtureRunner(fixture, t, config, positions) 42 | runner.ScanFixtureForTestCases() 43 | runner.RunTestCases() 44 | } 45 | 46 | func ensureEmbeddedFixture(fixture any, t TestingT) { 47 | fixtureType := reflect.TypeOf(fixture) 48 | embedded, _ := fixtureType.Elem().FieldByName("Fixture") 49 | if embedded.Type != embeddedGoodExample.Type { 50 | t.Fatalf("Type (%v) lacks embedded *gunit.Fixture.", fixtureType) 51 | } 52 | } 53 | 54 | var embeddedGoodExample, _ = reflect.TypeOf(new(struct{ *Fixture })).Elem().FieldByName("Fixture") 55 | -------------------------------------------------------------------------------- /examples/bowling_game_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/smarty/gunit" 7 | "github.com/smarty/gunit/assert/should" 8 | ) 9 | 10 | func TestBowlingGameScoringFixture(t *testing.T) { 11 | gunit.Run(new(BowlingGameScoringFixture), t) 12 | } 13 | 14 | type BowlingGameScoringFixture struct { 15 | *gunit.Fixture 16 | game *Game 17 | } 18 | 19 | func (this *BowlingGameScoringFixture) Setup() { 20 | this.game = NewGame() 21 | } 22 | 23 | func (this *BowlingGameScoringFixture) TestAfterAllGutterBallsTheScoreShouldBeZero() { 24 | this.rollMany(20, 0) 25 | this.assertScore(0) 26 | } 27 | 28 | func (this *BowlingGameScoringFixture) TestAfterAllOnesTheScoreShouldBeTwenty() { 29 | this.rollMany(20, 1) 30 | this.assertScore(20) 31 | } 32 | 33 | func (this *BowlingGameScoringFixture) TestSpareReceivesSingleRollBonus() { 34 | this.rollSpare() 35 | this.game.Roll(4) 36 | this.game.Roll(3) 37 | this.rollMany(16, 0) 38 | this.assertScore(21) 39 | } 40 | 41 | func (this *BowlingGameScoringFixture) TestStrikeReceivesDoubleRollBonus() { 42 | this.rollStrike() 43 | this.game.Roll(4) 44 | this.game.Roll(3) 45 | this.rollMany(16, 0) 46 | this.assertScore(24) 47 | } 48 | 49 | func (this *BowlingGameScoringFixture) TestPerfectGame() { 50 | this.rollMany(12, 10) 51 | this.assertScore(300) 52 | } 53 | 54 | func (this *BowlingGameScoringFixture) assertScore(expected int) { 55 | this.So(this.game.Score(), should.Equal, expected) 56 | } 57 | func (this *BowlingGameScoringFixture) rollMany(times, pins int) { 58 | for x := 0; x < times; x++ { 59 | this.game.Roll(pins) 60 | } 61 | } 62 | func (this *BowlingGameScoringFixture) rollSpare() { 63 | this.rollMany(2, 5) 64 | } 65 | func (this *BowlingGameScoringFixture) rollStrike() { 66 | this.game.Roll(10) 67 | } 68 | -------------------------------------------------------------------------------- /reports/runtime.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | func StackTrace() (frames []Frame) { 11 | stack := make([]uintptr, maxStackDepth) 12 | runtime.Callers(0, stack) 13 | scanner := runtime.CallersFrames(stack) 14 | for { 15 | frame, more := scanner.Next() 16 | if !more { 17 | break 18 | } 19 | frames = append(frames, Frame{ 20 | File: frame.File, 21 | Line: frame.Line, 22 | Function: frame.Function, 23 | }) 24 | } 25 | return frames 26 | } 27 | 28 | type Frame struct { 29 | Function string 30 | File string 31 | Line int 32 | } 33 | 34 | func (frame Frame) isBlank() bool { 35 | return frame.Function == "" && frame.File == "" && frame.Line == 0 36 | } 37 | 38 | func (frame Frame) isFromStandardLibrary() bool { 39 | home, err := os.UserHomeDir() 40 | if err == nil && strings.HasPrefix(frame.File, home) { 41 | return false 42 | } 43 | return strings.Contains(frame.File, "/libexec/src/") || // homebrew 44 | strings.Contains(frame.File, "/go/src/") // traditional 45 | } 46 | 47 | func (frame Frame) isFromGunit() bool { 48 | const gunitBasicExamples = "github.com/smarty/gunit/basic_examples" 49 | const gunitAdvancedExamples = "github.com/smarty/gunit/examples" 50 | const gunitFolder = "github.com/smarty/gunit" 51 | const goModuleVersionSeparator = "@" // Go module path w/ '@' separator example: 52 | // /Users/mike/go/pkg/mod/github.com/smarty/gunit@v1.0.1-0.20190705210239-badfae8b004a/reports/failure_report.go:23 53 | 54 | dir := filepath.Dir(frame.File) 55 | parts := strings.Split(dir, goModuleVersionSeparator) 56 | if len(parts) > 1 { 57 | dir = parts[0] 58 | } 59 | if strings.Contains(dir, gunitBasicExamples) || strings.Contains(dir, gunitAdvancedExamples) { 60 | return false 61 | } 62 | return strings.Contains(dir, gunitFolder) 63 | } 64 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | In general, the code posted to the [Smarty github organization](https://github.com/smarty) is created to solve specific problems at Smarty that are ancillary to our core products in the address verification industry and may or may not be useful to other organizations or developers. Our reason for posting said code isn't necessarily to solicit feedback or contributions from the community but more as a showcase of some of the approaches to solving problems we have adopted. 4 | 5 | Having stated that, we do consider issues raised by other githubbers as well as contributions submitted via pull requests. When submitting such a pull request, please follow these guidelines: 6 | 7 | - _Look before you leap:_ If the changes you plan to make are significant, it's in everyone's best interest for you to discuss them with a Smarty team member prior to opening a pull request. 8 | - _License and ownership:_ If modifying the `LICENSE.md` file, limit your changes to fixing typographical mistakes. Do NOT modify the actual terms in the license or the copyright by **Smarty, LLC**. Code submitted to Smarty projects becomes property of Smarty and must be compatible with the associated license. 9 | - _Testing:_ If the code you are submitting resides in packages/modules covered by automated tests, be sure to add passing tests that cover your changes and assert expected behavior and state. Submit the additional test cases as part of your change set. 10 | - _Style:_ Match your approach to **naming** and **formatting** with the surrounding code. Basically, the code you submit shouldn't stand out. 11 | - "Naming" refers to such constructs as variables, methods, functions, classes, structs, interfaces, packages, modules, directories, files, etc... 12 | - "Formatting" refers to such constructs as whitespace, horizontal line length, vertical function length, vertical file length, indentation, curly braces, etc... 13 | -------------------------------------------------------------------------------- /assert/so_test.go: -------------------------------------------------------------------------------- 1 | package assert_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/smarty/gunit/assert" 10 | "github.com/smarty/gunit/assert/better" 11 | "github.com/smarty/gunit/assert/should" 12 | ) 13 | 14 | func TestSoSuccess(t *testing.T) { 15 | fakeT := &FakeT{buffer: &bytes.Buffer{}} 16 | assert.So(fakeT, 1, should.Equal, 1) 17 | if fakeT.buffer.String() != "" { 18 | t.Errorf("\n"+ 19 | "expected: \n"+ 20 | "actual: %s", fakeT.buffer.String(), 21 | ) 22 | } 23 | } 24 | func TestSoFailure(t *testing.T) { 25 | fakeT := &FakeT{buffer: &bytes.Buffer{}} 26 | assert.So(fakeT, 1, should.Equal, 2) 27 | actual := strings.Join(strings.Fields(fakeT.buffer.String()), " ") 28 | expected := `Expected: 2 Actual: 1 (Should equal)!` 29 | if !strings.HasPrefix(actual, expected) { 30 | t.Errorf("\n"+ 31 | "expected: %s\n"+ 32 | "actual: %s", expected, actual) 33 | } 34 | if fakeT.failCount != 1 { 35 | t.Error("Expected 1 failure, got:", fakeT.failCount) 36 | } 37 | } 38 | func TestSoFatal(t *testing.T) { 39 | fakeT := &FakeT{buffer: &bytes.Buffer{}} 40 | assert.So(fakeT, 1, better.Equal, 2) 41 | actual := strings.Join(strings.Fields(fakeT.buffer.String()), " ") 42 | expected := "<<>> Expected: 2 Actual: 1 (Should equal)!" 43 | if !strings.HasPrefix(actual, expected) { 44 | t.Errorf("\n"+ 45 | "expected: %s\n"+ 46 | "actual: %s", expected, actual) 47 | } 48 | if fakeT.fatalCount != 1 { 49 | t.Error("Expected 1 fatal failure, got:", fakeT.failCount) 50 | } 51 | } 52 | 53 | type FakeT struct { 54 | buffer *bytes.Buffer 55 | failCount int 56 | fatalCount int 57 | } 58 | 59 | func (this *FakeT) Helper() {} 60 | func (this *FakeT) Error(a ...any) { 61 | this.failCount++ 62 | _, _ = fmt.Fprint(this.buffer, a...) 63 | } 64 | func (this *FakeT) Fatal(a ...any) { 65 | this.fatalCount++ 66 | _, _ = fmt.Fprint(this.buffer, a...) 67 | } 68 | -------------------------------------------------------------------------------- /examples/basic_examples_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/smarty/gunit" 7 | "github.com/smarty/gunit/assert/should" 8 | ) 9 | 10 | func TestExampleFixture(t *testing.T) { 11 | gunit.Run(new(ExampleFixture), t) 12 | } 13 | 14 | type ExampleFixture struct { 15 | *gunit.Fixture // Required: Embedding this type is what allows gunit.Run to run the tests in this fixture. 16 | 17 | // Declare useful state here (probably the stuff being testing, any fakes, etc...). 18 | } 19 | 20 | func (this *ExampleFixture) SetupStuff() { 21 | // This optional method will be executed before each "Test" 22 | // method because it starts with "Setup". 23 | } 24 | func (this *ExampleFixture) TeardownStuff() { 25 | // This optional method will be executed after each "Test" 26 | // method (because it starts with "Teardown"), even if the test method panics. 27 | } 28 | 29 | func (this *ExampleFixture) SkipTestWithError() { 30 | this.Error("hi") 31 | } 32 | 33 | func (this *ExampleFixture) SkipTestWithErrorf() { 34 | this.Errorf("hi") 35 | } 36 | 37 | func (this *ExampleFixture) TestWithPrint() { 38 | this.Print("hi") 39 | } 40 | 41 | // This is an actual test case: 42 | func (this *ExampleFixture) TestWithAssertions() { 43 | this.So(42, should.Equal, 42) 44 | this.So("Hello, World!", should.ContainSubstring, "World") 45 | } 46 | 47 | func (this *ExampleFixture) SkipTestWithNothing() { 48 | // Because this method's name starts with 'Skip', this will be skipped when `go test` is run. 49 | } 50 | 51 | func (this *ExampleFixture) LongTest() { 52 | // Because this method's name starts with 'Long', it will be skipped if the -short flag is passed to `go test`. 53 | } 54 | 55 | func (this *ExampleFixture) SkipLongTest() { 56 | // Because this method's name starts with 'Skip', it will be skipped when `go test` is run. Removing the 'Skip' 57 | // prefix would reveal the 'Long' prefix, explained above. 58 | } 59 | -------------------------------------------------------------------------------- /assert/assertions/type.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | // ShouldHaveSameTypeAs receives exactly two parameters and compares their underlying types for equality. 10 | func ShouldHaveSameTypeAs(actual any, expected ...any) string { 11 | if fail := need(1, expected); fail != success { 12 | return fail 13 | } 14 | 15 | first := reflect.TypeOf(actual) 16 | second := reflect.TypeOf(expected[0]) 17 | 18 | if first != second { 19 | return fmt.Sprintf(shouldHaveBeenA, actual, second, first) 20 | } 21 | 22 | return success 23 | } 24 | 25 | // ShouldNotHaveSameTypeAs receives exactly two parameters and compares their underlying types for inequality. 26 | func ShouldNotHaveSameTypeAs(actual any, expected ...any) string { 27 | if fail := need(1, expected); fail != success { 28 | return fail 29 | } 30 | 31 | first := reflect.TypeOf(actual) 32 | second := reflect.TypeOf(expected[0]) 33 | 34 | if (actual == nil && expected[0] == nil) || first == second { 35 | return fmt.Sprintf(shouldNotHaveBeenA, actual, second) 36 | } 37 | return success 38 | } 39 | 40 | // ShouldWrap asserts that the first argument (which must be an error value) 41 | // 'wraps' the second/final argument (which must also be an error value). 42 | // It relies on errors.Is to make the determination (https://golang.org/pkg/errors/#Is). 43 | func ShouldWrap(actual any, expected ...any) string { 44 | if fail := need(1, expected); fail != success { 45 | return fail 46 | } 47 | 48 | if !isError(actual) || !isError(expected[0]) { 49 | return fmt.Sprintf(shouldWrapInvalidTypes, reflect.TypeOf(actual), reflect.TypeOf(expected[0])) 50 | } 51 | 52 | if !errors.Is(actual.(error), expected[0].(error)) { 53 | return fmt.Sprintf(`Expected error("%s") to wrap error("%s") but it didn't.`, actual, expected[0]) 54 | } 55 | 56 | return success 57 | } 58 | 59 | func isError(value any) bool { _, ok := value.(error); return ok } 60 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-render/pre-commit-go.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/maruel/pre-commit-go configuration file to run checks 2 | # automatically on commit, on push and on continuous integration service after 3 | # a push or on merge of a pull request. 4 | # 5 | # See https://godoc.org/github.com/maruel/pre-commit-go/checks for more 6 | # information. 7 | 8 | min_version: 0.4.7 9 | modes: 10 | continuous-integration: 11 | checks: 12 | build: 13 | - build_all: false 14 | extra_args: [] 15 | coverage: 16 | - use_global_inference: false 17 | use_coveralls: true 18 | global: 19 | min_coverage: 50 20 | max_coverage: 100 21 | per_dir_default: 22 | min_coverage: 1 23 | max_coverage: 100 24 | per_dir: {} 25 | gofmt: 26 | - {} 27 | goimports: 28 | - {} 29 | test: 30 | - extra_args: 31 | - -v 32 | - -race 33 | max_duration: 600 34 | lint: 35 | checks: 36 | golint: 37 | - blacklist: [] 38 | govet: 39 | - blacklist: 40 | - ' composite literal uses unkeyed fields' 41 | max_duration: 15 42 | pre-commit: 43 | checks: 44 | build: 45 | - build_all: false 46 | extra_args: [] 47 | gofmt: 48 | - {} 49 | test: 50 | - extra_args: 51 | - -short 52 | max_duration: 35 53 | pre-push: 54 | checks: 55 | coverage: 56 | - use_global_inference: false 57 | use_coveralls: false 58 | global: 59 | min_coverage: 50 60 | max_coverage: 100 61 | per_dir_default: 62 | min_coverage: 1 63 | max_coverage: 100 64 | per_dir: {} 65 | goimports: 66 | - {} 67 | test: 68 | - extra_args: 69 | - -v 70 | - -race 71 | max_duration: 35 72 | 73 | ignore_patterns: 74 | - .* 75 | - _* 76 | - '*.pb.go' 77 | - '*_string.go' 78 | - '*-gen.go' 79 | -------------------------------------------------------------------------------- /assert/assertions/internal/oglematchers/contains.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Aaron Jacobs. All Rights Reserved. 2 | // Author: aaronjjacobs@gmail.com (Aaron Jacobs) 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package oglematchers 17 | 18 | import ( 19 | "fmt" 20 | "reflect" 21 | ) 22 | 23 | // Return a matcher that matches arrays slices with at least one element that 24 | // matches the supplied argument. If the argument x is not itself a Matcher, 25 | // this is equivalent to Contains(Equals(x)). 26 | func Contains(x any) Matcher { 27 | var result containsMatcher 28 | var ok bool 29 | 30 | if result.elementMatcher, ok = x.(Matcher); !ok { 31 | result.elementMatcher = DeepEquals(x) 32 | } 33 | 34 | return &result 35 | } 36 | 37 | type containsMatcher struct { 38 | elementMatcher Matcher 39 | } 40 | 41 | func (m *containsMatcher) Description() string { 42 | return fmt.Sprintf("contains: %s", m.elementMatcher.Description()) 43 | } 44 | 45 | func (m *containsMatcher) Matches(candidate any) error { 46 | // The candidate must be a slice or an array. 47 | v := reflect.ValueOf(candidate) 48 | if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { 49 | return NewFatalError("which is not a slice or array") 50 | } 51 | 52 | // Check each element. 53 | for i := 0; i < v.Len(); i++ { 54 | elem := v.Index(i) 55 | if matchErr := m.elementMatcher.Matches(elem.Interface()); matchErr == nil { 56 | return nil 57 | } 58 | } 59 | 60 | return fmt.Errorf("") 61 | } 62 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-diff/diffmatchpatch/diffmatchpatch.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2016 The go-diff authors. All rights reserved. 2 | // https://github.com/sergi/go-diff 3 | // See the included LICENSE file for license details. 4 | // 5 | // go-diff is a Go implementation of Google's Diff, Match, and Patch library 6 | // Original library is Copyright (c) 2006 Google Inc. 7 | // http://code.google.com/p/google-diff-match-patch/ 8 | 9 | // Package diffmatchpatch offers robust algorithms to perform the operations required for synchronizing plain text. 10 | package diffmatchpatch 11 | 12 | import ( 13 | "time" 14 | ) 15 | 16 | // DiffMatchPatch holds the configuration for diff-match-patch operations. 17 | type DiffMatchPatch struct { 18 | // Number of seconds to map a diff before giving up (0 for infinity). 19 | DiffTimeout time.Duration 20 | // Cost of an empty edit operation in terms of edit characters. 21 | DiffEditCost int 22 | // How far to search for a match (0 = exact location, 1000+ = broad match). A match this many characters away from the expected location will add 1.0 to the score (0.0 is a perfect match). 23 | MatchDistance int 24 | // When deleting a large block of text (over ~64 characters), how close do the contents have to be to match the expected contents. (0.0 = perfection, 1.0 = very loose). Note that MatchThreshold controls how closely the end points of a delete need to match. 25 | PatchDeleteThreshold float64 26 | // Chunk size for context length. 27 | PatchMargin int 28 | // The number of bits in an int. 29 | MatchMaxBits int 30 | // At what point is no match declared (0.0 = perfection, 1.0 = very loose). 31 | MatchThreshold float64 32 | } 33 | 34 | // New creates a new DiffMatchPatch object with default parameters. 35 | func New() *DiffMatchPatch { 36 | // Defaults. 37 | return &DiffMatchPatch{ 38 | DiffTimeout: time.Second, 39 | DiffEditCost: 4, 40 | MatchThreshold: 0.5, 41 | MatchDistance: 1000, 42 | PatchDeleteThreshold: 0.5, 43 | PatchMargin: 4, 44 | MatchMaxBits: 32, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /config_test.go: -------------------------------------------------------------------------------- 1 | package gunit 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/smarty/gunit/assertions" 7 | ) 8 | 9 | func TestConfigOptions(t *testing.T) { 10 | assert := assertions.New(t) 11 | 12 | assert.AssertDeepEqual(configuration{}, newConfig()) 13 | 14 | assert.AssertDeepEqual( 15 | configuration{ 16 | LongRunningTestCases: true, 17 | }, 18 | newConfig(Options.LongRunning()), 19 | ) 20 | 21 | assert.AssertDeepEqual( 22 | configuration{ 23 | SkippedTestCases: true, 24 | }, 25 | newConfig(Options.SkipAll()), 26 | ) 27 | 28 | assert.AssertDeepEqual( 29 | configuration{ 30 | SequentialFixture: true, 31 | }, 32 | newConfig(Options.SequentialFixture()), 33 | ) 34 | 35 | assert.AssertDeepEqual( 36 | configuration{ 37 | SequentialTestCases: true, 38 | }, 39 | newConfig(Options.SequentialTestCases()), 40 | ) 41 | 42 | assert.AssertDeepEqual( 43 | configuration{ 44 | SequentialTestCases: true, 45 | SequentialFixture: true, 46 | }, 47 | newConfig(Options.AllSequential()), 48 | ) 49 | 50 | assert.AssertDeepEqual( 51 | configuration{ 52 | SequentialFixture: true, 53 | SequentialTestCases: true, 54 | SkippedTestCases: true, 55 | LongRunningTestCases: true, 56 | }, 57 | newConfig(Options.AllSequential(), Options.SkipAll(), Options.LongRunning()), 58 | ) 59 | } 60 | 61 | func TestConfigMethods(t *testing.T) { 62 | assert := assertions.New(t) 63 | 64 | parallel := newConfig() 65 | assert.AssertTrue(parallel.ParallelFixture()) 66 | assert.AssertTrue(parallel.ParallelTestCases()) 67 | 68 | sequentialFixture := newConfig(Options.SequentialFixture()) 69 | assert.AssertFalse(sequentialFixture.ParallelFixture()) 70 | assert.AssertTrue(sequentialFixture.ParallelTestCases()) 71 | 72 | sequentialTestCases := newConfig(Options.SequentialTestCases()) 73 | assert.AssertTrue(sequentialTestCases.ParallelFixture()) 74 | assert.AssertFalse(sequentialTestCases.ParallelTestCases()) 75 | 76 | sequential := newConfig(Options.AllSequential()) 77 | assert.AssertFalse(sequential.ParallelFixture()) 78 | assert.AssertFalse(sequential.ParallelTestCases()) 79 | } 80 | -------------------------------------------------------------------------------- /scan/3_method_finder.go: -------------------------------------------------------------------------------- 1 | package scan 2 | 3 | import ( 4 | "go/ast" 5 | "strings" 6 | ) 7 | 8 | type fixtureMethodFinder struct { 9 | fixtures map[string]*fixtureInfo 10 | } 11 | 12 | func newFixtureMethodFinder(fixtures map[string]*fixtureInfo) *fixtureMethodFinder { 13 | return &fixtureMethodFinder{fixtures: fixtures} 14 | } 15 | 16 | func (this *fixtureMethodFinder) Find(file *ast.File) map[string]*fixtureInfo { 17 | ast.Walk(this, file) // Calls this.Visit(...) recursively. 18 | return this.fixtures 19 | } 20 | 21 | func (this *fixtureMethodFinder) Visit(node ast.Node) ast.Visitor { 22 | function, isFunction := node.(*ast.FuncDecl) 23 | if !isFunction { 24 | return this 25 | } 26 | 27 | if function.Recv.NumFields() == 0 { 28 | return nil 29 | } 30 | 31 | receiver, isPointer := function.Recv.List[0].Type.(*ast.StarExpr) 32 | if !isPointer { 33 | return this 34 | } 35 | 36 | fixtureName := receiver.X.(*ast.Ident).Name 37 | fixture, functionMatchesFixture := this.fixtures[fixtureName] 38 | if !functionMatchesFixture { 39 | return nil 40 | } 41 | 42 | if !isExportedAndVoidAndNiladic(function) { 43 | return this 44 | } 45 | 46 | this.attach(function, fixture) 47 | return nil 48 | } 49 | 50 | func isExportedAndVoidAndNiladic(function *ast.FuncDecl) bool { 51 | if isExported := function.Name.IsExported(); !isExported { 52 | return false 53 | } 54 | if isNiladic := function.Type.Params.NumFields() == 0; !isNiladic { 55 | return false 56 | } 57 | isVoid := function.Type.Results.NumFields() == 0 58 | return isVoid 59 | } 60 | 61 | func (this *fixtureMethodFinder) attach(function *ast.FuncDecl, fixture *fixtureInfo) { 62 | if IsTestCase(function.Name.Name) { 63 | fixture.TestCases = append(fixture.TestCases, &testCaseInfo{ 64 | CharacterPosition: int(function.Pos()), 65 | Name: function.Name.Name, 66 | }) 67 | } 68 | } 69 | 70 | func IsTestCase(name string) bool { 71 | return strings.HasPrefix(name, "Test") || 72 | strings.HasPrefix(name, "LongTest") || 73 | strings.HasPrefix(name, "FocusTest") || 74 | strings.HasPrefix(name, "FocusLongTest") || 75 | strings.HasPrefix(name, "SkipTest") || 76 | strings.HasPrefix(name, "SkipLongTest") 77 | } 78 | -------------------------------------------------------------------------------- /assert/assertions/internal/oglematchers/README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/smarty/assertions/internal/oglematchers?status.svg)](https://godoc.org/github.com/smarty/assertions/internal/oglematchers) 2 | 3 | `oglematchers` is a package for the Go programming language containing a set of 4 | matchers, useful in a testing or mocking framework, inspired by and mostly 5 | compatible with [Google Test][googletest] for C++ and 6 | [Google JS Test][google-js-test]. The package is used by the 7 | [ogletest][ogletest] testing framework and [oglemock][oglemock] mocking 8 | framework, which may be more directly useful to you, but can be generically used 9 | elsewhere as well. 10 | 11 | A "matcher" is simply an object with a `Matches` method defining a set of golang 12 | values matched by the matcher, and a `Description` method describing that set. 13 | For example, here are some matchers: 14 | 15 | ```go 16 | // Numbers 17 | Equals(17.13) 18 | LessThan(19) 19 | 20 | // Strings 21 | Equals("taco") 22 | HasSubstr("burrito") 23 | MatchesRegex("t.*o") 24 | 25 | // Combining matchers 26 | AnyOf(LessThan(17), GreaterThan(19)) 27 | ``` 28 | 29 | There are lots more; see [here][reference] for a reference. You can also add 30 | your own simply by implementing the `oglematchers.Matcher` interface. 31 | 32 | 33 | Installation 34 | ------------ 35 | 36 | First, make sure you have installed Go 1.0.2 or newer. See 37 | [here][golang-install] for instructions. 38 | 39 | Use the following command to install `oglematchers` and keep it up to date: 40 | 41 | go get -u github.com/smarty/assertions/internal/oglematchers 42 | 43 | 44 | Documentation 45 | ------------- 46 | 47 | See [here][reference] for documentation. Alternatively, you can install the 48 | package and then use `godoc`: 49 | 50 | godoc github.com/smarty/assertions/internal/oglematchers 51 | 52 | 53 | [reference]: http://godoc.org/github.com/smarty/assertions/internal/oglematchers 54 | [golang-install]: http://golang.org/doc/install.html 55 | [googletest]: http://code.google.com/p/googletest/ 56 | [google-js-test]: http://code.google.com/p/google-js-test/ 57 | [ogletest]: http://github.com/smarty/assertions/internal/ogletest 58 | [oglemock]: http://github.com/smarty/assertions/internal/oglemock 59 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package gunit 2 | 3 | type option func(*configuration) 4 | 5 | var Options singleton 6 | 7 | type singleton struct{} 8 | 9 | // SkipAll is an option meant to be passed to gunit.Run(...) 10 | // and causes each and every "Test" method in the corresponding 11 | // fixture to be skipped (as if each had been prefixed with 12 | // "Skip"). Even "Test" methods marked with the "Focus" prefix 13 | // will be skipped. 14 | func (singleton) SkipAll() option { 15 | return func(this *configuration) { 16 | this.SkippedTestCases = true 17 | } 18 | } 19 | 20 | // LongRunning is an option meant to be passed to 21 | // gunit.Run(...) and, in the case that the -short testing 22 | // flag has been passed at the command line, it causes each 23 | // and every "Test" method in the corresponding fixture to 24 | // be skipped (as if each had been prefixed with "Skip"). 25 | func (singleton) LongRunning() option { 26 | return func(this *configuration) { 27 | this.LongRunningTestCases = true 28 | } 29 | } 30 | 31 | // SequentialFixture is an option meant to be passed to 32 | // gunit.Run(...) and signals that the corresponding fixture 33 | // is not to be run in parallel with any tests (by not calling 34 | // t.Parallel() on the provided *testing.T). 35 | func (singleton) SequentialFixture() option { 36 | return func(this *configuration) { 37 | this.SequentialFixture = true 38 | } 39 | } 40 | 41 | // SequentialTestCases is an option meant to be passed to 42 | // gunit.Run(...) and prevents gunit from calling t.Parallel() 43 | // on the inner instances of *testing.T passed to the 'subtests' 44 | // corresponding to "Test" methods which are created during 45 | // the natural course of the corresponding invocation of 46 | // gunit.Run(...). 47 | func (singleton) SequentialTestCases() option { 48 | return func(this *configuration) { 49 | this.SequentialTestCases = true 50 | } 51 | } 52 | 53 | // AllSequential has the combined effect of passing the 54 | // following options to gunit.Run(...): 55 | // 1. SequentialFixture 56 | // 2. SequentialTestCases 57 | func (singleton) AllSequential() option { 58 | return Options.composite( 59 | Options.SequentialFixture(), 60 | Options.SequentialTestCases(), 61 | ) 62 | } 63 | 64 | // composite allows graceful chaining of options. 65 | func (singleton) composite(options ...option) option { 66 | return func(this *configuration) { 67 | for _, option := range options { 68 | option(this) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/environment_controller_hvac_fake_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import "strings" 4 | 5 | type FakeHVAC struct { 6 | state map[string]bool 7 | temperature int 8 | } 9 | 10 | func NewFakeHardware() *FakeHVAC { 11 | return &FakeHVAC{ 12 | state: map[string]bool{ 13 | "heater": false, 14 | "blower": false, 15 | "cooler": false, 16 | "high-temp-alarm": false, 17 | "low-temp-alarm": false, 18 | }, 19 | } 20 | } 21 | 22 | func (this *FakeHVAC) ActivateHeater() { this.state["heater"] = true } 23 | func (this *FakeHVAC) ActivateBlower() { this.state["blower"] = true } 24 | func (this *FakeHVAC) ActivateCooler() { this.state["cooler"] = true } 25 | func (this *FakeHVAC) ActivateHighTemperatureAlarm() { this.state["high"] = true } 26 | func (this *FakeHVAC) ActivateLowTemperatureAlarm() { this.state["low"] = true } 27 | 28 | func (this *FakeHVAC) DeactivateHeater() { this.state["heater"] = false } 29 | func (this *FakeHVAC) DeactivateBlower() { this.state["blower"] = false } 30 | func (this *FakeHVAC) DeactivateCooler() { this.state["cooler"] = false } 31 | func (this *FakeHVAC) DeactivateHighTemperatureAlarm() { this.state["high"] = false } 32 | func (this *FakeHVAC) DeactivateLowTemperatureAlarm() { this.state["low"] = false } 33 | 34 | func (this *FakeHVAC) IsHeating() bool { return this.state["heater"] } 35 | func (this *FakeHVAC) IsBlowing() bool { return this.state["blower"] } 36 | func (this *FakeHVAC) IsCooling() bool { return this.state["cooler"] } 37 | func (this *FakeHVAC) HighTemperatureAlarm() bool { return this.state["high"] } 38 | func (this *FakeHVAC) LowTemperatureAlarm() bool { return this.state["low"] } 39 | 40 | func (this *FakeHVAC) SetCurrentTemperature(value int) { this.temperature = value } 41 | func (this *FakeHVAC) CurrentTemperature() int { return this.temperature } 42 | 43 | // String returns the status of each hardware component encoded in a single space-delimited string. 44 | // UPPERCASE components are activated. 45 | // lowercase components are deactivated. 46 | func (this *FakeHVAC) String() string { 47 | current := []string{"heater", "blower", "cooler", "low", "high"} 48 | for i, component := range current { 49 | if this.state[component] { 50 | current[i] = strings.ToUpper(current[i]) 51 | } 52 | } 53 | return strings.Join(current, " ") 54 | } 55 | -------------------------------------------------------------------------------- /assert/assertions/type_test.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import "fmt" 4 | 5 | func (this *AssertionsFixture) TestShouldHaveSameTypeAs() { 6 | this.fail(so(1, ShouldHaveSameTypeAs), "This assertion requires exactly 1 comparison values (you provided 0).") 7 | this.fail(so(1, ShouldHaveSameTypeAs, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).") 8 | 9 | this.fail(so(nil, ShouldHaveSameTypeAs, 0), "Expected '' to be: 'int' (but was: '')!") 10 | this.fail(so(1, ShouldHaveSameTypeAs, "asdf"), "Expected '1' to be: 'string' (but was: 'int')!") 11 | 12 | this.pass(so(1, ShouldHaveSameTypeAs, 0)) 13 | this.pass(so(nil, ShouldHaveSameTypeAs, nil)) 14 | } 15 | 16 | func (this *AssertionsFixture) TestShouldNotHaveSameTypeAs() { 17 | this.fail(so(1, ShouldNotHaveSameTypeAs), "This assertion requires exactly 1 comparison values (you provided 0).") 18 | this.fail(so(1, ShouldNotHaveSameTypeAs, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).") 19 | 20 | this.fail(so(1, ShouldNotHaveSameTypeAs, 0), "Expected '1' to NOT be: 'int' (but it was)!") 21 | this.fail(so(nil, ShouldNotHaveSameTypeAs, nil), "Expected '' to NOT be: '' (but it was)!") 22 | 23 | this.pass(so(nil, ShouldNotHaveSameTypeAs, 0)) 24 | this.pass(so(1, ShouldNotHaveSameTypeAs, "asdf")) 25 | } 26 | 27 | func (this *AssertionsFixture) TestShouldWrapError() { 28 | inner := fmt.Errorf("inner") 29 | middle := fmt.Errorf("middle(%w)", inner) 30 | outer := fmt.Errorf("outer(%w)", middle) 31 | 32 | this.fail(so(outer, ShouldWrap, "too", "many"), "This assertion requires exactly 1 comparison values (you provided 2).") 33 | this.fail(so(outer, ShouldWrap), "This assertion requires exactly 1 comparison values (you provided 0).") 34 | 35 | this.fail(so(42, ShouldWrap, 42), "The first and last arguments to this assertion must both be error values (you provided: 'int' and 'int').") 36 | this.fail(so(inner, ShouldWrap, 42), "The first and last arguments to this assertion must both be error values (you provided: '*errors.errorString' and 'int').") 37 | this.fail(so(42, ShouldWrap, inner), "The first and last arguments to this assertion must both be error values (you provided: 'int' and '*errors.errorString').") 38 | 39 | this.fail(so(inner, ShouldWrap, outer), `Expected error("inner") to wrap error("outer(middle(inner))") but it didn't.`) 40 | this.pass(so(middle, ShouldWrap, inner)) 41 | this.pass(so(outer, ShouldWrap, middle)) 42 | this.pass(so(outer, ShouldWrap, inner)) 43 | } 44 | -------------------------------------------------------------------------------- /fixture_runner.go: -------------------------------------------------------------------------------- 1 | package gunit 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/smarty/gunit/scan" 8 | ) 9 | 10 | func newFixtureRunner( 11 | fixture any, 12 | outerT *testing.T, 13 | config configuration, 14 | positions scan.TestCasePositions, 15 | ) *fixtureRunner { 16 | if config.ParallelFixture() { 17 | outerT.Parallel() 18 | } 19 | return &fixtureRunner{ 20 | config: config, 21 | setup: -1, 22 | teardown: -1, 23 | outerT: outerT, 24 | fixtureType: reflect.ValueOf(fixture).Type(), 25 | positions: positions, 26 | } 27 | } 28 | 29 | type fixtureRunner struct { 30 | outerT *testing.T 31 | fixtureType reflect.Type 32 | 33 | config configuration 34 | setup int 35 | teardown int 36 | focus []*testCase 37 | tests []*testCase 38 | positions scan.TestCasePositions 39 | } 40 | 41 | func (this *fixtureRunner) ScanFixtureForTestCases() { 42 | for methodIndex := 0; methodIndex < this.fixtureType.NumMethod(); methodIndex++ { 43 | methodName := this.fixtureType.Method(methodIndex).Name 44 | this.scanFixtureMethod(methodIndex, this.newFixtureMethodInfo(methodName)) 45 | } 46 | } 47 | 48 | func (this *fixtureRunner) scanFixtureMethod(methodIndex int, method fixtureMethodInfo) { 49 | switch { 50 | case method.isSetup: 51 | this.setup = methodIndex 52 | case method.isTeardown: 53 | this.teardown = methodIndex 54 | case method.isFocusTest: 55 | this.focus = append(this.focus, this.buildTestCase(methodIndex, method)) 56 | case method.isTest: 57 | this.tests = append(this.tests, this.buildTestCase(methodIndex, method)) 58 | } 59 | } 60 | 61 | func (this *fixtureRunner) buildTestCase(methodIndex int, method fixtureMethodInfo) *testCase { 62 | return newTestCase(methodIndex, method, this.config, this.positions) 63 | } 64 | 65 | func (this *fixtureRunner) RunTestCases() { 66 | this.outerT.Helper() 67 | 68 | if len(this.focus) > 0 { 69 | this.tests = append(this.focus, skipped(this.tests)...) 70 | } 71 | if len(this.tests) > 0 { 72 | this.runTestCases(this.tests) 73 | } else { 74 | this.outerT.Skipf("Fixture (%v) has no test cases.", this.fixtureType) 75 | } 76 | } 77 | 78 | func (this *fixtureRunner) runTestCases(cases []*testCase) { 79 | this.outerT.Helper() 80 | 81 | for _, test := range cases { 82 | test.Prepare(this.setup, this.teardown, this.fixtureType) 83 | test.Run(this.outerT) 84 | } 85 | } 86 | 87 | func skipped(cases []*testCase) []*testCase { 88 | for _, test := range cases { 89 | test.skipped = true 90 | } 91 | return cases 92 | } 93 | -------------------------------------------------------------------------------- /assert/assertions/internal/oglematchers/deep_equals.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Aaron Jacobs. All Rights Reserved. 2 | // Author: aaronjjacobs@gmail.com (Aaron Jacobs) 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package oglematchers 17 | 18 | import ( 19 | "bytes" 20 | "errors" 21 | "fmt" 22 | "reflect" 23 | ) 24 | 25 | var byteSliceType reflect.Type = reflect.TypeOf([]byte{}) 26 | 27 | // DeepEquals returns a matcher that matches based on 'deep equality', as 28 | // defined by the reflect package. This matcher requires that values have 29 | // identical types to x. 30 | func DeepEquals(x any) Matcher { 31 | return &deepEqualsMatcher{x} 32 | } 33 | 34 | type deepEqualsMatcher struct { 35 | x any 36 | } 37 | 38 | func (m *deepEqualsMatcher) Description() string { 39 | xDesc := fmt.Sprintf("%v", m.x) 40 | xValue := reflect.ValueOf(m.x) 41 | 42 | // Special case: fmt.Sprintf presents nil slices as "[]", but 43 | // reflect.DeepEqual makes a distinction between nil and empty slices. Make 44 | // this less confusing. 45 | if xValue.Kind() == reflect.Slice && xValue.IsNil() { 46 | xDesc = "" 47 | } 48 | 49 | return fmt.Sprintf("deep equals: %s", xDesc) 50 | } 51 | 52 | func (m *deepEqualsMatcher) Matches(c any) error { 53 | // Make sure the types match. 54 | ct := reflect.TypeOf(c) 55 | xt := reflect.TypeOf(m.x) 56 | 57 | if ct != xt { 58 | return NewFatalError(fmt.Sprintf("which is of type %v", ct)) 59 | } 60 | 61 | // Special case: handle byte slices more efficiently. 62 | cValue := reflect.ValueOf(c) 63 | xValue := reflect.ValueOf(m.x) 64 | 65 | if ct == byteSliceType && !cValue.IsNil() && !xValue.IsNil() { 66 | xBytes := m.x.([]byte) 67 | cBytes := c.([]byte) 68 | 69 | if bytes.Equal(cBytes, xBytes) { 70 | return nil 71 | } 72 | 73 | return errors.New("") 74 | } 75 | 76 | // Defer to the reflect package. 77 | if reflect.DeepEqual(m.x, c) { 78 | return nil 79 | } 80 | 81 | // Special case: if the comparison failed because c is the nil slice, given 82 | // an indication of this (since its value is printed as "[]"). 83 | if cValue.Kind() == reflect.Slice && cValue.IsNil() { 84 | return errors.New("which is nil") 85 | } 86 | 87 | return errors.New("") 88 | } 89 | -------------------------------------------------------------------------------- /reports/failure_report.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/smarty/gunit/scan" 11 | ) 12 | 13 | func FailureReport(failure string, stack []Frame) string { 14 | report := newFailureReport(failure, readFile) 15 | report.scanStack(stack) 16 | return report.composeReport() 17 | } 18 | 19 | type failureReport struct { 20 | stack []string 21 | files map[string][]string 22 | reader fileReader 23 | 24 | method string 25 | fixture string 26 | package_ string 27 | failure string 28 | } 29 | 30 | func newFailureReport(failure string, reader fileReader) *failureReport { 31 | return &failureReport{ 32 | failure: failure, 33 | files: make(map[string][]string), 34 | reader: reader, 35 | } 36 | } 37 | 38 | func (this *failureReport) scanStack(stack []Frame) { 39 | for _, frame := range stack { 40 | if frame.isBlank() || frame.isFromStandardLibrary() || frame.isFromGunit() { 41 | continue 42 | } 43 | this.parseTestName(frame.Function) 44 | this.loadFile(frame) 45 | code := this.extractLineOfCode(frame) 46 | filename := filepath.Base(frame.File) 47 | stack := fmt.Sprintf("%s // %s:%d", code, filename, frame.Line) 48 | this.stack = append(this.stack, strings.TrimSpace(stack)) 49 | } 50 | } 51 | 52 | func (this *failureReport) loadFile(frame Frame) { 53 | if _, found := this.files[frame.File]; !found { 54 | this.files[frame.File] = strings.Split(this.reader(frame.File), "\n") 55 | } 56 | } 57 | 58 | func (this *failureReport) extractLineOfCode(frame Frame) string { 59 | file := this.files[frame.File] 60 | if len(file) < frame.Line { 61 | return "" 62 | } 63 | return strings.TrimSpace(file[frame.Line-1]) 64 | } 65 | 66 | func (this *failureReport) parseTestName(name string) { 67 | if len(this.method) > 0 { 68 | return 69 | } 70 | parts := strings.Split(name, ".") 71 | partCount := len(parts) 72 | last := partCount - 1 73 | if partCount < 3 { 74 | return 75 | } 76 | 77 | if method := parts[last]; scan.IsTestCase(method) { 78 | this.method = method 79 | this.fixture = parts[last-1] 80 | this.package_ = strings.Join(parts[0:last-1], ".") 81 | } 82 | } 83 | 84 | func (this *failureReport) composeReport() string { 85 | buffer := new(bytes.Buffer) 86 | for i, stack := range this.stack { 87 | _, _ = fmt.Fprintf(buffer, "(%d): %s\n", len(this.stack)-i-1, stack) 88 | } 89 | _, _ = fmt.Fprint(buffer, this.failure) 90 | return buffer.String() + "\n\n" 91 | } 92 | 93 | const maxStackDepth = 32 94 | 95 | type fileReader func(path string) string 96 | 97 | func readFile(path string) string { 98 | raw, err := os.ReadFile(path) 99 | if err != nil { 100 | return "" 101 | } 102 | return string(raw) 103 | } 104 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-diff/diffmatchpatch/stringutil.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2016 The go-diff authors. All rights reserved. 2 | // https://github.com/sergi/go-diff 3 | // See the included LICENSE file for license details. 4 | // 5 | // go-diff is a Go implementation of Google's Diff, Match, and Patch library 6 | // Original library is Copyright (c) 2006 Google Inc. 7 | // http://code.google.com/p/google-diff-match-patch/ 8 | 9 | package diffmatchpatch 10 | 11 | import ( 12 | "strings" 13 | "unicode/utf8" 14 | ) 15 | 16 | // unescaper unescapes selected chars for compatibility with JavaScript's encodeURI. 17 | // In speed critical applications this could be dropped since the receiving application will certainly decode these fine. Note that this function is case-sensitive. Thus "%3F" would not be unescaped. But this is ok because it is only called with the output of HttpUtility.UrlEncode which returns lowercase hex. Example: "%3f" -> "?", "%24" -> "$", etc. 18 | var unescaper = strings.NewReplacer( 19 | "%21", "!", "%7E", "~", "%27", "'", 20 | "%28", "(", "%29", ")", "%3B", ";", 21 | "%2F", "/", "%3F", "?", "%3A", ":", 22 | "%40", "@", "%26", "&", "%3D", "=", 23 | "%2B", "+", "%24", "$", "%2C", ",", "%23", "#", "%2A", "*") 24 | 25 | // indexOf returns the first index of pattern in str, starting at str[i]. 26 | func indexOf(str string, pattern string, i int) int { 27 | if i > len(str)-1 { 28 | return -1 29 | } 30 | if i <= 0 { 31 | return strings.Index(str, pattern) 32 | } 33 | ind := strings.Index(str[i:], pattern) 34 | if ind == -1 { 35 | return -1 36 | } 37 | return ind + i 38 | } 39 | 40 | // lastIndexOf returns the last index of pattern in str, starting at str[i]. 41 | func lastIndexOf(str string, pattern string, i int) int { 42 | if i < 0 { 43 | return -1 44 | } 45 | if i >= len(str) { 46 | return strings.LastIndex(str, pattern) 47 | } 48 | _, size := utf8.DecodeRuneInString(str[i:]) 49 | return strings.LastIndex(str[:i+size], pattern) 50 | } 51 | 52 | // runesIndexOf returns the index of pattern in target, starting at target[i]. 53 | func runesIndexOf(target, pattern []rune, i int) int { 54 | if i > len(target)-1 { 55 | return -1 56 | } 57 | if i <= 0 { 58 | return runesIndex(target, pattern) 59 | } 60 | ind := runesIndex(target[i:], pattern) 61 | if ind == -1 { 62 | return -1 63 | } 64 | return ind + i 65 | } 66 | 67 | func runesEqual(r1, r2 []rune) bool { 68 | if len(r1) != len(r2) { 69 | return false 70 | } 71 | for i, c := range r1 { 72 | if c != r2[i] { 73 | return false 74 | } 75 | } 76 | return true 77 | } 78 | 79 | // runesIndex is the equivalent of strings.Index for rune slices. 80 | func runesIndex(r1, r2 []rune) int { 81 | last := len(r1) - len(r2) 82 | for i := 0; i <= last; i++ { 83 | if runesEqual(r1[i:i+len(r2)], r2) { 84 | return i 85 | } 86 | } 87 | return -1 88 | } 89 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-render/README.md: -------------------------------------------------------------------------------- 1 | go-render: A verbose recursive Go type-to-string conversion library. 2 | ==================================================================== 3 | 4 | [![GoDoc](https://godoc.org/github.com/luci/go-render?status.svg)](https://godoc.org/github.com/luci/go-render) 5 | [![Build Status](https://travis-ci.org/luci/go-render.svg)](https://travis-ci.org/luci/go-render) 6 | 7 | This is not an official Google product. 8 | 9 | ## Overview 10 | 11 | The *render* package implements a more verbose form of the standard Go string 12 | formatter, `fmt.Sprintf("%#v", value)`, adding: 13 | - Pointer recursion. Normally, Go stops at the first pointer and prints its 14 | address. The *render* package will recurse and continue to render pointer 15 | values. 16 | - Recursion loop detection. Recursion is nice, but if a recursion path detects 17 | a loop, *render* will note this and move on. 18 | - Custom type name rendering. 19 | - Deterministic key sorting for `string`- and `int`-keyed maps. 20 | - Testing! 21 | 22 | Call `render.Render` and pass it an `any`. 23 | 24 | For example: 25 | 26 | ```Go 27 | type customType int 28 | type testStruct struct { 29 | S string 30 | V *map[string]int 31 | I any 32 | } 33 | 34 | a := testStruct{ 35 | S: "hello", 36 | V: &map[string]int{"foo": 0, "bar": 1}, 37 | I: customType(42), 38 | } 39 | 40 | fmt.Println("Render test:") 41 | fmt.Printf("fmt.Printf: %#v\n", a))) 42 | fmt.Printf("render.Render: %s\n", Render(a)) 43 | ``` 44 | 45 | Yields: 46 | ``` 47 | fmt.Printf: render.testStruct{S:"hello", V:(*map[string]int)(0x600dd065), I:42} 48 | render.Render: render.testStruct{S:"hello", V:(*map[string]int){"bar":1, "foo":0}, I:render.customType(42)} 49 | ``` 50 | 51 | This is not intended to be a high-performance library, but it's not terrible 52 | either. 53 | 54 | Contributing 55 | ------------ 56 | 57 | * Sign the [Google CLA](https://cla.developers.google.com/clas). 58 | * Make sure your `user.email` and `user.name` are configured in `git config`. 59 | * Install the [pcg](https://github.com/maruel/pre-commit-go) git hook: 60 | `go get -u github.com/maruel/pre-commit-go/cmd/... && pcg` 61 | 62 | Run the following to setup the code review tool and create your first review: 63 | 64 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git $HOME/src/depot_tools 65 | export PATH="$PATH:$HOME/src/depot_tools" 66 | cd $GOROOT/github.com/luci/go-render 67 | git checkout -b work origin/master 68 | 69 | # hack hack 70 | 71 | git commit -a -m "This is awesome\nR=joe@example.com" 72 | # This will ask for your Google Account credentials. 73 | git cl upload -s 74 | # Wait for LGTM over email. 75 | # Check the commit queue box in codereview website. 76 | # Wait for the change to be tested and landed automatically. 77 | 78 | Use `git cl help` and `git cl help ` for more details. 79 | -------------------------------------------------------------------------------- /test_case.go: -------------------------------------------------------------------------------- 1 | package gunit 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/smarty/gunit/scan" 8 | ) 9 | 10 | type testCase struct { 11 | methodIndex int 12 | description string 13 | skipped bool 14 | long bool 15 | parallel bool 16 | 17 | setup int 18 | teardown int 19 | innerFixture *Fixture 20 | outerFixtureType reflect.Type 21 | outerFixture reflect.Value 22 | positions scan.TestCasePositions 23 | } 24 | 25 | func newTestCase(methodIndex int, method fixtureMethodInfo, config configuration, positions scan.TestCasePositions) *testCase { 26 | return &testCase{ 27 | parallel: config.ParallelTestCases(), 28 | methodIndex: methodIndex, 29 | description: method.name, 30 | skipped: method.isSkippedTest || config.SkippedTestCases, 31 | long: method.isLongTest || config.LongRunningTestCases, 32 | positions: positions, 33 | } 34 | } 35 | 36 | func (this *testCase) Prepare(setup, teardown int, outerFixtureType reflect.Type) { 37 | this.setup = setup 38 | this.teardown = teardown 39 | this.outerFixtureType = outerFixtureType 40 | } 41 | 42 | func (this *testCase) Run(t *testing.T) { 43 | t.Helper() 44 | 45 | if this.skipped { 46 | t.Run(this.description, this.skip) 47 | } else if this.long && testing.Short() { 48 | t.Run(this.description, this.skipLong) 49 | } else { 50 | t.Run(this.description, this.run) 51 | } 52 | } 53 | 54 | func (this *testCase) skip(innerT *testing.T) { 55 | innerT.Skip("\n" + this.positions[innerT.Name()]) 56 | } 57 | func (this *testCase) skipLong(innerT *testing.T) { 58 | innerT.Skip("Skipped long-running test:\n" + this.positions[innerT.Name()]) 59 | } 60 | func (this *testCase) run(innerT *testing.T) { 61 | innerT.Helper() 62 | 63 | if this.parallel { 64 | innerT.Parallel() 65 | } 66 | this.initializeFixture(innerT) 67 | defer this.innerFixture.finalize() 68 | this.runWithSetupAndTeardown() 69 | if innerT.Failed() { 70 | innerT.Log("Test definition:\n" + this.positions[innerT.Name()]) 71 | } 72 | } 73 | func (this *testCase) initializeFixture(innerT *testing.T) { 74 | this.innerFixture = newFixture(innerT, testing.Verbose()) 75 | this.outerFixture = reflect.New(this.outerFixtureType.Elem()) 76 | this.outerFixture.Elem().FieldByName("Fixture").Set(reflect.ValueOf(this.innerFixture)) 77 | } 78 | 79 | func (this *testCase) runWithSetupAndTeardown() { 80 | this.runSetup() 81 | defer this.runTeardown() 82 | this.runTest() 83 | } 84 | 85 | func (this *testCase) runSetup() { 86 | if this.setup >= 0 { 87 | this.outerFixture.Method(this.setup).Call(nil) 88 | } 89 | } 90 | 91 | func (this *testCase) runTest() { 92 | this.outerFixture.Method(this.methodIndex).Call(nil) 93 | } 94 | 95 | func (this *testCase) runTeardown() { 96 | if this.teardown >= 0 { 97 | this.outerFixture.Method(this.teardown).Call(nil) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /scan/parser_test.go: -------------------------------------------------------------------------------- 1 | package scan 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | ////////////////////////////////////////////////////////////////////////////// 9 | 10 | func TestParseFileWithValidFixturesAndConstructs(t *testing.T) { 11 | test := &FixtureParsingFixture{t: t, input: comprehensiveTestCode} 12 | test.ParseFixtures() 13 | test.AssertFixturesParsedAccuratelyAndCompletely() 14 | } 15 | 16 | ////////////////////////////////////////////////////////////////////////////// 17 | 18 | type FixtureParsingFixture struct { 19 | t *testing.T 20 | 21 | input string 22 | readError error 23 | parseError error 24 | fixtures []*fixtureInfo 25 | } 26 | 27 | func (this *FixtureParsingFixture) ParseFixtures() { 28 | this.fixtures, this.parseError = scanForFixtures(this.input) 29 | } 30 | 31 | func (this *FixtureParsingFixture) AssertFixturesParsedAccuratelyAndCompletely() { 32 | this.assertFileWasReadWithoutError() 33 | this.assertFileWasParsedWithoutError() 34 | this.assertAllFixturesParsed() 35 | this.assertParsedFixturesAreCorrect() 36 | } 37 | func (this *FixtureParsingFixture) assertFileWasReadWithoutError() { 38 | if this.readError != nil { 39 | this.t.Error("Problem: couldn't read the input file:", this.readError) 40 | this.t.FailNow() 41 | } 42 | } 43 | func (this *FixtureParsingFixture) assertFileWasParsedWithoutError() { 44 | if this.parseError != nil { 45 | this.t.Error("Problem: unexpected parsing error: ", this.parseError) 46 | this.t.FailNow() 47 | } 48 | } 49 | func (this *FixtureParsingFixture) assertAllFixturesParsed() { 50 | if len(this.fixtures) != len(expected) { 51 | this.t.Logf("Problem: Got back the wrong number of fixtures. Expected: %d Got: %d", len(expected), len(this.fixtures)) 52 | this.t.FailNow() 53 | } 54 | } 55 | func (this *FixtureParsingFixture) assertParsedFixturesAreCorrect() { 56 | for x := 0; x < len(expected); x++ { 57 | key := this.fixtures[x].StructName 58 | if !reflect.DeepEqual(expected[key], this.fixtures[x]) { 59 | this.t.Errorf("\nExpected: [%#v]\nActual: [%#v]", expected[key], this.fixtures[x]) 60 | } 61 | } 62 | } 63 | 64 | func (this *FixtureParsingFixture) AssertErrorWasReturned() { 65 | if this.parseError == nil { 66 | this.t.Error("Expected an error, but got nil instead") 67 | } 68 | } 69 | 70 | ////////////////////////////////////////////////////////////////////////////// 71 | 72 | var expected = map[string]*fixtureInfo{ 73 | "BowlingGameScoringTests": { 74 | StructName: "BowlingGameScoringTests", 75 | TestCases: []*testCaseInfo{ 76 | {CharacterPosition: 323, Name: "TestAfterAllGutterBallsTheScoreShouldBeZero"}, 77 | {CharacterPosition: 478, Name: "TestAfterAllOnesTheScoreShouldBeTwenty"}, 78 | {CharacterPosition: 629, Name: "SkipTestASpareDeservesABonus"}, 79 | {CharacterPosition: 706, Name: "LongTestPerfectGame"}, 80 | {CharacterPosition: 840, Name: "SkipLongTestPerfectGame"}, 81 | }, 82 | }, 83 | } 84 | 85 | ////////////////////////////////////////////////////////////////////////////// 86 | -------------------------------------------------------------------------------- /assert/assertions/internal/oglematchers/any_of.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Aaron Jacobs. All Rights Reserved. 2 | // Author: aaronjjacobs@gmail.com (Aaron Jacobs) 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package oglematchers 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | "reflect" 22 | "strings" 23 | ) 24 | 25 | // AnyOf accepts a set of values S and returns a matcher that follows the 26 | // algorithm below when considering a candidate c: 27 | // 28 | // 1. If there exists a value m in S such that m implements the Matcher 29 | // interface and m matches c, return true. 30 | // 31 | // 2. Otherwise, if there exists a value v in S such that v does not implement 32 | // the Matcher interface and the matcher Equals(v) matches c, return true. 33 | // 34 | // 3. Otherwise, if there is a value m in S such that m implements the Matcher 35 | // interface and m returns a fatal error for c, return that fatal error. 36 | // 37 | // 4. Otherwise, return false. 38 | // 39 | // This is akin to a logical OR operation for matchers, with non-matchers x 40 | // being treated as Equals(x). 41 | func AnyOf(vals ...any) Matcher { 42 | // Get ahold of a type variable for the Matcher interface. 43 | var dummy *Matcher 44 | matcherType := reflect.TypeOf(dummy).Elem() 45 | 46 | // Create a matcher for each value, or use the value itself if it's already a 47 | // matcher. 48 | wrapped := make([]Matcher, len(vals)) 49 | for i, v := range vals { 50 | t := reflect.TypeOf(v) 51 | if t != nil && t.Implements(matcherType) { 52 | wrapped[i] = v.(Matcher) 53 | } else { 54 | wrapped[i] = Equals(v) 55 | } 56 | } 57 | 58 | return &anyOfMatcher{wrapped} 59 | } 60 | 61 | type anyOfMatcher struct { 62 | wrapped []Matcher 63 | } 64 | 65 | func (m *anyOfMatcher) Description() string { 66 | wrappedDescs := make([]string, len(m.wrapped)) 67 | for i, matcher := range m.wrapped { 68 | wrappedDescs[i] = matcher.Description() 69 | } 70 | 71 | return fmt.Sprintf("or(%s)", strings.Join(wrappedDescs, ", ")) 72 | } 73 | 74 | func (m *anyOfMatcher) Matches(c any) (err error) { 75 | err = errors.New("") 76 | 77 | // Try each matcher in turn. 78 | for _, matcher := range m.wrapped { 79 | wrappedErr := matcher.Matches(c) 80 | 81 | // Return immediately if there's a match. 82 | if wrappedErr == nil { 83 | err = nil 84 | return 85 | } 86 | 87 | // Note the fatal error, if any. 88 | if _, isFatal := wrappedErr.(*FatalError); isFatal { 89 | err = wrappedErr 90 | } 91 | } 92 | 93 | return 94 | } 95 | -------------------------------------------------------------------------------- /examples/environment_controller_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/smarty/gunit" 7 | ) 8 | 9 | func TestEnvironmentControllerFixture(t *testing.T) { 10 | gunit.Run(new(EnvironmentControllerFixture), t) 11 | } 12 | 13 | type EnvironmentControllerFixture struct { 14 | *gunit.Fixture 15 | hardware *FakeHVAC 16 | controller *EnvironmentController 17 | } 18 | 19 | func (this *EnvironmentControllerFixture) Setup() { 20 | this.hardware = NewFakeHardware() 21 | this.controller = NewController(this.hardware) 22 | } 23 | 24 | func (this *EnvironmentControllerFixture) TestEverythingTurnedOffAtStartup() { 25 | this.activateAllHardwareComponents() 26 | this.controller = NewController(this.hardware) 27 | this.assertAllHardwareComponentsAreDeactivated() 28 | } 29 | 30 | func (this *EnvironmentControllerFixture) TestEverythingOffWhenComfortable() { 31 | this.setupComfortableEnvironment() 32 | this.assertAllHardwareComponentsAreDeactivated() 33 | } 34 | 35 | func (this *EnvironmentControllerFixture) TestCoolerAndBlowerWhenHot() { 36 | this.setupHotEnvironment() 37 | this.AssertEqual("heater BLOWER COOLER low high", this.hardware.String()) 38 | } 39 | 40 | func (this *EnvironmentControllerFixture) TestHeaterAndBlowerWhenCold() { 41 | this.setupColdEnvironment() 42 | this.AssertEqual("HEATER BLOWER cooler low high", this.hardware.String()) 43 | } 44 | 45 | func (this *EnvironmentControllerFixture) TestHighAlarmOnIfAtThreshold() { 46 | this.setupBlazingEnvironment() 47 | this.AssertEqual("heater BLOWER COOLER low HIGH", this.hardware.String()) 48 | } 49 | 50 | func (this *EnvironmentControllerFixture) TestLowAlarmOnIfAtThreshold() { 51 | this.setupFreezingEnvironment() 52 | this.AssertEqual("HEATER BLOWER cooler LOW high", this.hardware.String()) 53 | } 54 | 55 | func (this *EnvironmentControllerFixture) setupComfortableEnvironment() { 56 | this.hardware.SetCurrentTemperature(COMFORTABLE) 57 | this.controller.Regulate() 58 | } 59 | func (this *EnvironmentControllerFixture) setupHotEnvironment() { 60 | this.hardware.SetCurrentTemperature(TOO_HOT) 61 | this.controller.Regulate() 62 | } 63 | func (this *EnvironmentControllerFixture) setupBlazingEnvironment() { 64 | this.hardware.SetCurrentTemperature(WAY_TOO_HOT) 65 | this.controller.Regulate() 66 | } 67 | func (this *EnvironmentControllerFixture) setupColdEnvironment() { 68 | this.hardware.SetCurrentTemperature(TOO_COLD) 69 | this.controller.Regulate() 70 | } 71 | func (this *EnvironmentControllerFixture) setupFreezingEnvironment() { 72 | this.hardware.SetCurrentTemperature(WAY_TOO_COLD) 73 | this.controller.Regulate() 74 | } 75 | 76 | func (this *EnvironmentControllerFixture) activateAllHardwareComponents() { 77 | this.hardware.ActivateBlower() 78 | this.hardware.ActivateHeater() 79 | this.hardware.ActivateCooler() 80 | this.hardware.ActivateHighTemperatureAlarm() 81 | this.hardware.ActivateLowTemperatureAlarm() 82 | } 83 | 84 | func (this *EnvironmentControllerFixture) assertAllHardwareComponentsAreDeactivated() { 85 | this.AssertEqual("heater blower cooler low high", this.hardware.String()) 86 | } 87 | -------------------------------------------------------------------------------- /reports/failure_report_test.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "log" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestFailureReport(t *testing.T) { 10 | report := newFailureReport("Failure", readExampleFile) 11 | report.scanStack(exampleStackFrames) 12 | actual := report.composeReport() 13 | 14 | actual = strings.TrimSpace(actual) 15 | if actual != expectedFailureReport { 16 | t.Errorf("Incorrect failure report.\nGot:\n%s\n\nWant:\n%s", actual, expectedFailureReport) 17 | } 18 | } 19 | 20 | func readExampleFile(path string) string { 21 | content, found := exampleFiles[path] 22 | if !found { 23 | log.Panicln("file not found:", path) 24 | } 25 | return content 26 | } 27 | 28 | var exampleFiles = map[string]string{ 29 | "/Users/mike/src/github.com/smarty/gunit/examples/bowling_game_test.go": populateFile("this.AssertEqual(expected, this.game.Score())", 54), 30 | "/Users/mike/src/github.com/smarty/gunit/examples/bowling_game2_test.go": populateFile("this.assertScore(0)", 24), 31 | } 32 | 33 | func populateFile(content string, line int) (final string) { 34 | return strings.Repeat("\n", line-1) + content + strings.Repeat("\n", 10) 35 | } 36 | 37 | var expectedFailureReport = strings.TrimSpace(` 38 | (1): this.AssertEqual(expected, this.game.Score()) // bowling_game_test.go:54 39 | (0): this.assertScore(0) // bowling_game2_test.go:24 40 | Failure`) 41 | 42 | var exampleStackFrames = []Frame{ 43 | {Line: 211, Function: "runtime.Callers", File: "/usr/local/go/src/runtime/extern.go"}, 44 | {Line: 11, Function: "github.com/smarty/gunit/reports.StackTrace", File: "/Users/mike/src/github.com/smarty/gunit/reports/runtime.go"}, 45 | {Line: 93, Function: "github.com/smarty/gunit.(*Fixture).fail", File: "/Users/mike/src/github.com/smarty/gunit/fixture.go"}, 46 | {Line: 61, Function: "github.com/smarty/gunit.(*Fixture).Assert", File: "/Users/mike/src/github.com/smarty/gunit/fixture.go"}, 47 | {Line: 66, Function: "github.com/smarty/gunit.(*Fixture).AssertEqual", File: "/Users/mike/src/github.com/smarty/gunit/fixture.go"}, 48 | {Line: 54, Function: "github.com/smarty/gunit/examples.(*BowlingGameScoringFixture).assertScore", File: "/Users/mike/src/github.com/smarty/gunit/examples/bowling_game_test.go"}, 49 | {Line: 24, Function: "github.com/smarty/gunit/examples.(*BowlingGameScoringFixture).TestAfterAllGutterBallsTheScoreShouldBeZero", File: "/Users/mike/src/github.com/smarty/gunit/examples/bowling_game2_test.go"}, 50 | {Line: 460, Function: "reflect.Value.call", File: "/usr/local/go/src/reflect/value.go"}, 51 | {Line: 321, Function: "reflect.Value.Call", File: "/usr/local/go/src/reflect/value.go"}, 52 | {Line: 86, Function: "github.com/smarty/gunit.(*testCase).runTest", File: "/Users/mike/src/github.com/smarty/gunit/test_case.go"}, 53 | {Line: 76, Function: "github.com/smarty/gunit.(*testCase).runWithSetupAndTeardown", File: "/Users/mike/src/github.com/smarty/gunit/test_case.go"}, 54 | {Line: 64, Function: "github.com/smarty/gunit.(*testCase).run", File: "/Users/mike/src/github.com/smarty/gunit/test_case.go"}, 55 | {Line: 909, Function: "testing.tRunner", File: "/usr/local/go/src/testing/testing.go"}, 56 | {}, // Simulate conditions in go 1.11, which returned errant blank stack frames. 57 | } 58 | -------------------------------------------------------------------------------- /assert/assertions/panic_test.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | func (this *AssertionsFixture) TestShouldPanic() { 9 | this.fail(so(func() {}, ShouldPanic, 1), "This assertion requires exactly 0 comparison values (you provided 1).") 10 | this.fail(so(func() {}, ShouldPanic, 1, 2, 3), "This assertion requires exactly 0 comparison values (you provided 3).") 11 | 12 | this.fail(so(1, ShouldPanic), shouldUseVoidNiladicFunction) 13 | this.fail(so(func(i int) {}, ShouldPanic), shouldUseVoidNiladicFunction) 14 | this.fail(so(func() int { panic("hi") }, ShouldPanic), shouldUseVoidNiladicFunction) 15 | 16 | this.fail(so(func() {}, ShouldPanic), shouldHavePanicked) 17 | this.pass(so(func() { panic("hi") }, ShouldPanic)) 18 | } 19 | 20 | func (this *AssertionsFixture) TestShouldNotPanic() { 21 | this.fail(so(func() {}, ShouldNotPanic, 1), "This assertion requires exactly 0 comparison values (you provided 1).") 22 | this.fail(so(func() {}, ShouldNotPanic, 1, 2, 3), "This assertion requires exactly 0 comparison values (you provided 3).") 23 | 24 | this.fail(so(1, ShouldNotPanic), shouldUseVoidNiladicFunction) 25 | this.fail(so(func(i int) {}, ShouldNotPanic), shouldUseVoidNiladicFunction) 26 | 27 | this.fail(so(func() { panic("hi") }, ShouldNotPanic), fmt.Sprintf(shouldNotHavePanicked, "hi")) 28 | this.pass(so(func() {}, ShouldNotPanic)) 29 | } 30 | 31 | func (this *AssertionsFixture) TestShouldPanicWith() { 32 | this.fail(so(func() {}, ShouldPanicWith), "This assertion requires exactly 1 comparison values (you provided 0).") 33 | this.fail(so(func() {}, ShouldPanicWith, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).") 34 | 35 | this.fail(so(1, ShouldPanicWith, 1), shouldUseVoidNiladicFunction) 36 | this.fail(so(func(i int) {}, ShouldPanicWith, "hi"), shouldUseVoidNiladicFunction) 37 | this.fail(so(func() {}, ShouldPanicWith, "bye"), shouldHavePanicked) 38 | this.fail(so(func() { panic("hi") }, ShouldPanicWith, "bye"), "Expected func() to panic with 'bye' (but it panicked with 'hi')!") 39 | this.fail(so(func() { panic(errInner) }, ShouldPanicWith, errOuter), "Expected func() to panic with 'outer inner' (but it panicked with 'inner')!") 40 | 41 | this.pass(so(func() { panic("hi") }, ShouldPanicWith, "hi")) 42 | this.pass(so(func() { panic(errOuter) }, ShouldPanicWith, errInner)) 43 | } 44 | 45 | func (this *AssertionsFixture) TestShouldNotPanicWith() { 46 | this.fail(so(func() {}, ShouldNotPanicWith), "This assertion requires exactly 1 comparison values (you provided 0).") 47 | this.fail(so(func() {}, ShouldNotPanicWith, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).") 48 | 49 | this.fail(so(1, ShouldNotPanicWith, 1), shouldUseVoidNiladicFunction) 50 | this.fail(so(func(i int) {}, ShouldNotPanicWith, "hi"), shouldUseVoidNiladicFunction) 51 | this.fail(so(func() { panic("hi") }, ShouldNotPanicWith, "hi"), "Expected func() NOT to panic with 'hi' (but it did)!") 52 | this.fail(so(func() { panic(errOuter) }, ShouldNotPanicWith, errInner), "Expected func() NOT to panic with 'inner' (but it did)!") 53 | 54 | this.pass(so(func() {}, ShouldNotPanicWith, "bye")) 55 | this.pass(so(func() { panic("hi") }, ShouldNotPanicWith, "bye")) 56 | this.pass(so(func() { panic(errInner) }, ShouldNotPanicWith, errOuter)) 57 | 58 | } 59 | 60 | var ( 61 | errInner = errors.New("inner") 62 | errOuter = fmt.Errorf("outer %w", errInner) 63 | ) 64 | -------------------------------------------------------------------------------- /reports/panic_report_test.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import "testing" 4 | 5 | func TestPanicReport(t *testing.T) { 6 | actual := PanicReport(examplePanic, exampleStackTrace) 7 | if actual != expectedPanicReport { 8 | t.Errorf("Incorrect panic report.\nGot:\n%s\n\nWant:\n%s", actual, expectedPanicReport) 9 | } 10 | } 11 | 12 | const expectedPanicReport = `PANIC: runtime error: invalid memory address or nil pointer dereference 13 | ... 14 | github.com/smarty/gunit/examples.(*Game).Roll(...) 15 | /Users/mike/src/github.com/smarty/gunit/examples/bowling_game.go:13 16 | github.com/smarty/gunit/examples.(*BowlingGameScoringFixture).rollMany(0xc000091310, 0x14, 0x0) 17 | /Users/mike/src/github.com/smarty/gunit/examples/bowling_game_test.go:58 +0x38 18 | github.com/smarty/gunit/examples.(*BowlingGameScoringFixture).TestAfterAllGutterBallsTheScoreShouldBeZero(0xc000091310) 19 | /Users/mike/src/github.com/smarty/gunit/examples/bowling_game_test.go:23 +0x3d` 20 | 21 | const examplePanic = "runtime error: invalid memory address or nil pointer dereference" 22 | 23 | /* 24 | Consider the stack trace below. We want to filter out all lines 25 | except those that represent actual program behavior (middle block), 26 | not the runtime/gunit code that handles the panic (shown in the first 27 | block) or the reflect/gunit/testing code (shown in the last block). 28 | */ 29 | 30 | var exampleStackTrace = []byte(`goroutine 22 [running]: 31 | runtime/debug.Stack(0x11ec660, 0xc0000b7fa0, 0xc0000fd888) 32 | /usr/local/go/src/runtime/debug/stack.go:24 +0x9d 33 | github.com/smarty/gunit/reports.PanicReport(0x1182b60, 0x12fe320, 0x10674a0, 0x1306400) 34 | /Users/mike/src/github.com/smarty/gunit/reports/panic_report.go:15 +0x13e 35 | github.com/smarty/gunit.(*Fixture).recoverPanic(0xc0000b7f60, 0x1182b60, 0x12fe320) 36 | /Users/mike/src/github.com/smarty/gunit/fixture.go:106 +0x4f 37 | github.com/smarty/gunit.(*Fixture).finalize(0xc0000b7f60) 38 | /Users/mike/src/github.com/smarty/gunit/fixture.go:97 +0x211 39 | panic(0x1182b60, 0x12fe320) 40 | /usr/local/go/src/runtime/panic.go:679 +0x1b2 41 | github.com/smarty/gunit/examples.(*Game).Roll(...) 42 | /Users/mike/src/github.com/smarty/gunit/examples/bowling_game.go:13 43 | github.com/smarty/gunit/examples.(*BowlingGameScoringFixture).rollMany(0xc000091310, 0x14, 0x0) 44 | /Users/mike/src/github.com/smarty/gunit/examples/bowling_game_test.go:58 +0x38 45 | github.com/smarty/gunit/examples.(*BowlingGameScoringFixture).TestAfterAllGutterBallsTheScoreShouldBeZero(0xc000091310) 46 | /Users/mike/src/github.com/smarty/gunit/examples/bowling_game_test.go:23 +0x3d 47 | reflect.Value.call(0x11af5e0, 0xc000091310, 0x3e13, 0x11b8925, 0x4, 0x0, 0x0, 0x0, 0x15, 0xc00009fe40, ...) 48 | /usr/local/go/src/reflect/value.go:460 +0x5f6 49 | reflect.Value.Call(0x11af5e0, 0xc000091310, 0x3e13, 0x0, 0x0, 0x0, 0x3e13, 0x0, 0x0) 50 | /usr/local/go/src/reflect/value.go:321 +0xb4 51 | github.com/smarty/gunit.(*testCase).runTest(0xc00012c1c0) 52 | /Users/mike/src/github.com/smarty/gunit/test_case.go:86 +0x7c 53 | github.com/smarty/gunit.(*testCase).runWithSetupAndTeardown(0xc00012c1c0) 54 | /Users/mike/src/github.com/smarty/gunit/test_case.go:76 +0x69 55 | github.com/smarty/gunit.(*testCase).run(0xc00012c1c0, 0xc0000d4400) 56 | /Users/mike/src/github.com/smarty/gunit/test_case.go:64 +0x81 57 | testing.tRunner(0xc0000d4400, 0xc000091140) 58 | /usr/local/go/src/testing/testing.go:909 +0xc9 59 | created by testing.(*T).Run 60 | /usr/local/go/src/testing/testing.go:960 +0x350`) 61 | -------------------------------------------------------------------------------- /assert/should/should.go: -------------------------------------------------------------------------------- 1 | // Package should is simply a rewording of the assertion 2 | // functions in the assertions package. 3 | package should 4 | 5 | import ( 6 | "github.com/smarty/gunit/assert" 7 | "github.com/smarty/gunit/assert/assertions" 8 | ) 9 | 10 | // So is an alias of assert.So, preserved by popular demand, though use of assert.So is preferred for new code. 11 | var So = assert.So 12 | 13 | var ( 14 | AlmostEqual = assertions.ShouldAlmostEqual 15 | BeBetween = assertions.ShouldBeBetween 16 | BeBetweenOrEqual = assertions.ShouldBeBetweenOrEqual 17 | BeBlank = assertions.ShouldBeBlank 18 | BeChronological = assertions.ShouldBeChronological 19 | BeEmpty = assertions.ShouldBeEmpty 20 | BeFalse = assertions.ShouldBeFalse 21 | BeGreaterThan = assertions.ShouldBeGreaterThan 22 | BeGreaterThanOrEqualTo = assertions.ShouldBeGreaterThanOrEqualTo 23 | BeIn = assertions.ShouldBeIn 24 | BeLessThan = assertions.ShouldBeLessThan 25 | BeLessThanOrEqualTo = assertions.ShouldBeLessThanOrEqualTo 26 | BeNil = assertions.ShouldBeNil 27 | BeTrue = assertions.ShouldBeTrue 28 | BeZeroValue = assertions.ShouldBeZeroValue 29 | Contain = assertions.ShouldContain 30 | ContainKey = assertions.ShouldContainKey 31 | ContainSubstring = assertions.ShouldContainSubstring 32 | EndWith = assertions.ShouldEndWith 33 | Equal = assertions.ShouldEqual 34 | HappenAfter = assertions.ShouldHappenAfter 35 | HappenBefore = assertions.ShouldHappenBefore 36 | HappenBetween = assertions.ShouldHappenBetween 37 | HappenOnOrAfter = assertions.ShouldHappenOnOrAfter 38 | HappenOnOrBefore = assertions.ShouldHappenOnOrBefore 39 | HappenOnOrBetween = assertions.ShouldHappenOnOrBetween 40 | HappenWithin = assertions.ShouldHappenWithin 41 | HaveLength = assertions.ShouldHaveLength 42 | HaveSameTypeAs = assertions.ShouldHaveSameTypeAs 43 | NotAlmostEqual = assertions.ShouldNotAlmostEqual 44 | NotBeBetween = assertions.ShouldNotBeBetween 45 | NotBeBetweenOrEqual = assertions.ShouldNotBeBetweenOrEqual 46 | NotBeBlank = assertions.ShouldNotBeBlank 47 | NotBeChronological = assertions.ShouldNotBeChronological 48 | NotBeEmpty = assertions.ShouldNotBeEmpty 49 | NotBeIn = assertions.ShouldNotBeIn 50 | NotBeNil = assertions.ShouldNotBeNil 51 | NotBeZeroValue = assertions.ShouldNotBeZeroValue 52 | NotContain = assertions.ShouldNotContain 53 | NotContainKey = assertions.ShouldNotContainKey 54 | NotContainSubstring = assertions.ShouldNotContainSubstring 55 | NotEndWith = assertions.ShouldNotEndWith 56 | NotEqual = assertions.ShouldNotEqual 57 | NotHappenOnOrBetween = assertions.ShouldNotHappenOnOrBetween 58 | NotHappenWithin = assertions.ShouldNotHappenWithin 59 | NotHaveSameTypeAs = assertions.ShouldNotHaveSameTypeAs 60 | NotPanic = assertions.ShouldNotPanic 61 | NotPanicWith = assertions.ShouldNotPanicWith 62 | NotStartWith = assertions.ShouldNotStartWith 63 | Panic = assertions.ShouldPanic 64 | PanicWith = assertions.ShouldPanicWith 65 | StartWith = assertions.ShouldStartWith 66 | Wrap = assertions.ShouldWrap 67 | ) 68 | -------------------------------------------------------------------------------- /assert/better/better.go: -------------------------------------------------------------------------------- 1 | // Package better contains the same listing as the should package, 2 | // but each assertion is wrapped in behavior that decorates failure 3 | // output with a 'fatal' prefix/indicator so that the So method can 4 | // invoke testing.T.Fatal(...) to immediately end a test case. 5 | package better 6 | 7 | import ( 8 | "github.com/smarty/gunit/assert" 9 | "github.com/smarty/gunit/assert/should" 10 | ) 11 | 12 | func fatal(so assert.Func) assert.Func { 13 | return func(actual any, expected ...any) string { 14 | result := so(actual, expected...) 15 | if result == "" { 16 | return "" 17 | } 18 | return "<<>>\n" + result 19 | } 20 | } 21 | 22 | var ( 23 | AlmostEqual = fatal(should.AlmostEqual) 24 | BeBetween = fatal(should.BeBetween) 25 | BeBetweenOrEqual = fatal(should.BeBetweenOrEqual) 26 | BeBlank = fatal(should.BeBlank) 27 | BeChronological = fatal(should.BeChronological) 28 | BeEmpty = fatal(should.BeEmpty) 29 | BeFalse = fatal(should.BeFalse) 30 | BeGreaterThan = fatal(should.BeGreaterThan) 31 | BeGreaterThanOrEqualTo = fatal(should.BeGreaterThanOrEqualTo) 32 | BeIn = fatal(should.BeIn) 33 | BeLessThan = fatal(should.BeLessThan) 34 | BeLessThanOrEqualTo = fatal(should.BeLessThanOrEqualTo) 35 | BeNil = fatal(should.BeNil) 36 | BeTrue = fatal(should.BeTrue) 37 | BeZeroValue = fatal(should.BeZeroValue) 38 | Contain = fatal(should.Contain) 39 | ContainKey = fatal(should.ContainKey) 40 | ContainSubstring = fatal(should.ContainSubstring) 41 | EndWith = fatal(should.EndWith) 42 | Equal = fatal(should.Equal) 43 | HappenAfter = fatal(should.HappenAfter) 44 | HappenBefore = fatal(should.HappenBefore) 45 | HappenBetween = fatal(should.HappenBetween) 46 | HappenOnOrAfter = fatal(should.HappenOnOrAfter) 47 | HappenOnOrBefore = fatal(should.HappenOnOrBefore) 48 | HappenOnOrBetween = fatal(should.HappenOnOrBetween) 49 | HappenWithin = fatal(should.HappenWithin) 50 | HaveLength = fatal(should.HaveLength) 51 | HaveSameTypeAs = fatal(should.HaveSameTypeAs) 52 | NotAlmostEqual = fatal(should.NotAlmostEqual) 53 | NotBeBetween = fatal(should.NotBeBetween) 54 | NotBeBetweenOrEqual = fatal(should.NotBeBetweenOrEqual) 55 | NotBeBlank = fatal(should.NotBeBlank) 56 | NotBeChronological = fatal(should.NotBeChronological) 57 | NotBeEmpty = fatal(should.NotBeEmpty) 58 | NotBeIn = fatal(should.NotBeIn) 59 | NotBeNil = fatal(should.NotBeNil) 60 | NotBeZeroValue = fatal(should.NotBeZeroValue) 61 | NotContain = fatal(should.NotContain) 62 | NotContainKey = fatal(should.NotContainKey) 63 | NotContainSubstring = fatal(should.NotContainSubstring) 64 | NotEndWith = fatal(should.NotEndWith) 65 | NotEqual = fatal(should.NotEqual) 66 | NotHappenOnOrBetween = fatal(should.NotHappenOnOrBetween) 67 | NotHappenWithin = fatal(should.NotHappenWithin) 68 | NotHaveSameTypeAs = fatal(should.NotHaveSameTypeAs) 69 | NotPanic = fatal(should.NotPanic) 70 | NotPanicWith = fatal(should.NotPanicWith) 71 | NotStartWith = fatal(should.NotStartWith) 72 | Panic = fatal(should.Panic) 73 | PanicWith = fatal(should.PanicWith) 74 | StartWith = fatal(should.StartWith) 75 | Wrap = fatal(should.Wrap) 76 | ) 77 | -------------------------------------------------------------------------------- /assert/assertions/panic.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // ShouldPanic receives a void, niladic function and expects to recover a panic. 9 | func ShouldPanic(actual any, expected ...any) (message string) { 10 | if fail := need(0, expected); fail != success { 11 | return fail 12 | } 13 | 14 | action, _ := actual.(func()) 15 | 16 | if action == nil { 17 | message = shouldUseVoidNiladicFunction 18 | return 19 | } 20 | 21 | defer func() { 22 | recovered := recover() 23 | if recovered == nil { 24 | message = shouldHavePanicked 25 | } else { 26 | message = success 27 | } 28 | }() 29 | action() 30 | 31 | return 32 | } 33 | 34 | // ShouldNotPanic receives a void, niladic function and expects to execute the function without any panic. 35 | func ShouldNotPanic(actual any, expected ...any) (message string) { 36 | if fail := need(0, expected); fail != success { 37 | return fail 38 | } 39 | 40 | action, _ := actual.(func()) 41 | 42 | if action == nil { 43 | message = shouldUseVoidNiladicFunction 44 | return 45 | } 46 | 47 | defer func() { 48 | recovered := recover() 49 | if recovered != nil { 50 | message = fmt.Sprintf(shouldNotHavePanicked, recovered) 51 | } else { 52 | message = success 53 | } 54 | }() 55 | action() 56 | 57 | return 58 | } 59 | 60 | // ShouldPanicWith receives a void, niladic function and expects to recover a panic with the second argument as the content. 61 | // If the expected value is an error and the recovered value is an error, errors.Is will be used to compare them. 62 | func ShouldPanicWith(actual any, expected ...any) (message string) { 63 | if fail := need(1, expected); fail != success { 64 | return fail 65 | } 66 | 67 | action, _ := actual.(func()) 68 | 69 | if action == nil { 70 | message = shouldUseVoidNiladicFunction 71 | return 72 | } 73 | 74 | defer func() { 75 | recovered := recover() 76 | if recovered == nil { 77 | message = shouldHavePanicked 78 | } else { 79 | recoveredErr, errFound := recovered.(error) 80 | expectedErr, expectedFound := expected[0].(error) 81 | if errFound && expectedFound && errors.Is(recoveredErr, expectedErr) { 82 | message = success 83 | } else if equal := ShouldEqual(recovered, expected[0]); equal != success { 84 | message = fmt.Sprintf(shouldHavePanickedWith, expected[0], recovered) 85 | } else { 86 | message = success 87 | } 88 | } 89 | }() 90 | action() 91 | 92 | return 93 | } 94 | 95 | // ShouldNotPanicWith receives a void, niladic function and expects to recover a panic whose content differs from the second argument. 96 | // If the expected value is an error and the recovered value is an error, errors.Is will be used to compare them. 97 | func ShouldNotPanicWith(actual any, expected ...any) (message string) { 98 | if fail := need(1, expected); fail != success { 99 | return fail 100 | } 101 | 102 | action, _ := actual.(func()) 103 | 104 | if action == nil { 105 | message = shouldUseVoidNiladicFunction 106 | return 107 | } 108 | 109 | defer func() { 110 | recovered := recover() 111 | if recovered == nil { 112 | message = success 113 | } else { 114 | recoveredErr, errFound := recovered.(error) 115 | expectedErr, expectedFound := expected[0].(error) 116 | if errFound && expectedFound && errors.Is(recoveredErr, expectedErr) { 117 | message = fmt.Sprintf(shouldNotHavePanickedWith, expected[0]) 118 | } else if equal := ShouldEqual(recovered, expected[0]); equal == success { 119 | message = fmt.Sprintf(shouldNotHavePanickedWith, expected[0]) 120 | } else { 121 | message = success 122 | } 123 | } 124 | }() 125 | action() 126 | 127 | return 128 | } 129 | -------------------------------------------------------------------------------- /assert/assertions/internal/oglematchers/matcher.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Aaron Jacobs. All Rights Reserved. 2 | // Author: aaronjjacobs@gmail.com (Aaron Jacobs) 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | // Package oglematchers provides a set of matchers useful in a testing or 17 | // mocking framework. These matchers are inspired by and mostly compatible with 18 | // Google Test for C++ and Google JS Test. 19 | // 20 | // This package is used by github.com/smarty/assertions/internal/ogletest and 21 | // github.com/smarty/assertions/internal/oglemock, which may be more directly useful if you're not 22 | // writing your own testing package or defining your own matchers. 23 | package oglematchers 24 | 25 | // A Matcher is some predicate implicitly defining a set of values that it 26 | // matches. For example, GreaterThan(17) matches all numeric values greater 27 | // than 17, and HasSubstr("taco") matches all strings with the substring 28 | // "taco". 29 | // 30 | // Matchers are typically exposed to tests via constructor functions like 31 | // HasSubstr. In order to implement such a function you can either define your 32 | // own matcher type or use NewMatcher. 33 | type Matcher interface { 34 | // Check whether the supplied value belongs to the the set defined by the 35 | // matcher. Return a non-nil error if and only if it does not. 36 | // 37 | // The error describes why the value doesn't match. The error text is a 38 | // relative clause that is suitable for being placed after the value. For 39 | // example, a predicate that matches strings with a particular substring may, 40 | // when presented with a numerical value, return the following error text: 41 | // 42 | // "which is not a string" 43 | // 44 | // Then the failure message may look like: 45 | // 46 | // Expected: has substring "taco" 47 | // Actual: 17, which is not a string 48 | // 49 | // If the error is self-apparent based on the description of the matcher, the 50 | // error text may be empty (but the error still non-nil). For example: 51 | // 52 | // Expected: 17 53 | // Actual: 19 54 | // 55 | // If you are implementing a new matcher, see also the documentation on 56 | // FatalError. 57 | Matches(candidate any) error 58 | 59 | // Description returns a string describing the property that values matching 60 | // this matcher have, as a verb phrase where the subject is the value. For 61 | // example, "is greather than 17" or "has substring "taco"". 62 | Description() string 63 | } 64 | 65 | // FatalError is an implementation of the error interface that may be returned 66 | // from matchers, indicating the error should be propagated. Returning a 67 | // *FatalError indicates that the matcher doesn't process values of the 68 | // supplied type, or otherwise doesn't know how to handle the value. 69 | // 70 | // For example, if GreaterThan(17) returned false for the value "taco" without 71 | // a fatal error, then Not(GreaterThan(17)) would return true. This is 72 | // technically correct, but is surprising and may mask failures where the wrong 73 | // sort of matcher is accidentally used. Instead, GreaterThan(17) can return a 74 | // fatal error, which will be propagated by Not(). 75 | type FatalError struct { 76 | errorText string 77 | } 78 | 79 | // NewFatalError creates a FatalError struct with the supplied error text. 80 | func NewFatalError(s string) *FatalError { 81 | return &FatalError{s} 82 | } 83 | 84 | func (e *FatalError) Error() string { 85 | return e.errorText 86 | } 87 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-render/PRESUBMIT.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The Chromium Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style license that can be 3 | # found in the LICENSE file. 4 | 5 | """Top-level presubmit script. 6 | 7 | See https://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for 8 | details on the presubmit API built into depot_tools. 9 | """ 10 | 11 | import os 12 | import sys 13 | 14 | 15 | def PreCommitGo(input_api, output_api, pcg_mode): 16 | """Run go-specific checks via pre-commit-go (pcg) if it's in PATH.""" 17 | if input_api.is_committing: 18 | error_type = output_api.PresubmitError 19 | else: 20 | error_type = output_api.PresubmitPromptWarning 21 | 22 | exe = 'pcg.exe' if sys.platform == 'win32' else 'pcg' 23 | pcg = None 24 | for p in os.environ['PATH'].split(os.pathsep): 25 | pcg = os.path.join(p, exe) 26 | if os.access(pcg, os.X_OK): 27 | break 28 | else: 29 | return [ 30 | error_type( 31 | 'pre-commit-go executable (pcg) could not be found in PATH. All Go ' 32 | 'checks are skipped. See https://github.com/maruel/pre-commit-go.') 33 | ] 34 | 35 | cmd = [pcg, 'run', '-m', ','.join(pcg_mode)] 36 | if input_api.verbose: 37 | cmd.append('-v') 38 | # pcg can figure out what files to check on its own based on upstream ref, 39 | # but on PRESUBMIT try builder upsteram isn't set, and it's just 1 commit. 40 | if os.getenv('PRESUBMIT_BUILDER', ''): 41 | cmd.extend(['-r', 'HEAD~1']) 42 | return input_api.RunTests([ 43 | input_api.Command( 44 | name='pre-commit-go: %s' % ', '.join(pcg_mode), 45 | cmd=cmd, 46 | kwargs={}, 47 | message=error_type), 48 | ]) 49 | 50 | 51 | def header(input_api): 52 | """Returns the expected license header regexp for this project.""" 53 | current_year = int(input_api.time.strftime('%Y')) 54 | allowed_years = (str(s) for s in reversed(xrange(2011, current_year + 1))) 55 | years_re = '(' + '|'.join(allowed_years) + ')' 56 | license_header = ( 57 | r'.*? Copyright %(year)s The Chromium Authors\. ' 58 | r'All rights reserved\.\n' 59 | r'.*? Use of this source code is governed by a BSD-style license ' 60 | r'that can be\n' 61 | r'.*? found in the LICENSE file\.(?: \*/)?\n' 62 | ) % { 63 | 'year': years_re, 64 | } 65 | return license_header 66 | 67 | 68 | def source_file_filter(input_api): 69 | """Returns filter that selects source code files only.""" 70 | bl = list(input_api.DEFAULT_BLACK_LIST) + [ 71 | r'.+\.pb\.go$', 72 | r'.+_string\.go$', 73 | ] 74 | wl = list(input_api.DEFAULT_WHITE_LIST) + [ 75 | r'.+\.go$', 76 | ] 77 | return lambda x: input_api.FilterSourceFile(x, white_list=wl, black_list=bl) 78 | 79 | 80 | def CommonChecks(input_api, output_api): 81 | results = [] 82 | results.extend( 83 | input_api.canned_checks.CheckChangeHasNoStrayWhitespace( 84 | input_api, output_api, 85 | source_file_filter=source_file_filter(input_api))) 86 | results.extend( 87 | input_api.canned_checks.CheckLicense( 88 | input_api, output_api, header(input_api), 89 | source_file_filter=source_file_filter(input_api))) 90 | return results 91 | 92 | 93 | def CheckChangeOnUpload(input_api, output_api): 94 | results = CommonChecks(input_api, output_api) 95 | results.extend(PreCommitGo(input_api, output_api, ['lint', 'pre-commit'])) 96 | return results 97 | 98 | 99 | def CheckChangeOnCommit(input_api, output_api): 100 | results = CommonChecks(input_api, output_api) 101 | results.extend(input_api.canned_checks.CheckChangeHasDescription( 102 | input_api, output_api)) 103 | results.extend(input_api.canned_checks.CheckDoNotSubmitInDescription( 104 | input_api, output_api)) 105 | results.extend(input_api.canned_checks.CheckDoNotSubmitInFiles( 106 | input_api, output_api)) 107 | results.extend(PreCommitGo( 108 | input_api, output_api, ['continuous-integration'])) 109 | return results 110 | -------------------------------------------------------------------------------- /assert/assertions/equality_specs.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "math" 5 | "reflect" 6 | "time" 7 | ) 8 | 9 | type specification interface { 10 | assertable(a, b any) bool 11 | passes(a, b any) bool 12 | } 13 | 14 | var equalitySpecs = []specification{ 15 | numericEquality{}, 16 | timeEquality{}, 17 | pointerEquality{}, 18 | deepEquality{}, 19 | } 20 | 21 | // deepEquality compares any two values using reflect.DeepEqual. 22 | // https://golang.org/pkg/reflect/#DeepEqual 23 | type deepEquality struct{} 24 | 25 | func (deepEquality) assertable(a, b any) bool { 26 | return reflect.TypeOf(a) == reflect.TypeOf(b) 27 | } 28 | func (deepEquality) passes(a, b any) bool { 29 | return reflect.DeepEqual(a, b) 30 | } 31 | 32 | // numericEquality compares numeric values using the built-in equality 33 | // operator (`==`). Values of differing numeric reflect.Kind are each 34 | // converted to the type of the other and are compared with `==` in both 35 | // directions, with one exception: two mixed integers (one signed and one 36 | // unsigned) are always unequal in the case that the unsigned value is 37 | // greater than math.MaxInt64. https://golang.org/pkg/reflect/#Kind 38 | type numericEquality struct{} 39 | 40 | func (numericEquality) assertable(a, b any) bool { 41 | return isNumeric(a) && isNumeric(b) 42 | } 43 | func (numericEquality) passes(a, b any) bool { 44 | aValue := reflect.ValueOf(a) 45 | bValue := reflect.ValueOf(b) 46 | if isUnsignedInteger(a) && isSignedInteger(b) && aValue.Uint() >= math.MaxInt64 { 47 | return false 48 | } 49 | if isSignedInteger(a) && isUnsignedInteger(b) && bValue.Uint() >= math.MaxInt64 { 50 | return false 51 | } 52 | aAsB := aValue.Convert(bValue.Type()).Interface() 53 | bAsA := bValue.Convert(aValue.Type()).Interface() 54 | return a == bAsA && b == aAsB 55 | } 56 | func isNumeric(v any) bool { 57 | of := reflect.TypeOf(v) 58 | if of == nil { 59 | return false 60 | } 61 | _, found := numericKinds[of.Kind()] 62 | return found 63 | } 64 | func isSignedInteger(v any) bool { 65 | _, found := signedIntegerKinds[reflect.TypeOf(v).Kind()] 66 | return found 67 | } 68 | 69 | var unsignedIntegerKinds = map[reflect.Kind]struct{}{ 70 | reflect.Uint: {}, 71 | reflect.Uint8: {}, 72 | reflect.Uint16: {}, 73 | reflect.Uint32: {}, 74 | reflect.Uint64: {}, 75 | reflect.Uintptr: {}, 76 | } 77 | 78 | func isUnsignedInteger(v any) bool { 79 | _, found := unsignedIntegerKinds[reflect.TypeOf(v).Kind()] 80 | return found 81 | } 82 | 83 | var signedIntegerKinds = map[reflect.Kind]struct{}{ 84 | reflect.Int: {}, 85 | reflect.Int8: {}, 86 | reflect.Int16: {}, 87 | reflect.Int32: {}, 88 | reflect.Int64: {}, 89 | } 90 | 91 | var numericKinds = map[reflect.Kind]struct{}{ 92 | reflect.Int: {}, 93 | reflect.Int8: {}, 94 | reflect.Int16: {}, 95 | reflect.Int32: {}, 96 | reflect.Int64: {}, 97 | reflect.Uint: {}, 98 | reflect.Uint8: {}, 99 | reflect.Uint16: {}, 100 | reflect.Uint32: {}, 101 | reflect.Uint64: {}, 102 | reflect.Float32: {}, 103 | reflect.Float64: {}, 104 | } 105 | 106 | // timeEquality compares values both of type time.Time using their Equal method. 107 | // https://golang.org/pkg/time/#Time.Equal 108 | type timeEquality struct{} 109 | 110 | func (timeEquality) assertable(a, b any) bool { 111 | return isTime(a) && isTime(b) 112 | } 113 | func (timeEquality) passes(a, b any) bool { 114 | return a.(time.Time).Equal(b.(time.Time)) 115 | } 116 | func isTime(v any) bool { 117 | _, ok := v.(time.Time) 118 | return ok 119 | } 120 | 121 | type pointerEquality struct{} 122 | 123 | func (pointerEquality) assertable(a, b any) bool { 124 | return areSameType(a, b) && isKind(reflect.Func, a) 125 | } 126 | func (pointerEquality) passes(a, b any) bool { 127 | return reflect.ValueOf(a).Pointer() == reflect.ValueOf(b).Pointer() 128 | } 129 | func areSameType(a, b any) bool { 130 | return reflect.TypeOf(a) == reflect.TypeOf(b) 131 | } 132 | func isKind(kind reflect.Kind, a any) bool { 133 | return reflect.ValueOf(a).Kind() == kind 134 | } 135 | -------------------------------------------------------------------------------- /assert/assertions/internal/oglematchers/less_than.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Aaron Jacobs. All Rights Reserved. 2 | // Author: aaronjjacobs@gmail.com (Aaron Jacobs) 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package oglematchers 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | "math" 22 | "reflect" 23 | ) 24 | 25 | // LessThan returns a matcher that matches integer, floating point, or strings 26 | // values v such that v < x. Comparison is not defined between numeric and 27 | // string types, but is defined between all integer and floating point types. 28 | // 29 | // x must itself be an integer, floating point, or string type; otherwise, 30 | // LessThan will panic. 31 | func LessThan(x any) Matcher { 32 | v := reflect.ValueOf(x) 33 | kind := v.Kind() 34 | 35 | switch { 36 | case isInteger(v): 37 | case isFloat(v): 38 | case kind == reflect.String: 39 | 40 | default: 41 | panic(fmt.Sprintf("LessThan: unexpected kind %v", kind)) 42 | } 43 | 44 | return &lessThanMatcher{v} 45 | } 46 | 47 | type lessThanMatcher struct { 48 | limit reflect.Value 49 | } 50 | 51 | func (m *lessThanMatcher) Description() string { 52 | // Special case: make it clear that strings are strings. 53 | if m.limit.Kind() == reflect.String { 54 | return fmt.Sprintf("less than \"%s\"", m.limit.String()) 55 | } 56 | 57 | return fmt.Sprintf("less than %v", m.limit.Interface()) 58 | } 59 | 60 | func compareIntegers(v1, v2 reflect.Value) (err error) { 61 | err = errors.New("") 62 | 63 | switch { 64 | case isSignedInteger(v1) && isSignedInteger(v2): 65 | if v1.Int() < v2.Int() { 66 | err = nil 67 | } 68 | return 69 | 70 | case isSignedInteger(v1) && isUnsignedInteger(v2): 71 | if v1.Int() < 0 || uint64(v1.Int()) < v2.Uint() { 72 | err = nil 73 | } 74 | return 75 | 76 | case isUnsignedInteger(v1) && isSignedInteger(v2): 77 | if v1.Uint() <= math.MaxInt64 && int64(v1.Uint()) < v2.Int() { 78 | err = nil 79 | } 80 | return 81 | 82 | case isUnsignedInteger(v1) && isUnsignedInteger(v2): 83 | if v1.Uint() < v2.Uint() { 84 | err = nil 85 | } 86 | return 87 | } 88 | 89 | panic(fmt.Sprintf("compareIntegers: %v %v", v1, v2)) 90 | } 91 | 92 | func getFloat(v reflect.Value) float64 { 93 | switch { 94 | case isSignedInteger(v): 95 | return float64(v.Int()) 96 | 97 | case isUnsignedInteger(v): 98 | return float64(v.Uint()) 99 | 100 | case isFloat(v): 101 | return v.Float() 102 | } 103 | 104 | panic(fmt.Sprintf("getFloat: %v", v)) 105 | } 106 | 107 | func (m *lessThanMatcher) Matches(c any) (err error) { 108 | v1 := reflect.ValueOf(c) 109 | v2 := m.limit 110 | 111 | err = errors.New("") 112 | 113 | // Handle strings as a special case. 114 | if v1.Kind() == reflect.String && v2.Kind() == reflect.String { 115 | if v1.String() < v2.String() { 116 | err = nil 117 | } 118 | return 119 | } 120 | 121 | // If we get here, we require that we are dealing with integers or floats. 122 | v1Legal := isInteger(v1) || isFloat(v1) 123 | v2Legal := isInteger(v2) || isFloat(v2) 124 | if !v1Legal || !v2Legal { 125 | err = NewFatalError("which is not comparable") 126 | return 127 | } 128 | 129 | // Handle the various comparison cases. 130 | switch { 131 | // Both integers 132 | case isInteger(v1) && isInteger(v2): 133 | return compareIntegers(v1, v2) 134 | 135 | // At least one float32 136 | case v1.Kind() == reflect.Float32 || v2.Kind() == reflect.Float32: 137 | if float32(getFloat(v1)) < float32(getFloat(v2)) { 138 | err = nil 139 | } 140 | return 141 | 142 | // At least one float64 143 | case v1.Kind() == reflect.Float64 || v2.Kind() == reflect.Float64: 144 | if getFloat(v1) < getFloat(v2) { 145 | err = nil 146 | } 147 | return 148 | } 149 | 150 | // We shouldn't get here. 151 | panic(fmt.Sprintf("lessThanMatcher.Matches: Shouldn't get here: %v %v", v1, v2)) 152 | } 153 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-diff/README.md: -------------------------------------------------------------------------------- 1 | # go-diff [![GoDoc](https://godoc.org/github.com/sergi/go-diff?status.png)](https://godoc.org/github.com/sergi/go-diff/diffmatchpatch) [![Build Status](https://travis-ci.org/sergi/go-diff.svg?branch=master)](https://travis-ci.org/sergi/go-diff) [![Coverage Status](https://coveralls.io/repos/sergi/go-diff/badge.png?branch=master)](https://coveralls.io/r/sergi/go-diff?branch=master) 2 | 3 | go-diff offers algorithms to perform operations required for synchronizing plain text: 4 | 5 | - Compare two texts and return their differences. 6 | - Perform fuzzy matching of text. 7 | - Apply patches onto text. 8 | 9 | ## Installation 10 | 11 | ```bash 12 | go get -u github.com/sergi/go-diff/... 13 | ``` 14 | 15 | ## Usage 16 | 17 | The following example compares two texts and writes out the differences to standard output. 18 | 19 | ```go 20 | package main 21 | 22 | import ( 23 | "fmt" 24 | 25 | "github.com/sergi/go-diff/diffmatchpatch" 26 | ) 27 | 28 | const ( 29 | text1 = "Lorem ipsum dolor." 30 | text2 = "Lorem dolor sit amet." 31 | ) 32 | 33 | func main() { 34 | dmp := diffmatchpatch.New() 35 | 36 | diffs := dmp.DiffMain(text1, text2, false) 37 | 38 | fmt.Println(dmp.DiffPrettyText(diffs)) 39 | } 40 | ``` 41 | 42 | ## Found a bug or are you missing a feature in go-diff? 43 | 44 | Please make sure to have the latest version of go-diff. If the problem still persists go through the [open issues](https://github.com/sergi/go-diff/issues) in the tracker first. If you cannot find your request just open up a [new issue](https://github.com/sergi/go-diff/issues/new). 45 | 46 | ## How to contribute? 47 | 48 | You want to contribute to go-diff? GREAT! If you are here because of a bug you want to fix or a feature you want to add, you can just read on. Otherwise we have a list of [open issues in the tracker](https://github.com/sergi/go-diff/issues). Just choose something you think you can work on and discuss your plans in the issue by commenting on it. 49 | 50 | Please make sure that every behavioral change is accompanied by test cases. Additionally, every contribution must pass the `lint` and `test` Makefile targets which can be run using the following commands in the repository root directory. 51 | 52 | ```bash 53 | make lint 54 | make test 55 | ``` 56 | 57 | After your contribution passes these commands, [create a PR](https://help.github.com/articles/creating-a-pull-request/) and we will review your contribution. 58 | 59 | ## Origins 60 | 61 | go-diff is a Go language port of Neil Fraser's google-diff-match-patch code. His original code is available at [http://code.google.com/p/google-diff-match-patch/](http://code.google.com/p/google-diff-match-patch/). 62 | 63 | ## Copyright and License 64 | 65 | The original Google Diff, Match and Patch Library is licensed under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0). The full terms of that license are included here in the [APACHE-LICENSE-2.0](/APACHE-LICENSE-2.0) file. 66 | 67 | Diff, Match and Patch Library 68 | 69 | > Written by Neil Fraser 70 | > Copyright (c) 2006 Google Inc. 71 | > 72 | 73 | This Go version of Diff, Match and Patch Library is licensed under the [MIT License](http://www.opensource.org/licenses/MIT) (a.k.a. the Expat License) which is included here in the [LICENSE](/LICENSE) file. 74 | 75 | Go version of Diff, Match and Patch Library 76 | 77 | > Copyright (c) 2012-2016 The go-diff authors. All rights reserved. 78 | > 79 | 80 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 81 | 82 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 83 | 84 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 85 | -------------------------------------------------------------------------------- /assert/assertions/internal/unit/fixture.go: -------------------------------------------------------------------------------- 1 | // package unit implements a light-weight x-Unit style testing framework. 2 | // It is basically a scaled-down version of github.com/smarty/gunit. 3 | // See https://smarty.com/blog/2018/07/lets-build-xunit-in-go for 4 | // an explanation of the basic moving parts. 5 | package unit 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "reflect" 11 | "runtime" 12 | "strings" 13 | "testing" 14 | 15 | "github.com/smarty/gunit/assert" 16 | ) 17 | 18 | func Run(fixture any, t *testing.T) { 19 | fixtureType := reflect.TypeOf(fixture) 20 | 21 | for x := 0; x < fixtureType.NumMethod(); x++ { 22 | testMethodName := fixtureType.Method(x).Name 23 | if strings.HasPrefix(testMethodName, "Test") { 24 | t.Run(testMethodName, func(t *testing.T) { 25 | instance := reflect.New(fixtureType.Elem()) 26 | 27 | innerFixture := newFixture(t, testing.Verbose()) 28 | field := instance.Elem().FieldByName("Fixture") 29 | field.Set(reflect.ValueOf(innerFixture)) 30 | 31 | defer innerFixture.Finalize() 32 | 33 | if setup := instance.MethodByName("Setup"); setup.IsValid() { 34 | setup.Call(nil) 35 | } 36 | 37 | instance.MethodByName(testMethodName).Call(nil) 38 | 39 | if teardown := instance.MethodByName("Teardown"); teardown.IsValid() { 40 | teardown.Call(nil) 41 | } 42 | }) 43 | } 44 | } 45 | } 46 | 47 | type Fixture struct { 48 | t *testing.T 49 | log *bytes.Buffer 50 | verbose bool 51 | } 52 | 53 | func newFixture(t *testing.T, verbose bool) *Fixture { 54 | return &Fixture{t: t, verbose: verbose, log: &bytes.Buffer{}} 55 | } 56 | 57 | func (this *Fixture) So(actual any, assert assert.Func, expected ...any) bool { 58 | failure := assert(actual, expected...) 59 | failed := len(failure) > 0 60 | if failed { 61 | this.fail(failure) 62 | } 63 | return !failed 64 | } 65 | 66 | func (this *Fixture) fail(failure string) { 67 | this.t.Fail() 68 | this.Print(failure) 69 | } 70 | 71 | // Assert tests a boolean which, if not true, marks the current test case as failed and 72 | // prints the provided message. 73 | func (this *Fixture) Assert(condition bool, messages ...string) bool { 74 | if !condition { 75 | if len(messages) == 0 { 76 | messages = append(messages, "Expected condition to be true, was false instead.") 77 | } 78 | this.fail(strings.Join(messages, ", ")) 79 | } 80 | return condition 81 | } 82 | func (this *Fixture) AssertEqual(expected, actual any) bool { 83 | return this.Assert(expected == actual, fmt.Sprintf(comparisonFormat, fmt.Sprint(expected), fmt.Sprint(actual))) 84 | } 85 | func (this *Fixture) AssertSprintEqual(expected, actual any) bool { 86 | return this.AssertEqual(fmt.Sprint(expected), fmt.Sprint(actual)) 87 | } 88 | func (this *Fixture) AssertSprintfEqual(expected, actual any, format string) bool { 89 | return this.AssertEqual(fmt.Sprintf(format, expected), fmt.Sprintf(format, actual)) 90 | } 91 | func (this *Fixture) AssertDeepEqual(expected, actual any) bool { 92 | return this.Assert(reflect.DeepEqual(expected, actual), 93 | fmt.Sprintf(comparisonFormat, fmt.Sprintf("%#v", expected), fmt.Sprintf("%#v", actual))) 94 | } 95 | 96 | const comparisonFormat = "Expected: [%s]\nActual: [%s]" 97 | 98 | func (this *Fixture) Error(args ...any) { this.fail(fmt.Sprint(args...)) } 99 | func (this *Fixture) Errorf(f string, args ...any) { this.fail(fmt.Sprintf(f, args...)) } 100 | 101 | func (this *Fixture) Print(a ...any) { _, _ = fmt.Fprint(this.log, a...) } 102 | func (this *Fixture) Printf(format string, a ...any) { _, _ = fmt.Fprintf(this.log, format, a...) } 103 | func (this *Fixture) Println(a ...any) { _, _ = fmt.Fprintln(this.log, a...) } 104 | 105 | func (this *Fixture) Write(p []byte) (int, error) { return this.log.Write(p) } 106 | func (this *Fixture) Failed() bool { return this.t.Failed() } 107 | func (this *Fixture) Name() string { return this.t.Name() } 108 | 109 | func (this *Fixture) Finalize() { 110 | if r := recover(); r != nil { 111 | this.recoverPanic(r) 112 | } 113 | 114 | if this.t.Failed() || (this.verbose && this.log.Len() > 0) { 115 | this.t.Log("\n" + strings.TrimSpace(this.log.String()) + "\n") 116 | } 117 | } 118 | func (this *Fixture) recoverPanic(r any) { 119 | this.Println("PANIC:", r) 120 | buffer := make([]byte, 1024*16) 121 | runtime.Stack(buffer, false) 122 | this.Println(strings.TrimSpace(string(buffer))) 123 | this.t.Fail() 124 | } 125 | -------------------------------------------------------------------------------- /scan/parser_test_inputs_test.go: -------------------------------------------------------------------------------- 1 | package scan 2 | 3 | const comprehensiveTestCode = `package parse 4 | 5 | import ( 6 | "github.com/smarty/gunit" 7 | "github.com/smarty/gunit/assert/should" 8 | ) 9 | 10 | type BowlingGameScoringTests struct { 11 | *gunit.Fixture 12 | 13 | game *Game 14 | } 15 | 16 | func (self *BowlingGameScoringTests) SetupTheGame() { 17 | self.game = NewGame() 18 | } 19 | 20 | func (self *BowlingGameScoringTests) TeardownTheGame() { 21 | self.game = nil 22 | } 23 | 24 | func (self *BowlingGameScoringTests) TestAfterAllGutterBallsTheScoreShouldBeZero() { 25 | self.rollMany(20, 0) 26 | self.So(self.game.Score(), should.Equal, 0) 27 | } 28 | 29 | func (self *BowlingGameScoringTests) TestAfterAllOnesTheScoreShouldBeTwenty() { 30 | self.rollMany(20, 1) 31 | self.So(self.game.Score(), should.Equal, 20) 32 | } 33 | 34 | func (self *BowlingGameScoringTests) SkipTestASpareDeservesABonus() {} 35 | 36 | func (self *BowlingGameScoringTests) LongTestPerfectGame() { 37 | self.rollMany(12, 10) 38 | self.So(self.game.Score(), should.Equal, 300) 39 | } 40 | 41 | func (self *BowlingGameScoringTests) SkipLongTestPerfectGame() { 42 | self.rollMany(12, 10) 43 | self.So(self.game.Score(), should.Equal, 300) 44 | } 45 | 46 | func (self *BowlingGameScoringTests) rollMany(times, pins int) { 47 | for x := 0; x < times; x++ { 48 | self.game.Roll(pins) 49 | } 50 | } 51 | func (self *BowlingGameScoringTests) rollSpare() { 52 | self.game.Roll(5) 53 | self.game.Roll(5) 54 | } 55 | func (self *BowlingGameScoringTests) rollStrike() { 56 | self.game.Roll(10) 57 | } 58 | 59 | func (self *BowlingGameScoringTests) TestNotNiladic_ShouldNotBeCollected(a int) { 60 | // This should not be collected (it's not niladic) 61 | } 62 | func (self *BowlingGameScoringTests) TestNotVoid_ShouldNOTBeCollected() int { 63 | return -1 64 | // This should not be collected (it's not void) 65 | } 66 | 67 | ////////////////////////////////////////////////////////////////////////////// 68 | 69 | // Game contains the state of a bowling game. 70 | type Game struct { 71 | rolls []int 72 | current int 73 | } 74 | 75 | // NewGame allocates and starts a new game of bowling. 76 | func NewGame() *Game { 77 | game := new(Game) 78 | game.rolls = make([]int, maxThrowsPerGame) 79 | return game 80 | } 81 | 82 | // Roll rolls the ball and knocks down the number of pins specified by pins. 83 | func (self *Game) Roll(pins int) { 84 | self.rolls[self.current] = pins 85 | self.current++ 86 | } 87 | 88 | // Score calculates and returns the player's current score. 89 | func (self *Game) Score() (sum int) { 90 | for throw, frame := 0, 0; frame < framesPerGame; frame++ { 91 | if self.isStrike(throw) { 92 | sum += self.strikeBonusFor(throw) 93 | throw += 1 94 | } else if self.isSpare(throw) { 95 | sum += self.spareBonusFor(throw) 96 | throw += 2 97 | } else { 98 | sum += self.framePointsAt(throw) 99 | throw += 2 100 | } 101 | } 102 | return sum 103 | } 104 | 105 | // isStrike determines if a given throw is a strike or not. A strike is knocking 106 | // down all pins in one throw. 107 | func (self *Game) isStrike(throw int) bool { 108 | return self.rolls[throw] == allPins 109 | } 110 | 111 | // strikeBonusFor calculates and returns the strike bonus for a throw. 112 | func (self *Game) strikeBonusFor(throw int) int { 113 | return allPins + self.framePointsAt(throw+1) 114 | } 115 | 116 | // isSpare determines if a given frame is a spare or not. A spare is knocking 117 | // down all pins in one frame with two throws. 118 | func (self *Game) isSpare(throw int) bool { 119 | return self.framePointsAt(throw) == allPins 120 | } 121 | 122 | // spareBonusFor calculates and returns the spare bonus for a throw. 123 | func (self *Game) spareBonusFor(throw int) int { 124 | return allPins + self.rolls[throw+2] 125 | } 126 | 127 | // framePointsAt computes and returns the score in a frame specified by throw. 128 | func (self *Game) framePointsAt(throw int) int { 129 | return self.rolls[throw] + self.rolls[throw+1] 130 | } 131 | 132 | const ( 133 | // allPins is the number of pins allocated per fresh throw. 134 | allPins = 10 135 | 136 | // framesPerGame is the number of frames per bowling game. 137 | framesPerGame = 10 138 | 139 | // maxThrowsPerGame is the maximum number of throws possible in a single game. 140 | maxThrowsPerGame = 21 141 | ) 142 | 143 | ////////////////////////////////////////////////////////////////////////////// 144 | // These types shouldn't be parsed as fixtures: 145 | 146 | type TestFixtureWrongTestCase struct { 147 | *blah.Fixture 148 | } 149 | type TestFixtureWrongPackage struct { 150 | *gunit.Fixture2 151 | } 152 | 153 | type Hah interface { 154 | Hi() string 155 | } 156 | 157 | type BlahFixture struct { 158 | blah int 159 | } 160 | 161 | ////////////////////////////////////////////////////////////////////////////// 162 | ` 163 | -------------------------------------------------------------------------------- /fixture.go: -------------------------------------------------------------------------------- 1 | // Package gunit provides "testing" package hooks and convenience 2 | // functions for writing tests in an xUnit style. 3 | // See the README file and the examples folder for examples. 4 | package gunit 5 | 6 | import ( 7 | "fmt" 8 | "reflect" 9 | "runtime/debug" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/smarty/gunit/assert" 14 | "github.com/smarty/gunit/reports" 15 | ) 16 | 17 | // Fixture keeps track of test status (failed, passed, skipped) and 18 | // handles custom logging for xUnit style tests as an embedded field. 19 | // The Fixture manages an instance of *testing.T. Certain methods 20 | // defined herein merely forward to calls on the *testing.T: 21 | // 22 | // - Fixture.Error(...) ----> *testing.T.Error 23 | // - Fixture.Errorf(...) ---> *testing.T.Errorf 24 | // - Fixture.Print(...) ----> *testing.T.Log or fmt.Print 25 | // - Fixture.Printf(...) ---> *testing.T.Logf or fmt.Printf 26 | // - Fixture.Println(...) --> *testing.T.Log or fmt.Println 27 | // - Fixture.Failed() ------> *testing.T.Failed() 28 | // - Fixture.fail() --------> *testing.T.Fail() 29 | // 30 | // We don't use these methods much, preferring instead to lean heavily 31 | // on Fixture.So and the rich set of should-style assertions provided at 32 | // github.com/smarty/assertions/should 33 | type Fixture struct { 34 | t TestingT 35 | verbose bool 36 | } 37 | 38 | func newFixture(t TestingT, verbose bool) *Fixture { 39 | t.Helper() 40 | 41 | return &Fixture{t: t, verbose: verbose} 42 | } 43 | 44 | // T exposes the TestingT (*testing.T) instance. 45 | func (this *Fixture) T() TestingT { return this.t } 46 | 47 | // Run is analogous to *testing.T.Run and allows for running subtests from 48 | // test fixture methods (such as for table-driven tests). 49 | func (this *Fixture) Run(name string, test func(fixture *Fixture)) { 50 | this.t.(*testing.T).Run(name, func(t *testing.T) { 51 | t.Helper() 52 | 53 | fixture := newFixture(t, this.verbose) 54 | defer fixture.finalize() 55 | test(fixture) 56 | }) 57 | } 58 | 59 | // So is a convenience method for reporting assertion failure messages, 60 | // from the many assertion functions found in github.com/smarty/gunit/assert/should. 61 | // Example: this.So(actual, should.Equal, expected) 62 | func (this *Fixture) So(actual any, assertion assert.Func, expected ...any) bool { 63 | result := assertion(actual, expected...) 64 | if strings.HasPrefix(result, "<<>>\n") { 65 | this.fatal(result) 66 | } 67 | failed := result != "" 68 | if failed { 69 | this.fail(result) 70 | } 71 | return !failed 72 | } 73 | 74 | // Assert tests a boolean which, if not true, marks the current test case as failed and 75 | // prints the provided message. 76 | func (this *Fixture) Assert(condition bool, messages ...string) bool { 77 | if !condition { 78 | if len(messages) == 0 { 79 | messages = append(messages, "Expected condition to be true, was false instead.") 80 | } 81 | this.fail(strings.Join(messages, ", ")) 82 | } 83 | return condition 84 | } 85 | func (this *Fixture) AssertEqual(expected, actual any) bool { 86 | return this.Assert(expected == actual, fmt.Sprintf(comparisonFormat, fmt.Sprint(expected), fmt.Sprint(actual))) 87 | } 88 | func (this *Fixture) AssertSprintEqual(expected, actual any) bool { 89 | return this.AssertEqual(fmt.Sprint(expected), fmt.Sprint(actual)) 90 | } 91 | func (this *Fixture) AssertSprintfEqual(expected, actual any, format string) bool { 92 | return this.AssertEqual(fmt.Sprintf(format, expected), fmt.Sprintf(format, actual)) 93 | } 94 | func (this *Fixture) AssertDeepEqual(expected, actual any) bool { 95 | return this.Assert(reflect.DeepEqual(expected, actual), 96 | fmt.Sprintf(comparisonFormat, fmt.Sprintf("%#v", expected), fmt.Sprintf("%#v", actual))) 97 | } 98 | 99 | func (this *Fixture) Error(args ...any) { this.fail(fmt.Sprint(args...)) } 100 | func (this *Fixture) Errorf(f string, args ...any) { this.fail(fmt.Sprintf(f, args...)) } 101 | 102 | func (this *Fixture) Print(a ...any) { 103 | _, _ = fmt.Fprintln(this.t.Output(), a...) 104 | } 105 | func (this *Fixture) Printf(f string, a ...any) { 106 | _, _ = fmt.Fprintln(this.t.Output(), fmt.Sprintf(f, a...)) 107 | } 108 | func (this *Fixture) Println(a ...any) { 109 | _, _ = fmt.Fprintln(this.t.Output(), a...) 110 | } 111 | 112 | // Write implements io.Writer. There are rare times when this is convenient (debugging via `log.SetOutput(fixture)`). 113 | func (this *Fixture) Write(p []byte) (int, error) { return this.t.Output().Write(p) } 114 | func (this *Fixture) Failed() bool { return this.t.Failed() } 115 | func (this *Fixture) Name() string { return this.t.Name() } 116 | 117 | func (this *Fixture) fail(failure string) { 118 | this.t.Fail() 119 | this.Print(reports.FailureReport(failure, reports.StackTrace())) 120 | } 121 | func (this *Fixture) fatal(failure string) { 122 | this.t.Fatalf(reports.FailureReport(failure, reports.StackTrace())) 123 | } 124 | 125 | func (this *Fixture) finalize() { 126 | this.t.Helper() 127 | 128 | if r := recover(); r != nil { 129 | this.recoverPanic(r) 130 | } 131 | } 132 | func (this *Fixture) recoverPanic(r any) { 133 | this.t.Fail() 134 | this.Print(reports.PanicReport(r, debug.Stack())) 135 | } 136 | 137 | const comparisonFormat = "Expected: [%s]\nActual: [%s]" 138 | -------------------------------------------------------------------------------- /assert/assertions/quantity.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/smarty/gunit/assert/assertions/internal/oglematchers" 7 | ) 8 | 9 | // ShouldBeGreaterThan receives exactly two parameters and ensures that the first is greater than the second. 10 | func ShouldBeGreaterThan(actual any, expected ...any) string { 11 | if fail := need(1, expected); fail != success { 12 | return fail 13 | } 14 | 15 | if matchError := oglematchers.GreaterThan(expected[0]).Matches(actual); matchError != nil { 16 | return fmt.Sprintf(shouldHaveBeenGreater, actual, expected[0]) 17 | } 18 | return success 19 | } 20 | 21 | // ShouldBeGreaterThanOrEqualTo receives exactly two parameters and ensures that the first is greater than or equal to the second. 22 | func ShouldBeGreaterThanOrEqualTo(actual any, expected ...any) string { 23 | if fail := need(1, expected); fail != success { 24 | return fail 25 | } else if matchError := oglematchers.GreaterOrEqual(expected[0]).Matches(actual); matchError != nil { 26 | return fmt.Sprintf(shouldHaveBeenGreaterOrEqual, actual, expected[0]) 27 | } 28 | return success 29 | } 30 | 31 | // ShouldBeLessThan receives exactly two parameters and ensures that the first is less than the second. 32 | func ShouldBeLessThan(actual any, expected ...any) string { 33 | if fail := need(1, expected); fail != success { 34 | return fail 35 | } else if matchError := oglematchers.LessThan(expected[0]).Matches(actual); matchError != nil { 36 | return fmt.Sprintf(shouldHaveBeenLess, actual, expected[0]) 37 | } 38 | return success 39 | } 40 | 41 | // ShouldBeLessThanOrEqualTo receives exactly two parameters and ensures that the first is less than or equal to the second. 42 | func ShouldBeLessThanOrEqualTo(actual any, expected ...any) string { 43 | if fail := need(1, expected); fail != success { 44 | return fail 45 | } else if matchError := oglematchers.LessOrEqual(expected[0]).Matches(actual); matchError != nil { 46 | return fmt.Sprintf(shouldHaveBeenLessOrEqual, actual, expected[0]) 47 | } 48 | return success 49 | } 50 | 51 | // ShouldBeBetween receives exactly three parameters: an actual value, a lower bound, and an upper bound. 52 | // It ensures that the actual value is between both bounds (but not equal to either of them). 53 | func ShouldBeBetween(actual any, expected ...any) string { 54 | if fail := need(2, expected); fail != success { 55 | return fail 56 | } 57 | lower, upper, fail := deriveBounds(expected) 58 | 59 | if fail != success { 60 | return fail 61 | } else if !isBetween(actual, lower, upper) { 62 | return fmt.Sprintf(shouldHaveBeenBetween, actual, lower, upper) 63 | } 64 | return success 65 | } 66 | 67 | // ShouldNotBeBetween receives exactly three parameters: an actual value, a lower bound, and an upper bound. 68 | // It ensures that the actual value is NOT between both bounds. 69 | func ShouldNotBeBetween(actual any, expected ...any) string { 70 | if fail := need(2, expected); fail != success { 71 | return fail 72 | } 73 | lower, upper, fail := deriveBounds(expected) 74 | 75 | if fail != success { 76 | return fail 77 | } else if isBetween(actual, lower, upper) { 78 | return fmt.Sprintf(shouldNotHaveBeenBetween, actual, lower, upper) 79 | } 80 | return success 81 | } 82 | func deriveBounds(values []any) (lower any, upper any, fail string) { 83 | lower = values[0] 84 | upper = values[1] 85 | 86 | if ShouldNotEqual(lower, upper) != success { 87 | return nil, nil, fmt.Sprintf(shouldHaveDifferentUpperAndLower, lower) 88 | } else if ShouldBeLessThan(lower, upper) != success { 89 | lower, upper = upper, lower 90 | } 91 | return lower, upper, success 92 | } 93 | func isBetween(value, lower, upper any) bool { 94 | if ShouldBeGreaterThan(value, lower) != success { 95 | return false 96 | } else if ShouldBeLessThan(value, upper) != success { 97 | return false 98 | } 99 | return true 100 | } 101 | 102 | // ShouldBeBetweenOrEqual receives exactly three parameters: an actual value, a lower bound, and an upper bound. 103 | // It ensures that the actual value is between both bounds or equal to one of them. 104 | func ShouldBeBetweenOrEqual(actual any, expected ...any) string { 105 | if fail := need(2, expected); fail != success { 106 | return fail 107 | } 108 | lower, upper, fail := deriveBounds(expected) 109 | 110 | if fail != success { 111 | return fail 112 | } else if !isBetweenOrEqual(actual, lower, upper) { 113 | return fmt.Sprintf(shouldHaveBeenBetweenOrEqual, actual, lower, upper) 114 | } 115 | return success 116 | } 117 | 118 | // ShouldNotBeBetweenOrEqual receives exactly three parameters: an actual value, a lower bound, and an upper bound. 119 | // It ensures that the actual value is nopt between the bounds nor equal to either of them. 120 | func ShouldNotBeBetweenOrEqual(actual any, expected ...any) string { 121 | if fail := need(2, expected); fail != success { 122 | return fail 123 | } 124 | lower, upper, fail := deriveBounds(expected) 125 | 126 | if fail != success { 127 | return fail 128 | } else if isBetweenOrEqual(actual, lower, upper) { 129 | return fmt.Sprintf(shouldNotHaveBeenBetweenOrEqual, actual, lower, upper) 130 | } 131 | return success 132 | } 133 | 134 | func isBetweenOrEqual(value, lower, upper any) bool { 135 | if ShouldBeGreaterThanOrEqualTo(value, lower) != success { 136 | return false 137 | } else if ShouldBeLessThanOrEqualTo(value, upper) != success { 138 | return false 139 | } 140 | return true 141 | } 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### SMARTY DISCLAIMER: Subject to the terms of the associated license agreement, this software is freely available for your use. This software is FREE, AS IN PUPPIES, and is a gift. Enjoy your new responsibility. This means that while we may consider enhancement requests, we may or may not choose to entertain requests at our sole and absolute discretion. 2 | 3 | 4 | # gunit 5 | 6 | [![GoDoc](https://godoc.org/github.com/smarty/gunit?status.svg)](http://godoc.org/github.com/smarty/gunit) 7 | 8 | Installation: 9 | 10 | ``` 11 | $ go get github.com/smarty/gunit 12 | ``` 13 | 14 | ------------------------- 15 | 16 | We now present `gunit`, yet another testing tool for Go. 17 | 18 | > Not again... ([GoConvey](http://goconvey.co) was crazy enough...but sort of cool, ok I'll pay attention...) 19 | 20 | No wait, this tool has some very interesting properties. It's a mix of good things provided by the built-in testing package, the [assertions](https://github.com/smarty/assertions) you know and love from the [GoConvey](http://goconvey.co) project, the [xUnit](https://en.wikipedia.org/wiki/XUnit) testing style (the first real unit testing framework), and it's all glued together with `go test`. 21 | 22 | > Blah, blah, yeah, yeah. Ok, so what's wrong with just using the standard "testing" package? What's better about this `gunit` thing? 23 | 24 | The convention established by the "testing" package and the `go test` tool only allows for local function scope: 25 | 26 | ``` 27 | func TestSomething(t *testing.T) { 28 | // blah blah blah 29 | } 30 | ``` 31 | 32 | This limited scope makes extracting functions or structs inconvenient as state will have to be passed to such extractions or state returned from them. It can get messy to keep a test nice and short. Here's the basic idea of what the test author using `gunit` would implement in a `*_test.go` file: 33 | 34 | ```go 35 | 36 | package examples 37 | 38 | import ( 39 | "time" 40 | "testing" 41 | 42 | "github.com/smarty/gunit/" 43 | "github.com/smarty/gunit/assert/should" 44 | ) 45 | 46 | func TestExampleFixture(t *testing.T) { 47 | gunit.Run(new(ExampleFixture), t) 48 | } 49 | 50 | type ExampleFixture struct { 51 | *gunit.Fixture // Required: Embedding this type is what makes the magic happen. 52 | 53 | // Declare useful state here (probably the stuff being tested, any fakes, etc...). 54 | } 55 | 56 | func (this *ExampleFixture) SetupStuff() { 57 | // This optional method will be executed before each "Test" 58 | // method (because it starts with "Setup"). 59 | } 60 | func (this *ExampleFixture) TeardownStuff() { 61 | // This optional method will be executed after each "Test" 62 | // method (because it starts with "Teardown"), even if the test method panics. 63 | } 64 | 65 | 66 | // This is an actual test case: 67 | func (this *ExampleFixture) TestWithAssertions() { 68 | // Here's how to use the functions from the `should` 69 | // package at github.com/smarty/assertions/should 70 | // to perform assertions: 71 | this.So(42, should.Equal, 42) 72 | this.So("Hello, World!", should.ContainSubstring, "World") 73 | } 74 | 75 | func (this *ExampleFixture) SkipTestWithNothing() { 76 | // Because this method's name starts with 'Skip', it will be skipped. 77 | } 78 | 79 | func (this *ExampleFixture) LongTestSlowOperation() { 80 | // Because this method's name starts with 'Long', it will be skipped if `go test` is run with the `short` flag. 81 | time.Sleep(time.Hour) 82 | this.So(true, should.BeTrue) 83 | } 84 | ``` 85 | 86 | ------------------------- 87 | 88 | > So, I see just one traditional test function and it's only one line long. What's the deal with that? 89 | 90 | Astute observations. `gunit` allows the test author to use a _struct_ as the scope for a group of related test cases, in the style of [xUnit](https://en.wikipedia.org/wiki/XUnit) fixtures. This makes extraction of setup/teardown behavior (as well as invoking the system under test) much simpler because all state for the test can be declared as fields on a struct which embeds the `Fixture` type from the `gunit` package. All you have to do is create a Test function and pass a new instance of your fixture struct to gunit's Run function along with the *testing.T and it will run all defined Test methods along with the Setup and Teardown method. 91 | 92 | Enjoy. 93 | 94 | ### Parallelism 95 | By default all fixtures are run in parallel as they should be independent, but if you for some reason have fixtures which need to be run sequentially, you can change the `Run()` method to `RunSequential()`, e.g. in the above example 96 | 97 | ```go 98 | func TestExampleFixture(t *testing.T) { 99 | gunit.RunSequential(new(ExampleFixture), t) 100 | } 101 | ``` 102 | 103 | [Advanced Examples](https://github.com/smarty/gunit/tree/master/advanced_examples) 104 | 105 | ---------------------------------------------------------------------------- 106 | 107 | For users of JetBrains IDEs, here's LiveTemplate you can use for generating the scaffolding for a new fixture: 108 | 109 | - Abbreviation: `fixture` 110 | - Description: `Generate gunit Fixture boilerplate` 111 | - Template Text: 112 | 113 | ``` 114 | func Test$NAME$Fixture(t *testing.T) { 115 | gunit.Run(new($NAME$Fixture), t) 116 | } 117 | 118 | type $NAME$Fixture struct { 119 | *gunit.Fixture 120 | } 121 | 122 | func (this *$NAME$Fixture) Setup() { 123 | } 124 | 125 | func (this *$NAME$Fixture) Test$END$() { 126 | } 127 | 128 | ``` 129 | 130 | Be sure to specify that this LiveTemplate is applicable in Go files. 131 | -------------------------------------------------------------------------------- /assert/assertions/internal/go-diff/diffmatchpatch/match.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2016 The go-diff authors. All rights reserved. 2 | // https://github.com/sergi/go-diff 3 | // See the included LICENSE file for license details. 4 | // 5 | // go-diff is a Go implementation of Google's Diff, Match, and Patch library 6 | // Original library is Copyright (c) 2006 Google Inc. 7 | // http://code.google.com/p/google-diff-match-patch/ 8 | 9 | package diffmatchpatch 10 | 11 | import ( 12 | "math" 13 | ) 14 | 15 | // MatchMain locates the best instance of 'pattern' in 'text' near 'loc'. 16 | // Returns -1 if no match found. 17 | func (dmp *DiffMatchPatch) MatchMain(text, pattern string, loc int) int { 18 | // Check for null inputs not needed since null can't be passed in C#. 19 | 20 | loc = int(math.Max(0, math.Min(float64(loc), float64(len(text))))) 21 | if text == pattern { 22 | // Shortcut (potentially not guaranteed by the algorithm) 23 | return 0 24 | } else if len(text) == 0 { 25 | // Nothing to match. 26 | return -1 27 | } else if loc+len(pattern) <= len(text) && text[loc:loc+len(pattern)] == pattern { 28 | // Perfect match at the perfect spot! (Includes case of null pattern) 29 | return loc 30 | } 31 | // Do a fuzzy compare. 32 | return dmp.MatchBitap(text, pattern, loc) 33 | } 34 | 35 | // MatchBitap locates the best instance of 'pattern' in 'text' near 'loc' using the Bitap algorithm. 36 | // Returns -1 if no match was found. 37 | func (dmp *DiffMatchPatch) MatchBitap(text, pattern string, loc int) int { 38 | // Initialise the alphabet. 39 | s := dmp.MatchAlphabet(pattern) 40 | 41 | // Highest score beyond which we give up. 42 | scoreThreshold := dmp.MatchThreshold 43 | // Is there a nearby exact match? (speedup) 44 | bestLoc := indexOf(text, pattern, loc) 45 | if bestLoc != -1 { 46 | scoreThreshold = math.Min(dmp.matchBitapScore(0, bestLoc, loc, 47 | pattern), scoreThreshold) 48 | // What about in the other direction? (speedup) 49 | bestLoc = lastIndexOf(text, pattern, loc+len(pattern)) 50 | if bestLoc != -1 { 51 | scoreThreshold = math.Min(dmp.matchBitapScore(0, bestLoc, loc, 52 | pattern), scoreThreshold) 53 | } 54 | } 55 | 56 | // Initialise the bit arrays. 57 | matchmask := 1 << uint((len(pattern) - 1)) 58 | bestLoc = -1 59 | 60 | var binMin, binMid int 61 | binMax := len(pattern) + len(text) 62 | lastRd := []int{} 63 | for d := 0; d < len(pattern); d++ { 64 | // Scan for the best match; each iteration allows for one more error. Run a binary search to determine how far from 'loc' we can stray at this error level. 65 | binMin = 0 66 | binMid = binMax 67 | for binMin < binMid { 68 | if dmp.matchBitapScore(d, loc+binMid, loc, pattern) <= scoreThreshold { 69 | binMin = binMid 70 | } else { 71 | binMax = binMid 72 | } 73 | binMid = (binMax-binMin)/2 + binMin 74 | } 75 | // Use the result from this iteration as the maximum for the next. 76 | binMax = binMid 77 | start := int(math.Max(1, float64(loc-binMid+1))) 78 | finish := int(math.Min(float64(loc+binMid), float64(len(text))) + float64(len(pattern))) 79 | 80 | rd := make([]int, finish+2) 81 | rd[finish+1] = (1 << uint(d)) - 1 82 | 83 | for j := finish; j >= start; j-- { 84 | var charMatch int 85 | if len(text) <= j-1 { 86 | // Out of range. 87 | charMatch = 0 88 | } else if _, ok := s[text[j-1]]; !ok { 89 | charMatch = 0 90 | } else { 91 | charMatch = s[text[j-1]] 92 | } 93 | 94 | if d == 0 { 95 | // First pass: exact match. 96 | rd[j] = ((rd[j+1] << 1) | 1) & charMatch 97 | } else { 98 | // Subsequent passes: fuzzy match. 99 | rd[j] = ((rd[j+1]<<1)|1)&charMatch | (((lastRd[j+1] | lastRd[j]) << 1) | 1) | lastRd[j+1] 100 | } 101 | if (rd[j] & matchmask) != 0 { 102 | score := dmp.matchBitapScore(d, j-1, loc, pattern) 103 | // This match will almost certainly be better than any existing match. But check anyway. 104 | if score <= scoreThreshold { 105 | // Told you so. 106 | scoreThreshold = score 107 | bestLoc = j - 1 108 | if bestLoc > loc { 109 | // When passing loc, don't exceed our current distance from loc. 110 | start = int(math.Max(1, float64(2*loc-bestLoc))) 111 | } else { 112 | // Already passed loc, downhill from here on in. 113 | break 114 | } 115 | } 116 | } 117 | } 118 | if dmp.matchBitapScore(d+1, loc, loc, pattern) > scoreThreshold { 119 | // No hope for a (better) match at greater error levels. 120 | break 121 | } 122 | lastRd = rd 123 | } 124 | return bestLoc 125 | } 126 | 127 | // matchBitapScore computes and returns the score for a match with e errors and x location. 128 | func (dmp *DiffMatchPatch) matchBitapScore(e, x, loc int, pattern string) float64 { 129 | accuracy := float64(e) / float64(len(pattern)) 130 | proximity := math.Abs(float64(loc - x)) 131 | if dmp.MatchDistance == 0 { 132 | // Dodge divide by zero error. 133 | if proximity == 0 { 134 | return accuracy 135 | } 136 | 137 | return 1.0 138 | } 139 | return accuracy + (proximity / float64(dmp.MatchDistance)) 140 | } 141 | 142 | // MatchAlphabet initialises the alphabet for the Bitap algorithm. 143 | func (dmp *DiffMatchPatch) MatchAlphabet(pattern string) map[byte]int { 144 | s := map[byte]int{} 145 | charPattern := []byte(pattern) 146 | for _, c := range charPattern { 147 | _, ok := s[c] 148 | if !ok { 149 | s[c] = 0 150 | } 151 | } 152 | i := 0 153 | 154 | for _, c := range charPattern { 155 | value := s[c] | int(uint(1)<' NOT to start with '' (but it did)!") 24 | this.fail(so("superman", ShouldNotStartWith, "super"), "Expected 'superman' NOT to start with 'super' (but it did)!") 25 | this.pass(so("superman", ShouldNotStartWith, "bat")) 26 | this.pass(so("superman", ShouldNotStartWith, "man")) 27 | 28 | this.fail(so(1, ShouldNotStartWith, 2), "Both arguments to this assertion must be strings (you provided int and int).") 29 | } 30 | 31 | func (this *AssertionsFixture) TestShouldEndWith() { 32 | this.fail(so("", ShouldEndWith), "This assertion requires exactly 1 comparison values (you provided 0).") 33 | this.fail(so("", ShouldEndWith, "", ""), "This assertion requires exactly 1 comparison values (you provided 2).") 34 | 35 | this.pass(so("", ShouldEndWith, "")) 36 | this.fail(so("", ShouldEndWith, "z"), "Expected '' to end with 'z' (but it didn't)!") 37 | this.pass(so("xyz", ShouldEndWith, "xyz")) 38 | this.fail(so("xyz", ShouldEndWith, "wxyz"), "Expected 'xyz' to end with 'wxyz' (but it didn't)!") 39 | 40 | this.pass(so("superman", ShouldEndWith, "man")) 41 | this.fail(so("superman", ShouldEndWith, "super"), "Expected 'superman' to end with 'super' (but it didn't)!") 42 | this.fail(so("superman", ShouldEndWith, "blah"), "Expected 'superman' to end with 'blah' (but it didn't)!") 43 | 44 | this.fail(so(1, ShouldEndWith, 2), "Both arguments to this assertion must be strings (you provided int and int).") 45 | } 46 | 47 | func (this *AssertionsFixture) TestShouldNotEndWith() { 48 | this.fail(so("", ShouldNotEndWith), "This assertion requires exactly 1 comparison values (you provided 0).") 49 | this.fail(so("", ShouldNotEndWith, "", ""), "This assertion requires exactly 1 comparison values (you provided 2).") 50 | 51 | this.fail(so("", ShouldNotEndWith, ""), "Expected '' NOT to end with '' (but it did)!") 52 | this.fail(so("superman", ShouldNotEndWith, "man"), "Expected 'superman' NOT to end with 'man' (but it did)!") 53 | this.pass(so("superman", ShouldNotEndWith, "super")) 54 | 55 | this.fail(so(1, ShouldNotEndWith, 2), "Both arguments to this assertion must be strings (you provided int and int).") 56 | } 57 | 58 | func (this *AssertionsFixture) TestShouldContainSubstring() { 59 | this.fail(so("asdf", ShouldContainSubstring), "This assertion requires exactly 1 comparison values (you provided 0).") 60 | this.fail(so("asdf", ShouldContainSubstring, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).") 61 | 62 | this.fail(so(123, ShouldContainSubstring, 23), "Both arguments to this assertion must be strings (you provided int and int).") 63 | 64 | this.pass(so("asdf", ShouldContainSubstring, "sd")) 65 | this.fail(so("qwer", ShouldContainSubstring, "sd"), "Expected 'qwer' to contain substring 'sd' (but it didn't)!") 66 | } 67 | 68 | func (this *AssertionsFixture) TestShouldNotContainSubstring() { 69 | this.fail(so("asdf", ShouldNotContainSubstring), "This assertion requires exactly 1 comparison values (you provided 0).") 70 | this.fail(so("asdf", ShouldNotContainSubstring, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).") 71 | 72 | this.fail(so(123, ShouldNotContainSubstring, 23), "Both arguments to this assertion must be strings (you provided int and int).") 73 | 74 | this.pass(so("qwer", ShouldNotContainSubstring, "sd")) 75 | this.fail(so("asdf", ShouldNotContainSubstring, "sd"), "Expected 'asdf' NOT to contain substring 'sd' (but it did)!") 76 | } 77 | 78 | func (this *AssertionsFixture) TestShouldBeBlank() { 79 | this.fail(so("", ShouldBeBlank, "adsf"), "This assertion requires exactly 0 comparison values (you provided 1).") 80 | this.fail(so(1, ShouldBeBlank), "The argument to this assertion must be a string (you provided int).") 81 | 82 | this.fail(so("asdf", ShouldBeBlank), "Expected 'asdf' to be blank (but it wasn't)!") 83 | this.pass(so("", ShouldBeBlank)) 84 | } 85 | 86 | func (this *AssertionsFixture) TestShouldNotBeBlank() { 87 | this.fail(so("", ShouldNotBeBlank, "adsf"), "This assertion requires exactly 0 comparison values (you provided 1).") 88 | this.fail(so(1, ShouldNotBeBlank), "The argument to this assertion must be a string (you provided int).") 89 | 90 | this.fail(so("", ShouldNotBeBlank), "Expected value to NOT be blank (but it was)!") 91 | this.pass(so("asdf", ShouldNotBeBlank)) 92 | } 93 | -------------------------------------------------------------------------------- /assert/assertions/messages.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | const ( 4 | shouldHaveBeenAlmostEqual = "Expected '%v' to almost equal '%v' (but it didn't)!" 5 | shouldHaveNotBeenAlmostEqual = "Expected '%v' to NOT almost equal '%v' (but it did)!" 6 | 7 | shouldHaveBeenEqual = "Expected: %s\nActual: %s\n(Should equal)!" 8 | shouldHaveBeenEqualButTypeDiff = "Expected: %s\nActual: %s\n(Should equal, but there is a type difference within the two)!" 9 | shouldNotHaveBeenEqual = "Expected '%v'\nto NOT equal '%v'\n(but it did)!" 10 | 11 | shouldHaveBeenNil = "Expected: nil\nActual: '%v'" 12 | shouldNotHaveBeenNil = "Expected '%+v' to NOT be nil (but it was)!" 13 | 14 | shouldHaveBeenTrue = "Expected: true\nActual: %v" 15 | shouldHaveBeenFalse = "Expected: false\nActual: %v" 16 | 17 | shouldHaveBeenZeroValue = "'%+v' should have been the zero value" // "Expected: (zero value)\nActual: %v" 18 | shouldNotHaveBeenZeroValue = "'%+v' should NOT have been the zero value" 19 | 20 | shouldHaveBeenGreater = "Expected '%v' to be greater than '%v' (but it wasn't)!" 21 | shouldHaveBeenGreaterOrEqual = "Expected '%v' to be greater than or equal to '%v' (but it wasn't)!" 22 | 23 | shouldHaveBeenLess = "Expected '%v' to be less than '%v' (but it wasn't)!" 24 | shouldHaveBeenLessOrEqual = "Expected '%v' to be less than or equal to '%v' (but it wasn't)!" 25 | 26 | shouldHaveBeenBetween = "Expected '%v' to be between '%v' and '%v' (but it wasn't)!" 27 | shouldNotHaveBeenBetween = "Expected '%v' NOT to be between '%v' and '%v' (but it was)!" 28 | shouldHaveDifferentUpperAndLower = "The lower and upper bounds must be different values (they were both '%v')." 29 | 30 | shouldHaveBeenBetweenOrEqual = "Expected '%v' to be between '%v' and '%v' or equal to one of them (but it wasn't)!" 31 | shouldNotHaveBeenBetweenOrEqual = "Expected '%v' NOT to be between '%v' and '%v' or equal to one of them (but it was)!" 32 | 33 | shouldHaveContained = "Expected the container (%v) to contain: '%v' (but it didn't)!" 34 | shouldNotHaveContained = "Expected the container (%v) NOT to contain: '%v' (but it did)!" 35 | shouldHaveBeenAValidCollection = "You must provide a valid container (was %v)!" 36 | 37 | shouldHaveContainedKey = "Expected the %v to contain the key: %v (but it didn't)!" 38 | shouldNotHaveContainedKey = "Expected the %v NOT to contain the key: %v (but it did)!" 39 | shouldHaveBeenAValidMap = "You must provide a valid map type (was %v)!" 40 | 41 | shouldHaveBeenIn = "Expected '%v' to be in the container (%v), but it wasn't!" 42 | shouldNotHaveBeenIn = "Expected '%v' NOT to be in the container (%v), but it was!" 43 | 44 | shouldHaveBeenEmpty = "Expected %+v to be empty (but it wasn't)!" 45 | shouldNotHaveBeenEmpty = "Expected %+v to NOT be empty (but it was)!" 46 | shouldHaveBeenEmptyWrongKind = "Expected value's kind to be slice, chan, map, or string (you provided %s)!" 47 | 48 | shouldHaveBeenAValidInteger = "You must provide a valid integer (was %v)!" 49 | shouldHaveBeenAValidLength = "You must provide a valid positive integer (was %v)!" 50 | shouldHaveHadLength = "Expected collection to have length equal to [%v], but its length was [%v] instead! contents: %+v" 51 | 52 | shouldHaveStartedWith = "Expected '%v'\nto start with '%v'\n(but it didn't)!" 53 | shouldNotHaveStartedWith = "Expected '%v'\nNOT to start with '%v'\n(but it did)!" 54 | 55 | shouldHaveEndedWith = "Expected '%v'\nto end with '%v'\n(but it didn't)!" 56 | shouldNotHaveEndedWith = "Expected '%v'\nNOT to end with '%v'\n(but it did)!" 57 | 58 | shouldBothBeStrings = "Both arguments to this assertion must be strings (you provided %v and %v)." 59 | 60 | shouldHaveContainedSubstring = "Expected '%s' to contain substring '%s' (but it didn't)!" 61 | shouldNotHaveContainedSubstring = "Expected '%s' NOT to contain substring '%s' (but it did)!" 62 | 63 | shouldBeString = "The argument to this assertion must be a string (you provided %v)." 64 | shouldHaveBeenBlank = "Expected '%s' to be blank (but it wasn't)!" 65 | shouldNotHaveBeenBlank = "Expected value to NOT be blank (but it was)!" 66 | 67 | shouldUseVoidNiladicFunction = "You must provide a void, niladic function as the first argument!" 68 | shouldHavePanicked = "Expected func() to panic (but it didn't)!" 69 | shouldNotHavePanicked = "Expected func() NOT to panic (error: '%+v')!" 70 | 71 | shouldHavePanickedWith = "Expected func() to panic with '%v' (but it panicked with '%v')!" 72 | shouldNotHavePanickedWith = "Expected func() NOT to panic with '%v' (but it did)!" 73 | 74 | shouldHaveBeenA = "Expected '%v' to be: '%v' (but was: '%v')!" 75 | shouldNotHaveBeenA = "Expected '%v' to NOT be: '%v' (but it was)!" 76 | 77 | shouldWrapInvalidTypes = "The first and last arguments to this assertion must both be error values (you provided: '%v' and '%v')." 78 | 79 | shouldUseTimes = "You must provide time instances as arguments to this assertion." 80 | shouldUseTimeSlice = "You must provide a slice of time instances as the first argument to this assertion." 81 | shouldUseDurationAndTime = "You must provide a duration and a time as arguments to this assertion." 82 | 83 | shouldHaveHappenedBefore = "Expected '%v' to happen before '%v' (it happened '%v' after)!" 84 | shouldHaveHappenedAfter = "Expected '%v' to happen after '%v' (it happened '%v' before)!" 85 | shouldHaveHappenedBetween = "Expected '%v' to happen between '%v' and '%v' (it happened '%v' outside threshold)!" 86 | shouldNotHaveHappenedOnOrBetween = "Expected '%v' to NOT happen on or between '%v' and '%v' (but it did)!" 87 | 88 | // format params: incorrect-index, previous-index, previous-time, incorrect-index, incorrect-time 89 | shouldHaveBeenChronological = "The 'Time' at index [%d] should have happened after the previous one (but it didn't!):\n [%d]: %s\n [%d]: %s (see, it happened before!)" 90 | shouldNotHaveBeenChronological = "The provided times should NOT be chronological, but they were." 91 | ) 92 | -------------------------------------------------------------------------------- /assert/assertions/strings.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | // ShouldStartWith receives exactly 2 string parameters and ensures that the first starts with the second. 10 | func ShouldStartWith(actual any, expected ...any) string { 11 | if fail := need(1, expected); fail != success { 12 | return fail 13 | } 14 | 15 | value, valueIsString := actual.(string) 16 | prefix, prefixIsString := expected[0].(string) 17 | 18 | if !valueIsString || !prefixIsString { 19 | return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0])) 20 | } 21 | 22 | return shouldStartWith(value, prefix) 23 | } 24 | func shouldStartWith(value, prefix string) string { 25 | if !strings.HasPrefix(value, prefix) { 26 | shortval := value 27 | if len(shortval) > len(prefix) { 28 | shortval = shortval[:len(prefix)] + "..." 29 | } 30 | return fmt.Sprintf(shouldHaveStartedWith, value, prefix) 31 | } 32 | return success 33 | } 34 | 35 | // ShouldNotStartWith receives exactly 2 string parameters and ensures that the first does not start with the second. 36 | func ShouldNotStartWith(actual any, expected ...any) string { 37 | if fail := need(1, expected); fail != success { 38 | return fail 39 | } 40 | 41 | value, valueIsString := actual.(string) 42 | prefix, prefixIsString := expected[0].(string) 43 | 44 | if !valueIsString || !prefixIsString { 45 | return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0])) 46 | } 47 | 48 | return shouldNotStartWith(value, prefix) 49 | } 50 | func shouldNotStartWith(value, prefix string) string { 51 | if strings.HasPrefix(value, prefix) { 52 | if value == "" { 53 | value = "" 54 | } 55 | if prefix == "" { 56 | prefix = "" 57 | } 58 | return fmt.Sprintf(shouldNotHaveStartedWith, value, prefix) 59 | } 60 | return success 61 | } 62 | 63 | // ShouldEndWith receives exactly 2 string parameters and ensures that the first ends with the second. 64 | func ShouldEndWith(actual any, expected ...any) string { 65 | if fail := need(1, expected); fail != success { 66 | return fail 67 | } 68 | 69 | value, valueIsString := actual.(string) 70 | suffix, suffixIsString := expected[0].(string) 71 | 72 | if !valueIsString || !suffixIsString { 73 | return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0])) 74 | } 75 | 76 | return shouldEndWith(value, suffix) 77 | } 78 | func shouldEndWith(value, suffix string) string { 79 | if !strings.HasSuffix(value, suffix) { 80 | shortval := value 81 | if len(shortval) > len(suffix) { 82 | shortval = "..." + shortval[len(shortval)-len(suffix):] 83 | } 84 | return fmt.Sprintf(shouldHaveEndedWith, value, suffix) 85 | } 86 | return success 87 | } 88 | 89 | // ShouldNotEndWith receives exactly 2 string parameters and ensures that the first does not end with the second. 90 | func ShouldNotEndWith(actual any, expected ...any) string { 91 | if fail := need(1, expected); fail != success { 92 | return fail 93 | } 94 | 95 | value, valueIsString := actual.(string) 96 | suffix, suffixIsString := expected[0].(string) 97 | 98 | if !valueIsString || !suffixIsString { 99 | return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0])) 100 | } 101 | 102 | return shouldNotEndWith(value, suffix) 103 | } 104 | func shouldNotEndWith(value, suffix string) string { 105 | if strings.HasSuffix(value, suffix) { 106 | if value == "" { 107 | value = "" 108 | } 109 | if suffix == "" { 110 | suffix = "" 111 | } 112 | return fmt.Sprintf(shouldNotHaveEndedWith, value, suffix) 113 | } 114 | return success 115 | } 116 | 117 | // ShouldContainSubstring receives exactly 2 string parameters and ensures that the first contains the second as a substring. 118 | func ShouldContainSubstring(actual any, expected ...any) string { 119 | if fail := need(1, expected); fail != success { 120 | return fail 121 | } 122 | 123 | long, longOk := actual.(string) 124 | short, shortOk := expected[0].(string) 125 | 126 | if !longOk || !shortOk { 127 | return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0])) 128 | } 129 | 130 | if !strings.Contains(long, short) { 131 | return fmt.Sprintf(shouldHaveContainedSubstring, long, short) 132 | } 133 | return success 134 | } 135 | 136 | // ShouldNotContainSubstring receives exactly 2 string parameters and ensures that the first does NOT contain the second as a substring. 137 | func ShouldNotContainSubstring(actual any, expected ...any) string { 138 | if fail := need(1, expected); fail != success { 139 | return fail 140 | } 141 | 142 | long, longOk := actual.(string) 143 | short, shortOk := expected[0].(string) 144 | 145 | if !longOk || !shortOk { 146 | return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0])) 147 | } 148 | 149 | if strings.Contains(long, short) { 150 | return fmt.Sprintf(shouldNotHaveContainedSubstring, long, short) 151 | } 152 | return success 153 | } 154 | 155 | // ShouldBeBlank receives exactly 1 string parameter and ensures that it is equal to "". 156 | func ShouldBeBlank(actual any, expected ...any) string { 157 | if fail := need(0, expected); fail != success { 158 | return fail 159 | } 160 | value, ok := actual.(string) 161 | if !ok { 162 | return fmt.Sprintf(shouldBeString, reflect.TypeOf(actual)) 163 | } 164 | if value != "" { 165 | return fmt.Sprintf(shouldHaveBeenBlank, value) 166 | } 167 | return success 168 | } 169 | 170 | // ShouldNotBeBlank receives exactly 1 string parameter and ensures that it is not equal to "". 171 | func ShouldNotBeBlank(actual any, expected ...any) string { 172 | if fail := need(0, expected); fail != success { 173 | return fail 174 | } 175 | value, ok := actual.(string) 176 | if !ok { 177 | return fmt.Sprintf(shouldBeString, reflect.TypeOf(actual)) 178 | } 179 | if value == "" { 180 | return shouldNotHaveBeenBlank 181 | } 182 | return success 183 | } 184 | -------------------------------------------------------------------------------- /assert/assertions/time.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // ShouldHappenBefore receives exactly 2 time.Time arguments and asserts that the first happens before the second. 9 | func ShouldHappenBefore(actual any, expected ...any) string { 10 | if fail := need(1, expected); fail != success { 11 | return fail 12 | } 13 | actualTime, firstOk := actual.(time.Time) 14 | expectedTime, secondOk := expected[0].(time.Time) 15 | 16 | if !firstOk || !secondOk { 17 | return shouldUseTimes 18 | } 19 | 20 | if !actualTime.Before(expectedTime) { 21 | return fmt.Sprintf(shouldHaveHappenedBefore, actualTime, expectedTime, actualTime.Sub(expectedTime)) 22 | } 23 | 24 | return success 25 | } 26 | 27 | // ShouldHappenOnOrBefore receives exactly 2 time.Time arguments and asserts that the first happens on or before the second. 28 | func ShouldHappenOnOrBefore(actual any, expected ...any) string { 29 | if fail := need(1, expected); fail != success { 30 | return fail 31 | } 32 | actualTime, firstOk := actual.(time.Time) 33 | expectedTime, secondOk := expected[0].(time.Time) 34 | 35 | if !firstOk || !secondOk { 36 | return shouldUseTimes 37 | } 38 | 39 | if actualTime.Equal(expectedTime) { 40 | return success 41 | } 42 | return ShouldHappenBefore(actualTime, expectedTime) 43 | } 44 | 45 | // ShouldHappenAfter receives exactly 2 time.Time arguments and asserts that the first happens after the second. 46 | func ShouldHappenAfter(actual any, expected ...any) string { 47 | if fail := need(1, expected); fail != success { 48 | return fail 49 | } 50 | actualTime, firstOk := actual.(time.Time) 51 | expectedTime, secondOk := expected[0].(time.Time) 52 | 53 | if !firstOk || !secondOk { 54 | return shouldUseTimes 55 | } 56 | if !actualTime.After(expectedTime) { 57 | return fmt.Sprintf(shouldHaveHappenedAfter, actualTime, expectedTime, expectedTime.Sub(actualTime)) 58 | } 59 | return success 60 | } 61 | 62 | // ShouldHappenOnOrAfter receives exactly 2 time.Time arguments and asserts that the first happens on or after the second. 63 | func ShouldHappenOnOrAfter(actual any, expected ...any) string { 64 | if fail := need(1, expected); fail != success { 65 | return fail 66 | } 67 | actualTime, firstOk := actual.(time.Time) 68 | expectedTime, secondOk := expected[0].(time.Time) 69 | 70 | if !firstOk || !secondOk { 71 | return shouldUseTimes 72 | } 73 | if actualTime.Equal(expectedTime) { 74 | return success 75 | } 76 | return ShouldHappenAfter(actualTime, expectedTime) 77 | } 78 | 79 | // ShouldHappenBetween receives exactly 3 time.Time arguments and asserts that the first happens between (not on) the second and third. 80 | func ShouldHappenBetween(actual any, expected ...any) string { 81 | if fail := need(2, expected); fail != success { 82 | return fail 83 | } 84 | actualTime, firstOk := actual.(time.Time) 85 | min, secondOk := expected[0].(time.Time) 86 | max, thirdOk := expected[1].(time.Time) 87 | 88 | if !firstOk || !secondOk || !thirdOk { 89 | return shouldUseTimes 90 | } 91 | 92 | if !actualTime.After(min) { 93 | return fmt.Sprintf(shouldHaveHappenedBetween, actualTime, min, max, min.Sub(actualTime)) 94 | } 95 | if !actualTime.Before(max) { 96 | return fmt.Sprintf(shouldHaveHappenedBetween, actualTime, min, max, actualTime.Sub(max)) 97 | } 98 | return success 99 | } 100 | 101 | // ShouldHappenOnOrBetween receives exactly 3 time.Time arguments and asserts that the first happens between or on the second and third. 102 | func ShouldHappenOnOrBetween(actual any, expected ...any) string { 103 | if fail := need(2, expected); fail != success { 104 | return fail 105 | } 106 | actualTime, firstOk := actual.(time.Time) 107 | min, secondOk := expected[0].(time.Time) 108 | max, thirdOk := expected[1].(time.Time) 109 | 110 | if !firstOk || !secondOk || !thirdOk { 111 | return shouldUseTimes 112 | } 113 | if actualTime.Equal(min) || actualTime.Equal(max) { 114 | return success 115 | } 116 | return ShouldHappenBetween(actualTime, min, max) 117 | } 118 | 119 | // ShouldNotHappenOnOrBetween receives exactly 3 time.Time arguments and asserts that the first 120 | // does NOT happen between or on the second or third. 121 | func ShouldNotHappenOnOrBetween(actual any, expected ...any) string { 122 | if fail := need(2, expected); fail != success { 123 | return fail 124 | } 125 | actualTime, firstOk := actual.(time.Time) 126 | min, secondOk := expected[0].(time.Time) 127 | max, thirdOk := expected[1].(time.Time) 128 | 129 | if !firstOk || !secondOk || !thirdOk { 130 | return shouldUseTimes 131 | } 132 | if actualTime.Equal(min) || actualTime.Equal(max) { 133 | return fmt.Sprintf(shouldNotHaveHappenedOnOrBetween, actualTime, min, max) 134 | } 135 | if actualTime.After(min) && actualTime.Before(max) { 136 | return fmt.Sprintf(shouldNotHaveHappenedOnOrBetween, actualTime, min, max) 137 | } 138 | return success 139 | } 140 | 141 | // ShouldHappenWithin receives a time.Time, a time.Duration, and a time.Time (3 arguments) 142 | // and asserts that the first time.Time happens within or on the duration specified relative to 143 | // the other time.Time. 144 | func ShouldHappenWithin(actual any, expected ...any) string { 145 | if fail := need(2, expected); fail != success { 146 | return fail 147 | } 148 | actualTime, firstOk := actual.(time.Time) 149 | tolerance, secondOk := expected[0].(time.Duration) 150 | threshold, thirdOk := expected[1].(time.Time) 151 | 152 | if !firstOk || !secondOk || !thirdOk { 153 | return shouldUseDurationAndTime 154 | } 155 | 156 | min := threshold.Add(-tolerance) 157 | max := threshold.Add(tolerance) 158 | return ShouldHappenOnOrBetween(actualTime, min, max) 159 | } 160 | 161 | // ShouldNotHappenWithin receives a time.Time, a time.Duration, and a time.Time (3 arguments) 162 | // and asserts that the first time.Time does NOT happen within or on the duration specified relative to 163 | // the other time.Time. 164 | func ShouldNotHappenWithin(actual any, expected ...any) string { 165 | if fail := need(2, expected); fail != success { 166 | return fail 167 | } 168 | actualTime, firstOk := actual.(time.Time) 169 | tolerance, secondOk := expected[0].(time.Duration) 170 | threshold, thirdOk := expected[1].(time.Time) 171 | 172 | if !firstOk || !secondOk || !thirdOk { 173 | return shouldUseDurationAndTime 174 | } 175 | 176 | min := threshold.Add(-tolerance) 177 | max := threshold.Add(tolerance) 178 | return ShouldNotHappenOnOrBetween(actualTime, min, max) 179 | } 180 | 181 | // ShouldBeChronological receives a []time.Time slice and asserts that they are 182 | // in chronological order starting with the first time.Time as the earliest. 183 | func ShouldBeChronological(actual any, expected ...any) string { 184 | if fail := need(0, expected); fail != success { 185 | return fail 186 | } 187 | 188 | times, ok := actual.([]time.Time) 189 | if !ok { 190 | return shouldUseTimeSlice 191 | } 192 | 193 | var previous time.Time 194 | for i, current := range times { 195 | if i > 0 && current.Before(previous) { 196 | return fmt.Sprintf(shouldHaveBeenChronological, 197 | i, i-1, previous.String(), i, current.String()) 198 | } 199 | previous = current 200 | } 201 | return "" 202 | } 203 | 204 | // ShouldNotBeChronological receives a []time.Time slice and asserts that they are 205 | // NOT in chronological order. 206 | func ShouldNotBeChronological(actual any, expected ...any) string { 207 | if fail := need(0, expected); fail != success { 208 | return fail 209 | } 210 | if _, ok := actual.([]time.Time); !ok { 211 | return shouldUseTimeSlice 212 | } 213 | result := ShouldBeChronological(actual, expected...) 214 | if result != "" { 215 | return "" 216 | } 217 | return shouldNotHaveBeenChronological 218 | } 219 | -------------------------------------------------------------------------------- /assert/assertions/equality.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math" 7 | "reflect" 8 | 9 | "github.com/smarty/gunit/assert/assertions/internal/go-render/render" 10 | ) 11 | 12 | // ShouldEqual receives exactly two parameters and does an equality check 13 | // using the following semantics: 14 | // It uses reflect.DeepEqual in most cases, but also compares numerics 15 | // regardless of specific type and compares time.Time values using the 16 | // time.Equal method. 17 | func ShouldEqual(actual any, expected ...any) string { 18 | if message := need(1, expected); message != success { 19 | return message 20 | } 21 | return shouldEqual(actual, expected[0]) 22 | } 23 | func shouldEqual(actual, expected any) string { 24 | for _, spec := range equalitySpecs { 25 | if !spec.assertable(actual, expected) { 26 | continue 27 | } 28 | if spec.passes(actual, expected) { 29 | return success 30 | } 31 | break 32 | } 33 | renderedExpected, renderedActual := render.Render(expected), render.Render(actual) 34 | if renderedActual == renderedExpected { 35 | return fmt.Sprintf(shouldHaveBeenEqualButTypeDiff, renderedExpected, renderedActual) 36 | } 37 | return fmt.Sprintf(shouldHaveBeenEqual, renderedExpected, renderedActual) + 38 | composePrettyDiff(renderedExpected, renderedActual) 39 | } 40 | 41 | // ShouldNotEqual receives exactly two parameters and does an inequality check. 42 | // See ShouldEqual for details on how equality is determined. 43 | func ShouldNotEqual(actual any, expected ...any) string { 44 | if fail := need(1, expected); fail != success { 45 | return fail 46 | } else if ShouldEqual(actual, expected[0]) == success { 47 | return fmt.Sprintf(shouldNotHaveBeenEqual, actual, expected[0]) 48 | } 49 | return success 50 | } 51 | 52 | // ShouldAlmostEqual makes sure that two parameters are close enough to being equal. 53 | // The acceptable delta may be specified with a third argument, 54 | // or a very small default delta will be used. 55 | func ShouldAlmostEqual(actual any, expected ...any) string { 56 | actualFloat, expectedFloat, deltaFloat, err := cleanAlmostEqualInput(actual, expected...) 57 | 58 | if err != "" { 59 | return err 60 | } 61 | 62 | if math.Abs(actualFloat-expectedFloat) <= deltaFloat { 63 | return success 64 | } else { 65 | return fmt.Sprintf(shouldHaveBeenAlmostEqual, actualFloat, expectedFloat) 66 | } 67 | } 68 | 69 | // ShouldNotAlmostEqual is the inverse of ShouldAlmostEqual 70 | func ShouldNotAlmostEqual(actual any, expected ...any) string { 71 | actualFloat, expectedFloat, deltaFloat, err := cleanAlmostEqualInput(actual, expected...) 72 | 73 | if err != "" { 74 | return err 75 | } 76 | 77 | if math.Abs(actualFloat-expectedFloat) > deltaFloat { 78 | return success 79 | } else { 80 | return fmt.Sprintf(shouldHaveNotBeenAlmostEqual, actualFloat, expectedFloat) 81 | } 82 | } 83 | 84 | func cleanAlmostEqualInput(actual any, expected ...any) (float64, float64, float64, string) { 85 | deltaFloat := 0.0000000001 86 | 87 | if len(expected) == 0 { 88 | return 0.0, 0.0, 0.0, "This assertion requires exactly one comparison value and an optional delta (you provided neither)" 89 | } else if len(expected) == 2 { 90 | delta, err := getFloat(expected[1]) 91 | 92 | if err != nil { 93 | return 0.0, 0.0, 0.0, "The delta value " + err.Error() 94 | } 95 | 96 | deltaFloat = delta 97 | } else if len(expected) > 2 { 98 | return 0.0, 0.0, 0.0, "This assertion requires exactly one comparison value and an optional delta (you provided more values)" 99 | } 100 | 101 | actualFloat, err := getFloat(actual) 102 | if err != nil { 103 | return 0.0, 0.0, 0.0, "The actual value " + err.Error() 104 | } 105 | 106 | expectedFloat, err := getFloat(expected[0]) 107 | if err != nil { 108 | return 0.0, 0.0, 0.0, "The comparison value " + err.Error() 109 | } 110 | 111 | return actualFloat, expectedFloat, deltaFloat, "" 112 | } 113 | 114 | // returns the float value of any real number, or error if it is not a numerical type 115 | func getFloat(num any) (float64, error) { 116 | numValue := reflect.ValueOf(num) 117 | numKind := numValue.Kind() 118 | 119 | if numKind == reflect.Int || 120 | numKind == reflect.Int8 || 121 | numKind == reflect.Int16 || 122 | numKind == reflect.Int32 || 123 | numKind == reflect.Int64 { 124 | return float64(numValue.Int()), nil 125 | } else if numKind == reflect.Uint || 126 | numKind == reflect.Uint8 || 127 | numKind == reflect.Uint16 || 128 | numKind == reflect.Uint32 || 129 | numKind == reflect.Uint64 { 130 | return float64(numValue.Uint()), nil 131 | } else if numKind == reflect.Float32 || 132 | numKind == reflect.Float64 { 133 | return numValue.Float(), nil 134 | } else { 135 | return 0.0, errors.New("must be a numerical type, but was: " + numKind.String()) 136 | } 137 | } 138 | 139 | // ShouldBeNil receives a single parameter and ensures that it is nil. 140 | func ShouldBeNil(actual any, expected ...any) string { 141 | if fail := need(0, expected); fail != success { 142 | return fail 143 | } else if actual == nil { 144 | return success 145 | } else if interfaceHasNilValue(actual) { 146 | return success 147 | } 148 | return fmt.Sprintf(shouldHaveBeenNil, actual) 149 | } 150 | func interfaceHasNilValue(actual any) bool { 151 | value := reflect.ValueOf(actual) 152 | kind := value.Kind() 153 | nilable := kind == reflect.Slice || 154 | kind == reflect.Chan || 155 | kind == reflect.Func || 156 | kind == reflect.Ptr || 157 | kind == reflect.Map 158 | 159 | // Careful: reflect.Value.IsNil() will panic unless it's an interface, chan, map, func, slice, or ptr 160 | // Reference: http://golang.org/pkg/reflect/#Value.IsNil 161 | return nilable && value.IsNil() 162 | } 163 | 164 | // ShouldNotBeNil receives a single parameter and ensures that it is not nil. 165 | func ShouldNotBeNil(actual any, expected ...any) string { 166 | if fail := need(0, expected); fail != success { 167 | return fail 168 | } else if ShouldBeNil(actual) == success { 169 | return fmt.Sprintf(shouldNotHaveBeenNil, actual) 170 | } 171 | return success 172 | } 173 | 174 | // ShouldBeTrue receives a single parameter and ensures that it is true. 175 | func ShouldBeTrue(actual any, expected ...any) string { 176 | if fail := need(0, expected); fail != success { 177 | return fail 178 | } else if actual != true { 179 | return fmt.Sprintf(shouldHaveBeenTrue, actual) 180 | } 181 | return success 182 | } 183 | 184 | // ShouldBeFalse receives a single parameter and ensures that it is false. 185 | func ShouldBeFalse(actual any, expected ...any) string { 186 | if fail := need(0, expected); fail != success { 187 | return fail 188 | } else if actual != false { 189 | return fmt.Sprintf(shouldHaveBeenFalse, actual) 190 | } 191 | return success 192 | } 193 | 194 | // ShouldBeZeroValue receives a single parameter and ensures that it is 195 | // the Go equivalent of the default value, or "zero" value. 196 | func ShouldBeZeroValue(actual any, expected ...any) string { 197 | if fail := need(0, expected); fail != success { 198 | return fail 199 | } 200 | zeroVal := reflect.Zero(reflect.TypeOf(actual)).Interface() 201 | if !reflect.DeepEqual(zeroVal, actual) { 202 | return fmt.Sprintf(shouldHaveBeenZeroValue, actual) 203 | } 204 | return success 205 | } 206 | 207 | // ShouldNotBeZeroValue receives a single parameter and ensures that it is NOT 208 | // the Go equivalent of the default value, or "zero" value. 209 | func ShouldNotBeZeroValue(actual any, expected ...any) string { 210 | if fail := need(0, expected); fail != success { 211 | return fail 212 | } 213 | zeroVal := reflect.Zero(reflect.TypeOf(actual)).Interface() 214 | if reflect.DeepEqual(zeroVal, actual) { 215 | return fmt.Sprintf(shouldNotHaveBeenZeroValue, actual) 216 | } 217 | return success 218 | } 219 | -------------------------------------------------------------------------------- /assert/assertions/collections.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/smarty/gunit/assert/assertions/internal/oglematchers" 8 | ) 9 | 10 | // ShouldContain receives exactly two parameters. The first is a slice and the 11 | // second is a proposed member. Membership is determined using ShouldEqual. 12 | func ShouldContain(actual any, expected ...any) string { 13 | if fail := need(1, expected); fail != success { 14 | return fail 15 | } 16 | 17 | if matchError := oglematchers.Contains(expected[0]).Matches(actual); matchError != nil { 18 | typeName := reflect.TypeOf(actual) 19 | 20 | if fmt.Sprintf("%v", matchError) == "which is not a slice or array" { 21 | return fmt.Sprintf(shouldHaveBeenAValidCollection, typeName) 22 | } 23 | return fmt.Sprintf(shouldHaveContained, typeName, expected[0]) 24 | } 25 | return success 26 | } 27 | 28 | // ShouldNotContain receives exactly two parameters. The first is a slice and the 29 | // second is a proposed member. Membership is determined using ShouldEqual. 30 | func ShouldNotContain(actual any, expected ...any) string { 31 | if fail := need(1, expected); fail != success { 32 | return fail 33 | } 34 | typeName := reflect.TypeOf(actual) 35 | 36 | if matchError := oglematchers.Contains(expected[0]).Matches(actual); matchError != nil { 37 | if fmt.Sprintf("%v", matchError) == "which is not a slice or array" { 38 | return fmt.Sprintf(shouldHaveBeenAValidCollection, typeName) 39 | } 40 | return success 41 | } 42 | return fmt.Sprintf(shouldNotHaveContained, typeName, expected[0]) 43 | } 44 | 45 | // ShouldContainKey receives exactly two parameters. The first is a map and the 46 | // second is a proposed key. Keys are compared with a simple '=='. 47 | func ShouldContainKey(actual any, expected ...any) string { 48 | if fail := need(1, expected); fail != success { 49 | return fail 50 | } 51 | 52 | keys, isMap := mapKeys(actual) 53 | if !isMap { 54 | return fmt.Sprintf(shouldHaveBeenAValidMap, reflect.TypeOf(actual)) 55 | } 56 | 57 | if !keyFound(keys, expected[0]) { 58 | return fmt.Sprintf(shouldHaveContainedKey, reflect.TypeOf(actual), expected) 59 | } 60 | 61 | return "" 62 | } 63 | 64 | // ShouldNotContainKey receives exactly two parameters. The first is a map and the 65 | // second is a proposed absent key. Keys are compared with a simple '=='. 66 | func ShouldNotContainKey(actual any, expected ...any) string { 67 | if fail := need(1, expected); fail != success { 68 | return fail 69 | } 70 | 71 | keys, isMap := mapKeys(actual) 72 | if !isMap { 73 | return fmt.Sprintf(shouldHaveBeenAValidMap, reflect.TypeOf(actual)) 74 | } 75 | 76 | if keyFound(keys, expected[0]) { 77 | return fmt.Sprintf(shouldNotHaveContainedKey, reflect.TypeOf(actual), expected) 78 | } 79 | 80 | return "" 81 | } 82 | 83 | func mapKeys(m any) ([]reflect.Value, bool) { 84 | value := reflect.ValueOf(m) 85 | if value.Kind() != reflect.Map { 86 | return nil, false 87 | } 88 | return value.MapKeys(), true 89 | } 90 | func keyFound(keys []reflect.Value, expectedKey any) bool { 91 | found := false 92 | for _, key := range keys { 93 | if key.Interface() == expectedKey { 94 | found = true 95 | } 96 | } 97 | return found 98 | } 99 | 100 | // ShouldBeIn receives at least 2 parameters. The first is a proposed member of the collection 101 | // that is passed in either as the second parameter, or of the collection that consists 102 | // of all the remaining parameters. This assertion ensures that the proposed member is in 103 | // the collection (using ShouldEqual). 104 | func ShouldBeIn(actual any, expected ...any) string { 105 | if fail := atLeast(1, expected); fail != success { 106 | return fail 107 | } 108 | 109 | if len(expected) == 1 { 110 | return shouldBeIn(actual, expected[0]) 111 | } 112 | return shouldBeIn(actual, expected) 113 | } 114 | func shouldBeIn(actual any, expected any) string { 115 | if matchError := oglematchers.Contains(actual).Matches(expected); matchError != nil { 116 | return fmt.Sprintf(shouldHaveBeenIn, actual, reflect.TypeOf(expected)) 117 | } 118 | return success 119 | } 120 | 121 | // ShouldNotBeIn receives at least 2 parameters. The first is a proposed member of the collection 122 | // that is passed in either as the second parameter, or of the collection that consists 123 | // of all the remaining parameters. This assertion ensures that the proposed member is NOT in 124 | // the collection (using ShouldEqual). 125 | func ShouldNotBeIn(actual any, expected ...any) string { 126 | if fail := atLeast(1, expected); fail != success { 127 | return fail 128 | } 129 | 130 | if len(expected) == 1 { 131 | return shouldNotBeIn(actual, expected[0]) 132 | } 133 | return shouldNotBeIn(actual, expected) 134 | } 135 | func shouldNotBeIn(actual any, expected any) string { 136 | if matchError := oglematchers.Contains(actual).Matches(expected); matchError == nil { 137 | return fmt.Sprintf(shouldNotHaveBeenIn, actual, reflect.TypeOf(expected)) 138 | } 139 | return success 140 | } 141 | 142 | // ShouldBeEmpty receives a single parameter (actual) and determines whether 143 | // calling len(actual) would return `0`. It obeys the rules specified by the len 144 | // function for determining length: http://golang.org/pkg/builtin/#len 145 | func ShouldBeEmpty(actual any, expected ...any) string { 146 | if fail := need(0, expected); fail != success { 147 | return fail 148 | } 149 | 150 | if actual == nil { 151 | return success 152 | } 153 | 154 | value := reflect.ValueOf(actual) 155 | if value.Kind() == reflect.Ptr { 156 | value = value.Elem() 157 | } 158 | switch value.Kind() { 159 | case reflect.Array, reflect.Slice, reflect.Chan, reflect.Map, reflect.String: 160 | if value.Len() == 0 { 161 | return success 162 | } else { 163 | return fmt.Sprintf(shouldHaveBeenEmpty, actual) 164 | } 165 | default: 166 | return fmt.Sprintf(shouldHaveBeenEmptyWrongKind, value.Kind()) 167 | } 168 | } 169 | 170 | // ShouldNotBeEmpty receives a single parameter (actual) and determines whether 171 | // calling len(actual) would return a value greater than zero. It obeys the rules 172 | // specified by the `len` function for determining length: http://golang.org/pkg/builtin/#len 173 | func ShouldNotBeEmpty(actual any, expected ...any) string { 174 | if fail := need(0, expected); fail != success { 175 | return fail 176 | } 177 | 178 | if empty := ShouldBeEmpty(actual, expected...); empty != success { 179 | return success 180 | } 181 | return fmt.Sprintf(shouldNotHaveBeenEmpty, actual) 182 | } 183 | 184 | // ShouldHaveLength receives 2 parameters. The first is a collection to check 185 | // the length of, the second being the expected length. It obeys the rules 186 | // specified by the len function for determining length: 187 | // http://golang.org/pkg/builtin/#len 188 | func ShouldHaveLength(actual any, expected ...any) string { 189 | if fail := need(1, expected); fail != success { 190 | return fail 191 | } 192 | 193 | var expectedLen int64 194 | lenValue := reflect.ValueOf(expected[0]) 195 | switch lenValue.Kind() { 196 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 197 | expectedLen = lenValue.Int() 198 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 199 | expectedLen = int64(lenValue.Uint()) 200 | default: 201 | return fmt.Sprintf(shouldHaveBeenAValidInteger, reflect.TypeOf(expected[0])) 202 | } 203 | 204 | if expectedLen < 0 { 205 | return fmt.Sprintf(shouldHaveBeenAValidLength, expected[0]) 206 | } 207 | 208 | value := reflect.ValueOf(actual) 209 | switch value.Kind() { 210 | case reflect.Slice, 211 | reflect.Chan, 212 | reflect.Map, 213 | reflect.String: 214 | if int64(value.Len()) == expectedLen { 215 | return success 216 | } else { 217 | return fmt.Sprintf(shouldHaveHadLength, expectedLen, value.Len(), actual) 218 | } 219 | case reflect.Ptr: 220 | elem := value.Elem() 221 | kind := elem.Kind() 222 | if kind == reflect.Slice || kind == reflect.Array { 223 | if int64(elem.Len()) == expectedLen { 224 | return success 225 | } else { 226 | return fmt.Sprintf(shouldHaveHadLength, expectedLen, elem.Len(), actual) 227 | } 228 | } 229 | } 230 | return fmt.Sprintf(shouldHaveBeenAValidCollection, reflect.TypeOf(actual)) 231 | } 232 | --------------------------------------------------------------------------------