├── .github └── workflows │ ├── codeql-analysis.yml │ └── go.yml ├── .gitignore ├── CHANGES.md ├── LICENSE ├── README.md ├── SECURITY.md ├── deep.go ├── deep_test.go ├── go.mod ├── go.sum └── test ├── coverage ├── v1 └── errors.go └── v2 └── errors.go /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '35 10 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | go: 19 | - '1.24' 20 | - '1.23' 21 | - '1.22' 22 | steps: 23 | - uses: actions/checkout@v3 24 | 25 | - name: Set up Go 26 | uses: actions/setup-go@v3 27 | with: 28 | go-version: ${{ matrix.go }} 29 | 30 | - name: Build 31 | run: go build 32 | 33 | - name: Test 34 | run: go test -v -coverprofile=profile.cov 35 | 36 | - name: coveralls.io 37 | uses: shogo82148/actions-goveralls@v1 38 | with: 39 | path-to-profile: profile.cov 40 | flag-name: Go-${{ matrix.go }} 41 | parallel: true 42 | 43 | finish: 44 | needs: test 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: shogo82148/actions-goveralls@v1 48 | with: 49 | parallel-finished: true 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.out 3 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # go-test/deep Changelog 2 | 3 | ## v1.1.1 released 2024-06-23 4 | 5 | * Added `NilPointersAreZero` option: causes a nil pointer to be equal to a zero value (PR #61) (@seveas) 6 | * Updated test matrix to go1.22, go1.21, and go1.20 7 | 8 | ## v1.1.0 released 2022-12-09 9 | 10 | * Add optional flags: `Equal(a, b, flags..)` and `FLAG_IGNORE_SLICE_ORDER` (issue #28, PR #56) (@alenkacz) 11 | 12 | --- 13 | 14 | ## v1.0.9 released 2022-12-09 15 | 16 | * Fixed issue #45: Panic when comparing errors in unexported fields (PR #54) (@seveas) 17 | * Fixed issue #46: Functions are handled differently from reflect.DeepEqual (PR #55) (@countcb) 18 | * Updated test matrix to go1.17, go1.18, and go1.19 and moved testing to GitHub Actions 19 | 20 | ## v1.0.8 released 2021-10-13 21 | 22 | * Updated test matrix to go1.15, go1.16, and go1.17 23 | * Added SECURITY.md and GitHub code analysis 24 | 25 | ## v1.0.7 released 2020-07-11 26 | 27 | * Fixed issue #39: Confusing diff when comparing distinct types with the same name (PR #44) 28 | 29 | ## v1.0.6 released 2020-04-21 30 | 31 | * Added `NilMapsAreEmpty` variable which causes a nil map to equal an empty map (PR #43) (@yalegko) 32 | 33 | ## v1.0.5 released 2020-01-16 34 | 35 | * Added `NilSlicesAreEmpty` variable which causes a nil slice to be equal to an empty slice (PR #27) (@Anaminus) 36 | 37 | ## v1.0.4 released 2019-09-15 38 | 39 | * Added \`deep:"-"\` structure field tag to ignore field (PR #38) (@flga) 40 | 41 | ## v1.0.3 released 2019-08-18 42 | 43 | * Fixed issue #31: panic on typed primitives that implement error interface 44 | 45 | ## v1.0.2 released 2019-07-14 46 | 47 | * Enabled Go module (@radeksimko) 48 | * Changed supported and tested Go versions: 1.10, 1.11, and 1.12 (dropped 1.9) 49 | * Changed Error equality: additional struct fields are compared too (PR #29) (@andrewmostello) 50 | * Fixed typos and ineffassign issues (PR #25) (@tariq1890) 51 | * Fixed diff order for nil comparison (PR #16) (@gmarik) 52 | * Fixed slice equality when slices are extracted from the same array (PR #11) (@risteli) 53 | * Fixed test spelling and messages (PR #19) (@sofuture) 54 | * Fixed issue #15: panic on comparing struct with anonymous time.Time 55 | * Fixed issue #18: Panic when comparing structs with time.Time value and CompareUnexportedFields is true 56 | * Fixed issue #21: Set default MaxDepth = 0 (disabled) (PR #23) 57 | 58 | ## v1.0.1 released 2018-01-28 59 | 60 | * Fixed issue #12: Arrays are not properly compared (@samlitowitz) 61 | 62 | ## v1.0.0 releaesd 2017-10-27 63 | 64 | * First release 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2015-2017 Daniel Nichter 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deep Variable Equality for Humans 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/go-test/deep)](https://goreportcard.com/report/github.com/go-test/deep) 4 | [![Coverage Status](https://coveralls.io/repos/github/go-test/deep/badge.svg?branch=master)](https://coveralls.io/github/go-test/deep?branch=master) 5 | [![Go Reference](https://pkg.go.dev/badge/github.com/go-test/deep.svg)](https://pkg.go.dev/github.com/go-test/deep) 6 | 7 | This package provides a single function: `deep.Equal`. It's like [reflect.DeepEqual](http://golang.org/pkg/reflect/#DeepEqual) but much friendlier to humans (or any sentient being) for two reason: 8 | 9 | * `deep.Equal` returns a list of differences 10 | * `deep.Equal` does not compare unexported fields (by default) 11 | 12 | `reflect.DeepEqual` is good (like all things Golang!), but it's a game of [Hunt the Wumpus](https://en.wikipedia.org/wiki/Hunt_the_Wumpus). For large maps, slices, and structs, finding the difference is difficult. 13 | 14 | `deep.Equal` doesn't play games with you, it lists the differences: 15 | 16 | ```go 17 | package main_test 18 | 19 | import ( 20 | "testing" 21 | "github.com/go-test/deep" 22 | ) 23 | 24 | type T struct { 25 | Name string 26 | Numbers []float64 27 | } 28 | 29 | func TestDeepEqual(t *testing.T) { 30 | // Can you spot the difference? 31 | t1 := T{ 32 | Name: "Isabella", 33 | Numbers: []float64{1.13459, 2.29343, 3.010100010}, 34 | } 35 | t2 := T{ 36 | Name: "Isabella", 37 | Numbers: []float64{1.13459, 2.29843, 3.010100010}, 38 | } 39 | 40 | if diff := deep.Equal(t1, t2); diff != nil { 41 | t.Error(diff) 42 | } 43 | } 44 | ``` 45 | 46 | 47 | ``` 48 | $ go test 49 | --- FAIL: TestDeepEqual (0.00s) 50 | main_test.go:25: [Numbers.slice[1]: 2.29343 != 2.29843] 51 | ``` 52 | 53 | The difference is in `Numbers.slice[1]`: the two values aren't equal using Go `==`. 54 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | For security patches, the latest release is supported: 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 1.1.x | Yes | 10 | | 1.0.x | No | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | To report a vulnerability, [create an issue](https://github.com/go-test/deep/issues) with the _security_ label. 15 | 16 | This project is developed and maintained by volunteers during their free time, 17 | so there is no SLA or ETA for fixing vulnerabilities (or any issues). 18 | Please help by submitting a PR to fix an issue. 19 | -------------------------------------------------------------------------------- /deep.go: -------------------------------------------------------------------------------- 1 | // Package deep provides function deep.Equal which is like reflect.DeepEqual but 2 | // returns a list of differences. This is helpful when comparing complex types 3 | // like structures and maps. 4 | package deep 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "log" 10 | "reflect" 11 | "strings" 12 | ) 13 | 14 | var ( 15 | // FloatPrecision is the number of decimal places to round float values 16 | // to when comparing. 17 | FloatPrecision = 10 18 | 19 | // MaxDiff specifies the maximum number of differences to return. 20 | MaxDiff = 10 21 | 22 | // MaxDepth specifies the maximum levels of a struct to recurse into, 23 | // if greater than zero. If zero, there is no limit. 24 | MaxDepth = 0 25 | 26 | // LogErrors causes errors to be logged to STDERR when true. 27 | LogErrors = false 28 | 29 | // CompareUnexportedFields causes unexported struct fields, like s in 30 | // T{s int}, to be compared when true. This does not work for comparing 31 | // error or Time types on unexported fields because methods on unexported 32 | // fields cannot be called. 33 | CompareUnexportedFields = false 34 | 35 | // CompareFunctions compares functions the same as reflect.DeepEqual: 36 | // only two nil functions are equal. Every other combination is not equal. 37 | // This is disabled by default because previous versions of this package 38 | // ignored functions. Enabling it can possibly report new diffs. 39 | CompareFunctions = false 40 | 41 | // NilSlicesAreEmpty causes a nil slice to be equal to an empty slice. 42 | NilSlicesAreEmpty = false 43 | 44 | // NilMapsAreEmpty causes a nil map to be equal to an empty map. 45 | NilMapsAreEmpty = false 46 | 47 | // NilPointersAreZero causes a nil pointer to be equal to a zero value. 48 | NilPointersAreZero = false 49 | ) 50 | 51 | var ( 52 | // ErrMaxRecursion is logged when MaxDepth is reached. 53 | ErrMaxRecursion = errors.New("recursed to MaxDepth") 54 | 55 | // ErrTypeMismatch is logged when Equal passed two different types of values. 56 | ErrTypeMismatch = errors.New("variables are different reflect.Type") 57 | 58 | // ErrNotHandled is logged when a primitive Go kind is not handled. 59 | ErrNotHandled = errors.New("cannot compare the reflect.Kind") 60 | ) 61 | 62 | const ( 63 | // FLAG_NONE is a placeholder for default Equal behavior. You don't have to 64 | // pass it to Equal; if you do, it does nothing. 65 | FLAG_NONE byte = iota 66 | 67 | // FLAG_IGNORE_SLICE_ORDER causes Equal to ignore slice order so that 68 | // []int{1, 2} and []int{2, 1} are equal. Only slices of primitive scalars 69 | // like numbers and strings are supported. Slices of complex types, 70 | // like []T where T is a struct, are undefined because Equal does not 71 | // recurse into the slice value when this flag is enabled. 72 | FLAG_IGNORE_SLICE_ORDER 73 | ) 74 | 75 | type cmp struct { 76 | diff []string 77 | buff []string 78 | floatFormat string 79 | flag map[byte]bool 80 | } 81 | 82 | var errorType = reflect.TypeOf((*error)(nil)).Elem() 83 | 84 | // Equal compares variables a and b, recursing into their structure up to 85 | // MaxDepth levels deep (if greater than zero), and returns a list of differences, 86 | // or nil if there are none. Some differences may not be found if an error is 87 | // also returned. 88 | // 89 | // If a type has an Equal method, like time.Equal, it is called to check for 90 | // equality. 91 | // 92 | // When comparing a struct, if a field has the tag `deep:"-"` then it will be 93 | // ignored. 94 | func Equal(a, b interface{}, flags ...interface{}) []string { 95 | aVal := reflect.ValueOf(a) 96 | bVal := reflect.ValueOf(b) 97 | c := &cmp{ 98 | diff: []string{}, 99 | buff: []string{}, 100 | floatFormat: fmt.Sprintf("%%.%df", FloatPrecision), 101 | flag: map[byte]bool{}, 102 | } 103 | for i := range flags { 104 | c.flag[flags[i].(byte)] = true 105 | } 106 | if a == nil && b == nil { 107 | return nil 108 | } else if a == nil && b != nil { 109 | c.saveDiff("", b) 110 | } else if a != nil && b == nil { 111 | c.saveDiff(a, "") 112 | } 113 | if len(c.diff) > 0 { 114 | return c.diff 115 | } 116 | 117 | c.equals(aVal, bVal, 0) 118 | if len(c.diff) > 0 { 119 | return c.diff // diffs 120 | } 121 | return nil // no diffs 122 | } 123 | 124 | func (c *cmp) equals(a, b reflect.Value, level int) { 125 | if MaxDepth > 0 && level > MaxDepth { 126 | logError(ErrMaxRecursion) 127 | return 128 | } 129 | 130 | // Check if one value is nil, e.g. T{x: *X} and T.x is nil 131 | if !a.IsValid() || !b.IsValid() { 132 | if a.IsValid() && !b.IsValid() { 133 | c.saveDiff(a.Type(), "") 134 | } else if !a.IsValid() && b.IsValid() { 135 | c.saveDiff("", b.Type()) 136 | } 137 | return 138 | } 139 | 140 | // If different types, they can't be equal 141 | aType := a.Type() 142 | bType := b.Type() 143 | if aType != bType { 144 | // Built-in types don't have a name, so don't report [3]int != [2]int as " != " 145 | if aType.Name() == "" || aType.Name() != bType.Name() { 146 | c.saveDiff(aType, bType) 147 | } else { 148 | // Type names can be the same, e.g. pkg/v1.Error and pkg/v2.Error 149 | // are both exported as pkg, so unless we include the full pkg path 150 | // the diff will be "pkg.Error != pkg.Error" 151 | // https://github.com/go-test/deep/issues/39 152 | aFullType := aType.PkgPath() + "." + aType.Name() 153 | bFullType := bType.PkgPath() + "." + bType.Name() 154 | c.saveDiff(aFullType, bFullType) 155 | } 156 | logError(ErrTypeMismatch) 157 | return 158 | } 159 | 160 | // Primitive https://golang.org/pkg/reflect/#Kind 161 | aKind := a.Kind() 162 | bKind := b.Kind() 163 | 164 | // Do a and b have underlying elements? Yes if they're ptr or interface. 165 | aElem := aKind == reflect.Ptr || aKind == reflect.Interface 166 | bElem := bKind == reflect.Ptr || bKind == reflect.Interface 167 | 168 | // If both types implement the error interface, compare the error strings. 169 | // This must be done before dereferencing because errors.New() returns a 170 | // pointer to a struct that implements the interface: 171 | // func (e *errorString) Error() string { 172 | // And we check CanInterface as a hack to make sure the underlying method 173 | // is callable because https://github.com/golang/go/issues/32438 174 | // Issues: 175 | // https://github.com/go-test/deep/issues/31 176 | // https://github.com/go-test/deep/issues/45 177 | if (aType.Implements(errorType) && bType.Implements(errorType)) && 178 | ((!aElem || !a.IsNil()) && (!bElem || !b.IsNil())) && 179 | (a.CanInterface() && b.CanInterface()) { 180 | aString := a.MethodByName("Error").Call(nil)[0].String() 181 | bString := b.MethodByName("Error").Call(nil)[0].String() 182 | if aString != bString { 183 | c.saveDiff(aString, bString) 184 | } 185 | return 186 | } 187 | 188 | // Dereference pointers and interface{} 189 | if aElem || bElem { 190 | if aElem { 191 | a = a.Elem() 192 | } 193 | if bElem { 194 | b = b.Elem() 195 | } 196 | if aElem && NilPointersAreZero && !a.IsValid() && b.IsValid() { 197 | a = reflect.Zero(b.Type()) 198 | } 199 | if bElem && NilPointersAreZero && !b.IsValid() && a.IsValid() { 200 | b = reflect.Zero(a.Type()) 201 | } 202 | c.equals(a, b, level+1) 203 | return 204 | } 205 | 206 | switch aKind { 207 | 208 | ///////////////////////////////////////////////////////////////////// 209 | // Iterable kinds 210 | ///////////////////////////////////////////////////////////////////// 211 | 212 | case reflect.Struct: 213 | /* 214 | The variables are structs like: 215 | type T struct { 216 | FirstName string 217 | LastName string 218 | } 219 | Type = .T, Kind = reflect.Struct 220 | 221 | Iterate through the fields (FirstName, LastName), recurse into their values. 222 | */ 223 | 224 | // Types with an Equal() method, like time.Time, only if struct field 225 | // is exported (CanInterface) 226 | if eqFunc := a.MethodByName("Equal"); eqFunc.IsValid() && eqFunc.CanInterface() { 227 | // Handle https://github.com/go-test/deep/issues/15: 228 | // Don't call T.Equal if the method is from an embedded struct, like: 229 | // type Foo struct { time.Time } 230 | // First, we'll encounter Equal(Ttime, time.Time) but if we pass b 231 | // as the 2nd arg we'll panic: "Call using pkg.Foo as type time.Time" 232 | // As far as I can tell, there's no way to see that the method is from 233 | // time.Time not Foo. So we check the type of the 1st (0) arg and skip 234 | // unless it's b type. Later, we'll encounter the time.Time anonymous/ 235 | // embedded field and then we'll have Equal(time.Time, time.Time). 236 | funcType := eqFunc.Type() 237 | if funcType.NumIn() == 1 && funcType.In(0) == bType { 238 | retVals := eqFunc.Call([]reflect.Value{b}) 239 | if !retVals[0].Bool() { 240 | c.saveDiff(a, b) 241 | } 242 | return 243 | } 244 | } 245 | 246 | for i := 0; i < a.NumField(); i++ { 247 | if aType.Field(i).PkgPath != "" && !CompareUnexportedFields { 248 | continue // skip unexported field, e.g. s in type T struct {s string} 249 | } 250 | 251 | if aType.Field(i).Tag.Get("deep") == "-" { 252 | continue // field wants to be ignored 253 | } 254 | 255 | c.push(aType.Field(i).Name) // push field name to buff 256 | 257 | // Get the Value for each field, e.g. FirstName has Type = string, 258 | // Kind = reflect.String. 259 | af := a.Field(i) 260 | bf := b.Field(i) 261 | 262 | // Recurse to compare the field values 263 | c.equals(af, bf, level+1) 264 | 265 | c.pop() // pop field name from buff 266 | 267 | if len(c.diff) >= MaxDiff { 268 | break 269 | } 270 | } 271 | case reflect.Map: 272 | /* 273 | The variables are maps like: 274 | map[string]int{ 275 | "foo": 1, 276 | "bar": 2, 277 | } 278 | Type = map[string]int, Kind = reflect.Map 279 | 280 | Or: 281 | type T map[string]int{} 282 | Type = .T, Kind = reflect.Map 283 | 284 | Iterate through the map keys (foo, bar), recurse into their values. 285 | */ 286 | 287 | if a.IsNil() || b.IsNil() { 288 | if NilMapsAreEmpty { 289 | if a.IsNil() && b.Len() != 0 { 290 | c.saveDiff("", b) 291 | return 292 | } else if a.Len() != 0 && b.IsNil() { 293 | c.saveDiff(a, "") 294 | return 295 | } 296 | } else { 297 | if a.IsNil() && !b.IsNil() { 298 | c.saveDiff("", b) 299 | } else if !a.IsNil() && b.IsNil() { 300 | c.saveDiff(a, "") 301 | } 302 | } 303 | return 304 | } 305 | 306 | if a.Pointer() == b.Pointer() { 307 | return 308 | } 309 | 310 | for _, key := range a.MapKeys() { 311 | c.push(fmt.Sprintf("map[%v]", key)) 312 | 313 | aVal := a.MapIndex(key) 314 | bVal := b.MapIndex(key) 315 | if bVal.IsValid() { 316 | c.equals(aVal, bVal, level+1) 317 | } else { 318 | c.saveDiff(aVal, "") 319 | } 320 | 321 | c.pop() 322 | 323 | if len(c.diff) >= MaxDiff { 324 | return 325 | } 326 | } 327 | 328 | for _, key := range b.MapKeys() { 329 | if aVal := a.MapIndex(key); aVal.IsValid() { 330 | continue 331 | } 332 | 333 | c.push(fmt.Sprintf("map[%v]", key)) 334 | c.saveDiff("", b.MapIndex(key)) 335 | c.pop() 336 | if len(c.diff) >= MaxDiff { 337 | return 338 | } 339 | } 340 | case reflect.Array: 341 | n := a.Len() 342 | for i := 0; i < n; i++ { 343 | c.push(fmt.Sprintf("array[%d]", i)) 344 | c.equals(a.Index(i), b.Index(i), level+1) 345 | c.pop() 346 | if len(c.diff) >= MaxDiff { 347 | break 348 | } 349 | } 350 | case reflect.Slice: 351 | if NilSlicesAreEmpty { 352 | if a.IsNil() && b.Len() != 0 { 353 | c.saveDiff("", b) 354 | return 355 | } else if a.Len() != 0 && b.IsNil() { 356 | c.saveDiff(a, "") 357 | return 358 | } 359 | } else { 360 | if a.IsNil() && !b.IsNil() { 361 | c.saveDiff("", b) 362 | return 363 | } else if !a.IsNil() && b.IsNil() { 364 | c.saveDiff(a, "") 365 | return 366 | } 367 | } 368 | 369 | // Equal if same underlying pointer and same length, this latter handles 370 | // foo := []int{1, 2, 3, 4} 371 | // a := foo[0:2] // == {1,2} 372 | // b := foo[2:4] // == {3,4} 373 | // a and b are same pointer but different slices (lengths) of the underlying 374 | // array, so not equal. 375 | aLen := a.Len() 376 | bLen := b.Len() 377 | if a.Pointer() == b.Pointer() && aLen == bLen { 378 | return 379 | } 380 | 381 | if c.flag[FLAG_IGNORE_SLICE_ORDER] { 382 | // Compare slices by value and value count; ignore order. 383 | // Value equality is impliclity established by the maps: 384 | // any value v1 will hash to the same map value if it's equal 385 | // to another value v2. Then equality is determiend by value 386 | // count: presuming v1==v2, then the slics are equal if there 387 | // are equal numbers of v1 in each slice. 388 | am := map[interface{}]int{} 389 | for i := 0; i < a.Len(); i++ { 390 | am[a.Index(i).Interface()] += 1 391 | } 392 | bm := map[interface{}]int{} 393 | for i := 0; i < b.Len(); i++ { 394 | bm[b.Index(i).Interface()] += 1 395 | } 396 | c.cmpMapValueCounts(a, b, am, bm, true) // a cmp b 397 | c.cmpMapValueCounts(b, a, bm, am, false) // b cmp a 398 | } else { 399 | // Compare slices by order 400 | n := aLen 401 | if bLen > aLen { 402 | n = bLen 403 | } 404 | for i := 0; i < n; i++ { 405 | c.push(fmt.Sprintf("slice[%d]", i)) 406 | if i < aLen && i < bLen { 407 | c.equals(a.Index(i), b.Index(i), level+1) 408 | } else if i < aLen { 409 | c.saveDiff(a.Index(i), "") 410 | } else { 411 | c.saveDiff("", b.Index(i)) 412 | } 413 | c.pop() 414 | if len(c.diff) >= MaxDiff { 415 | break 416 | } 417 | } 418 | } 419 | 420 | ///////////////////////////////////////////////////////////////////// 421 | // Primitive kinds 422 | ///////////////////////////////////////////////////////////////////// 423 | 424 | case reflect.Float32, reflect.Float64: 425 | // Round floats to FloatPrecision decimal places to compare with 426 | // user-defined precision. As is commonly know, floats have "imprecision" 427 | // such that 0.1 becomes 0.100000001490116119384765625. This cannot 428 | // be avoided; it can only be handled. Issue 30 suggested that floats 429 | // be compared using an epsilon: equal = |a-b| < epsilon. 430 | // In many cases the result is the same, but I think epsilon is a little 431 | // less clear for users to reason about. See issue 30 for details. 432 | aval := fmt.Sprintf(c.floatFormat, a.Float()) 433 | bval := fmt.Sprintf(c.floatFormat, b.Float()) 434 | if aval != bval { 435 | c.saveDiff(a.Float(), b.Float()) 436 | } 437 | case reflect.Bool: 438 | if a.Bool() != b.Bool() { 439 | c.saveDiff(a.Bool(), b.Bool()) 440 | } 441 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 442 | if a.Int() != b.Int() { 443 | c.saveDiff(a.Int(), b.Int()) 444 | } 445 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 446 | if a.Uint() != b.Uint() { 447 | c.saveDiff(a.Uint(), b.Uint()) 448 | } 449 | case reflect.String: 450 | if a.String() != b.String() { 451 | c.saveDiff(a.String(), b.String()) 452 | } 453 | case reflect.Func: 454 | if CompareFunctions { 455 | if !a.IsNil() || !b.IsNil() { 456 | aVal, bVal := "nil func", "nil func" 457 | if !a.IsNil() { 458 | aVal = "func" 459 | } 460 | if !b.IsNil() { 461 | bVal = "func" 462 | } 463 | c.saveDiff(aVal, bVal) 464 | } 465 | } 466 | default: 467 | logError(ErrNotHandled) 468 | } 469 | } 470 | 471 | func (c *cmp) push(name string) { 472 | c.buff = append(c.buff, name) 473 | } 474 | 475 | func (c *cmp) pop() { 476 | if len(c.buff) > 0 { 477 | c.buff = c.buff[0 : len(c.buff)-1] 478 | } 479 | } 480 | 481 | func (c *cmp) saveDiff(aval, bval interface{}) { 482 | if len(c.buff) > 0 { 483 | varName := strings.Join(c.buff, ".") 484 | c.diff = append(c.diff, fmt.Sprintf("%s: %v != %v", varName, aval, bval)) 485 | } else { 486 | c.diff = append(c.diff, fmt.Sprintf("%v != %v", aval, bval)) 487 | } 488 | } 489 | 490 | func (c *cmp) cmpMapValueCounts(a, b reflect.Value, am, bm map[interface{}]int, a2b bool) { 491 | for v := range am { 492 | aCount, _ := am[v] 493 | bCount, _ := bm[v] 494 | 495 | if aCount != bCount { 496 | c.push(fmt.Sprintf("(unordered) slice[]=%v: value count", v)) 497 | if a2b { 498 | c.saveDiff(fmt.Sprintf("%d", aCount), fmt.Sprintf("%d", bCount)) 499 | } else { 500 | c.saveDiff(fmt.Sprintf("%d", bCount), fmt.Sprintf("%d", aCount)) 501 | } 502 | c.pop() 503 | } 504 | delete(am, v) 505 | delete(bm, v) 506 | } 507 | } 508 | 509 | func logError(err error) { 510 | if LogErrors { 511 | log.Println(err) 512 | } 513 | } 514 | -------------------------------------------------------------------------------- /deep_test.go: -------------------------------------------------------------------------------- 1 | package deep_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "sort" 8 | "testing" 9 | "time" 10 | "unsafe" 11 | 12 | "github.com/go-test/deep" 13 | v1 "github.com/go-test/deep/test/v1" 14 | v2 "github.com/go-test/deep/test/v2" 15 | ) 16 | 17 | func TestString(t *testing.T) { 18 | diff := deep.Equal("foo", "foo") 19 | if len(diff) > 0 { 20 | t.Error("should be equal:", diff) 21 | } 22 | 23 | diff = deep.Equal("foo", "bar") 24 | if diff == nil { 25 | t.Fatal("no diff") 26 | } 27 | if len(diff) != 1 { 28 | t.Error("too many diff:", diff) 29 | } 30 | if diff[0] != "foo != bar" { 31 | t.Error("wrong diff:", diff[0]) 32 | } 33 | } 34 | 35 | func TestFloat(t *testing.T) { 36 | diff := deep.Equal(1.1, 1.1) 37 | if len(diff) > 0 { 38 | t.Error("should be equal:", diff) 39 | } 40 | 41 | diff = deep.Equal(1.1234561, 1.1234562) 42 | if diff == nil { 43 | t.Error("no diff") 44 | } 45 | 46 | defaultFloatPrecision := deep.FloatPrecision 47 | deep.FloatPrecision = 6 48 | defer func() { deep.FloatPrecision = defaultFloatPrecision }() 49 | 50 | diff = deep.Equal(1.1234561, 1.1234562) 51 | if len(diff) > 0 { 52 | t.Error("should be equal:", diff) 53 | } 54 | 55 | diff = deep.Equal(1.123456, 1.123457) 56 | if diff == nil { 57 | t.Fatal("no diff") 58 | } 59 | if len(diff) != 1 { 60 | t.Error("too many diff:", diff) 61 | } 62 | if diff[0] != "1.123456 != 1.123457" { 63 | t.Error("wrong diff:", diff[0]) 64 | } 65 | 66 | } 67 | 68 | func TestInt(t *testing.T) { 69 | diff := deep.Equal(1, 1) 70 | if len(diff) > 0 { 71 | t.Error("should be equal:", diff) 72 | } 73 | 74 | diff = deep.Equal(1, 2) 75 | if diff == nil { 76 | t.Fatal("no diff") 77 | } 78 | if len(diff) != 1 { 79 | t.Error("too many diff:", diff) 80 | } 81 | if diff[0] != "1 != 2" { 82 | t.Error("wrong diff:", diff[0]) 83 | } 84 | } 85 | 86 | func TestUint(t *testing.T) { 87 | diff := deep.Equal(uint(2), uint(2)) 88 | if len(diff) > 0 { 89 | t.Error("should be equal:", diff) 90 | } 91 | 92 | diff = deep.Equal(uint(2), uint(3)) 93 | if diff == nil { 94 | t.Fatal("no diff") 95 | } 96 | if len(diff) != 1 { 97 | t.Error("too many diff:", diff) 98 | } 99 | if diff[0] != "2 != 3" { 100 | t.Error("wrong diff:", diff[0]) 101 | } 102 | } 103 | 104 | func TestBool(t *testing.T) { 105 | diff := deep.Equal(true, true) 106 | if len(diff) > 0 { 107 | t.Error("should be equal:", diff) 108 | } 109 | 110 | diff = deep.Equal(false, false) 111 | if len(diff) > 0 { 112 | t.Error("should be equal:", diff) 113 | } 114 | 115 | diff = deep.Equal(true, false) 116 | if diff == nil { 117 | t.Fatal("no diff") 118 | } 119 | if len(diff) != 1 { 120 | t.Error("too many diff:", diff) 121 | } 122 | if diff[0] != "true != false" { // unless you're fipar 123 | t.Error("wrong diff:", diff[0]) 124 | } 125 | } 126 | 127 | func TestTypeMismatch(t *testing.T) { 128 | type T1 int // same type kind (int) 129 | type T2 int // but different type 130 | var t1 T1 = 1 131 | var t2 T2 = 1 132 | diff := deep.Equal(t1, t2) 133 | if diff == nil { 134 | t.Fatal("no diff") 135 | } 136 | if len(diff) != 1 { 137 | t.Error("too many diff:", diff) 138 | } 139 | if diff[0] != "deep_test.T1 != deep_test.T2" { 140 | t.Error("wrong diff:", diff[0]) 141 | } 142 | 143 | // Same pkg name but differnet full paths 144 | // https://github.com/go-test/deep/issues/39 145 | err1 := v1.Error{} 146 | err2 := v2.Error{} 147 | diff = deep.Equal(err1, err2) 148 | if diff == nil { 149 | t.Fatal("no diff") 150 | } 151 | if len(diff) != 1 { 152 | t.Error("too many diff:", diff) 153 | } 154 | if diff[0] != "github.com/go-test/deep/test/v1.Error != github.com/go-test/deep/test/v2.Error" { 155 | t.Error("wrong diff:", diff[0]) 156 | } 157 | } 158 | 159 | func TestKindMismatch(t *testing.T) { 160 | deep.LogErrors = true 161 | 162 | var x int = 100 163 | var y float64 = 100 164 | diff := deep.Equal(x, y) 165 | if diff == nil { 166 | t.Fatal("no diff") 167 | } 168 | if len(diff) != 1 { 169 | t.Error("too many diff:", diff) 170 | } 171 | if diff[0] != "int != float64" { 172 | t.Error("wrong diff:", diff[0]) 173 | } 174 | 175 | deep.LogErrors = false 176 | } 177 | 178 | func TestDeepRecursion(t *testing.T) { 179 | deep.MaxDepth = 2 180 | defer func() { deep.MaxDepth = 10 }() 181 | 182 | type s3 struct { 183 | S int 184 | } 185 | type s2 struct { 186 | S s3 187 | } 188 | type s1 struct { 189 | S s2 190 | } 191 | foo := map[string]s1{ 192 | "foo": { // 1 193 | S: s2{ // 2 194 | S: s3{ // 3 195 | S: 42, // 4 196 | }, 197 | }, 198 | }, 199 | } 200 | bar := map[string]s1{ 201 | "foo": { 202 | S: s2{ 203 | S: s3{ 204 | S: 100, 205 | }, 206 | }, 207 | }, 208 | } 209 | // No diffs because MaxDepth=2 prevents seeing the diff at 3rd level down 210 | diff := deep.Equal(foo, bar) 211 | if diff != nil { 212 | t.Errorf("got %d diffs, expected none: %v", len(diff), diff) 213 | } 214 | 215 | defaultMaxDepth := deep.MaxDepth 216 | deep.MaxDepth = 4 217 | defer func() { deep.MaxDepth = defaultMaxDepth }() 218 | 219 | diff = deep.Equal(foo, bar) 220 | if diff == nil { 221 | t.Fatal("no diff") 222 | } 223 | if len(diff) != 1 { 224 | t.Error("too many diff:", diff) 225 | } 226 | if diff[0] != "map[foo].S.S.S: 42 != 100" { 227 | t.Error("wrong diff:", diff[0]) 228 | } 229 | } 230 | 231 | func TestMaxDiff(t *testing.T) { 232 | a := []int{1, 2, 3, 4, 5, 6, 7} 233 | b := []int{0, 0, 0, 0, 0, 0, 0} 234 | 235 | defaultMaxDiff := deep.MaxDiff 236 | deep.MaxDiff = 3 237 | defer func() { deep.MaxDiff = defaultMaxDiff }() 238 | 239 | diff := deep.Equal(a, b) 240 | if diff == nil { 241 | t.Fatal("no diffs") 242 | } 243 | if len(diff) != deep.MaxDiff { 244 | t.Errorf("got %d diffs, expected %d", len(diff), deep.MaxDiff) 245 | } 246 | 247 | defaultCompareUnexportedFields := deep.CompareUnexportedFields 248 | deep.CompareUnexportedFields = true 249 | defer func() { deep.CompareUnexportedFields = defaultCompareUnexportedFields }() 250 | type fiveFields struct { 251 | a int // unexported fields require ^ 252 | b int 253 | c int 254 | d int 255 | e int 256 | } 257 | t1 := fiveFields{1, 2, 3, 4, 5} 258 | t2 := fiveFields{0, 0, 0, 0, 0} 259 | diff = deep.Equal(t1, t2) 260 | if diff == nil { 261 | t.Fatal("no diffs") 262 | } 263 | if len(diff) != deep.MaxDiff { 264 | t.Errorf("got %d diffs, expected %d", len(diff), deep.MaxDiff) 265 | } 266 | 267 | // Same keys, too many diffs 268 | m1 := map[int]int{ 269 | 1: 1, 270 | 2: 2, 271 | 3: 3, 272 | 4: 4, 273 | 5: 5, 274 | } 275 | m2 := map[int]int{ 276 | 1: 0, 277 | 2: 0, 278 | 3: 0, 279 | 4: 0, 280 | 5: 0, 281 | } 282 | diff = deep.Equal(m1, m2) 283 | if diff == nil { 284 | t.Fatal("no diffs") 285 | } 286 | if len(diff) != deep.MaxDiff { 287 | t.Log(diff) 288 | t.Errorf("got %d diffs, expected %d", len(diff), deep.MaxDiff) 289 | } 290 | 291 | // Too many missing keys 292 | m1 = map[int]int{ 293 | 1: 1, 294 | 2: 2, 295 | } 296 | m2 = map[int]int{ 297 | 1: 1, 298 | 2: 2, 299 | 3: 0, 300 | 4: 0, 301 | 5: 0, 302 | 6: 0, 303 | 7: 0, 304 | } 305 | diff = deep.Equal(m1, m2) 306 | if diff == nil { 307 | t.Fatal("no diffs") 308 | } 309 | if len(diff) != deep.MaxDiff { 310 | t.Log(diff) 311 | t.Errorf("got %d diffs, expected %d", len(diff), deep.MaxDiff) 312 | } 313 | } 314 | 315 | func TestNotHandled(t *testing.T) { 316 | // UnsafePointer is pretty much the only kind not handled now 317 | v := []int{1} 318 | a := unsafe.Pointer(&v) 319 | b := unsafe.Pointer(&v) 320 | // UnsafePointer added in Go 1.88. Use these lines once this pkg 321 | // no longer supports Go 1.17. 322 | //a := reflect.ValueOf(v).UnsafePointer() 323 | //b := reflect.ValueOf(v).UnsafePointer() 324 | diff := deep.Equal(a, b) 325 | if len(diff) > 0 { 326 | t.Error("got diffs:", diff) 327 | } 328 | } 329 | 330 | func TestStruct(t *testing.T) { 331 | type s1 struct { 332 | id int 333 | Name string 334 | Number int 335 | } 336 | sa := s1{ 337 | id: 1, 338 | Name: "foo", 339 | Number: 2, 340 | } 341 | sb := sa 342 | diff := deep.Equal(sa, sb) 343 | if len(diff) > 0 { 344 | t.Error("should be equal:", diff) 345 | } 346 | 347 | sb.Name = "bar" 348 | diff = deep.Equal(sa, sb) 349 | if diff == nil { 350 | t.Fatal("no diff") 351 | } 352 | if len(diff) != 1 { 353 | t.Error("too many diff:", diff) 354 | } 355 | if diff[0] != "Name: foo != bar" { 356 | t.Error("wrong diff:", diff[0]) 357 | } 358 | 359 | sb.Number = 22 360 | diff = deep.Equal(sa, sb) 361 | if diff == nil { 362 | t.Fatal("no diff") 363 | } 364 | if len(diff) != 2 { 365 | t.Error("too many diff:", diff) 366 | } 367 | if diff[0] != "Name: foo != bar" { 368 | t.Error("wrong diff:", diff[0]) 369 | } 370 | if diff[1] != "Number: 2 != 22" { 371 | t.Error("wrong diff:", diff[1]) 372 | } 373 | 374 | sb.id = 11 375 | diff = deep.Equal(sa, sb) 376 | if diff == nil { 377 | t.Fatal("no diff") 378 | } 379 | if len(diff) != 2 { 380 | t.Error("too many diff:", diff) 381 | } 382 | if diff[0] != "Name: foo != bar" { 383 | t.Error("wrong diff:", diff[0]) 384 | } 385 | if diff[1] != "Number: 2 != 22" { 386 | t.Error("wrong diff:", diff[1]) 387 | } 388 | } 389 | 390 | func TestStructWithTags(t *testing.T) { 391 | type s1 struct { 392 | same int 393 | modified int 394 | sameIgnored int `deep:"-"` 395 | modifiedIgnored int `deep:"-"` 396 | ExportedSame int 397 | ExportedModified int 398 | ExportedSameIgnored int `deep:"-"` 399 | ExportedModifiedIgnored int `deep:"-"` 400 | } 401 | type s2 struct { 402 | s1 403 | same int 404 | modified int 405 | sameIgnored int `deep:"-"` 406 | modifiedIgnored int `deep:"-"` 407 | ExportedSame int 408 | ExportedModified int 409 | ExportedSameIgnored int `deep:"-"` 410 | ExportedModifiedIgnored int `deep:"-"` 411 | recurseInline s1 412 | recursePtr *s2 413 | } 414 | sa := s2{ 415 | s1: s1{ 416 | same: 0, 417 | modified: 1, 418 | sameIgnored: 2, 419 | modifiedIgnored: 3, 420 | ExportedSame: 4, 421 | ExportedModified: 5, 422 | ExportedSameIgnored: 6, 423 | ExportedModifiedIgnored: 7, 424 | }, 425 | same: 0, 426 | modified: 1, 427 | sameIgnored: 2, 428 | modifiedIgnored: 3, 429 | ExportedSame: 4, 430 | ExportedModified: 5, 431 | ExportedSameIgnored: 6, 432 | ExportedModifiedIgnored: 7, 433 | recurseInline: s1{ 434 | same: 0, 435 | modified: 1, 436 | sameIgnored: 2, 437 | modifiedIgnored: 3, 438 | ExportedSame: 4, 439 | ExportedModified: 5, 440 | ExportedSameIgnored: 6, 441 | ExportedModifiedIgnored: 7, 442 | }, 443 | recursePtr: &s2{ 444 | same: 0, 445 | modified: 1, 446 | sameIgnored: 2, 447 | modifiedIgnored: 3, 448 | ExportedSame: 4, 449 | ExportedModified: 5, 450 | ExportedSameIgnored: 6, 451 | ExportedModifiedIgnored: 7, 452 | }, 453 | } 454 | sb := s2{ 455 | s1: s1{ 456 | same: 0, 457 | modified: 10, 458 | sameIgnored: 2, 459 | modifiedIgnored: 30, 460 | ExportedSame: 4, 461 | ExportedModified: 50, 462 | ExportedSameIgnored: 6, 463 | ExportedModifiedIgnored: 70, 464 | }, 465 | same: 0, 466 | modified: 10, 467 | sameIgnored: 2, 468 | modifiedIgnored: 30, 469 | ExportedSame: 4, 470 | ExportedModified: 50, 471 | ExportedSameIgnored: 6, 472 | ExportedModifiedIgnored: 70, 473 | recurseInline: s1{ 474 | same: 0, 475 | modified: 10, 476 | sameIgnored: 2, 477 | modifiedIgnored: 30, 478 | ExportedSame: 4, 479 | ExportedModified: 50, 480 | ExportedSameIgnored: 6, 481 | ExportedModifiedIgnored: 70, 482 | }, 483 | recursePtr: &s2{ 484 | same: 0, 485 | modified: 10, 486 | sameIgnored: 2, 487 | modifiedIgnored: 30, 488 | ExportedSame: 4, 489 | ExportedModified: 50, 490 | ExportedSameIgnored: 6, 491 | ExportedModifiedIgnored: 70, 492 | }, 493 | } 494 | 495 | orig := deep.CompareUnexportedFields 496 | deep.CompareUnexportedFields = true 497 | diff := deep.Equal(sa, sb) 498 | deep.CompareUnexportedFields = orig 499 | 500 | want := []string{ 501 | "s1.modified: 1 != 10", 502 | "s1.ExportedModified: 5 != 50", 503 | "modified: 1 != 10", 504 | "ExportedModified: 5 != 50", 505 | "recurseInline.modified: 1 != 10", 506 | "recurseInline.ExportedModified: 5 != 50", 507 | "recursePtr.modified: 1 != 10", 508 | "recursePtr.ExportedModified: 5 != 50", 509 | } 510 | if !reflect.DeepEqual(want, diff) { 511 | t.Errorf("got %v, want %v", diff, want) 512 | } 513 | } 514 | 515 | func TestNestedStruct(t *testing.T) { 516 | type s2 struct { 517 | Nickname string 518 | } 519 | type s1 struct { 520 | Name string 521 | Alias s2 522 | } 523 | sa := s1{ 524 | Name: "Robert", 525 | Alias: s2{Nickname: "Bob"}, 526 | } 527 | sb := sa 528 | diff := deep.Equal(sa, sb) 529 | if len(diff) > 0 { 530 | t.Error("should be equal:", diff) 531 | } 532 | 533 | sb.Alias.Nickname = "Bobby" 534 | diff = deep.Equal(sa, sb) 535 | if diff == nil { 536 | t.Fatal("no diff") 537 | } 538 | if len(diff) != 1 { 539 | t.Error("too many diff:", diff) 540 | } 541 | if diff[0] != "Alias.Nickname: Bob != Bobby" { 542 | t.Error("wrong diff:", diff[0]) 543 | } 544 | } 545 | 546 | func TestMap(t *testing.T) { 547 | ma := map[string]int{ 548 | "foo": 1, 549 | "bar": 2, 550 | } 551 | mb := map[string]int{ 552 | "foo": 1, 553 | "bar": 2, 554 | } 555 | diff := deep.Equal(ma, mb) 556 | if len(diff) > 0 { 557 | t.Error("should be equal:", diff) 558 | } 559 | 560 | diff = deep.Equal(ma, ma) 561 | if len(diff) > 0 { 562 | t.Error("should be equal:", diff) 563 | } 564 | 565 | mb["foo"] = 111 566 | diff = deep.Equal(ma, mb) 567 | if diff == nil { 568 | t.Fatal("no diff") 569 | } 570 | if len(diff) != 1 { 571 | t.Error("too many diff:", diff) 572 | } 573 | if diff[0] != "map[foo]: 1 != 111" { 574 | t.Error("wrong diff:", diff[0]) 575 | } 576 | 577 | delete(mb, "foo") 578 | diff = deep.Equal(ma, mb) 579 | if diff == nil { 580 | t.Fatal("no diff") 581 | } 582 | if len(diff) != 1 { 583 | t.Error("too many diff:", diff) 584 | } 585 | if diff[0] != "map[foo]: 1 != " { 586 | t.Error("wrong diff:", diff[0]) 587 | } 588 | 589 | diff = deep.Equal(mb, ma) 590 | if diff == nil { 591 | t.Fatal("no diff") 592 | } 593 | if len(diff) != 1 { 594 | t.Error("too many diff:", diff) 595 | } 596 | if diff[0] != "map[foo]: != 1" { 597 | t.Error("wrong diff:", diff[0]) 598 | } 599 | 600 | var mc map[string]int 601 | diff = deep.Equal(ma, mc) 602 | if diff == nil { 603 | t.Fatal("no diff") 604 | } 605 | if len(diff) != 1 { 606 | t.Error("too many diff:", diff) 607 | } 608 | // handle hash order randomness 609 | if diff[0] != "map[foo:1 bar:2] != " && diff[0] != "map[bar:2 foo:1] != " { 610 | t.Error("wrong diff:", diff[0]) 611 | } 612 | 613 | diff = deep.Equal(mc, ma) 614 | if diff == nil { 615 | t.Fatal("no diff") 616 | } 617 | if len(diff) != 1 { 618 | t.Error("too many diff:", diff) 619 | } 620 | if diff[0] != " != map[foo:1 bar:2]" && diff[0] != " != map[bar:2 foo:1]" { 621 | t.Error("wrong diff:", diff[0]) 622 | } 623 | } 624 | 625 | func TestArray(t *testing.T) { 626 | a := [3]int{1, 2, 3} 627 | b := [3]int{1, 2, 3} 628 | 629 | diff := deep.Equal(a, b) 630 | if len(diff) > 0 { 631 | t.Error("should be equal:", diff) 632 | } 633 | 634 | diff = deep.Equal(a, a) 635 | if len(diff) > 0 { 636 | t.Error("should be equal:", diff) 637 | } 638 | 639 | b[2] = 333 640 | diff = deep.Equal(a, b) 641 | if diff == nil { 642 | t.Fatal("no diff") 643 | } 644 | if len(diff) != 1 { 645 | t.Error("too many diff:", diff) 646 | } 647 | if diff[0] != "array[2]: 3 != 333" { 648 | t.Error("wrong diff:", diff[0]) 649 | } 650 | 651 | c := [3]int{1, 2, 2} 652 | diff = deep.Equal(a, c) 653 | if diff == nil { 654 | t.Fatal("no diff") 655 | } 656 | if len(diff) != 1 { 657 | t.Error("too many diff:", diff) 658 | } 659 | if diff[0] != "array[2]: 3 != 2" { 660 | t.Error("wrong diff:", diff[0]) 661 | } 662 | 663 | var d [2]int 664 | diff = deep.Equal(a, d) 665 | if diff == nil { 666 | t.Fatal("no diff") 667 | } 668 | if len(diff) != 1 { 669 | t.Error("too many diff:", diff) 670 | } 671 | if diff[0] != "[3]int != [2]int" { 672 | t.Error("wrong diff:", diff[0]) 673 | } 674 | 675 | e := [12]int{} 676 | f := [12]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 677 | diff = deep.Equal(e, f) 678 | if diff == nil { 679 | t.Fatal("no diff") 680 | } 681 | if len(diff) != deep.MaxDiff { 682 | t.Error("not enough diffs:", diff) 683 | } 684 | for i := 0; i < deep.MaxDiff; i++ { 685 | if diff[i] != fmt.Sprintf("array[%d]: 0 != %d", i+1, i+1) { 686 | t.Error("wrong diff:", diff[i]) 687 | } 688 | } 689 | } 690 | 691 | func TestSlice(t *testing.T) { 692 | a := []int{1, 2, 3} 693 | b := []int{1, 2, 3} 694 | 695 | diff := deep.Equal(a, b) 696 | if len(diff) > 0 { 697 | t.Error("should be equal:", diff) 698 | } 699 | 700 | diff = deep.Equal(a, a) 701 | if len(diff) > 0 { 702 | t.Error("should be equal:", diff) 703 | } 704 | 705 | b[2] = 333 706 | diff = deep.Equal(a, b) 707 | if diff == nil { 708 | t.Fatal("no diff") 709 | } 710 | if len(diff) != 1 { 711 | t.Error("too many diff:", diff) 712 | } 713 | if diff[0] != "slice[2]: 3 != 333" { 714 | t.Error("wrong diff:", diff[0]) 715 | } 716 | 717 | b = b[0:2] 718 | diff = deep.Equal(a, b) 719 | if diff == nil { 720 | t.Fatal("no diff") 721 | } 722 | if len(diff) != 1 { 723 | t.Error("too many diff:", diff) 724 | } 725 | if diff[0] != "slice[2]: 3 != " { 726 | t.Error("wrong diff:", diff[0]) 727 | } 728 | 729 | diff = deep.Equal(b, a) 730 | if diff == nil { 731 | t.Fatal("no diff") 732 | } 733 | if len(diff) != 1 { 734 | t.Error("too many diff:", diff) 735 | } 736 | if diff[0] != "slice[2]: != 3" { 737 | t.Error("wrong diff:", diff[0]) 738 | } 739 | 740 | var c []int 741 | diff = deep.Equal(a, c) 742 | if diff == nil { 743 | t.Fatal("no diff") 744 | } 745 | if len(diff) != 1 { 746 | t.Error("too many diff:", diff) 747 | } 748 | if diff[0] != "[1 2 3] != " { 749 | t.Error("wrong diff:", diff[0]) 750 | } 751 | 752 | diff = deep.Equal(c, a) 753 | if diff == nil { 754 | t.Fatal("no diff") 755 | } 756 | if len(diff) != 1 { 757 | t.Error("too many diff:", diff) 758 | } 759 | if diff[0] != " != [1 2 3]" { 760 | t.Error("wrong diff:", diff[0]) 761 | } 762 | } 763 | 764 | func TestSiblingSlices(t *testing.T) { 765 | father := []int{1, 2, 3, 4} 766 | a := father[0:3] 767 | b := father[0:3] 768 | 769 | diff := deep.Equal(a, b) 770 | if len(diff) > 0 { 771 | t.Error("should be equal:", diff) 772 | } 773 | diff = deep.Equal(b, a) 774 | if len(diff) > 0 { 775 | t.Error("should be equal:", diff) 776 | } 777 | 778 | a = father[0:3] 779 | b = father[0:2] 780 | diff = deep.Equal(a, b) 781 | if diff == nil { 782 | t.Fatal("no diff") 783 | } 784 | if len(diff) != 1 { 785 | t.Error("too many diff:", diff) 786 | } 787 | if diff[0] != "slice[2]: 3 != " { 788 | t.Error("wrong diff:", diff[0]) 789 | } 790 | 791 | a = father[0:2] 792 | b = father[0:3] 793 | 794 | diff = deep.Equal(a, b) 795 | if diff == nil { 796 | t.Fatal("no diff") 797 | } 798 | if len(diff) != 1 { 799 | t.Error("too many diff:", diff) 800 | } 801 | if diff[0] != "slice[2]: != 3" { 802 | t.Error("wrong diff:", diff[0]) 803 | } 804 | 805 | a = father[0:2] 806 | b = father[2:4] 807 | 808 | diff = deep.Equal(a, b) 809 | if diff == nil { 810 | t.Fatal("no diff") 811 | } 812 | if len(diff) != 2 { 813 | t.Error("too many diff:", diff) 814 | } 815 | if diff[0] != "slice[0]: 1 != 3" { 816 | t.Error("wrong diff:", diff[0]) 817 | } 818 | if diff[1] != "slice[1]: 2 != 4" { 819 | t.Error("wrong diff:", diff[1]) 820 | } 821 | 822 | a = father[0:0] 823 | b = father[1:1] 824 | 825 | diff = deep.Equal(a, b) 826 | if len(diff) > 0 { 827 | t.Error("should be equal:", diff) 828 | } 829 | diff = deep.Equal(b, a) 830 | if len(diff) > 0 { 831 | t.Error("should be equal:", diff) 832 | } 833 | } 834 | 835 | func TestEmptySlice(t *testing.T) { 836 | a := []int{1} 837 | b := []int{} 838 | var c []int 839 | 840 | // Non-empty is not equal to empty. 841 | diff := deep.Equal(a, b) 842 | if diff == nil { 843 | t.Fatal("no diff") 844 | } 845 | if len(diff) != 1 { 846 | t.Error("too many diff:", diff) 847 | } 848 | if diff[0] != "slice[0]: 1 != " { 849 | t.Error("wrong diff:", diff[0]) 850 | } 851 | 852 | // Empty is not equal to non-empty. 853 | diff = deep.Equal(b, a) 854 | if diff == nil { 855 | t.Fatal("no diff") 856 | } 857 | if len(diff) != 1 { 858 | t.Error("too many diff:", diff) 859 | } 860 | if diff[0] != "slice[0]: != 1" { 861 | t.Error("wrong diff:", diff[0]) 862 | } 863 | 864 | // Empty is not equal to nil. 865 | diff = deep.Equal(b, c) 866 | if diff == nil { 867 | t.Fatal("no diff") 868 | } 869 | if len(diff) != 1 { 870 | t.Error("too many diff:", diff) 871 | } 872 | if diff[0] != "[] != " { 873 | t.Error("wrong diff:", diff[0]) 874 | } 875 | 876 | // Nil is not equal to empty. 877 | diff = deep.Equal(c, b) 878 | if diff == nil { 879 | t.Fatal("no diff") 880 | } 881 | if len(diff) != 1 { 882 | t.Error("too many diff:", diff) 883 | } 884 | if diff[0] != " != []" { 885 | t.Error("wrong diff:", diff[0]) 886 | } 887 | } 888 | 889 | func TestNilSlicesAreEmpty(t *testing.T) { 890 | defaultNilSlicesAreEmpty := deep.NilSlicesAreEmpty 891 | deep.NilSlicesAreEmpty = true 892 | defer func() { deep.NilSlicesAreEmpty = defaultNilSlicesAreEmpty }() 893 | 894 | a := []int{1} 895 | b := []int{} 896 | var c []int 897 | 898 | // Empty is equal to nil. 899 | diff := deep.Equal(b, c) 900 | if len(diff) > 0 { 901 | t.Error("should be equal:", diff) 902 | } 903 | 904 | // Nil is equal to empty. 905 | diff = deep.Equal(c, b) 906 | if len(diff) > 0 { 907 | t.Error("should be equal:", diff) 908 | } 909 | 910 | // Non-empty is not equal to nil. 911 | diff = deep.Equal(a, c) 912 | if diff == nil { 913 | t.Fatal("no diff") 914 | } 915 | if len(diff) != 1 { 916 | t.Error("too many diff:", diff) 917 | } 918 | if diff[0] != "[1] != " { 919 | t.Error("wrong diff:", diff[0]) 920 | } 921 | 922 | // Nil is not equal to non-empty. 923 | diff = deep.Equal(c, a) 924 | if diff == nil { 925 | t.Fatal("no diff") 926 | } 927 | if len(diff) != 1 { 928 | t.Error("too many diff:", diff) 929 | } 930 | if diff[0] != " != [1]" { 931 | t.Error("wrong diff:", diff[0]) 932 | } 933 | 934 | // Non-empty is not equal to empty. 935 | diff = deep.Equal(a, b) 936 | if diff == nil { 937 | t.Fatal("no diff") 938 | } 939 | if len(diff) != 1 { 940 | t.Error("too many diff:", diff) 941 | } 942 | if diff[0] != "slice[0]: 1 != " { 943 | t.Error("wrong diff:", diff[0]) 944 | } 945 | 946 | // Empty is not equal to non-empty. 947 | diff = deep.Equal(b, a) 948 | if diff == nil { 949 | t.Fatal("no diff") 950 | } 951 | if len(diff) != 1 { 952 | t.Error("too many diff:", diff) 953 | } 954 | if diff[0] != "slice[0]: != 1" { 955 | t.Error("wrong diff:", diff[0]) 956 | } 957 | } 958 | 959 | func TestNilMapsAreEmpty(t *testing.T) { 960 | defaultNilMapsAreEmpty := deep.NilMapsAreEmpty 961 | deep.NilMapsAreEmpty = true 962 | defer func() { deep.NilMapsAreEmpty = defaultNilMapsAreEmpty }() 963 | 964 | a := map[int]int{1: 1} 965 | b := map[int]int{} 966 | var c map[int]int 967 | 968 | // Empty is equal to nil. 969 | diff := deep.Equal(b, c) 970 | if len(diff) > 0 { 971 | t.Error("should be equal:", diff) 972 | } 973 | 974 | // Nil is equal to empty. 975 | diff = deep.Equal(c, b) 976 | if len(diff) > 0 { 977 | t.Error("should be equal:", diff) 978 | } 979 | 980 | // Non-empty is not equal to nil. 981 | diff = deep.Equal(a, c) 982 | if diff == nil { 983 | t.Fatal("no diff") 984 | } 985 | if len(diff) != 1 { 986 | t.Error("too many diff:", diff) 987 | } 988 | if diff[0] != "map[1:1] != " { 989 | t.Error("wrong diff:", diff[0]) 990 | } 991 | 992 | // Nil is not equal to non-empty. 993 | diff = deep.Equal(c, a) 994 | if diff == nil { 995 | t.Fatal("no diff") 996 | } 997 | if len(diff) != 1 { 998 | t.Error("too many diff:", diff) 999 | } 1000 | if diff[0] != " != map[1:1]" { 1001 | t.Error("wrong diff:", diff[0]) 1002 | } 1003 | 1004 | // Non-empty is not equal to empty. 1005 | diff = deep.Equal(a, b) 1006 | if diff == nil { 1007 | t.Fatal("no diff") 1008 | } 1009 | if len(diff) != 1 { 1010 | t.Error("too many diff:", diff) 1011 | } 1012 | if diff[0] != "map[1]: 1 != " { 1013 | t.Error("wrong diff:", diff[0]) 1014 | } 1015 | 1016 | // Empty is not equal to non-empty. 1017 | diff = deep.Equal(b, a) 1018 | if diff == nil { 1019 | t.Fatal("no diff") 1020 | } 1021 | if len(diff) != 1 { 1022 | t.Error("too many diff:", diff) 1023 | } 1024 | if diff[0] != "map[1]: != 1" { 1025 | t.Error("wrong diff:", diff[0]) 1026 | } 1027 | } 1028 | 1029 | func TestNilInterface(t *testing.T) { 1030 | type T struct{ i int } 1031 | 1032 | a := &T{i: 1} 1033 | diff := deep.Equal(nil, a) 1034 | if diff == nil { 1035 | t.Fatal("no diff") 1036 | } 1037 | if len(diff) != 1 { 1038 | t.Error("too many diff:", diff) 1039 | } 1040 | if diff[0] != " != &{1}" { 1041 | t.Error("wrong diff:", diff[0]) 1042 | } 1043 | 1044 | diff = deep.Equal(a, nil) 1045 | if diff == nil { 1046 | t.Fatal("no diff") 1047 | } 1048 | if len(diff) != 1 { 1049 | t.Error("too many diff:", diff) 1050 | } 1051 | if diff[0] != "&{1} != " { 1052 | t.Error("wrong diff:", diff[0]) 1053 | } 1054 | 1055 | diff = deep.Equal(nil, nil) 1056 | if len(diff) > 0 { 1057 | t.Error("should be equal:", diff) 1058 | } 1059 | } 1060 | 1061 | func TestPointer(t *testing.T) { 1062 | type T struct{ i int } 1063 | 1064 | a, b := &T{i: 1}, &T{i: 1} 1065 | diff := deep.Equal(a, b) 1066 | if len(diff) > 0 { 1067 | t.Error("should be equal:", diff) 1068 | } 1069 | 1070 | a, b = nil, &T{} 1071 | diff = deep.Equal(a, b) 1072 | if diff == nil { 1073 | t.Fatal("no diff") 1074 | } 1075 | if len(diff) != 1 { 1076 | t.Error("too many diff:", diff) 1077 | } 1078 | if diff[0] != " != deep_test.T" { 1079 | t.Error("wrong diff:", diff[0]) 1080 | } 1081 | 1082 | a, b = &T{}, nil 1083 | diff = deep.Equal(a, b) 1084 | if diff == nil { 1085 | t.Fatal("no diff") 1086 | } 1087 | if len(diff) != 1 { 1088 | t.Error("too many diff:", diff) 1089 | } 1090 | if diff[0] != "deep_test.T != " { 1091 | t.Error("wrong diff:", diff[0]) 1092 | } 1093 | 1094 | a, b = nil, nil 1095 | diff = deep.Equal(a, b) 1096 | if len(diff) > 0 { 1097 | t.Error("should be equal:", diff) 1098 | } 1099 | } 1100 | 1101 | func TestTime(t *testing.T) { 1102 | // In an interable kind (i.e. a struct) 1103 | type sTime struct { 1104 | T time.Time 1105 | } 1106 | now := time.Now() 1107 | got := sTime{T: now} 1108 | expect := sTime{T: now.Add(1 * time.Second)} 1109 | diff := deep.Equal(got, expect) 1110 | if len(diff) != 1 { 1111 | t.Error("expected 1 diff:", diff) 1112 | } 1113 | 1114 | // Directly 1115 | a := now 1116 | b := now 1117 | diff = deep.Equal(a, b) 1118 | if len(diff) > 0 { 1119 | t.Error("should be equal:", diff) 1120 | } 1121 | 1122 | // https://github.com/go-test/deep/issues/15 1123 | type Time15 struct { 1124 | time.Time 1125 | } 1126 | a15 := Time15{now} 1127 | b15 := Time15{now} 1128 | diff = deep.Equal(a15, b15) 1129 | if len(diff) > 0 { 1130 | t.Error("should be equal:", diff) 1131 | } 1132 | 1133 | later := now.Add(1 * time.Second) 1134 | b15 = Time15{later} 1135 | diff = deep.Equal(a15, b15) 1136 | if len(diff) != 1 { 1137 | t.Errorf("got %d diffs, expected 1: %s", len(diff), diff) 1138 | } 1139 | 1140 | // No diff in Equal should not affect diff of other fields (Foo) 1141 | type Time17 struct { 1142 | time.Time 1143 | Foo int 1144 | } 1145 | a17 := Time17{Time: now, Foo: 1} 1146 | b17 := Time17{Time: now, Foo: 2} 1147 | diff = deep.Equal(a17, b17) 1148 | if len(diff) != 1 { 1149 | t.Errorf("got %d diffs, expected 1: %s", len(diff), diff) 1150 | } 1151 | } 1152 | 1153 | func TestTimeUnexported(t *testing.T) { 1154 | // https://github.com/go-test/deep/issues/18 1155 | // Can't call Call() on exported Value func 1156 | defaultCompareUnexportedFields := deep.CompareUnexportedFields 1157 | deep.CompareUnexportedFields = true 1158 | defer func() { deep.CompareUnexportedFields = defaultCompareUnexportedFields }() 1159 | 1160 | now := time.Now() 1161 | type hiddenTime struct { 1162 | t time.Time 1163 | } 1164 | htA := &hiddenTime{t: now} 1165 | htB := &hiddenTime{t: now} 1166 | diff := deep.Equal(htA, htB) 1167 | if len(diff) > 0 { 1168 | t.Error("should be equal:", diff) 1169 | } 1170 | 1171 | // This doesn't call time.Time.Equal(), it compares the unexported fields 1172 | // in time.Time, causing a diff like: 1173 | // [t.wall: 13740788835924462040 != 13740788836998203864 t.ext: 1447549 != 1001447549] 1174 | later := now.Add(1 * time.Second) 1175 | htC := &hiddenTime{t: later} 1176 | diff = deep.Equal(htA, htC) 1177 | 1178 | expected := 1 1179 | if _, ok := reflect.TypeOf(htA.t).FieldByName("ext"); ok { 1180 | expected = 2 1181 | } 1182 | if len(diff) != expected { 1183 | t.Errorf("got %d diffs, expected %d: %s", len(diff), expected, diff) 1184 | } 1185 | } 1186 | 1187 | func TestInterface(t *testing.T) { 1188 | a := map[string]interface{}{ 1189 | "foo": map[string]string{ 1190 | "bar": "a", 1191 | }, 1192 | } 1193 | b := map[string]interface{}{ 1194 | "foo": map[string]string{ 1195 | "bar": "b", 1196 | }, 1197 | } 1198 | diff := deep.Equal(a, b) 1199 | if len(diff) == 0 { 1200 | t.Fatalf("expected 1 diff, got zero") 1201 | } 1202 | if len(diff) != 1 { 1203 | t.Errorf("expected 1 diff, got %d: %s", len(diff), diff) 1204 | } 1205 | } 1206 | 1207 | func TestInterface2(t *testing.T) { 1208 | defer func() { 1209 | if val := recover(); val != nil { 1210 | t.Fatalf("panic: %v", val) 1211 | } 1212 | }() 1213 | 1214 | a := map[string]interface{}{ 1215 | "bar": 1, 1216 | } 1217 | b := map[string]interface{}{ 1218 | "bar": 1.23, 1219 | } 1220 | diff := deep.Equal(a, b) 1221 | if len(diff) == 0 { 1222 | t.Fatalf("expected 1 diff, got zero") 1223 | } 1224 | if len(diff) != 1 { 1225 | t.Errorf("expected 1 diff, got %d: %s", len(diff), diff) 1226 | } 1227 | } 1228 | 1229 | func TestInterface3(t *testing.T) { 1230 | type Value struct{ int } 1231 | a := map[string]interface{}{ 1232 | "foo": &Value{}, 1233 | } 1234 | b := map[string]interface{}{ 1235 | "foo": 1.23, 1236 | } 1237 | diff := deep.Equal(a, b) 1238 | if len(diff) == 0 { 1239 | t.Fatalf("expected 1 diff, got zero") 1240 | } 1241 | 1242 | if len(diff) != 1 { 1243 | t.Errorf("expected 1 diff, got: %s", diff) 1244 | } 1245 | } 1246 | 1247 | func TestError(t *testing.T) { 1248 | a := errors.New("it broke") 1249 | b := errors.New("it broke") 1250 | 1251 | diff := deep.Equal(a, b) 1252 | if len(diff) != 0 { 1253 | t.Fatalf("expected zero diffs, got %d: %s", len(diff), diff) 1254 | } 1255 | 1256 | b = errors.New("it fell apart") 1257 | diff = deep.Equal(a, b) 1258 | if len(diff) != 1 { 1259 | t.Fatalf("expected 1 diff, got %d: %s", len(diff), diff) 1260 | } 1261 | if diff[0] != "it broke != it fell apart" { 1262 | t.Errorf("got '%s', expected 'it broke != it fell apart'", diff[0]) 1263 | } 1264 | 1265 | // Both errors set 1266 | type tWithError struct { 1267 | Error error 1268 | } 1269 | t1 := tWithError{ 1270 | Error: a, 1271 | } 1272 | t2 := tWithError{ 1273 | Error: b, 1274 | } 1275 | diff = deep.Equal(t1, t2) 1276 | if len(diff) != 1 { 1277 | t.Fatalf("expected 1 diff, got %d: %s", len(diff), diff) 1278 | } 1279 | if diff[0] != "Error: it broke != it fell apart" { 1280 | t.Errorf("got '%s', expected 'Error: it broke != it fell apart'", diff[0]) 1281 | } 1282 | 1283 | // Both errors nil 1284 | t1 = tWithError{ 1285 | Error: nil, 1286 | } 1287 | t2 = tWithError{ 1288 | Error: nil, 1289 | } 1290 | diff = deep.Equal(t1, t2) 1291 | if len(diff) != 0 { 1292 | t.Log(diff) 1293 | t.Fatalf("expected 0 diff, got %d: %s", len(diff), diff) 1294 | } 1295 | 1296 | // One error is nil 1297 | t1 = tWithError{ 1298 | Error: errors.New("foo"), 1299 | } 1300 | t2 = tWithError{ 1301 | Error: nil, 1302 | } 1303 | diff = deep.Equal(t1, t2) 1304 | if len(diff) != 1 { 1305 | t.Log(diff) 1306 | t.Fatalf("expected 1 diff, got %d: %s", len(diff), diff) 1307 | } 1308 | if diff[0] != "Error: *errors.errorString != " { 1309 | t.Errorf("got '%s', expected 'Error: *errors.errorString != '", diff[0]) 1310 | } 1311 | } 1312 | 1313 | func TestErrorWithOtherFields(t *testing.T) { 1314 | a := errors.New("it broke") 1315 | b := errors.New("it fell apart") 1316 | 1317 | // Both errors set 1318 | type tWithError struct { 1319 | Error error 1320 | Other string 1321 | } 1322 | t1 := tWithError{ 1323 | Error: a, 1324 | Other: "ok", 1325 | } 1326 | t2 := tWithError{ 1327 | Error: b, 1328 | Other: "ok", 1329 | } 1330 | diff := deep.Equal(t1, t2) 1331 | if len(diff) != 1 { 1332 | t.Fatalf("expected 1 diff, got %d: %s", len(diff), diff) 1333 | } 1334 | if diff[0] != "Error: it broke != it fell apart" { 1335 | t.Errorf("got '%s', expected 'Error: it broke != it fell apart'", diff[0]) 1336 | } 1337 | 1338 | // Both errors nil 1339 | t1 = tWithError{ 1340 | Error: nil, 1341 | Other: "ok", 1342 | } 1343 | t2 = tWithError{ 1344 | Error: nil, 1345 | Other: "ok", 1346 | } 1347 | diff = deep.Equal(t1, t2) 1348 | if len(diff) != 0 { 1349 | t.Log(diff) 1350 | t.Fatalf("expected 0 diff, got %d: %s", len(diff), diff) 1351 | } 1352 | 1353 | // Different Other value 1354 | t1 = tWithError{ 1355 | Error: nil, 1356 | Other: "ok", 1357 | } 1358 | t2 = tWithError{ 1359 | Error: nil, 1360 | Other: "nope", 1361 | } 1362 | diff = deep.Equal(t1, t2) 1363 | if len(diff) != 1 { 1364 | t.Fatalf("expected 1 diff, got %d: %s", len(diff), diff) 1365 | } 1366 | if diff[0] != "Other: ok != nope" { 1367 | t.Errorf("got '%s', expected 'Other: ok != nope'", diff[0]) 1368 | } 1369 | 1370 | // Different Other value, same error 1371 | t1 = tWithError{ 1372 | Error: a, 1373 | Other: "ok", 1374 | } 1375 | t2 = tWithError{ 1376 | Error: a, 1377 | Other: "nope", 1378 | } 1379 | diff = deep.Equal(t1, t2) 1380 | if len(diff) != 1 { 1381 | t.Fatalf("expected 1 diff, got %d: %s", len(diff), diff) 1382 | } 1383 | if diff[0] != "Other: ok != nope" { 1384 | t.Errorf("got '%s', expected 'Other: ok != nope'", diff[0]) 1385 | } 1386 | } 1387 | 1388 | type primKindError string 1389 | 1390 | func (e primKindError) Error() string { 1391 | return string(e) 1392 | } 1393 | 1394 | func TestErrorPrimitiveKind(t *testing.T) { 1395 | // The primKindError type above is valid and used by Go, e.g. 1396 | // url.EscapeError and url.InvalidHostError. Before fixing this bug 1397 | // (https://github.com/go-test/deep/issues/31), we presumed a and b 1398 | // were ptr or interface (and not nil), so a.Elem() worked. But when 1399 | // a/b are primitive kinds, Elem() causes a panic. 1400 | var err1 primKindError = "abc" 1401 | var err2 primKindError = "abc" 1402 | diff := deep.Equal(err1, err2) 1403 | if len(diff) != 0 { 1404 | t.Fatalf("expected zero diffs, got %d: %s", len(diff), diff) 1405 | } 1406 | 1407 | err2 = "def" 1408 | diff = deep.Equal(err1, err2) 1409 | if len(diff) != 1 { 1410 | t.Fatalf("expected 1 diff, got %d: %s", len(diff), diff) 1411 | } 1412 | } 1413 | 1414 | func TestErrorUnexported(t *testing.T) { 1415 | // https://github.com/go-test/deep/issues/45 1416 | type foo struct { 1417 | bar error 1418 | } 1419 | defaultCompareUnexportedFields := deep.CompareUnexportedFields 1420 | deep.CompareUnexportedFields = true 1421 | defer func() { deep.CompareUnexportedFields = defaultCompareUnexportedFields }() 1422 | e1 := foo{bar: fmt.Errorf("error")} 1423 | e2 := foo{bar: fmt.Errorf("error")} 1424 | deep.Equal(e1, e2) 1425 | } 1426 | 1427 | func TestNil(t *testing.T) { 1428 | type student struct { 1429 | name string 1430 | age int 1431 | } 1432 | 1433 | mark := student{"mark", 10} 1434 | var someNilThing interface{} = nil 1435 | diff := deep.Equal(someNilThing, mark) 1436 | if diff == nil { 1437 | t.Error("Nil value to comparison should not be equal") 1438 | } 1439 | diff = deep.Equal(mark, someNilThing) 1440 | if diff == nil { 1441 | t.Error("Nil value to comparison should not be equal") 1442 | } 1443 | diff = deep.Equal(someNilThing, someNilThing) 1444 | if diff != nil { 1445 | t.Error("Nil value to comparison should not be equal") 1446 | } 1447 | } 1448 | 1449 | var testFunc = func() {} 1450 | 1451 | func TestFunc(t *testing.T) { 1452 | // https://github.com/go-test/deep/issues/46 1453 | type TestStruct struct { 1454 | Function func() 1455 | } 1456 | t1 := TestStruct{ 1457 | Function: testFunc, 1458 | } 1459 | t2 := TestStruct{ 1460 | Function: testFunc, 1461 | } 1462 | 1463 | // CompareFunctions is off by default, so this should report no diff: 1464 | diff := deep.Equal(t1, t2) 1465 | if len(diff) != 0 { 1466 | t.Fatalf("expected 0 diff when CompareFunctions=false, got %d: %s", len(diff), diff) 1467 | } 1468 | 1469 | deep.CompareFunctions = true 1470 | defer func() { deep.CompareFunctions = false }() 1471 | 1472 | // Two funcs are not equal (even if they're the same func) 1473 | diff = deep.Equal(t1, t2) 1474 | if len(diff) != 1 { 1475 | t.Fatalf("expected 1 diff, got %d: %s", len(diff), diff) 1476 | } 1477 | if diff[0] != "Function: func != func" { 1478 | t.Errorf("got '%s', expected 'Function: func != func'", diff[0]) 1479 | } 1480 | 1481 | // One func nil, the other set: not equal 1482 | t1.Function = nil 1483 | diff = deep.Equal(t1, t2) 1484 | if len(diff) != 1 { 1485 | t.Fatalf("expected 1 diff, got %d: %s", len(diff), diff) 1486 | } 1487 | if diff[0] != "Function: nil func != func" { 1488 | t.Errorf("got '%s', expected 'Function: nil func != func'", diff[0]) 1489 | } 1490 | 1491 | // Two nil funcs are equal 1492 | t1.Function = nil 1493 | t2.Function = nil 1494 | diff = deep.Equal(t1, t2) 1495 | if len(diff) != 0 { 1496 | t.Errorf("expected 0 diff, got %d: %s", len(diff), diff) 1497 | } 1498 | } 1499 | 1500 | func TestSliceOrderString(t *testing.T) { 1501 | // https://github.com/go-test/deep/issues/28 1502 | 1503 | // These are equal if we ignore order 1504 | a := []string{"foo", "bar"} 1505 | b := []string{"bar", "foo"} 1506 | diff := deep.Equal(a, b, deep.FLAG_IGNORE_SLICE_ORDER) 1507 | if len(diff) != 0 { 1508 | t.Fatalf("expected 0 diff, got %d: %s", len(diff), diff) 1509 | } 1510 | 1511 | // Equal with dupes 1512 | a = []string{"foo", "foo", "bar"} 1513 | b = []string{"bar", "foo", "foo"} 1514 | diff = deep.Equal(a, b, deep.FLAG_IGNORE_SLICE_ORDER) 1515 | if len(diff) != 0 { 1516 | t.Fatalf("expected 0 diff, got %d: %s", len(diff), diff) 1517 | } 1518 | 1519 | // NOT equal with dupes 1520 | a = []string{"foo", "foo", "bar"} 1521 | b = []string{"bar", "bar", "foo"} 1522 | diff = deep.Equal(a, b, deep.FLAG_IGNORE_SLICE_ORDER) 1523 | if len(diff) != 2 { 1524 | t.Fatalf("expected 2 diff, got %d: %s", len(diff), diff) 1525 | } 1526 | m1 := "(unordered) slice[]=foo: value count: 2 != 1" 1527 | m2 := "(unordered) slice[]=bar: value count: 1 != 2" 1528 | if diff[0] != m1 && diff[0] != m2 { 1529 | t.Errorf("got %s, expected '%s' or '%s'", diff[0], m1, m2) 1530 | } 1531 | if diff[1] != m1 && diff[1] != m2 { 1532 | t.Errorf("got %s, expected '%s' or '%s'", diff[1], m1, m2) 1533 | } 1534 | 1535 | // NOT equal with one missing 1536 | a = []string{"foo", "bar"} 1537 | b = []string{"bar", "foo", "gone"} 1538 | diff = deep.Equal(a, b, deep.FLAG_IGNORE_SLICE_ORDER) 1539 | if len(diff) != 1 { 1540 | t.Fatalf("expected 2 diff, got %d: %s", len(diff), diff) 1541 | } 1542 | if diff[0] != "(unordered) slice[]=gone: value count: 0 != 1" { 1543 | t.Errorf("got %s, expected ''", diff[0]) 1544 | } 1545 | 1546 | // NOT equal at all 1547 | a = []string{"foo", "bar"} 1548 | b = []string{"x"} 1549 | diff = deep.Equal(a, b, deep.FLAG_IGNORE_SLICE_ORDER) 1550 | if len(diff) != 3 { 1551 | t.Fatalf("expected 2 diff, got %d: %s", len(diff), diff) 1552 | } 1553 | sort.Strings(diff) 1554 | if diff[0] != "(unordered) slice[]=bar: value count: 1 != 0" { 1555 | t.Errorf("got %s, expected '(unordered) slice[]=bar: value count: 1 != 0'", diff[0]) 1556 | } 1557 | if diff[1] != "(unordered) slice[]=foo: value count: 1 != 0" { 1558 | t.Errorf("got %s, expected '(unordered) slice[]=foo: value count: 1 != 0", diff[1]) 1559 | } 1560 | if diff[2] != "(unordered) slice[]=x: value count: 0 != 1" { 1561 | t.Errorf("got %s, expected '(unordered) slice[]=x: value count: 0 != 1'", diff[2]) 1562 | } 1563 | } 1564 | 1565 | func TestSliceOrderStruct(t *testing.T) { 1566 | // https://github.com/go-test/deep/issues/28 1567 | // This is NOT supported but Go is so wonderful that it just happens to work. 1568 | // But again: not supported. So if this test starts to fail or be a problem, 1569 | // it can and should be removed becuase the docs say it's not supported. 1570 | type T struct{ i int } 1571 | a := []T{ 1572 | {i: 1}, 1573 | {i: 2}, 1574 | } 1575 | b := []T{ 1576 | {i: 2}, 1577 | {i: 1}, 1578 | } 1579 | diff := deep.Equal(a, b, deep.FLAG_IGNORE_SLICE_ORDER) 1580 | if len(diff) != 0 { 1581 | t.Fatalf("expected 0 diff, got %d: %s", len(diff), diff) 1582 | } 1583 | } 1584 | 1585 | func TestNilPointersAreZero(t *testing.T) { 1586 | defaultNilPointersAreZero := deep.NilPointersAreZero 1587 | deep.NilPointersAreZero = true 1588 | defer func() { deep.NilPointersAreZero = defaultNilPointersAreZero }() 1589 | 1590 | type T struct { 1591 | S *string 1592 | } 1593 | 1594 | a := T{S: nil} 1595 | b := T{S: new(string)} 1596 | 1597 | diff := deep.Equal(a, b) 1598 | if len(diff) != 0 { 1599 | t.Fatalf("expected 0 diff, got %d: %s", len(diff), diff) 1600 | } 1601 | 1602 | *b.S = "hello" 1603 | diff = deep.Equal(a, b) 1604 | if len(diff) != 1 { 1605 | t.Fatalf("expected 1 diff, got %d: %s", len(diff), diff) 1606 | } 1607 | 1608 | a.S = new(string) 1609 | *a.S = "hi again" 1610 | b.S = nil 1611 | diff = deep.Equal(a, b) 1612 | if len(diff) != 1 { 1613 | t.Fatalf("expected 1 diff, got %d: %s", len(diff), diff) 1614 | } 1615 | } 1616 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-test/deep 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-test/deep/e1e45320724ff277aa119111361fb7f6691c0a15/go.sum -------------------------------------------------------------------------------- /test/coverage: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | go test -coverprofile=coverage.out "$@" 3 | go tool cover -html=coverage.out 4 | -------------------------------------------------------------------------------- /test/v1/errors.go: -------------------------------------------------------------------------------- 1 | package deeptest 2 | 3 | type Error struct{} 4 | 5 | func (e Error) Error() string { 6 | return "" 7 | } 8 | -------------------------------------------------------------------------------- /test/v2/errors.go: -------------------------------------------------------------------------------- 1 | package deeptest 2 | 3 | type Error struct{} 4 | 5 | func (e Error) Error() string { 6 | return "" 7 | } 8 | --------------------------------------------------------------------------------