├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.md ├── assert ├── assert.go ├── assert_failed_test.go ├── assert_passed_test.go ├── example │ └── main.go └── logger.go ├── collections.go ├── collections_test.go ├── doc.go ├── doc_test.go ├── equal_method.go ├── equal_method_test.go ├── equality.go ├── equality_diff.go ├── equality_specs.go ├── equality_test.go ├── filter.go ├── go.mod ├── internal ├── go-diff │ ├── .gitignore │ ├── .travis.yml │ ├── APACHE-LICENSE-2.0 │ ├── AUTHORS │ ├── CONTRIBUTORS │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── diffmatchpatch │ │ ├── diff.go │ │ ├── diffmatchpatch.go │ │ ├── match.go │ │ ├── mathutil.go │ │ ├── operation_string.go │ │ ├── patch.go │ │ └── stringutil.go │ ├── scripts │ │ └── lint.sh │ └── testdata │ │ ├── speedtest1.txt │ │ └── speedtest2.txt ├── go-render │ ├── .travis.yml │ ├── LICENSE │ ├── PRESUBMIT.py │ ├── README.md │ ├── WATCHLISTS │ ├── pre-commit-go.yml │ └── render │ │ ├── render.go │ │ ├── render_test.go │ │ └── render_time.go ├── oglematchers │ ├── .gitignore │ ├── .travis.yml │ ├── LICENSE │ ├── README.md │ ├── any_of.go │ ├── contains.go │ ├── deep_equals.go │ ├── equals.go │ ├── greater_or_equal.go │ ├── greater_than.go │ ├── less_or_equal.go │ ├── less_than.go │ ├── matcher.go │ ├── not.go │ └── transform_description.go └── unit │ └── fixture.go ├── messages.go ├── panic.go ├── panic_test.go ├── quantity.go ├── quantity_test.go ├── serializer.go ├── serializer_test.go ├── should └── should.go ├── strings.go ├── strings_test.go ├── time.go ├── time_test.go ├── type.go ├── type_test.go └── utilities_for_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /coverage.* 3 | .DS_Store 4 | *.iml 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | test: fmt 4 | GORACE="atexit_sleep_ms=50" go test -timeout=1s -race -cover -short -count=1 ./... 5 | 6 | fmt: 7 | go mod tidy && go fmt ./... 8 | 9 | compile: 10 | go build ./... 11 | 12 | build: test compile 13 | 14 | .PHONY: test compile build 15 | -------------------------------------------------------------------------------- /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 | [![Build Status](https://travis-ci.org/smarty/assertions.svg?branch=master)](https://travis-ci.org/smarty/assertions) 4 | [![Code Coverage](https://codecov.io/gh/smarty/assertions/branch/master/graph/badge.svg)](https://codecov.io/gh/smarty/assertions) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/smarty/assertions)](https://goreportcard.com/report/github.com/smarty/assertions) 6 | [![GoDoc](https://godoc.org/github.com/smarty/assertions?status.svg)](http://godoc.org/github.com/smarty/assertions) 7 | -------------------------------------------------------------------------------- /assert/assert.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "reflect" 8 | "runtime" 9 | "strings" 10 | ) 11 | 12 | // Result contains a single assertion failure as an error. 13 | // You should not create a Result directly, use So instead. 14 | // Once created, a Result is read-only and only allows 15 | // queries using the provided methods. 16 | type Result struct { 17 | invocation string 18 | err error 19 | 20 | stdout io.Writer 21 | logger *logger 22 | } 23 | 24 | // So is a convenience function (as opposed to an inconvenience function?) 25 | // for running assertions on arbitrary arguments in any context. It allows you to perform 26 | // assertion-like behavior and decide what happens in the event of a failure. 27 | // It is a variant of assertions.So in every respect except its return value. 28 | // In this case, the return value is a *Result which possesses several of its 29 | // own convenience methods: 30 | // 31 | // fmt.Println(assert.So(1, should.Equal, 1)) // Calls String() and prints the representation of the assertion. 32 | // assert.So(1, should.Equal, 1).Println() // Calls fmt.Print with the failure message and file:line header. 33 | // assert.So(1, should.Equal, 1).Log() // Calls log.Print with the failure message and file:line header. 34 | // assert.So(1, should.Equal, 1).Panic() // Calls log.Panic with the failure message and file:line header. 35 | // assert.So(1, should.Equal, 1).Fatal() // Calls log.Fatal with the failure message and file:line header. 36 | // if err := assert.So(1, should.Equal, 1).Error(); err != nil { 37 | // // Allows custom handling of the error, which will include the failure message and file:line header. 38 | // } 39 | func So(actual any, assert assertion, expected ...any) *Result { 40 | result := new(Result) 41 | result.stdout = os.Stdout 42 | result.invocation = fmt.Sprintf("So(actual: %v, %v, expected: %v)", actual, assertionName(assert), expected) 43 | if failure := assert(actual, expected...); len(failure) > 0 { 44 | _, file, line, _ := runtime.Caller(1) 45 | result.err = fmt.Errorf("Assertion failure at %s:%d\n%s", file, line, failure) 46 | } 47 | return result 48 | } 49 | func assertionName(i any) string { 50 | functionAddress := runtime.FuncForPC(reflect.ValueOf(i).Pointer()) 51 | fullNameStartingWithPackage := functionAddress.Name() 52 | parts := strings.Split(fullNameStartingWithPackage, "/") 53 | baseName := parts[len(parts)-1] 54 | return strings.Replace(baseName, "assertions.Should", "should.", 1) 55 | } 56 | 57 | // Failed returns true if the assertion failed, false if it passed. 58 | func (this *Result) Failed() bool { 59 | return !this.Passed() 60 | } 61 | 62 | // Passed returns true if the assertion passed, false if it failed. 63 | func (this *Result) Passed() bool { 64 | return this.err == nil 65 | } 66 | 67 | // Error returns the error representing an assertion failure, which is nil in the case of a passed assertion. 68 | func (this *Result) Error() error { 69 | return this.err 70 | } 71 | 72 | // String implements fmt.Stringer. 73 | // It returns the error as a string in the case of an assertion failure. 74 | // Unlike other methods defined herein, if returns a non-empty 75 | // representation of the assertion as confirmation of success. 76 | func (this *Result) String() string { 77 | if this.Passed() { 78 | return fmt.Sprintf("✔ %s", this.invocation) 79 | } else { 80 | return fmt.Sprintf("✘ %s\n%v", this.invocation, this.Error()) 81 | } 82 | } 83 | 84 | // Println calls fmt.Println in the case of an assertion failure. 85 | func (this *Result) Println() *Result { 86 | if this.Failed() { 87 | fmt.Fprintln(this.stdout, this) 88 | } 89 | return this 90 | } 91 | 92 | // Log calls log.Print in the case of an assertion failure. 93 | func (this *Result) Log() *Result { 94 | if this.Failed() { 95 | this.logger.Print(this) 96 | } 97 | return this 98 | } 99 | 100 | // Panic calls log.Panic in the case of an assertion failure. 101 | func (this *Result) Panic() *Result { 102 | if this.Failed() { 103 | this.logger.Panic(this) 104 | } 105 | return this 106 | } 107 | 108 | // Fatal calls log.Fatal in the case of an assertion failure. 109 | func (this *Result) Fatal() *Result { 110 | if this.Failed() { 111 | this.logger.Fatal(this) 112 | } 113 | return this 114 | } 115 | 116 | // assertion is a copy of github.com/smarty/assertions.assertion. 117 | type assertion func(actual any, expected ...any) string 118 | -------------------------------------------------------------------------------- /assert/assert_failed_test.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/smarty/assertions/internal/unit" 7 | "github.com/smarty/assertions/should" 8 | ) 9 | 10 | func TestFailedResultFixture(t *testing.T) { 11 | unit.Run(new(FailedResultFixture), t) 12 | } 13 | 14 | type FailedResultFixture struct { 15 | *unit.Fixture 16 | 17 | result *Result 18 | } 19 | 20 | func (this *FailedResultFixture) Setup() { 21 | this.result = So(1, should.Equal, 2) 22 | this.result.logger = capture() 23 | this.result.stdout = this.result.logger.Log 24 | } 25 | 26 | func (this *FailedResultFixture) assertLogMessageContents() { 27 | this.So(this.result.logger.Log.String(), should.ContainSubstring, "✘ So(actual: 1, should.Equal, expected: [2])") 28 | this.So(this.result.logger.Log.String(), should.ContainSubstring, "Assertion failure at ") 29 | this.So(this.result.logger.Log.String(), should.EndWith, "Expected: 2\nActual: 1\n(Should equal)!\n") 30 | } 31 | 32 | func (this *FailedResultFixture) TestQueryFunctions() { 33 | this.So(this.result.Failed(), should.BeTrue) 34 | this.So(this.result.Passed(), should.BeFalse) 35 | this.So(this.result.logger.Log.Len(), should.Equal, 0) 36 | 37 | this.result.logger.Print(this.result.String()) 38 | this.result.logger.Print(this.result.Error()) 39 | this.assertLogMessageContents() 40 | } 41 | 42 | func (this *FailedResultFixture) TestPrintln() { 43 | this.So(this.result.Println(), should.Equal, this.result) 44 | this.assertLogMessageContents() 45 | } 46 | 47 | func (this *FailedResultFixture) TestLog() { 48 | this.So(this.result.Log(), should.Equal, this.result) 49 | this.assertLogMessageContents() 50 | } 51 | 52 | func (this *FailedResultFixture) TestPanic() { 53 | this.So(func() { this.result.Panic() }, should.Panic) 54 | this.assertLogMessageContents() 55 | } 56 | 57 | func (this *FailedResultFixture) TestFatal() { 58 | this.So(this.result.Fatal(), should.Equal, this.result) 59 | this.assertLogMessageContents() 60 | } 61 | -------------------------------------------------------------------------------- /assert/assert_passed_test.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/smarty/assertions/internal/unit" 7 | "github.com/smarty/assertions/should" 8 | ) 9 | 10 | func TestPassedResultFixture(t *testing.T) { 11 | unit.Run(new(PassedResultFixture), t) 12 | } 13 | 14 | type PassedResultFixture struct { 15 | *unit.Fixture 16 | 17 | result *Result 18 | } 19 | 20 | func (this *PassedResultFixture) Setup() { 21 | this.result = So(1, should.Equal, 1) 22 | this.result.logger = capture() 23 | this.result.stdout = this.result.logger.Log 24 | } 25 | 26 | func (this *PassedResultFixture) TestQueryFunctions() { 27 | this.So(this.result.Error(), should.BeNil) 28 | this.So(this.result.Failed(), should.BeFalse) 29 | this.So(this.result.Passed(), should.BeTrue) 30 | this.So(this.result.String(), should.Equal, "✔ So(actual: 1, should.Equal, expected: [1])") 31 | } 32 | func (this *PassedResultFixture) TestPrintln() { 33 | this.So(this.result.Println(), should.Equal, this.result) 34 | this.So(this.result.logger.Log.String(), should.BeBlank) 35 | } 36 | func (this *PassedResultFixture) TestLog() { 37 | this.So(this.result.Log(), should.Equal, this.result) 38 | this.So(this.result.logger.Log.String(), should.BeBlank) 39 | } 40 | func (this *PassedResultFixture) TestPanic() { 41 | this.So(this.result.Panic(), should.Equal, this.result) 42 | this.So(this.result.logger.Log.String(), should.BeBlank) 43 | } 44 | func (this *PassedResultFixture) TestFatal() { 45 | this.So(this.result.Fatal(), should.Equal, this.result) 46 | this.So(this.result.logger.Log.String(), should.BeBlank) 47 | } 48 | -------------------------------------------------------------------------------- /assert/example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/smarty/assertions/assert" 7 | "github.com/smarty/assertions/should" 8 | ) 9 | 10 | func main() { 11 | exampleUsage(assert.So(1, should.Equal, 1)) // pass 12 | exampleUsage(assert.So(1, should.Equal, 2)) // fail 13 | } 14 | 15 | func exampleUsage(result *assert.Result) { 16 | if result.Passed() { 17 | fmt.Println("The assertion passed:", result) 18 | } else if result.Failed() { 19 | fmt.Println("The assertion failed:", result) 20 | } 21 | 22 | fmt.Print("\nAbout to see result.Error()...\n\n") 23 | 24 | if err := result.Error(); err != nil { 25 | fmt.Println(err) 26 | } 27 | 28 | fmt.Print("\nAbout to see result.Println()...\n\n") 29 | 30 | result.Println() 31 | 32 | fmt.Print("\nAbout to see result.Log()...\n\n") 33 | 34 | result.Log() 35 | 36 | fmt.Print("\nAbout to see result.Panic()...\n\n") 37 | 38 | defer func() { 39 | recover() 40 | 41 | fmt.Print("\nAbout to see result.Fatal()...\n\n") 42 | 43 | result.Fatal() 44 | 45 | fmt.Print("---------------------------------------------------------------\n\n") 46 | }() 47 | 48 | result.Panic() 49 | } 50 | -------------------------------------------------------------------------------- /assert/logger.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "os" 8 | ) 9 | 10 | // logger is meant be included as a pointer field on a struct. Leaving the 11 | // instance as a nil reference will cause any calls on the *logger to forward 12 | // to the corresponding functions from the standard log package. This is meant 13 | // to be the behavior in production. In testing, set the field to a non-nil 14 | // instance of a *logger to record log statements for later inspection. 15 | type logger struct { 16 | *log.Logger 17 | 18 | Log *bytes.Buffer 19 | Calls int 20 | } 21 | 22 | // capture creates a new *logger instance with an internal buffer. The prefix 23 | // and flags default to the values of log.Prefix() and log.Flags(), respectively. 24 | // This function is meant to be called from test code. See the godoc for the 25 | // logger struct for details. 26 | func capture() *logger { 27 | out := new(bytes.Buffer) 28 | inner := log.New(out, log.Prefix(), log.Flags()) 29 | inner.SetPrefix("") 30 | return &logger{ 31 | Log: out, 32 | Logger: inner, 33 | } 34 | } 35 | 36 | // Fatal -> log.Fatal (except in testing it uses log.Print) 37 | func (this *logger) Fatal(v ...any) { 38 | if this == nil { 39 | this.Output(3, fmt.Sprint(v...)) 40 | os.Exit(1) 41 | } else { 42 | this.Calls++ 43 | this.Logger.Print(v...) 44 | } 45 | } 46 | 47 | // Panic -> log.Panic 48 | func (this *logger) Panic(v ...any) { 49 | if this == nil { 50 | s := fmt.Sprint(v...) 51 | this.Output(3, s) 52 | panic(s) 53 | } else { 54 | this.Calls++ 55 | this.Logger.Panic(v...) 56 | } 57 | } 58 | 59 | // Print -> log.Print 60 | func (this *logger) Print(v ...any) { 61 | if this == nil { 62 | this.Output(3, fmt.Sprint(v...)) 63 | } else { 64 | this.Calls++ 65 | this.Logger.Print(v...) 66 | } 67 | } 68 | 69 | // Output -> log.Output 70 | func (this *logger) Output(calldepth int, s string) error { 71 | if this == nil { 72 | return log.Output(calldepth, s) 73 | } 74 | this.Calls++ 75 | return this.Logger.Output(calldepth, s) 76 | } 77 | -------------------------------------------------------------------------------- /collections.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/smarty/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 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package assertions contains the implementations for all assertions which 2 | // are referenced in goconvey's `convey` package 3 | // (github.com/smarty/goconvey/convey) and gunit (github.com/smarty/gunit) 4 | // for use with the So(...) method. 5 | // They can also be used in traditional Go test functions and even in 6 | // applications. 7 | // 8 | // https://smarty.com 9 | // 10 | // Many of the assertions lean heavily on work done by Aaron Jacobs in his excellent oglematchers library. 11 | // (https://github.com/jacobsa/oglematchers) 12 | // The ShouldResemble assertion leans heavily on work done by Daniel Jacques in his very helpful go-render library. 13 | // (https://github.com/luci/go-render) 14 | package assertions 15 | 16 | import ( 17 | "fmt" 18 | "runtime" 19 | ) 20 | 21 | // By default, we use a no-op serializer. The actual Serializer provides a JSON 22 | // representation of failure results on selected assertions so the goconvey 23 | // web UI can display a convenient diff. 24 | var serializer Serializer = new(noopSerializer) 25 | 26 | // GoConveyMode provides control over JSON serialization of failures. When 27 | // using the assertions in this package from the convey package JSON results 28 | // are very helpful and can be rendered in a DIFF view. In that case, this function 29 | // will be called with a true value to enable the JSON serialization. By default, 30 | // the assertions in this package will not serializer a JSON result, making 31 | // standalone usage more convenient. 32 | func GoConveyMode(yes bool) { 33 | if yes { 34 | serializer = newSerializer() 35 | } else { 36 | serializer = new(noopSerializer) 37 | } 38 | } 39 | 40 | type testingT interface { 41 | Error(args ...any) 42 | } 43 | 44 | type Assertion struct { 45 | t testingT 46 | failed bool 47 | } 48 | 49 | // New swallows the *testing.T struct and prints failed assertions using t.Error. 50 | // Example: assertions.New(t).So(1, should.Equal, 1) 51 | func New(t testingT) *Assertion { 52 | return &Assertion{t: t} 53 | } 54 | 55 | // Failed reports whether any calls to So (on this Assertion instance) have failed. 56 | func (this *Assertion) Failed() bool { 57 | return this.failed 58 | } 59 | 60 | // So calls the standalone So function and additionally, calls t.Error in failure scenarios. 61 | func (this *Assertion) So(actual any, assert SoFunc, expected ...any) bool { 62 | ok, result := So(actual, assert, expected...) 63 | if !ok { 64 | this.failed = true 65 | _, file, line, _ := runtime.Caller(1) 66 | this.t.Error(fmt.Sprintf("\n%s:%d\n%s", file, line, result)) 67 | } 68 | return ok 69 | } 70 | 71 | // So is a convenience function (as opposed to an inconvenience function?) 72 | // for running assertions on arbitrary arguments in any context, be it for testing or even 73 | // application logging. It allows you to perform assertion-like behavior (and get nicely 74 | // formatted messages detailing discrepancies) but without the program blowing up or panicking. 75 | // All that is required is to import this package and call `So` with one of the assertions 76 | // exported by this package as the second parameter. 77 | // The first return parameter is a boolean indicating if the assertion was true. The second 78 | // return parameter is the well-formatted message showing why an assertion was incorrect, or 79 | // blank if the assertion was correct. 80 | // 81 | // Example: 82 | // 83 | // if ok, message := So(x, ShouldBeGreaterThan, y); !ok { 84 | // log.Println(message) 85 | // } 86 | // 87 | // For an alternative implementation of So (that provides more flexible return options) 88 | // see the `So` function in the package at github.com/smarty/assertions/assert. 89 | func So(actual any, assert SoFunc, expected ...any) (bool, string) { 90 | result := so(actual, assert, expected...) 91 | return len(result) == 0, result 92 | } 93 | 94 | // so is like So, except that it only returns the string message, which is blank if the 95 | // assertion passed. Used to facilitate testing. 96 | func so(actual any, assert SoFunc, expected ...any) string { 97 | return assert(actual, expected...) 98 | } 99 | 100 | // SoFunc is an alias for a function with a signature that the So() 101 | // function can handle. Any future or custom assertions should conform to this 102 | // method signature. The return value should be an empty string if the SoFunc 103 | // passes and a well-formed failure message if not. 104 | type SoFunc func(actual any, expected ...any) string 105 | 106 | //////////////////////////////////////////////////////////////////////////// 107 | -------------------------------------------------------------------------------- /doc_test.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestGoConveyModeAffectsSerializer(t *testing.T) { 11 | if reflect.TypeOf(serializer) != reflect.TypeOf(new(noopSerializer)) { 12 | t.Error("Expected noop serializer as default") 13 | } 14 | 15 | GoConveyMode(true) 16 | if reflect.TypeOf(serializer) != reflect.TypeOf(new(failureSerializer)) { 17 | t.Error("Expected failure serializer") 18 | } 19 | 20 | GoConveyMode(false) 21 | if reflect.TypeOf(serializer) != reflect.TypeOf(new(noopSerializer)) { 22 | t.Error("Expected noop serializer") 23 | } 24 | } 25 | 26 | func TestPassingAssertion(t *testing.T) { 27 | fake := &FakeT{buffer: new(bytes.Buffer)} 28 | assertion := New(fake) 29 | passed := assertion.So(1, ShouldEqual, 1) 30 | 31 | if !passed { 32 | t.Error("Assertion failed when it should have passed.") 33 | } 34 | if fake.buffer.Len() > 0 { 35 | t.Error("Unexpected error message was printed.") 36 | } 37 | } 38 | 39 | func TestFailingAssertion(t *testing.T) { 40 | fake := &FakeT{buffer: new(bytes.Buffer)} 41 | assertion := New(fake) 42 | passed := assertion.So(1, ShouldEqual, 2) 43 | 44 | if passed { 45 | t.Error("Assertion passed when it should have failed.") 46 | } 47 | if fake.buffer.Len() == 0 { 48 | t.Error("Expected error message not printed.") 49 | } 50 | } 51 | 52 | func TestFailingGroupsOfAssertions(t *testing.T) { 53 | fake := &FakeT{buffer: new(bytes.Buffer)} 54 | assertion1 := New(fake) 55 | assertion2 := New(fake) 56 | 57 | assertion1.So(1, ShouldEqual, 2) // fail 58 | assertion2.So(1, ShouldEqual, 1) // pass 59 | 60 | if !assertion1.Failed() { 61 | t.Error("Expected the first assertion to have been marked as failed.") 62 | } 63 | if assertion2.Failed() { 64 | t.Error("Expected the second assertion to NOT have been marked as failed.") 65 | } 66 | } 67 | 68 | type FakeT struct { 69 | buffer *bytes.Buffer 70 | } 71 | 72 | func (this *FakeT) Error(args ...any) { 73 | fmt.Fprint(this.buffer, args...) 74 | } 75 | -------------------------------------------------------------------------------- /equal_method.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import "reflect" 4 | 5 | type equalityMethodSpecification struct{} 6 | 7 | func (this equalityMethodSpecification) assertable(a, b any) bool { 8 | if !bothAreSameType(a, b) { 9 | return false 10 | } 11 | if !typeHasEqualMethod(a) { 12 | return false 13 | } 14 | if !equalMethodReceivesSameTypeForComparison(a) { 15 | return false 16 | } 17 | if !equalMethodReturnsBool(a) { 18 | return false 19 | } 20 | return true 21 | } 22 | func bothAreSameType(a, b any) bool { 23 | aType := reflect.TypeOf(a) 24 | if aType == nil { 25 | return false 26 | } 27 | if aType.Kind() == reflect.Ptr { 28 | aType = aType.Elem() 29 | } 30 | bType := reflect.TypeOf(b) 31 | return aType == bType 32 | } 33 | func typeHasEqualMethod(a any) bool { 34 | aInstance := reflect.ValueOf(a) 35 | equalMethod := aInstance.MethodByName("Equal") 36 | return equalMethod != reflect.Value{} 37 | } 38 | func equalMethodReceivesSameTypeForComparison(a any) bool { 39 | aType := reflect.TypeOf(a) 40 | if aType.Kind() == reflect.Ptr { 41 | aType = aType.Elem() 42 | } 43 | aInstance := reflect.ValueOf(a) 44 | equalMethod := aInstance.MethodByName("Equal") 45 | signature := equalMethod.Type() 46 | return signature.NumIn() == 1 && signature.In(0) == aType 47 | } 48 | func equalMethodReturnsBool(a any) bool { 49 | aInstance := reflect.ValueOf(a) 50 | equalMethod := aInstance.MethodByName("Equal") 51 | signature := equalMethod.Type() 52 | return signature.NumOut() == 1 && signature.Out(0) == reflect.TypeOf(true) 53 | } 54 | 55 | func (this equalityMethodSpecification) passes(A, B any) bool { 56 | a := reflect.ValueOf(A) 57 | b := reflect.ValueOf(B) 58 | return areEqual(a, b) && areEqual(b, a) 59 | } 60 | func areEqual(receiver reflect.Value, argument reflect.Value) bool { 61 | equalMethod := receiver.MethodByName("Equal") 62 | argumentList := []reflect.Value{argument} 63 | result := equalMethod.Call(argumentList) 64 | return result[0].Bool() 65 | } 66 | -------------------------------------------------------------------------------- /equal_method_test.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/smarty/assertions/internal/unit" 7 | ) 8 | 9 | func TestEqualityFixture(t *testing.T) { 10 | unit.Run(new(EqualityFixture), t) 11 | } 12 | 13 | type EqualityFixture struct { 14 | *unit.Fixture 15 | } 16 | 17 | func (this *EqualityFixture) TestNilNil() { 18 | spec := equalityMethodSpecification{} 19 | this.So(spec.assertable(nil, nil), ShouldBeFalse) 20 | } 21 | 22 | func (this *EqualityFixture) TestEligible1() { 23 | a := Eligible1{"hi"} 24 | b := Eligible1{"hi"} 25 | specification := equalityMethodSpecification{} 26 | this.So(specification.assertable(a, b), ShouldBeTrue) 27 | this.So(specification.passes(a, b), ShouldBeTrue) 28 | } 29 | 30 | func (this *EqualityFixture) TestAreEqual() { 31 | a := Eligible1{"hi"} 32 | b := Eligible1{"hi"} 33 | specification := equalityMethodSpecification{} 34 | this.So(specification.assertable(a, b), ShouldBeTrue) 35 | this.So(specification.passes(a, b), ShouldBeTrue) 36 | } 37 | 38 | func (this *EqualityFixture) TestAreNotEqual() { 39 | a := Eligible1{"hi"} 40 | b := Eligible1{"bye"} 41 | specification := equalityMethodSpecification{} 42 | this.So(specification.assertable(a, b), ShouldBeTrue) 43 | this.So(specification.passes(a, b), ShouldBeFalse) 44 | } 45 | 46 | func (this *EqualityFixture) TestEligible2() { 47 | a := Eligible2{"hi"} 48 | b := Eligible2{"hi"} 49 | specification := equalityMethodSpecification{} 50 | this.So(specification.assertable(a, b), ShouldBeTrue) 51 | } 52 | 53 | func (this *EqualityFixture) TestEligible1_PointerReceiver() { 54 | a := &Eligible1{"hi"} 55 | b := Eligible1{"hi"} 56 | this.So(a.Equal(b), ShouldBeTrue) 57 | specification := equalityMethodSpecification{} 58 | this.So(specification.assertable(a, b), ShouldBeTrue) 59 | } 60 | 61 | func (this *EqualityFixture) TestIneligible_PrimitiveTypes() { 62 | specification := equalityMethodSpecification{} 63 | this.So(specification.assertable(1, 1), ShouldBeFalse) 64 | } 65 | 66 | func (this *EqualityFixture) TestIneligible_DisparateTypes() { 67 | a := Eligible1{"hi"} 68 | b := Eligible2{"hi"} 69 | specification := equalityMethodSpecification{} 70 | this.So(specification.assertable(a, b), ShouldBeFalse) 71 | } 72 | 73 | func (this *EqualityFixture) TestIneligible_NoEqualMethod() { 74 | a := Ineligible_NoEqualMethod{} 75 | b := Ineligible_NoEqualMethod{} 76 | specification := equalityMethodSpecification{} 77 | this.So(specification.assertable(a, b), ShouldBeFalse) 78 | } 79 | 80 | func (this *EqualityFixture) TestIneligible_EqualMethodReceivesNoInput() { 81 | a := Ineligible_EqualMethodNoInputs{} 82 | b := Ineligible_EqualMethodNoInputs{} 83 | specification := equalityMethodSpecification{} 84 | this.So(specification.assertable(a, b), ShouldBeFalse) 85 | } 86 | 87 | func (this *EqualityFixture) TestIneligible_EqualMethodReceivesTooManyInputs() { 88 | a := Ineligible_EqualMethodTooManyInputs{} 89 | b := Ineligible_EqualMethodTooManyInputs{} 90 | specification := equalityMethodSpecification{} 91 | this.So(specification.assertable(a, b), ShouldBeFalse) 92 | } 93 | 94 | func (this *EqualityFixture) TestIneligible_EqualMethodReceivesWrongInput() { 95 | a := Ineligible_EqualMethodWrongInput{} 96 | b := Ineligible_EqualMethodWrongInput{} 97 | specification := equalityMethodSpecification{} 98 | this.So(specification.assertable(a, b), ShouldBeFalse) 99 | } 100 | 101 | func (this *EqualityFixture) TestIneligible_EqualMethodReturnsNoOutputs() { 102 | a := Ineligible_EqualMethodNoOutputs{} 103 | b := Ineligible_EqualMethodNoOutputs{} 104 | specification := equalityMethodSpecification{} 105 | this.So(specification.assertable(a, b), ShouldBeFalse) 106 | } 107 | 108 | func (this *EqualityFixture) TestIneligible_EqualMethodReturnsTooManyOutputs() { 109 | a := Ineligible_EqualMethodTooManyOutputs{} 110 | b := Ineligible_EqualMethodTooManyOutputs{} 111 | specification := equalityMethodSpecification{} 112 | this.So(specification.assertable(a, b), ShouldBeFalse) 113 | } 114 | 115 | func (this *EqualityFixture) TestIneligible_EqualMethodReturnsWrongOutputs() { 116 | a := Ineligible_EqualMethodWrongOutput{} 117 | b := Ineligible_EqualMethodWrongOutput{} 118 | specification := equalityMethodSpecification{} 119 | this.So(specification.assertable(a, b), ShouldBeFalse) 120 | } 121 | 122 | func (this *EqualityFixture) TestEligibleAsymmetric_EqualMethodResultDiffersWhenArgumentsInverted() { 123 | a := EligibleAsymmetric{a: 0} 124 | b := EligibleAsymmetric{a: 1} 125 | specification := equalityMethodSpecification{} 126 | this.So(specification.assertable(a, b), ShouldBeTrue) 127 | this.So(specification.passes(a, b), ShouldBeFalse) 128 | } 129 | 130 | /**************************************************************************/ 131 | 132 | type ( 133 | Eligible1 struct{ a string } 134 | Eligible2 struct{ a string } 135 | EligibleAsymmetric struct{ a int } 136 | Ineligible_NoEqualMethod struct{} 137 | Ineligible_EqualMethodNoInputs struct{} 138 | Ineligible_EqualMethodNoOutputs struct{} 139 | Ineligible_EqualMethodTooManyInputs struct{} 140 | Ineligible_EqualMethodTooManyOutputs struct{} 141 | Ineligible_EqualMethodWrongInput struct{} 142 | Ineligible_EqualMethodWrongOutput struct{} 143 | ) 144 | 145 | func (this Eligible1) Equal(that Eligible1) bool { return this.a == that.a } 146 | func (this Eligible2) Equal(that Eligible2) bool { return this.a == that.a } 147 | func (this EligibleAsymmetric) Equal(that EligibleAsymmetric) bool { 148 | return this.a == 0 149 | } 150 | func (this Ineligible_EqualMethodNoInputs) Equal() bool { return true } 151 | func (this Ineligible_EqualMethodNoOutputs) Equal(that Ineligible_EqualMethodNoOutputs) {} 152 | func (this Ineligible_EqualMethodTooManyInputs) Equal(a, b bool) bool { return true } 153 | func (this Ineligible_EqualMethodTooManyOutputs) Equal(bool) (bool, bool) { return true, true } 154 | func (this Ineligible_EqualMethodWrongInput) Equal(a string) bool { return true } 155 | func (this Ineligible_EqualMethodWrongOutput) Equal(Ineligible_EqualMethodWrongOutput) int { return 0 } 156 | -------------------------------------------------------------------------------- /equality.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "math" 8 | "reflect" 9 | "strings" 10 | 11 | "github.com/smarty/assertions/internal/go-render/render" 12 | ) 13 | 14 | // ShouldEqual receives exactly two parameters and does an equality check 15 | // using the following semantics: 16 | // It uses reflect.DeepEqual in most cases, but also compares numerics 17 | // regardless of specific type and compares time.Time values using the 18 | // time.Equal method. 19 | func ShouldEqual(actual any, expected ...any) string { 20 | if message := need(1, expected); message != success { 21 | return message 22 | } 23 | return shouldEqual(actual, expected[0]) 24 | } 25 | func shouldEqual(actual, expected any) string { 26 | for _, spec := range equalitySpecs { 27 | if !spec.assertable(actual, expected) { 28 | continue 29 | } 30 | if spec.passes(actual, expected) { 31 | return success 32 | } 33 | break 34 | } 35 | renderedExpected, renderedActual := render.Render(expected), render.Render(actual) 36 | if renderedActual == renderedExpected { 37 | message := fmt.Sprintf(shouldHaveBeenEqualButTypeDiff, renderedExpected, renderedActual) 38 | return serializer.serializeDetailed(expected, actual, message) 39 | } 40 | message := fmt.Sprintf(shouldHaveBeenEqual, renderedExpected, renderedActual) + 41 | composePrettyDiff(renderedExpected, renderedActual) 42 | return serializer.serializeDetailed(expected, actual, message) 43 | } 44 | 45 | // ShouldNotEqual receives exactly two parameters and does an inequality check. 46 | // See ShouldEqual for details on how equality is determined. 47 | func ShouldNotEqual(actual any, expected ...any) string { 48 | if fail := need(1, expected); fail != success { 49 | return fail 50 | } else if ShouldEqual(actual, expected[0]) == success { 51 | return fmt.Sprintf(shouldNotHaveBeenEqual, actual, expected[0]) 52 | } 53 | return success 54 | } 55 | 56 | // ShouldAlmostEqual makes sure that two parameters are close enough to being equal. 57 | // The acceptable delta may be specified with a third argument, 58 | // or a very small default delta will be used. 59 | func ShouldAlmostEqual(actual any, expected ...any) string { 60 | actualFloat, expectedFloat, deltaFloat, err := cleanAlmostEqualInput(actual, expected...) 61 | 62 | if err != "" { 63 | return err 64 | } 65 | 66 | if math.Abs(actualFloat-expectedFloat) <= deltaFloat { 67 | return success 68 | } else { 69 | return fmt.Sprintf(shouldHaveBeenAlmostEqual, actualFloat, expectedFloat) 70 | } 71 | } 72 | 73 | // ShouldNotAlmostEqual is the inverse of ShouldAlmostEqual 74 | func ShouldNotAlmostEqual(actual any, expected ...any) string { 75 | actualFloat, expectedFloat, deltaFloat, err := cleanAlmostEqualInput(actual, expected...) 76 | 77 | if err != "" { 78 | return err 79 | } 80 | 81 | if math.Abs(actualFloat-expectedFloat) > deltaFloat { 82 | return success 83 | } else { 84 | return fmt.Sprintf(shouldHaveNotBeenAlmostEqual, actualFloat, expectedFloat) 85 | } 86 | } 87 | 88 | func cleanAlmostEqualInput(actual any, expected ...any) (float64, float64, float64, string) { 89 | deltaFloat := 0.0000000001 90 | 91 | if len(expected) == 0 { 92 | return 0.0, 0.0, 0.0, "This assertion requires exactly one comparison value and an optional delta (you provided neither)" 93 | } else if len(expected) == 2 { 94 | delta, err := getFloat(expected[1]) 95 | 96 | if err != nil { 97 | return 0.0, 0.0, 0.0, "The delta value " + err.Error() 98 | } 99 | 100 | deltaFloat = delta 101 | } else if len(expected) > 2 { 102 | return 0.0, 0.0, 0.0, "This assertion requires exactly one comparison value and an optional delta (you provided more values)" 103 | } 104 | 105 | actualFloat, err := getFloat(actual) 106 | if err != nil { 107 | return 0.0, 0.0, 0.0, "The actual value " + err.Error() 108 | } 109 | 110 | expectedFloat, err := getFloat(expected[0]) 111 | if err != nil { 112 | return 0.0, 0.0, 0.0, "The comparison value " + err.Error() 113 | } 114 | 115 | return actualFloat, expectedFloat, deltaFloat, "" 116 | } 117 | 118 | // returns the float value of any real number, or error if it is not a numerical type 119 | func getFloat(num any) (float64, error) { 120 | numValue := reflect.ValueOf(num) 121 | numKind := numValue.Kind() 122 | 123 | if numKind == reflect.Int || 124 | numKind == reflect.Int8 || 125 | numKind == reflect.Int16 || 126 | numKind == reflect.Int32 || 127 | numKind == reflect.Int64 { 128 | return float64(numValue.Int()), nil 129 | } else if numKind == reflect.Uint || 130 | numKind == reflect.Uint8 || 131 | numKind == reflect.Uint16 || 132 | numKind == reflect.Uint32 || 133 | numKind == reflect.Uint64 { 134 | return float64(numValue.Uint()), nil 135 | } else if numKind == reflect.Float32 || 136 | numKind == reflect.Float64 { 137 | return numValue.Float(), nil 138 | } else { 139 | return 0.0, errors.New("must be a numerical type, but was: " + numKind.String()) 140 | } 141 | } 142 | 143 | // ShouldEqualJSON receives exactly two parameters and does an equality check by marshalling to JSON 144 | func ShouldEqualJSON(actual any, expected ...any) string { 145 | if message := need(1, expected); message != success { 146 | return message 147 | } 148 | 149 | expectedString, expectedErr := remarshal(expected[0].(string)) 150 | if expectedErr != nil { 151 | return "Expected value not valid JSON: " + expectedErr.Error() 152 | } 153 | 154 | actualString, actualErr := remarshal(actual.(string)) 155 | if actualErr != nil { 156 | return "Actual value not valid JSON: " + actualErr.Error() 157 | } 158 | 159 | return ShouldEqual(actualString, expectedString) 160 | } 161 | func remarshal(value string) (string, error) { 162 | var structured any 163 | err := json.Unmarshal([]byte(value), &structured) 164 | if err != nil { 165 | return "", err 166 | } 167 | canonical, _ := json.Marshal(structured) 168 | return string(canonical), nil 169 | } 170 | 171 | // ShouldResemble is an alias for ShouldEqual. 172 | func ShouldResemble(actual any, expected ...any) string { 173 | return ShouldEqual(actual, expected...) 174 | } 175 | 176 | // ShouldNotResemble is an alias for ShouldNotEqual. 177 | func ShouldNotResemble(actual any, expected ...any) string { 178 | return ShouldNotEqual(actual, expected...) 179 | } 180 | 181 | // ShouldPointTo receives exactly two parameters and checks to see that they point to the same address. 182 | func ShouldPointTo(actual any, expected ...any) string { 183 | if message := need(1, expected); message != success { 184 | return message 185 | } 186 | return shouldPointTo(actual, expected[0]) 187 | 188 | } 189 | func shouldPointTo(actual, expected any) string { 190 | actualValue := reflect.ValueOf(actual) 191 | expectedValue := reflect.ValueOf(expected) 192 | 193 | if ShouldNotBeNil(actual) != success { 194 | return fmt.Sprintf(shouldHaveBeenNonNilPointer, "first", "nil") 195 | } else if ShouldNotBeNil(expected) != success { 196 | return fmt.Sprintf(shouldHaveBeenNonNilPointer, "second", "nil") 197 | } else if actualValue.Kind() != reflect.Ptr { 198 | return fmt.Sprintf(shouldHaveBeenNonNilPointer, "first", "not") 199 | } else if expectedValue.Kind() != reflect.Ptr { 200 | return fmt.Sprintf(shouldHaveBeenNonNilPointer, "second", "not") 201 | } else if ShouldEqual(actualValue.Pointer(), expectedValue.Pointer()) != success { 202 | actualAddress := reflect.ValueOf(actual).Pointer() 203 | expectedAddress := reflect.ValueOf(expected).Pointer() 204 | return serializer.serialize(expectedAddress, actualAddress, fmt.Sprintf(shouldHavePointedTo, 205 | actual, actualAddress, 206 | expected, expectedAddress)) 207 | } 208 | return success 209 | } 210 | 211 | // ShouldNotPointTo receives exactly two parameters and checks to see that they point to different addresess. 212 | func ShouldNotPointTo(actual any, expected ...any) string { 213 | if message := need(1, expected); message != success { 214 | return message 215 | } 216 | compare := ShouldPointTo(actual, expected[0]) 217 | if strings.HasPrefix(compare, shouldBePointers) { 218 | return compare 219 | } else if compare == success { 220 | return fmt.Sprintf(shouldNotHavePointedTo, actual, expected[0], reflect.ValueOf(actual).Pointer()) 221 | } 222 | return success 223 | } 224 | 225 | // ShouldBeNil receives a single parameter and ensures that it is nil. 226 | func ShouldBeNil(actual any, expected ...any) string { 227 | if fail := need(0, expected); fail != success { 228 | return fail 229 | } else if actual == nil { 230 | return success 231 | } else if interfaceHasNilValue(actual) { 232 | return success 233 | } 234 | return fmt.Sprintf(shouldHaveBeenNil, actual) 235 | } 236 | func interfaceHasNilValue(actual any) bool { 237 | value := reflect.ValueOf(actual) 238 | kind := value.Kind() 239 | nilable := kind == reflect.Slice || 240 | kind == reflect.Chan || 241 | kind == reflect.Func || 242 | kind == reflect.Ptr || 243 | kind == reflect.Map 244 | 245 | // Careful: reflect.Value.IsNil() will panic unless it's an interface, chan, map, func, slice, or ptr 246 | // Reference: http://golang.org/pkg/reflect/#Value.IsNil 247 | return nilable && value.IsNil() 248 | } 249 | 250 | // ShouldNotBeNil receives a single parameter and ensures that it is not nil. 251 | func ShouldNotBeNil(actual any, expected ...any) string { 252 | if fail := need(0, expected); fail != success { 253 | return fail 254 | } else if ShouldBeNil(actual) == success { 255 | return fmt.Sprintf(shouldNotHaveBeenNil, actual) 256 | } 257 | return success 258 | } 259 | 260 | // ShouldBeTrue receives a single parameter and ensures that it is true. 261 | func ShouldBeTrue(actual any, expected ...any) string { 262 | if fail := need(0, expected); fail != success { 263 | return fail 264 | } else if actual != true { 265 | return fmt.Sprintf(shouldHaveBeenTrue, actual) 266 | } 267 | return success 268 | } 269 | 270 | // ShouldBeFalse receives a single parameter and ensures that it is false. 271 | func ShouldBeFalse(actual any, expected ...any) string { 272 | if fail := need(0, expected); fail != success { 273 | return fail 274 | } else if actual != false { 275 | return fmt.Sprintf(shouldHaveBeenFalse, actual) 276 | } 277 | return success 278 | } 279 | 280 | // ShouldBeZeroValue receives a single parameter and ensures that it is 281 | // the Go equivalent of the default value, or "zero" value. 282 | func ShouldBeZeroValue(actual any, expected ...any) string { 283 | if fail := need(0, expected); fail != success { 284 | return fail 285 | } 286 | zeroVal := reflect.Zero(reflect.TypeOf(actual)).Interface() 287 | if !reflect.DeepEqual(zeroVal, actual) { 288 | return serializer.serialize(zeroVal, actual, fmt.Sprintf(shouldHaveBeenZeroValue, actual)) 289 | } 290 | return success 291 | } 292 | 293 | // ShouldNotBeZeroValue receives a single parameter and ensures that it is NOT 294 | // the Go equivalent of the default value, or "zero" value. 295 | func ShouldNotBeZeroValue(actual any, expected ...any) string { 296 | if fail := need(0, expected); fail != success { 297 | return fail 298 | } 299 | zeroVal := reflect.Zero(reflect.TypeOf(actual)).Interface() 300 | if reflect.DeepEqual(zeroVal, actual) { 301 | return serializer.serialize(zeroVal, actual, fmt.Sprintf(shouldNotHaveBeenZeroValue, actual)) 302 | } 303 | return success 304 | } 305 | -------------------------------------------------------------------------------- /equality_diff.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/smarty/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 | -------------------------------------------------------------------------------- /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 | equalityMethodSpecification{}, // Now that we have the timeEquality spec, this could probably be removed.. 20 | } 21 | 22 | // deepEquality compares any two values using reflect.DeepEqual. 23 | // https://golang.org/pkg/reflect/#DeepEqual 24 | type deepEquality struct{} 25 | 26 | func (deepEquality) assertable(a, b any) bool { 27 | return reflect.TypeOf(a) == reflect.TypeOf(b) 28 | } 29 | func (deepEquality) passes(a, b any) bool { 30 | return reflect.DeepEqual(a, b) 31 | } 32 | 33 | // numericEquality compares numeric values using the built-in equality 34 | // operator (`==`). Values of differing numeric reflect.Kind are each 35 | // converted to the type of the other and are compared with `==` in both 36 | // directions, with one exception: two mixed integers (one signed and one 37 | // unsigned) are always unequal in the case that the unsigned value is 38 | // greater than math.MaxInt64. https://golang.org/pkg/reflect/#Kind 39 | type numericEquality struct{} 40 | 41 | func (numericEquality) assertable(a, b any) bool { 42 | return isNumeric(a) && isNumeric(b) 43 | } 44 | func (numericEquality) passes(a, b any) bool { 45 | aValue := reflect.ValueOf(a) 46 | bValue := reflect.ValueOf(b) 47 | if isUnsignedInteger(a) && isSignedInteger(b) && aValue.Uint() >= math.MaxInt64 { 48 | return false 49 | } 50 | if isSignedInteger(a) && isUnsignedInteger(b) && bValue.Uint() >= math.MaxInt64 { 51 | return false 52 | } 53 | aAsB := aValue.Convert(bValue.Type()).Interface() 54 | bAsA := bValue.Convert(aValue.Type()).Interface() 55 | return a == bAsA && b == aAsB 56 | } 57 | func isNumeric(v any) bool { 58 | of := reflect.TypeOf(v) 59 | if of == nil { 60 | return false 61 | } 62 | _, found := numericKinds[of.Kind()] 63 | return found 64 | } 65 | func isSignedInteger(v any) bool { 66 | _, found := signedIntegerKinds[reflect.TypeOf(v).Kind()] 67 | return found 68 | } 69 | 70 | var unsignedIntegerKinds = map[reflect.Kind]struct{}{ 71 | reflect.Uint: {}, 72 | reflect.Uint8: {}, 73 | reflect.Uint16: {}, 74 | reflect.Uint32: {}, 75 | reflect.Uint64: {}, 76 | reflect.Uintptr: {}, 77 | } 78 | 79 | func isUnsignedInteger(v any) bool { 80 | _, found := unsignedIntegerKinds[reflect.TypeOf(v).Kind()] 81 | return found 82 | } 83 | 84 | var signedIntegerKinds = map[reflect.Kind]struct{}{ 85 | reflect.Int: {}, 86 | reflect.Int8: {}, 87 | reflect.Int16: {}, 88 | reflect.Int32: {}, 89 | reflect.Int64: {}, 90 | } 91 | 92 | var numericKinds = map[reflect.Kind]struct{}{ 93 | reflect.Int: {}, 94 | reflect.Int8: {}, 95 | reflect.Int16: {}, 96 | reflect.Int32: {}, 97 | reflect.Int64: {}, 98 | reflect.Uint: {}, 99 | reflect.Uint8: {}, 100 | reflect.Uint16: {}, 101 | reflect.Uint32: {}, 102 | reflect.Uint64: {}, 103 | reflect.Float32: {}, 104 | reflect.Float64: {}, 105 | } 106 | 107 | // timeEquality compares values both of type time.Time using their Equal method. 108 | // https://golang.org/pkg/time/#Time.Equal 109 | type timeEquality struct{} 110 | 111 | func (timeEquality) assertable(a, b any) bool { 112 | return isTime(a) && isTime(b) 113 | } 114 | func (timeEquality) passes(a, b any) bool { 115 | return a.(time.Time).Equal(b.(time.Time)) 116 | } 117 | func isTime(v any) bool { 118 | _, ok := v.(time.Time) 119 | return ok 120 | } 121 | 122 | type pointerEquality struct{} 123 | 124 | func (pointerEquality) assertable(a, b any) bool { 125 | return areSameType(a, b) && isKind(reflect.Func, a) 126 | } 127 | func (pointerEquality) passes(a, b any) bool { 128 | return reflect.ValueOf(a).Pointer() == reflect.ValueOf(b).Pointer() 129 | } 130 | func areSameType(a, b any) bool { 131 | return reflect.TypeOf(a) == reflect.TypeOf(b) 132 | } 133 | func isKind(kind reflect.Kind, a any) bool { 134 | return reflect.ValueOf(a).Kind() == kind 135 | } 136 | -------------------------------------------------------------------------------- /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 | needFewerValues = "This assertion allows %d or fewer comparison values (you provided %d)." 10 | ) 11 | 12 | func need(needed int, expected []any) string { 13 | if len(expected) != needed { 14 | return fmt.Sprintf(needExactValues, needed, len(expected)) 15 | } 16 | return success 17 | } 18 | 19 | func atLeast(minimum int, expected []any) string { 20 | if len(expected) < minimum { 21 | return needNonEmptyCollection 22 | } 23 | return success 24 | } 25 | 26 | func atMost(max int, expected []any) string { 27 | if len(expected) > max { 28 | return fmt.Sprintf(needFewerValues, max, len(expected)) 29 | } 30 | return success 31 | } 32 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/smarty/assertions 2 | 3 | go 1.22 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /internal/go-diff/APACHE-LICENSE-2.0: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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)< y { 20 | return x 21 | } 22 | return y 23 | } 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /internal/go-render/render/render_test.go: -------------------------------------------------------------------------------- 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 | package render 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "reflect" 11 | "regexp" 12 | "runtime" 13 | "testing" 14 | "time" 15 | ) 16 | 17 | func init() { 18 | // For testing purposes, pointers will render as "PTR" so that they are 19 | // deterministic. 20 | renderPointer = func(buf *bytes.Buffer, p uintptr) { 21 | buf.WriteString("PTR") 22 | } 23 | } 24 | 25 | func assertRendersLike(t *testing.T, name string, v any, exp string) { 26 | act := Render(v) 27 | if act != exp { 28 | _, _, line, _ := runtime.Caller(1) 29 | t.Errorf("On line #%d, [%s] did not match expectations:\nExpected: %s\nActual : %s\n", line, name, exp, act) 30 | } 31 | } 32 | 33 | func TestRenderList(t *testing.T) { 34 | t.Parallel() 35 | 36 | // Note that we make some of the fields exportable. This is to avoid a fun case 37 | // where the first reflect.Value has a read-only bit set, but follow-on values 38 | // do not, so recursion tests are off by one. 39 | type testStruct struct { 40 | Name string 41 | I any 42 | 43 | m string 44 | } 45 | 46 | type myStringSlice []string 47 | type myStringMap map[string]string 48 | type myIntType int 49 | type myStringType string 50 | type myTypeWithTime struct{ Public, private time.Time } 51 | 52 | var date = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) 53 | populatedTimes := myTypeWithTime{date, date} 54 | zeroTimes := myTypeWithTime{} 55 | 56 | s0 := "string0" 57 | s0P := &s0 58 | mit := myIntType(42) 59 | stringer := fmt.Stringer(nil) 60 | 61 | for i, tc := range []struct { 62 | a any 63 | s string 64 | }{ 65 | {nil, `nil`}, 66 | {make(chan int), `(chan int)(PTR)`}, 67 | {&stringer, `(*fmt.Stringer)(nil)`}, 68 | {123, `123`}, 69 | {"hello", `"hello"`}, 70 | {(*testStruct)(nil), `(*render.testStruct)(nil)`}, 71 | {(**testStruct)(nil), `(**render.testStruct)(nil)`}, 72 | {[]***testStruct(nil), `[]***render.testStruct(nil)`}, 73 | {testStruct{Name: "foo", I: &testStruct{Name: "baz"}}, 74 | `render.testStruct{Name:"foo", I:(*render.testStruct){Name:"baz", I:any(nil), m:""}, m:""}`}, 75 | {[]byte(nil), `[]uint8(nil)`}, 76 | {[]byte{}, `[]uint8{}`}, 77 | {map[string]string(nil), `map[string]string(nil)`}, 78 | {[]*testStruct{ 79 | {Name: "foo"}, 80 | {Name: "bar"}, 81 | }, `[]*render.testStruct{(*render.testStruct){Name:"foo", I:any(nil), m:""}, ` + 82 | `(*render.testStruct){Name:"bar", I:any(nil), m:""}}`}, 83 | {myStringSlice{"foo", "bar"}, `render.myStringSlice{"foo", "bar"}`}, 84 | {myStringMap{"foo": "bar"}, `render.myStringMap{"foo":"bar"}`}, 85 | {myIntType(12), `render.myIntType(12)`}, 86 | {&mit, `(*render.myIntType)(42)`}, 87 | {myStringType("foo"), `render.myStringType("foo")`}, 88 | {zeroTimes, `render.myTypeWithTime{Public:time.Time{0}, private:time.Time{wall:0, ext:0, loc:(*time.Location)(nil)}}`}, 89 | {populatedTimes, `render.myTypeWithTime{Public:time.Time{2000-01-01 00:00:00 +0000 UTC}, private:time.Time{wall:0, ext:63082281600, loc:(*time.Location)(nil)}}`}, 90 | {struct { 91 | a int 92 | b string 93 | }{123, "foo"}, `struct { a int; b string }{123, "foo"}`}, 94 | {[]string{"foo", "foo", "bar", "baz", "qux", "qux"}, 95 | `[]string{"foo", "foo", "bar", "baz", "qux", "qux"}`}, 96 | {[...]int{1, 2, 3}, `[3]int{1, 2, 3}`}, 97 | {map[string]bool{ 98 | "foo": true, 99 | "bar": false, 100 | }, `map[string]bool{"bar":false, "foo":true}`}, 101 | {map[int]string{1: "foo", 2: "bar"}, `map[int]string{1:"foo", 2:"bar"}`}, 102 | {uint32(1337), `1337`}, 103 | {3.14, `3.14`}, 104 | {complex(3, 0.14), `(3+0.14i)`}, 105 | {&s0, `(*string)("string0")`}, 106 | {&s0P, `(**string)("string0")`}, 107 | {[]any{nil, 1, 2, nil}, `[]any{any(nil), 1, 2, any(nil)}`}, 108 | } { 109 | assertRendersLike(t, fmt.Sprintf("Input #%d", i), tc.a, tc.s) 110 | } 111 | } 112 | 113 | func TestRenderRecursiveStruct(t *testing.T) { 114 | type testStruct struct { 115 | Name string 116 | I any 117 | } 118 | 119 | s := &testStruct{ 120 | Name: "recursive", 121 | } 122 | s.I = s 123 | 124 | assertRendersLike(t, "Recursive struct", s, 125 | `(*render.testStruct){Name:"recursive", I:}`) 126 | } 127 | 128 | func TestRenderRecursiveArray(t *testing.T) { 129 | a := [2]any{} 130 | a[0] = &a 131 | a[1] = &a 132 | 133 | assertRendersLike(t, "Recursive array", &a, 134 | `(*[2]any){, }`) 135 | } 136 | 137 | func TestRenderRecursiveMap(t *testing.T) { 138 | m := map[string]any{} 139 | foo := "foo" 140 | m["foo"] = m 141 | m["bar"] = [](*string){&foo, &foo} 142 | v := []map[string]any{m, m} 143 | 144 | assertRendersLike(t, "Recursive map", v, 145 | `[]map[string]any{{`+ 146 | `"bar":[]*string{(*string)("foo"), (*string)("foo")}, `+ 147 | `"foo":}, {`+ 148 | `"bar":[]*string{(*string)("foo"), (*string)("foo")}, `+ 149 | `"foo":}}`) 150 | } 151 | 152 | func TestRenderImplicitType(t *testing.T) { 153 | type namedStruct struct{ a, b int } 154 | type namedInt int 155 | 156 | tcs := []struct { 157 | in any 158 | expect string 159 | }{ 160 | { 161 | []struct{ a, b int }{{1, 2}}, 162 | "[]struct { a int; b int }{{1, 2}}", 163 | }, 164 | { 165 | map[string]struct{ a, b int }{"hi": {1, 2}}, 166 | `map[string]struct { a int; b int }{"hi":{1, 2}}`, 167 | }, 168 | { 169 | map[namedInt]struct{}{10: {}}, 170 | `map[render.namedInt]struct {}{10:{}}`, 171 | }, 172 | { 173 | struct{ a, b int }{1, 2}, 174 | `struct { a int; b int }{1, 2}`, 175 | }, 176 | { 177 | namedStruct{1, 2}, 178 | "render.namedStruct{a:1, b:2}", 179 | }, 180 | } 181 | 182 | for _, tc := range tcs { 183 | assertRendersLike(t, reflect.TypeOf(tc.in).String(), tc.in, tc.expect) 184 | } 185 | } 186 | 187 | func ExampleInReadme() { 188 | type customType int 189 | type testStruct struct { 190 | S string 191 | V *map[string]int 192 | I any 193 | } 194 | 195 | a := testStruct{ 196 | S: "hello", 197 | V: &map[string]int{"foo": 0, "bar": 1}, 198 | I: customType(42), 199 | } 200 | 201 | fmt.Println("Render test:") 202 | fmt.Printf("fmt.Printf: %s\n", sanitizePointer(fmt.Sprintf("%#v", a))) 203 | fmt.Printf("render.Render: %s\n", Render(a)) 204 | // Output: Render test: 205 | // fmt.Printf: render.testStruct{S:"hello", V:(*map[string]int)(0x600dd065), I:42} 206 | // render.Render: render.testStruct{S:"hello", V:(*map[string]int){"bar":1, "foo":0}, I:render.customType(42)} 207 | } 208 | 209 | var pointerRE = regexp.MustCompile(`\(0x[a-f0-9]+\)`) 210 | 211 | func sanitizePointer(s string) string { 212 | return pointerRE.ReplaceAllString(s, "(0x600dd065)") 213 | } 214 | 215 | type chanList []chan int 216 | 217 | func (c chanList) Len() int { return len(c) } 218 | func (c chanList) Swap(i, j int) { c[i], c[j] = c[j], c[i] } 219 | func (c chanList) Less(i, j int) bool { 220 | return reflect.ValueOf(c[i]).Pointer() < reflect.ValueOf(c[j]).Pointer() 221 | } 222 | 223 | func TestMapSortRendering(t *testing.T) { 224 | type namedMapType map[int]struct{ a int } 225 | type mapKey struct{ a, b int } 226 | 227 | chans := make(chanList, 5) 228 | for i := range chans { 229 | chans[i] = make(chan int) 230 | } 231 | 232 | tcs := []struct { 233 | in any 234 | expect string 235 | }{ 236 | { 237 | map[uint32]struct{}{1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}}, 238 | "map[uint32]struct {}{1:{}, 2:{}, 3:{}, 4:{}, 5:{}, 6:{}, 7:{}, 8:{}}", 239 | }, 240 | { 241 | map[int8]struct{}{1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}}, 242 | "map[int8]struct {}{1:{}, 2:{}, 3:{}, 4:{}, 5:{}, 6:{}, 7:{}, 8:{}}", 243 | }, 244 | { 245 | map[uintptr]struct{}{1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}}, 246 | "map[uintptr]struct {}{1:{}, 2:{}, 3:{}, 4:{}, 5:{}, 6:{}, 7:{}, 8:{}}", 247 | }, 248 | { 249 | namedMapType{10: struct{ a int }{20}}, 250 | "render.namedMapType{10:struct { a int }{20}}", 251 | }, 252 | { 253 | map[mapKey]struct{}{mapKey{3, 1}: {}, mapKey{1, 3}: {}, mapKey{1, 2}: {}, mapKey{2, 1}: {}}, 254 | "map[render.mapKey]struct {}{render.mapKey{a:1, b:2}:{}, render.mapKey{a:1, b:3}:{}, render.mapKey{a:2, b:1}:{}, render.mapKey{a:3, b:1}:{}}", 255 | }, 256 | { 257 | map[float64]struct{}{10.5: {}, 10.15: {}, 1203: {}, 1: {}, 2: {}}, 258 | "map[float64]struct {}{1:{}, 2:{}, 10.15:{}, 10.5:{}, 1203:{}}", 259 | }, 260 | { 261 | map[bool]struct{}{true: {}, false: {}}, 262 | "map[bool]struct {}{false:{}, true:{}}", 263 | }, 264 | { 265 | map[any]struct{}{1: {}, 2: {}, 3: {}, "foo": {}}, 266 | `map[any]struct {}{"foo":{}, 1:{}, 2:{}, 3:{}}`, 267 | }, 268 | { 269 | map[complex64]struct{}{1 + 2i: {}, 2 + 1i: {}, 3 + 1i: {}, 1 + 3i: {}}, 270 | "map[complex64]struct {}{(1+2i):{}, (1+3i):{}, (2+1i):{}, (3+1i):{}}", 271 | }, 272 | { 273 | map[chan int]string{nil: "a", chans[0]: "b", chans[1]: "c", chans[2]: "d", chans[3]: "e", chans[4]: "f"}, 274 | `map[(chan int)]string{(chan int)(PTR):"a", (chan int)(PTR):"b", (chan int)(PTR):"c", (chan int)(PTR):"d", (chan int)(PTR):"e", (chan int)(PTR):"f"}`, 275 | }, 276 | } 277 | 278 | for _, tc := range tcs { 279 | assertRendersLike(t, reflect.TypeOf(tc.in).Name(), tc.in, tc.expect) 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /internal/oglematchers/.gitignore: -------------------------------------------------------------------------------- 1 | *.6 2 | 6.out 3 | _obj/ 4 | _test/ 5 | _testmain.go 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 16 | func Run(fixture any, t *testing.T) { 17 | fixtureType := reflect.TypeOf(fixture) 18 | 19 | for x := 0; x < fixtureType.NumMethod(); x++ { 20 | testMethodName := fixtureType.Method(x).Name 21 | if strings.HasPrefix(testMethodName, "Test") { 22 | t.Run(testMethodName, func(t *testing.T) { 23 | instance := reflect.New(fixtureType.Elem()) 24 | 25 | innerFixture := newFixture(t, testing.Verbose()) 26 | field := instance.Elem().FieldByName("Fixture") 27 | field.Set(reflect.ValueOf(innerFixture)) 28 | 29 | defer innerFixture.Finalize() 30 | 31 | if setup := instance.MethodByName("Setup"); setup.IsValid() { 32 | setup.Call(nil) 33 | } 34 | 35 | instance.MethodByName(testMethodName).Call(nil) 36 | 37 | if teardown := instance.MethodByName("Teardown"); teardown.IsValid() { 38 | teardown.Call(nil) 39 | } 40 | }) 41 | } 42 | } 43 | } 44 | 45 | type Fixture struct { 46 | t *testing.T 47 | log *bytes.Buffer 48 | verbose bool 49 | } 50 | 51 | func newFixture(t *testing.T, verbose bool) *Fixture { 52 | return &Fixture{t: t, verbose: verbose, log: &bytes.Buffer{}} 53 | } 54 | 55 | func (this *Fixture) So(actual any, assert assertion, expected ...any) bool { 56 | failure := assert(actual, expected...) 57 | failed := len(failure) > 0 58 | if failed { 59 | this.fail(failure) 60 | } 61 | return !failed 62 | } 63 | 64 | func (this *Fixture) fail(failure string) { 65 | this.t.Fail() 66 | this.Print(failure) 67 | } 68 | 69 | // Assert tests a boolean which, if not true, marks the current test case as failed and 70 | // prints the provided message. 71 | func (this *Fixture) Assert(condition bool, messages ...string) bool { 72 | if !condition { 73 | if len(messages) == 0 { 74 | messages = append(messages, "Expected condition to be true, was false instead.") 75 | } 76 | this.fail(strings.Join(messages, ", ")) 77 | } 78 | return condition 79 | } 80 | func (this *Fixture) AssertEqual(expected, actual any) bool { 81 | return this.Assert(expected == actual, fmt.Sprintf(comparisonFormat, fmt.Sprint(expected), fmt.Sprint(actual))) 82 | } 83 | func (this *Fixture) AssertSprintEqual(expected, actual any) bool { 84 | return this.AssertEqual(fmt.Sprint(expected), fmt.Sprint(actual)) 85 | } 86 | func (this *Fixture) AssertSprintfEqual(expected, actual any, format string) bool { 87 | return this.AssertEqual(fmt.Sprintf(format, expected), fmt.Sprintf(format, actual)) 88 | } 89 | func (this *Fixture) AssertDeepEqual(expected, actual any) bool { 90 | return this.Assert(reflect.DeepEqual(expected, actual), 91 | fmt.Sprintf(comparisonFormat, fmt.Sprintf("%#v", expected), fmt.Sprintf("%#v", actual))) 92 | } 93 | 94 | const comparisonFormat = "Expected: [%s]\nActual: [%s]" 95 | 96 | func (this *Fixture) Error(args ...any) { this.fail(fmt.Sprint(args...)) } 97 | func (this *Fixture) Errorf(f string, args ...any) { this.fail(fmt.Sprintf(f, args...)) } 98 | 99 | func (this *Fixture) Print(a ...any) { fmt.Fprint(this.log, a...) } 100 | func (this *Fixture) Printf(format string, a ...any) { fmt.Fprintf(this.log, format, a...) } 101 | func (this *Fixture) Println(a ...any) { fmt.Fprintln(this.log, a...) } 102 | 103 | func (this *Fixture) Write(p []byte) (int, error) { return this.log.Write(p) } 104 | func (this *Fixture) Failed() bool { return this.t.Failed() } 105 | func (this *Fixture) Name() string { return this.t.Name() } 106 | 107 | func (this *Fixture) Finalize() { 108 | if r := recover(); r != nil { 109 | this.recoverPanic(r) 110 | } 111 | 112 | if this.t.Failed() || (this.verbose && this.log.Len() > 0) { 113 | this.t.Log("\n" + strings.TrimSpace(this.log.String()) + "\n") 114 | } 115 | } 116 | func (this *Fixture) recoverPanic(r any) { 117 | this.Println("PANIC:", r) 118 | buffer := make([]byte, 1024*16) 119 | runtime.Stack(buffer, false) 120 | this.Println(strings.TrimSpace(string(buffer))) 121 | this.t.Fail() 122 | } 123 | 124 | // assertion is a copy of github.com/smarty/assertions.assertion. 125 | type assertion func(actual any, expected ...any) string 126 | -------------------------------------------------------------------------------- /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 | shouldBePointers = "Both arguments should be pointers " 12 | shouldHaveBeenNonNilPointer = shouldBePointers + "(the %s was %s)!" 13 | shouldHavePointedTo = "Expected '%+v' (address: '%v') and '%+v' (address: '%v') to be the same address (but their weren't)!" 14 | shouldNotHavePointedTo = "Expected '%+v' and '%+v' to be different references (but they matched: '%v')!" 15 | 16 | shouldHaveBeenNil = "Expected: nil\nActual: '%v'" 17 | shouldNotHaveBeenNil = "Expected '%+v' to NOT be nil (but it was)!" 18 | 19 | shouldHaveBeenTrue = "Expected: true\nActual: %v" 20 | shouldHaveBeenFalse = "Expected: false\nActual: %v" 21 | 22 | shouldHaveBeenZeroValue = "'%+v' should have been the zero value" //"Expected: (zero value)\nActual: %v" 23 | shouldNotHaveBeenZeroValue = "'%+v' should NOT have been the zero value" 24 | 25 | shouldHaveBeenGreater = "Expected '%v' to be greater than '%v' (but it wasn't)!" 26 | shouldHaveBeenGreaterOrEqual = "Expected '%v' to be greater than or equal to '%v' (but it wasn't)!" 27 | 28 | shouldHaveBeenLess = "Expected '%v' to be less than '%v' (but it wasn't)!" 29 | shouldHaveBeenLessOrEqual = "Expected '%v' to be less than or equal to '%v' (but it wasn't)!" 30 | 31 | shouldHaveBeenBetween = "Expected '%v' to be between '%v' and '%v' (but it wasn't)!" 32 | shouldNotHaveBeenBetween = "Expected '%v' NOT to be between '%v' and '%v' (but it was)!" 33 | shouldHaveDifferentUpperAndLower = "The lower and upper bounds must be different values (they were both '%v')." 34 | 35 | shouldHaveBeenBetweenOrEqual = "Expected '%v' to be between '%v' and '%v' or equal to one of them (but it wasn't)!" 36 | shouldNotHaveBeenBetweenOrEqual = "Expected '%v' NOT to be between '%v' and '%v' or equal to one of them (but it was)!" 37 | 38 | shouldHaveContained = "Expected the container (%v) to contain: '%v' (but it didn't)!" 39 | shouldNotHaveContained = "Expected the container (%v) NOT to contain: '%v' (but it did)!" 40 | shouldHaveBeenAValidCollection = "You must provide a valid container (was %v)!" 41 | 42 | shouldHaveContainedKey = "Expected the %v to contain the key: %v (but it didn't)!" 43 | shouldNotHaveContainedKey = "Expected the %v NOT to contain the key: %v (but it did)!" 44 | shouldHaveBeenAValidMap = "You must provide a valid map type (was %v)!" 45 | 46 | shouldHaveBeenIn = "Expected '%v' to be in the container (%v), but it wasn't!" 47 | shouldNotHaveBeenIn = "Expected '%v' NOT to be in the container (%v), but it was!" 48 | 49 | shouldHaveBeenEmpty = "Expected %+v to be empty (but it wasn't)!" 50 | shouldNotHaveBeenEmpty = "Expected %+v to NOT be empty (but it was)!" 51 | shouldHaveBeenEmptyWrongKind = "Expected value's kind to be slice, chan, map, or string (you provided %s)!" 52 | 53 | shouldHaveBeenAValidInteger = "You must provide a valid integer (was %v)!" 54 | shouldHaveBeenAValidLength = "You must provide a valid positive integer (was %v)!" 55 | shouldHaveHadLength = "Expected collection to have length equal to [%v], but its length was [%v] instead! contents: %+v" 56 | 57 | shouldHaveStartedWith = "Expected '%v'\nto start with '%v'\n(but it didn't)!" 58 | shouldNotHaveStartedWith = "Expected '%v'\nNOT to start with '%v'\n(but it did)!" 59 | 60 | shouldHaveEndedWith = "Expected '%v'\nto end with '%v'\n(but it didn't)!" 61 | shouldNotHaveEndedWith = "Expected '%v'\nNOT to end with '%v'\n(but it did)!" 62 | 63 | shouldAllBeStrings = "All arguments to this assertion must be strings (you provided: %v)." 64 | shouldBothBeStrings = "Both arguments to this assertion must be strings (you provided %v and %v)." 65 | 66 | shouldHaveContainedSubstring = "Expected '%s' to contain substring '%s' (but it didn't)!" 67 | shouldNotHaveContainedSubstring = "Expected '%s' NOT to contain substring '%s' (but it did)!" 68 | 69 | shouldBeString = "The argument to this assertion must be a string (you provided %v)." 70 | shouldHaveBeenBlank = "Expected '%s' to be blank (but it wasn't)!" 71 | shouldNotHaveBeenBlank = "Expected value to NOT be blank (but it was)!" 72 | 73 | shouldUseVoidNiladicFunction = "You must provide a void, niladic function as the first argument!" 74 | shouldHavePanicked = "Expected func() to panic (but it didn't)!" 75 | shouldNotHavePanicked = "Expected func() NOT to panic (error: '%+v')!" 76 | 77 | shouldHavePanickedWith = "Expected func() to panic with '%v' (but it panicked with '%v')!" 78 | shouldNotHavePanickedWith = "Expected func() NOT to panic with '%v' (but it did)!" 79 | 80 | shouldHaveBeenA = "Expected '%v' to be: '%v' (but was: '%v')!" 81 | shouldNotHaveBeenA = "Expected '%v' to NOT be: '%v' (but it was)!" 82 | 83 | shouldHaveImplemented = "Expected: '%v interface support'\nActual: '%v' does not implement the interface!" 84 | shouldNotHaveImplemented = "Expected '%v'\nto NOT implement '%v'\n(but it did)!" 85 | shouldCompareWithInterfacePointer = "The expected value must be a pointer to an interface type (eg. *fmt.Stringer)" 86 | shouldNotBeNilActual = "The actual value was 'nil' and should be a value or a pointer to a value!" 87 | 88 | shouldBeError = "Expected an error value (but was '%v' instead)!" 89 | shouldBeErrorInvalidComparisonValue = "The final argument to this assertion must be a string or an error value (you provided: '%v')." 90 | 91 | shouldWrapInvalidTypes = "The first and last arguments to this assertion must both be error values (you provided: '%v' and '%v')." 92 | 93 | shouldUseTimes = "You must provide time instances as arguments to this assertion." 94 | shouldUseTimeSlice = "You must provide a slice of time instances as the first argument to this assertion." 95 | shouldUseDurationAndTime = "You must provide a duration and a time as arguments to this assertion." 96 | 97 | shouldHaveHappenedBefore = "Expected '%v' to happen before '%v' (it happened '%v' after)!" 98 | shouldHaveHappenedAfter = "Expected '%v' to happen after '%v' (it happened '%v' before)!" 99 | shouldHaveHappenedBetween = "Expected '%v' to happen between '%v' and '%v' (it happened '%v' outside threshold)!" 100 | shouldNotHaveHappenedOnOrBetween = "Expected '%v' to NOT happen on or between '%v' and '%v' (but it did)!" 101 | 102 | // format params: incorrect-index, previous-index, previous-time, incorrect-index, incorrect-time 103 | 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!)" 104 | shouldNotHaveBeenChronological = "The provided times should NOT be chronological, but they were." 105 | ) 106 | -------------------------------------------------------------------------------- /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 = serializer.serialize(expected[0], recovered, 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 | -------------------------------------------------------------------------------- /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"), "bye|hi|Expected func() to panic with 'bye' (but it panicked with 'hi')!") 39 | this.fail(so(func() { panic(errInner) }, ShouldPanicWith, errOuter), "outer inner|inner|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 | -------------------------------------------------------------------------------- /quantity.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/smarty/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 | -------------------------------------------------------------------------------- /quantity_test.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | func (this *AssertionsFixture) TestShouldBeGreaterThan() { 4 | this.fail(so(1, ShouldBeGreaterThan), "This assertion requires exactly 1 comparison values (you provided 0).") 5 | this.fail(so(1, ShouldBeGreaterThan, 0, 0), "This assertion requires exactly 1 comparison values (you provided 2).") 6 | 7 | this.pass(so(1, ShouldBeGreaterThan, 0)) 8 | this.pass(so(1.1, ShouldBeGreaterThan, 1)) 9 | this.pass(so(1, ShouldBeGreaterThan, uint(0))) 10 | this.pass(so("b", ShouldBeGreaterThan, "a")) 11 | 12 | this.fail(so(0, ShouldBeGreaterThan, 1), "Expected '0' to be greater than '1' (but it wasn't)!") 13 | this.fail(so(1, ShouldBeGreaterThan, 1.1), "Expected '1' to be greater than '1.1' (but it wasn't)!") 14 | this.fail(so(uint(0), ShouldBeGreaterThan, 1.1), "Expected '0' to be greater than '1.1' (but it wasn't)!") 15 | this.fail(so("a", ShouldBeGreaterThan, "b"), "Expected 'a' to be greater than 'b' (but it wasn't)!") 16 | } 17 | 18 | func (this *AssertionsFixture) TestShouldBeGreaterThanOrEqual() { 19 | this.fail(so(1, ShouldBeGreaterThanOrEqualTo), "This assertion requires exactly 1 comparison values (you provided 0).") 20 | this.fail(so(1, ShouldBeGreaterThanOrEqualTo, 0, 0), "This assertion requires exactly 1 comparison values (you provided 2).") 21 | 22 | this.pass(so(1, ShouldBeGreaterThanOrEqualTo, 1)) 23 | this.pass(so(1.1, ShouldBeGreaterThanOrEqualTo, 1.1)) 24 | this.pass(so(1, ShouldBeGreaterThanOrEqualTo, uint(1))) 25 | this.pass(so("b", ShouldBeGreaterThanOrEqualTo, "b")) 26 | 27 | this.pass(so(1, ShouldBeGreaterThanOrEqualTo, 0)) 28 | this.pass(so(1.1, ShouldBeGreaterThanOrEqualTo, 1)) 29 | this.pass(so(1, ShouldBeGreaterThanOrEqualTo, uint(0))) 30 | this.pass(so("b", ShouldBeGreaterThanOrEqualTo, "a")) 31 | 32 | this.fail(so(0, ShouldBeGreaterThanOrEqualTo, 1), "Expected '0' to be greater than or equal to '1' (but it wasn't)!") 33 | this.fail(so(1, ShouldBeGreaterThanOrEqualTo, 1.1), "Expected '1' to be greater than or equal to '1.1' (but it wasn't)!") 34 | this.fail(so(uint(0), ShouldBeGreaterThanOrEqualTo, 1.1), "Expected '0' to be greater than or equal to '1.1' (but it wasn't)!") 35 | this.fail(so("a", ShouldBeGreaterThanOrEqualTo, "b"), "Expected 'a' to be greater than or equal to 'b' (but it wasn't)!") 36 | } 37 | 38 | func (this *AssertionsFixture) TestShouldBeLessThan() { 39 | this.fail(so(1, ShouldBeLessThan), "This assertion requires exactly 1 comparison values (you provided 0).") 40 | this.fail(so(1, ShouldBeLessThan, 0, 0), "This assertion requires exactly 1 comparison values (you provided 2).") 41 | 42 | this.pass(so(0, ShouldBeLessThan, 1)) 43 | this.pass(so(1, ShouldBeLessThan, 1.1)) 44 | this.pass(so(uint(0), ShouldBeLessThan, 1)) 45 | this.pass(so("a", ShouldBeLessThan, "b")) 46 | 47 | this.fail(so(1, ShouldBeLessThan, 0), "Expected '1' to be less than '0' (but it wasn't)!") 48 | this.fail(so(1.1, ShouldBeLessThan, 1), "Expected '1.1' to be less than '1' (but it wasn't)!") 49 | this.fail(so(1.1, ShouldBeLessThan, uint(0)), "Expected '1.1' to be less than '0' (but it wasn't)!") 50 | this.fail(so("b", ShouldBeLessThan, "a"), "Expected 'b' to be less than 'a' (but it wasn't)!") 51 | } 52 | 53 | func (this *AssertionsFixture) TestShouldBeLessThanOrEqualTo() { 54 | this.fail(so(1, ShouldBeLessThanOrEqualTo), "This assertion requires exactly 1 comparison values (you provided 0).") 55 | this.fail(so(1, ShouldBeLessThanOrEqualTo, 0, 0), "This assertion requires exactly 1 comparison values (you provided 2).") 56 | 57 | this.pass(so(1, ShouldBeLessThanOrEqualTo, 1)) 58 | this.pass(so(1.1, ShouldBeLessThanOrEqualTo, 1.1)) 59 | this.pass(so(uint(1), ShouldBeLessThanOrEqualTo, 1)) 60 | this.pass(so("b", ShouldBeLessThanOrEqualTo, "b")) 61 | 62 | this.pass(so(0, ShouldBeLessThanOrEqualTo, 1)) 63 | this.pass(so(1, ShouldBeLessThanOrEqualTo, 1.1)) 64 | this.pass(so(uint(0), ShouldBeLessThanOrEqualTo, 1)) 65 | this.pass(so("a", ShouldBeLessThanOrEqualTo, "b")) 66 | 67 | this.fail(so(1, ShouldBeLessThanOrEqualTo, 0), "Expected '1' to be less than or equal to '0' (but it wasn't)!") 68 | this.fail(so(1.1, ShouldBeLessThanOrEqualTo, 1), "Expected '1.1' to be less than or equal to '1' (but it wasn't)!") 69 | this.fail(so(1.1, ShouldBeLessThanOrEqualTo, uint(0)), "Expected '1.1' to be less than or equal to '0' (but it wasn't)!") 70 | this.fail(so("b", ShouldBeLessThanOrEqualTo, "a"), "Expected 'b' to be less than or equal to 'a' (but it wasn't)!") 71 | } 72 | 73 | func (this *AssertionsFixture) TestShouldBeBetween() { 74 | this.fail(so(1, ShouldBeBetween), "This assertion requires exactly 2 comparison values (you provided 0).") 75 | this.fail(so(1, ShouldBeBetween, 1, 2, 3), "This assertion requires exactly 2 comparison values (you provided 3).") 76 | 77 | this.fail(so(4, ShouldBeBetween, 1, 1), "The lower and upper bounds must be different values (they were both '1').") 78 | 79 | this.fail(so(7, ShouldBeBetween, 8, 12), "Expected '7' to be between '8' and '12' (but it wasn't)!") 80 | this.fail(so(8, ShouldBeBetween, 8, 12), "Expected '8' to be between '8' and '12' (but it wasn't)!") 81 | this.pass(so(9, ShouldBeBetween, 8, 12)) 82 | this.pass(so(10, ShouldBeBetween, 8, 12)) 83 | this.pass(so(11, ShouldBeBetween, 8, 12)) 84 | this.fail(so(12, ShouldBeBetween, 8, 12), "Expected '12' to be between '8' and '12' (but it wasn't)!") 85 | this.fail(so(13, ShouldBeBetween, 8, 12), "Expected '13' to be between '8' and '12' (but it wasn't)!") 86 | 87 | this.pass(so(1, ShouldBeBetween, 2, 0)) 88 | this.fail(so(-1, ShouldBeBetween, 2, 0), "Expected '-1' to be between '0' and '2' (but it wasn't)!") 89 | } 90 | 91 | func (this *AssertionsFixture) TestShouldNotBeBetween() { 92 | this.fail(so(1, ShouldNotBeBetween), "This assertion requires exactly 2 comparison values (you provided 0).") 93 | this.fail(so(1, ShouldNotBeBetween, 1, 2, 3), "This assertion requires exactly 2 comparison values (you provided 3).") 94 | 95 | this.fail(so(4, ShouldNotBeBetween, 1, 1), "The lower and upper bounds must be different values (they were both '1').") 96 | 97 | this.pass(so(7, ShouldNotBeBetween, 8, 12)) 98 | this.pass(so(8, ShouldNotBeBetween, 8, 12)) 99 | this.fail(so(9, ShouldNotBeBetween, 8, 12), "Expected '9' NOT to be between '8' and '12' (but it was)!") 100 | this.fail(so(10, ShouldNotBeBetween, 8, 12), "Expected '10' NOT to be between '8' and '12' (but it was)!") 101 | this.fail(so(11, ShouldNotBeBetween, 8, 12), "Expected '11' NOT to be between '8' and '12' (but it was)!") 102 | this.pass(so(12, ShouldNotBeBetween, 8, 12)) 103 | this.pass(so(13, ShouldNotBeBetween, 8, 12)) 104 | 105 | this.pass(so(-1, ShouldNotBeBetween, 2, 0)) 106 | this.fail(so(1, ShouldNotBeBetween, 2, 0), "Expected '1' NOT to be between '0' and '2' (but it was)!") 107 | } 108 | 109 | func (this *AssertionsFixture) TestShouldBeBetweenOrEqual() { 110 | this.fail(so(1, ShouldBeBetweenOrEqual), "This assertion requires exactly 2 comparison values (you provided 0).") 111 | this.fail(so(1, ShouldBeBetweenOrEqual, 1, 2, 3), "This assertion requires exactly 2 comparison values (you provided 3).") 112 | 113 | this.fail(so(4, ShouldBeBetweenOrEqual, 1, 1), "The lower and upper bounds must be different values (they were both '1').") 114 | 115 | this.fail(so(7, ShouldBeBetweenOrEqual, 8, 12), "Expected '7' to be between '8' and '12' or equal to one of them (but it wasn't)!") 116 | this.pass(so(8, ShouldBeBetweenOrEqual, 8, 12)) 117 | this.pass(so(9, ShouldBeBetweenOrEqual, 8, 12)) 118 | this.pass(so(10, ShouldBeBetweenOrEqual, 8, 12)) 119 | this.pass(so(11, ShouldBeBetweenOrEqual, 8, 12)) 120 | this.pass(so(12, ShouldBeBetweenOrEqual, 8, 12)) 121 | this.fail(so(13, ShouldBeBetweenOrEqual, 8, 12), "Expected '13' to be between '8' and '12' or equal to one of them (but it wasn't)!") 122 | 123 | this.pass(so(1, ShouldBeBetweenOrEqual, 2, 0)) 124 | this.fail(so(-1, ShouldBeBetweenOrEqual, 2, 0), "Expected '-1' to be between '0' and '2' or equal to one of them (but it wasn't)!") 125 | } 126 | 127 | func (this *AssertionsFixture) TestShouldNotBeBetweenOrEqual() { 128 | this.fail(so(1, ShouldNotBeBetweenOrEqual), "This assertion requires exactly 2 comparison values (you provided 0).") 129 | this.fail(so(1, ShouldNotBeBetweenOrEqual, 1, 2, 3), "This assertion requires exactly 2 comparison values (you provided 3).") 130 | 131 | this.fail(so(4, ShouldNotBeBetweenOrEqual, 1, 1), "The lower and upper bounds must be different values (they were both '1').") 132 | 133 | this.pass(so(7, ShouldNotBeBetweenOrEqual, 8, 12)) 134 | this.fail(so(8, ShouldNotBeBetweenOrEqual, 8, 12), "Expected '8' NOT to be between '8' and '12' or equal to one of them (but it was)!") 135 | this.fail(so(9, ShouldNotBeBetweenOrEqual, 8, 12), "Expected '9' NOT to be between '8' and '12' or equal to one of them (but it was)!") 136 | this.fail(so(10, ShouldNotBeBetweenOrEqual, 8, 12), "Expected '10' NOT to be between '8' and '12' or equal to one of them (but it was)!") 137 | this.fail(so(11, ShouldNotBeBetweenOrEqual, 8, 12), "Expected '11' NOT to be between '8' and '12' or equal to one of them (but it was)!") 138 | this.fail(so(12, ShouldNotBeBetweenOrEqual, 8, 12), "Expected '12' NOT to be between '8' and '12' or equal to one of them (but it was)!") 139 | this.pass(so(13, ShouldNotBeBetweenOrEqual, 8, 12)) 140 | 141 | this.pass(so(-1, ShouldNotBeBetweenOrEqual, 2, 0)) 142 | this.fail(so(1, ShouldNotBeBetweenOrEqual, 2, 0), "Expected '1' NOT to be between '0' and '2' or equal to one of them (but it was)!") 143 | } 144 | -------------------------------------------------------------------------------- /serializer.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/smarty/assertions/internal/go-render/render" 9 | ) 10 | 11 | type Serializer interface { 12 | serialize(expected, actual any, message string) string 13 | serializeDetailed(expected, actual any, message string) string 14 | } 15 | 16 | type failureSerializer struct{} 17 | 18 | func (self *failureSerializer) serializeDetailed(expected, actual any, message string) string { 19 | if index := strings.Index(message, " Diff:"); index > 0 { 20 | message = message[:index] 21 | } 22 | view := FailureView{ 23 | Message: message, 24 | Expected: render.Render(expected), 25 | Actual: render.Render(actual), 26 | } 27 | serialized, _ := json.Marshal(view) 28 | return string(serialized) 29 | } 30 | 31 | func (self *failureSerializer) serialize(expected, actual any, message string) string { 32 | if index := strings.Index(message, " Diff:"); index > 0 { 33 | message = message[:index] 34 | } 35 | view := FailureView{ 36 | Message: message, 37 | Expected: fmt.Sprintf("%+v", expected), 38 | Actual: fmt.Sprintf("%+v", actual), 39 | } 40 | serialized, _ := json.Marshal(view) 41 | return string(serialized) 42 | } 43 | 44 | func newSerializer() *failureSerializer { 45 | return &failureSerializer{} 46 | } 47 | 48 | /////////////////////////////////////////////////////////////////////////////// 49 | 50 | // FailureView is also declared in github.com/smarty/goconvey/convey/reporting. 51 | // The json struct tags should be equal in both declarations. 52 | type FailureView struct { 53 | Message string `json:"Message"` 54 | Expected string `json:"Expected"` 55 | Actual string `json:"Actual"` 56 | } 57 | 58 | /////////////////////////////////////////////////////// 59 | 60 | // noopSerializer just gives back the original message. This is useful when we are using 61 | // the assertions from a context other than the GoConvey Web UI, that requires the JSON 62 | // structure provided by the failureSerializer. 63 | type noopSerializer struct{} 64 | 65 | func (self *noopSerializer) serialize(expected, actual any, message string) string { 66 | return message 67 | } 68 | func (self *noopSerializer) serializeDetailed(expected, actual any, message string) string { 69 | return message 70 | } 71 | -------------------------------------------------------------------------------- /serializer_test.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestFailureSerializerCreatesSerializedVersionOfAssertionResult(t *testing.T) { 10 | thing1 := Thing1{"Hi"} 11 | thing2 := Thing2{"Bye"} 12 | message := "Super-hip failure message. Diff: Something that we don't need." 13 | serializer := newSerializer() 14 | 15 | actualResult := serializer.serialize(thing1, thing2, message) 16 | 17 | expectedResult, _ := json.Marshal(FailureView{ 18 | Message: "Super-hip failure message.", 19 | Expected: fmt.Sprintf("%+v", thing1), 20 | Actual: fmt.Sprintf("%+v", thing2), 21 | }) 22 | 23 | if actualResult != string(expectedResult) { 24 | t.Errorf("\nExpected: %s\nActual: %s", string(expectedResult), actualResult) 25 | } 26 | 27 | actualResult = serializer.serializeDetailed(thing1, thing2, message) 28 | expectedResult, _ = json.Marshal(FailureView{ 29 | Message: "Super-hip failure message.", 30 | Expected: fmt.Sprintf("%#v", thing1), 31 | Actual: fmt.Sprintf("%#v", thing2), 32 | }) 33 | if actualResult != string(expectedResult) { 34 | t.Errorf("\nExpected: %s\nActual: %s", string(expectedResult), actualResult) 35 | } 36 | } 37 | 38 | func TestNoopSerializerJustReturnsTheMessageInAllCases(t *testing.T) { 39 | thing1 := Thing1{"Hi"} 40 | thing2 := Thing2{"Bye"} 41 | expected := "Super-hip failure message." 42 | serializer := &noopSerializer{} 43 | actual := serializer.serialize(thing1, thing2, expected) 44 | if actual != expected { 45 | t.Errorf("\nExpected: %s\nActual: %s", string(expected), actual) 46 | } 47 | 48 | actual = serializer.serializeDetailed(thing1, thing2, expected) 49 | if actual != expected { 50 | t.Errorf("\nExpected: %s\nActual: %s", string(expected), actual) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /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 "github.com/smarty/assertions" 6 | 7 | var ( 8 | AlmostEqual = assertions.ShouldAlmostEqual 9 | BeBetween = assertions.ShouldBeBetween 10 | BeBetweenOrEqual = assertions.ShouldBeBetweenOrEqual 11 | BeBlank = assertions.ShouldBeBlank 12 | BeChronological = assertions.ShouldBeChronological 13 | BeEmpty = assertions.ShouldBeEmpty 14 | BeError = assertions.ShouldBeError 15 | BeFalse = assertions.ShouldBeFalse 16 | BeGreaterThan = assertions.ShouldBeGreaterThan 17 | BeGreaterThanOrEqualTo = assertions.ShouldBeGreaterThanOrEqualTo 18 | BeIn = assertions.ShouldBeIn 19 | BeLessThan = assertions.ShouldBeLessThan 20 | BeLessThanOrEqualTo = assertions.ShouldBeLessThanOrEqualTo 21 | BeNil = assertions.ShouldBeNil 22 | BeTrue = assertions.ShouldBeTrue 23 | BeZeroValue = assertions.ShouldBeZeroValue 24 | Contain = assertions.ShouldContain 25 | ContainKey = assertions.ShouldContainKey 26 | ContainSubstring = assertions.ShouldContainSubstring 27 | EndWith = assertions.ShouldEndWith 28 | Equal = assertions.ShouldEqual 29 | EqualJSON = assertions.ShouldEqualJSON 30 | EqualTrimSpace = assertions.ShouldEqualTrimSpace 31 | EqualWithout = assertions.ShouldEqualWithout 32 | HappenAfter = assertions.ShouldHappenAfter 33 | HappenBefore = assertions.ShouldHappenBefore 34 | HappenBetween = assertions.ShouldHappenBetween 35 | HappenOnOrAfter = assertions.ShouldHappenOnOrAfter 36 | HappenOnOrBefore = assertions.ShouldHappenOnOrBefore 37 | HappenOnOrBetween = assertions.ShouldHappenOnOrBetween 38 | HappenWithin = assertions.ShouldHappenWithin 39 | HaveLength = assertions.ShouldHaveLength 40 | HaveSameTypeAs = assertions.ShouldHaveSameTypeAs 41 | Implement = assertions.ShouldImplement 42 | NotAlmostEqual = assertions.ShouldNotAlmostEqual 43 | NotBeBetween = assertions.ShouldNotBeBetween 44 | NotBeBetweenOrEqual = assertions.ShouldNotBeBetweenOrEqual 45 | NotBeBlank = assertions.ShouldNotBeBlank 46 | NotBeChronological = assertions.ShouldNotBeChronological 47 | NotBeEmpty = assertions.ShouldNotBeEmpty 48 | NotBeIn = assertions.ShouldNotBeIn 49 | NotBeNil = assertions.ShouldNotBeNil 50 | NotBeZeroValue = assertions.ShouldNotBeZeroValue 51 | NotContain = assertions.ShouldNotContain 52 | NotContainKey = assertions.ShouldNotContainKey 53 | NotContainSubstring = assertions.ShouldNotContainSubstring 54 | NotEndWith = assertions.ShouldNotEndWith 55 | NotEqual = assertions.ShouldNotEqual 56 | NotHappenOnOrBetween = assertions.ShouldNotHappenOnOrBetween 57 | NotHappenWithin = assertions.ShouldNotHappenWithin 58 | NotHaveSameTypeAs = assertions.ShouldNotHaveSameTypeAs 59 | NotImplement = assertions.ShouldNotImplement 60 | NotPanic = assertions.ShouldNotPanic 61 | NotPanicWith = assertions.ShouldNotPanicWith 62 | NotPointTo = assertions.ShouldNotPointTo 63 | NotResemble = assertions.ShouldNotResemble 64 | NotStartWith = assertions.ShouldNotStartWith 65 | Panic = assertions.ShouldPanic 66 | PanicWith = assertions.ShouldPanicWith 67 | PointTo = assertions.ShouldPointTo 68 | Resemble = assertions.ShouldResemble 69 | StartWith = assertions.ShouldStartWith 70 | Wrap = assertions.ShouldWrap 71 | ) 72 | 73 | // So is a variation on other such functions/methods in this module. 74 | // Since it is defined alongside all these assertion definitions it allows 75 | // performing assertions in tests with a single import of this package. 76 | // Example: 77 | // should.So(t, 1, should.Equal, 1) 78 | func So(t testingT, actual any, assertion assertions.SoFunc, expected ...any) { 79 | if ok, result := assertions.So(actual, assertion, expected...); !ok { 80 | t.Helper() 81 | t.Errorf("\n%s", result) 82 | } 83 | } 84 | 85 | type testingT interface { 86 | Helper() 87 | Errorf(string, ...any) 88 | } 89 | -------------------------------------------------------------------------------- /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 serializer.serialize(prefix, shortval, 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 serializer.serialize(suffix, shortval, 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 serializer.serialize(expected[0], actual, 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 serializer.serialize("", value, 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 | 185 | // ShouldEqualWithout receives exactly 3 string parameters and ensures that the first is equal to the second 186 | // after removing all instances of the third from the first using strings.Replace(first, third, "", -1). 187 | func ShouldEqualWithout(actual any, expected ...any) string { 188 | if fail := need(2, expected); fail != success { 189 | return fail 190 | } 191 | actualString, ok1 := actual.(string) 192 | expectedString, ok2 := expected[0].(string) 193 | replace, ok3 := expected[1].(string) 194 | 195 | if !ok1 || !ok2 || !ok3 { 196 | return fmt.Sprintf(shouldAllBeStrings, []reflect.Type{ 197 | reflect.TypeOf(actual), 198 | reflect.TypeOf(expected[0]), 199 | reflect.TypeOf(expected[1]), 200 | }) 201 | } 202 | 203 | replaced := strings.Replace(actualString, replace, "", -1) 204 | if replaced == expectedString { 205 | return "" 206 | } 207 | 208 | return fmt.Sprintf("Expected '%s' to equal '%s' but without any '%s' (but it didn't).", actualString, expectedString, replace) 209 | } 210 | 211 | // ShouldEqualTrimSpace receives exactly 2 string parameters and ensures that the first is equal to the second 212 | // after removing all leading and trailing whitespace using strings.TrimSpace(first). 213 | func ShouldEqualTrimSpace(actual any, expected ...any) string { 214 | if fail := need(1, expected); fail != success { 215 | return fail 216 | } 217 | 218 | actualString, valueIsString := actual.(string) 219 | _, value2IsString := expected[0].(string) 220 | 221 | if !valueIsString || !value2IsString { 222 | return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0])) 223 | } 224 | 225 | actualString = strings.TrimSpace(actualString) 226 | return ShouldEqual(actualString, expected[0]) 227 | } 228 | -------------------------------------------------------------------------------- /strings_test.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | func (this *AssertionsFixture) TestShouldStartWith() { 4 | this.fail(so("", ShouldStartWith), "This assertion requires exactly 1 comparison values (you provided 0).") 5 | this.fail(so("", ShouldStartWith, "asdf", "asdf"), "This assertion requires exactly 1 comparison values (you provided 2).") 6 | 7 | this.pass(so("", ShouldStartWith, "")) 8 | this.fail(so("", ShouldStartWith, "x"), "x||Expected '' to start with 'x' (but it didn't)!") 9 | this.pass(so("abc", ShouldStartWith, "abc")) 10 | this.fail(so("abc", ShouldStartWith, "abcd"), "abcd|abc|Expected 'abc' to start with 'abcd' (but it didn't)!") 11 | 12 | this.pass(so("superman", ShouldStartWith, "super")) 13 | this.fail(so("superman", ShouldStartWith, "bat"), "bat|sup...|Expected 'superman' to start with 'bat' (but it didn't)!") 14 | this.fail(so("superman", ShouldStartWith, "man"), "man|sup...|Expected 'superman' to start with 'man' (but it didn't)!") 15 | 16 | this.fail(so(1, ShouldStartWith, 2), "Both arguments to this assertion must be strings (you provided int and int).") 17 | } 18 | 19 | func (this *AssertionsFixture) TestShouldNotStartWith() { 20 | this.fail(so("", ShouldNotStartWith), "This assertion requires exactly 1 comparison values (you provided 0).") 21 | this.fail(so("", ShouldNotStartWith, "asdf", "asdf"), "This assertion requires exactly 1 comparison values (you provided 2).") 22 | 23 | this.fail(so("", ShouldNotStartWith, ""), "Expected '' 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"), "z||Expected '' to end with 'z' (but it didn't)!") 37 | this.pass(so("xyz", ShouldEndWith, "xyz")) 38 | this.fail(so("xyz", ShouldEndWith, "wxyz"), "wxyz|xyz|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"), "super|...erman|Expected 'superman' to end with 'super' (but it didn't)!") 42 | this.fail(so("superman", ShouldEndWith, "blah"), "blah|...rman|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"), "sd|qwer|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), "|asdf|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 | 94 | func (this *AssertionsFixture) TestShouldEqualWithout() { 95 | this.fail(so("", ShouldEqualWithout, ""), "This assertion requires exactly 2 comparison values (you provided 1).") 96 | this.fail(so(1, ShouldEqualWithout, 2, 3), "All arguments to this assertion must be strings (you provided: [int int int]).") 97 | 98 | this.fail(so("asdf", ShouldEqualWithout, "qwer", "q"), "Expected 'asdf' to equal 'qwer' but without any 'q' (but it didn't).") 99 | this.pass(so("asdf", ShouldEqualWithout, "df", "as")) 100 | } 101 | 102 | func (this *AssertionsFixture) TestShouldEqualTrimSpace() { 103 | this.fail(so(" asdf ", ShouldEqualTrimSpace), "This assertion requires exactly 1 comparison values (you provided 0).") 104 | this.fail(so(1, ShouldEqualTrimSpace, 2), "Both arguments to this assertion must be strings (you provided int and int).") 105 | 106 | this.fail(so("asdf", ShouldEqualTrimSpace, "qwer"), `qwer|asdf|Expected: "qwer" Actual: "asdf" (Should equal)!`) 107 | this.pass(so(" asdf\t\n", ShouldEqualTrimSpace, "asdf")) 108 | } 109 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 serializer.serialize(second, first, 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 | // ShouldImplement receives exactly two parameters and ensures 41 | // that the first implements the interface type of the second. 42 | func ShouldImplement(actual any, expectedList ...any) string { 43 | if fail := need(1, expectedList); fail != success { 44 | return fail 45 | } 46 | 47 | expected := expectedList[0] 48 | if fail := ShouldBeNil(expected); fail != success { 49 | return shouldCompareWithInterfacePointer 50 | } 51 | 52 | if fail := ShouldNotBeNil(actual); fail != success { 53 | return shouldNotBeNilActual 54 | } 55 | 56 | var actualType reflect.Type 57 | if reflect.TypeOf(actual).Kind() != reflect.Ptr { 58 | actualType = reflect.PtrTo(reflect.TypeOf(actual)) 59 | } else { 60 | actualType = reflect.TypeOf(actual) 61 | } 62 | 63 | expectedType := reflect.TypeOf(expected) 64 | if fail := ShouldNotBeNil(expectedType); fail != success { 65 | return shouldCompareWithInterfacePointer 66 | } 67 | 68 | expectedInterface := expectedType.Elem() 69 | 70 | if !actualType.Implements(expectedInterface) { 71 | return fmt.Sprintf(shouldHaveImplemented, expectedInterface, actualType) 72 | } 73 | return success 74 | } 75 | 76 | // ShouldNotImplement receives exactly two parameters and ensures 77 | // that the first does NOT implement the interface type of the second. 78 | func ShouldNotImplement(actual any, expectedList ...any) string { 79 | if fail := need(1, expectedList); fail != success { 80 | return fail 81 | } 82 | 83 | expected := expectedList[0] 84 | if fail := ShouldBeNil(expected); fail != success { 85 | return shouldCompareWithInterfacePointer 86 | } 87 | 88 | if fail := ShouldNotBeNil(actual); fail != success { 89 | return shouldNotBeNilActual 90 | } 91 | 92 | var actualType reflect.Type 93 | if reflect.TypeOf(actual).Kind() != reflect.Ptr { 94 | actualType = reflect.PtrTo(reflect.TypeOf(actual)) 95 | } else { 96 | actualType = reflect.TypeOf(actual) 97 | } 98 | 99 | expectedType := reflect.TypeOf(expected) 100 | if fail := ShouldNotBeNil(expectedType); fail != success { 101 | return shouldCompareWithInterfacePointer 102 | } 103 | 104 | expectedInterface := expectedType.Elem() 105 | 106 | if actualType.Implements(expectedInterface) { 107 | return fmt.Sprintf(shouldNotHaveImplemented, actualType, expectedInterface) 108 | } 109 | return success 110 | } 111 | 112 | // ShouldBeError asserts that the first argument implements the error interface. 113 | // It also compares the first argument against the second argument if provided 114 | // (which must be an error message string or another error value). 115 | func ShouldBeError(actual any, expected ...any) string { 116 | if fail := atMost(1, expected); fail != success { 117 | return fail 118 | } 119 | 120 | if !isError(actual) { 121 | return fmt.Sprintf(shouldBeError, reflect.TypeOf(actual)) 122 | } 123 | 124 | if len(expected) == 0 { 125 | return success 126 | } 127 | 128 | if expected := expected[0]; !isString(expected) && !isError(expected) { 129 | return fmt.Sprintf(shouldBeErrorInvalidComparisonValue, reflect.TypeOf(expected)) 130 | } 131 | return ShouldEqual(fmt.Sprint(actual), fmt.Sprint(expected[0])) 132 | } 133 | 134 | // ShouldWrap asserts that the first argument (which must be an error value) 135 | // 'wraps' the second/final argument (which must also be an error value). 136 | // It relies on errors.Is to make the determination (https://golang.org/pkg/errors/#Is). 137 | func ShouldWrap(actual any, expected ...any) string { 138 | if fail := need(1, expected); fail != success { 139 | return fail 140 | } 141 | 142 | if !isError(actual) || !isError(expected[0]) { 143 | return fmt.Sprintf(shouldWrapInvalidTypes, reflect.TypeOf(actual), reflect.TypeOf(expected[0])) 144 | } 145 | 146 | if !errors.Is(actual.(error), expected[0].(error)) { 147 | return fmt.Sprintf(`Expected error("%s") to wrap error("%s") but it didn't.`, actual, expected[0]) 148 | } 149 | 150 | return success 151 | } 152 | 153 | func isString(value any) bool { _, ok := value.(string); return ok } 154 | func isError(value any) bool { _, ok := value.(error); return ok } 155 | -------------------------------------------------------------------------------- /type_test.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | func (this *AssertionsFixture) TestShouldHaveSameTypeAs() { 12 | this.fail(so(1, ShouldHaveSameTypeAs), "This assertion requires exactly 1 comparison values (you provided 0).") 13 | this.fail(so(1, ShouldHaveSameTypeAs, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).") 14 | 15 | this.fail(so(nil, ShouldHaveSameTypeAs, 0), "int||Expected '' to be: 'int' (but was: '')!") 16 | this.fail(so(1, ShouldHaveSameTypeAs, "asdf"), "string|int|Expected '1' to be: 'string' (but was: 'int')!") 17 | 18 | this.pass(so(1, ShouldHaveSameTypeAs, 0)) 19 | this.pass(so(nil, ShouldHaveSameTypeAs, nil)) 20 | } 21 | 22 | func (this *AssertionsFixture) TestShouldNotHaveSameTypeAs() { 23 | this.fail(so(1, ShouldNotHaveSameTypeAs), "This assertion requires exactly 1 comparison values (you provided 0).") 24 | this.fail(so(1, ShouldNotHaveSameTypeAs, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).") 25 | 26 | this.fail(so(1, ShouldNotHaveSameTypeAs, 0), "Expected '1' to NOT be: 'int' (but it was)!") 27 | this.fail(so(nil, ShouldNotHaveSameTypeAs, nil), "Expected '' to NOT be: '' (but it was)!") 28 | 29 | this.pass(so(nil, ShouldNotHaveSameTypeAs, 0)) 30 | this.pass(so(1, ShouldNotHaveSameTypeAs, "asdf")) 31 | } 32 | 33 | func (this *AssertionsFixture) TestShouldImplement() { 34 | var ioReader *io.Reader = nil 35 | var response http.Response = http.Response{} 36 | var responsePtr *http.Response = new(http.Response) 37 | var reader = bytes.NewBufferString("") 38 | 39 | this.fail(so(reader, ShouldImplement), "This assertion requires exactly 1 comparison values (you provided 0).") 40 | this.fail(so(reader, ShouldImplement, ioReader, ioReader), "This assertion requires exactly 1 comparison values (you provided 2).") 41 | this.fail(so(reader, ShouldImplement, ioReader, ioReader, ioReader), "This assertion requires exactly 1 comparison values (you provided 3).") 42 | 43 | this.fail(so(reader, ShouldImplement, "foo"), shouldCompareWithInterfacePointer) 44 | this.fail(so(reader, ShouldImplement, 1), shouldCompareWithInterfacePointer) 45 | this.fail(so(reader, ShouldImplement, nil), shouldCompareWithInterfacePointer) 46 | 47 | this.fail(so(nil, ShouldImplement, ioReader), shouldNotBeNilActual) 48 | this.fail(so(1, ShouldImplement, ioReader), "Expected: 'io.Reader interface support'\nActual: '*int' does not implement the interface!") 49 | 50 | this.fail(so(response, ShouldImplement, ioReader), "Expected: 'io.Reader interface support'\nActual: '*http.Response' does not implement the interface!") 51 | this.fail(so(responsePtr, ShouldImplement, ioReader), "Expected: 'io.Reader interface support'\nActual: '*http.Response' does not implement the interface!") 52 | this.pass(so(reader, ShouldImplement, ioReader)) 53 | this.pass(so(reader, ShouldImplement, (*io.Reader)(nil))) 54 | } 55 | 56 | func (this *AssertionsFixture) TestShouldNotImplement() { 57 | var ioReader *io.Reader = nil 58 | var response http.Response = http.Response{} 59 | var responsePtr *http.Response = new(http.Response) 60 | var reader io.Reader = bytes.NewBufferString("") 61 | 62 | this.fail(so(reader, ShouldNotImplement), "This assertion requires exactly 1 comparison values (you provided 0).") 63 | this.fail(so(reader, ShouldNotImplement, ioReader, ioReader), "This assertion requires exactly 1 comparison values (you provided 2).") 64 | this.fail(so(reader, ShouldNotImplement, ioReader, ioReader, ioReader), "This assertion requires exactly 1 comparison values (you provided 3).") 65 | 66 | this.fail(so(reader, ShouldNotImplement, "foo"), shouldCompareWithInterfacePointer) 67 | this.fail(so(reader, ShouldNotImplement, 1), shouldCompareWithInterfacePointer) 68 | this.fail(so(reader, ShouldNotImplement, nil), shouldCompareWithInterfacePointer) 69 | 70 | this.fail(so(reader, ShouldNotImplement, ioReader), "Expected '*bytes.Buffer'\nto NOT implement 'io.Reader' (but it did)!") 71 | this.fail(so(nil, ShouldNotImplement, ioReader), shouldNotBeNilActual) 72 | this.pass(so(1, ShouldNotImplement, ioReader)) 73 | this.pass(so(response, ShouldNotImplement, ioReader)) 74 | this.pass(so(responsePtr, ShouldNotImplement, ioReader)) 75 | } 76 | 77 | func (this *AssertionsFixture) TestShouldBeError() { 78 | this.fail(so(nil, ShouldBeError, "too", "many"), "This assertion allows 1 or fewer comparison values (you provided 2).") 79 | 80 | this.fail(so(1, ShouldBeError), "Expected an error value (but was 'int' instead)!") 81 | this.fail(so(nil, ShouldBeError), "Expected an error value (but was '' instead)!") 82 | 83 | error1 := errors.New("Message") 84 | 85 | this.fail(so(error1, ShouldBeError, 42), "The final argument to this assertion must be a string or an error value (you provided: 'int').") 86 | this.fail(so(error1, ShouldBeError, "Wrong error message"), `Wrong error message|Message|Expected: "Wrong error message" Actual: "Message" (Should equal)!`) 87 | 88 | this.pass(so(error1, ShouldBeError)) 89 | this.pass(so(error1, ShouldBeError, error1)) 90 | this.pass(so(error1, ShouldBeError, error1.Error())) 91 | } 92 | 93 | func (this *AssertionsFixture) TestShouldWrapError() { 94 | inner := fmt.Errorf("inner") 95 | middle := fmt.Errorf("middle(%w)", inner) 96 | outer := fmt.Errorf("outer(%w)", middle) 97 | 98 | this.fail(so(outer, ShouldWrap, "too", "many"), "This assertion requires exactly 1 comparison values (you provided 2).") 99 | this.fail(so(outer, ShouldWrap), "This assertion requires exactly 1 comparison values (you provided 0).") 100 | 101 | this.fail(so(42, ShouldWrap, 42), "The first and last arguments to this assertion must both be error values (you provided: 'int' and 'int').") 102 | 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').") 103 | 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').") 104 | 105 | this.fail(so(inner, ShouldWrap, outer), `Expected error("inner") to wrap error("outer(middle(inner))") but it didn't.`) 106 | this.pass(so(middle, ShouldWrap, inner)) 107 | this.pass(so(outer, ShouldWrap, middle)) 108 | this.pass(so(outer, ShouldWrap, inner)) 109 | } 110 | -------------------------------------------------------------------------------- /utilities_for_test.go: -------------------------------------------------------------------------------- 1 | package assertions 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/smarty/assertions/internal/unit" 9 | ) 10 | 11 | /**************************************************************************/ 12 | 13 | func TestAssertionsFixture(t *testing.T) { 14 | unit.Run(new(AssertionsFixture), t) 15 | } 16 | 17 | type AssertionsFixture struct { 18 | *unit.Fixture 19 | } 20 | 21 | func (this *AssertionsFixture) Setup() { 22 | serializer = this 23 | } 24 | 25 | func (self *AssertionsFixture) serialize(expected, actual any, message string) string { 26 | return fmt.Sprintf("%v|%v|%s", expected, actual, message) 27 | } 28 | 29 | func (self *AssertionsFixture) serializeDetailed(expected, actual any, message string) string { 30 | return fmt.Sprintf("%v|%v|%s", expected, actual, message) 31 | } 32 | 33 | func (this *AssertionsFixture) pass(result string) { 34 | this.Assert(result == success, result) 35 | } 36 | 37 | func (this *AssertionsFixture) fail(actual string, expected string) { 38 | actual = format(actual) 39 | expected = format(expected) 40 | 41 | if expected == "[no-check]" && actual == "" { 42 | this.Errorf("Expected fail, but assertion passed.") 43 | } else if expected == "[no-check]" { 44 | return 45 | } 46 | if actual != expected { 47 | if actual == "" { 48 | actual = "(empty)" 49 | } 50 | this.Errorf("Expected: %s\nActual: %s\n", expected, actual) 51 | } 52 | } 53 | func format(message string) string { 54 | message = strings.Replace(message, "\n", " ", -1) 55 | for strings.Contains(message, " ") { 56 | message = strings.Replace(message, " ", " ", -1) 57 | } 58 | message = strings.Replace(message, "\x1b[32m", "", -1) 59 | message = strings.Replace(message, "\x1b[31m", "", -1) 60 | message = strings.Replace(message, "\x1b[0m", "", -1) 61 | return message 62 | } 63 | 64 | /**************************************************************************/ 65 | 66 | type Thing1 struct { 67 | a string 68 | } 69 | type Thing2 struct { 70 | a string 71 | } 72 | 73 | type ThingInterface interface { 74 | Hi() 75 | } 76 | 77 | type ThingImplementation struct{} 78 | 79 | func (self *ThingImplementation) Hi() {} 80 | 81 | type IntAlias int 82 | type StringAlias string 83 | type StringSliceAlias []string 84 | type StringStringMapAlias map[string]string 85 | 86 | /**************************************************************************/ 87 | 88 | type ThingWithEqualMethod struct { 89 | a string 90 | } 91 | 92 | func (this ThingWithEqualMethod) Equal(that ThingWithEqualMethod) bool { 93 | return this.a == that.a 94 | } 95 | --------------------------------------------------------------------------------