├── example ├── example.go └── atom_test.go ├── .coveralls.yml ├── testdata └── testdata.go ├── CHANGELOG.md ├── .gitignore ├── internal └── natsort │ ├── README.md │ ├── LICENSE │ ├── natsort.go │ └── natsort_test.go ├── .travis.yml ├── LICENSE ├── bypasssafe.go ├── README.md ├── messagediff_test.go ├── bypass.go └── messagediff.go /example/example.go: -------------------------------------------------------------------------------- 1 | package examples 2 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: LWIe7rP7M3hBnAxpsMaZhrVBs2DSyhzoQ 2 | -------------------------------------------------------------------------------- /testdata/testdata.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | type Test struct { 4 | a int 5 | b string 6 | } 7 | 8 | func MakeTest(a int, b string) Test { 9 | return Test{a, b} 10 | } 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## nightly 2 | * Added support for ignoring fields. 3 | 4 | ## v1.1.0 5 | 6 | * Added support for recursive data structures. 7 | * Fixed bug with embedded fixed length arrays in structs. 8 | * Added `example/` directory. 9 | * Minor test bug fixes for future go versions. 10 | * Added change log. 11 | 12 | ## v1.0.0 13 | 14 | Initial tagged release release. 15 | -------------------------------------------------------------------------------- /.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 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /internal/natsort/README.md: -------------------------------------------------------------------------------- 1 | # natsort 2 | 3 | This is a fork of the `sortorder` package of [github.com/fvbommel/util](https://github.com/fvbommel/util). 4 | 5 | ## License 6 | 7 | * [MIT](./LICENSE) 8 | - the original implementation of `sortorder` was released by [Frits van Bommel](https://github.com/fvbommel) under an MIT license. 9 | * [Public domain](../../UNLICENSE) 10 | - any changes made in this fork are released into the public domain. 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | arch: 4 | - AMD64 5 | - ppc64le 6 | 7 | go_import_path: github.com/d4l3k/messagediff 8 | 9 | os: 10 | - linux 11 | 12 | go: 13 | - 1.14.x 14 | - 1.15.x 15 | - tip 16 | 17 | allow_failures: 18 | - go: tip 19 | 20 | 21 | before_install: 22 | - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi 23 | - go get github.com/axw/gocov/gocov 24 | - go get github.com/modocache/gover 25 | - go get github.com/mattn/goveralls 26 | 27 | script: 28 | - go test -v -coverprofile=example.coverprofile ./example 29 | - go test -v -coverprofile=main.coverprofile 30 | - $HOME/gopath/bin/gover 31 | - $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=gover.coverprofile 32 | -------------------------------------------------------------------------------- /internal/natsort/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2015 Frits van Bommel 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Tristan Rice 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 | -------------------------------------------------------------------------------- /example/atom_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/d4l3k/messagediff" 7 | "golang.org/x/net/html" 8 | "golang.org/x/net/html/atom" 9 | ) 10 | 11 | func ExampleAtom() { 12 | got := data2() 13 | want := data1() 14 | diff, equal := messagediff.PrettyDiff(want, got) 15 | fmt.Printf("%v %s", equal, diff) 16 | // Output: false modified: [0].FirstChild.NextSibling.Data = " baz" 17 | } 18 | 19 | func data1() []*html.Node { 20 | n := &html.Node{ 21 | Type: html.ElementNode, Data: atom.Span.String(), 22 | Attr: []html.Attribute{ 23 | {Key: atom.Class.String(), Val: "foo"}, 24 | }, 25 | } 26 | n.AppendChild( 27 | &html.Node{ 28 | Type: html.ElementNode, Data: atom.Span.String(), 29 | Attr: []html.Attribute{ 30 | {Key: atom.Class.String(), Val: "bar"}, 31 | }, 32 | }, 33 | ) 34 | n.AppendChild(&html.Node{ 35 | Type: html.TextNode, Data: "baz", 36 | }) 37 | return []*html.Node{n} 38 | } 39 | 40 | func data2() []*html.Node { 41 | n := &html.Node{ 42 | Type: html.ElementNode, Data: atom.Span.String(), 43 | Attr: []html.Attribute{ 44 | {Key: atom.Class.String(), Val: "foo"}, 45 | }, 46 | } 47 | n.AppendChild( 48 | &html.Node{ 49 | Type: html.ElementNode, Data: atom.Span.String(), 50 | Attr: []html.Attribute{ 51 | {Key: atom.Class.String(), Val: "bar"}, 52 | }, 53 | }, 54 | ) 55 | n.AppendChild(&html.Node{ 56 | Type: html.TextNode, Data: " baz", 57 | }) 58 | return []*html.Node{n} 59 | } 60 | -------------------------------------------------------------------------------- /bypasssafe.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Dave Collins 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // NOTE: Due to the following build constraints, this file will only be compiled 16 | // when either the code is running on Google App Engine or "-tags disableunsafe" 17 | // is added to the go build command line. 18 | // +build appengine disableunsafe 19 | 20 | package messagediff 21 | 22 | import "reflect" 23 | 24 | const ( 25 | // UnsafeDisabled is a build-time constant which specifies whether or 26 | // not access to the unsafe package is available. 27 | UnsafeDisabled = true 28 | ) 29 | 30 | // unsafeReflectValue typically converts the passed reflect.Value into a one 31 | // that bypasses the typical safety restrictions preventing access to 32 | // unaddressable and unexported data. However, doing this relies on access to 33 | // the unsafe package. This is a stub version which simply returns the passed 34 | // reflect.Value when the unsafe package is not available. 35 | func unsafeReflectValue(v reflect.Value) reflect.Value { 36 | return v 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # messagediff [![Build Status](https://travis-ci.org/d4l3k/messagediff.svg?branch=master)](https://travis-ci.org/d4l3k/messagediff) [![Coverage Status](https://coveralls.io/repos/github/d4l3k/messagediff/badge.svg?branch=master)](https://coveralls.io/github/d4l3k/messagediff?branch=master) [![GoDoc](https://godoc.org/github.com/d4l3k/messagediff?status.svg)](https://godoc.org/github.com/d4l3k/messagediff) 2 | 3 | A library for doing diffs of arbitrary Golang structs. 4 | 5 | If the unsafe package is available messagediff will diff unexported fields in 6 | addition to exported fields. This is primarily used for testing purposes as it 7 | allows for providing informative error messages. 8 | 9 | Optionally, fields in structs can be tagged as `testdiff:"ignore"` to make 10 | messagediff skip it when doing the comparison. 11 | 12 | 13 | ## Example Usage 14 | In a normal file: 15 | ```go 16 | package main 17 | 18 | import "gopkg.in/d4l3k/messagediff.v1" 19 | 20 | type someStruct struct { 21 | A, b int 22 | C []int 23 | } 24 | 25 | func main() { 26 | a := someStruct{1, 2, []int{1}} 27 | b := someStruct{1, 3, []int{1, 2}} 28 | diff, equal := messagediff.PrettyDiff(a, b) 29 | /* 30 | diff = 31 | `added: .C[1] = 2 32 | modified: .b = 3` 33 | 34 | equal = false 35 | */ 36 | } 37 | 38 | ``` 39 | In a test: 40 | ```go 41 | import "gopkg.in/d4l3k/messagediff.v1" 42 | 43 | ... 44 | 45 | type someStruct struct { 46 | A, b int 47 | C []int 48 | } 49 | 50 | func TestSomething(t *testing.T) { 51 | want := someStruct{1, 2, []int{1}} 52 | got := someStruct{1, 3, []int{1, 2}} 53 | if diff, equal := messagediff.PrettyDiff(want, got); !equal { 54 | t.Errorf("Something() = %#v\n%s", got, diff) 55 | } 56 | } 57 | ``` 58 | To ignore a field in a struct, just annotate it with testdiff:"ignore" like 59 | this: 60 | ```go 61 | package main 62 | 63 | import "gopkg.in/d4l3k/messagediff.v1" 64 | 65 | type someStruct struct { 66 | A int 67 | B int `testdiff:"ignore"` 68 | } 69 | 70 | func main() { 71 | a := someStruct{1, 2} 72 | b := someStruct{1, 3} 73 | diff, equal := messagediff.PrettyDiff(a, b) 74 | /* 75 | equal = true 76 | diff = "" 77 | */ 78 | } 79 | ``` 80 | 81 | See the `DeepDiff` function for using the diff results programmatically. 82 | 83 | ## License 84 | Copyright (c) 2015 [Tristan Rice](https://fn.lc) 85 | 86 | messagediff is licensed under the MIT license. See the LICENSE file for more information. 87 | 88 | bypass.go and bypasssafe.go are borrowed from 89 | [go-spew](https://github.com/davecgh/go-spew) and have a seperate copyright 90 | notice. 91 | -------------------------------------------------------------------------------- /internal/natsort/natsort.go: -------------------------------------------------------------------------------- 1 | // Package natsort implements natural sort. In "Natural Sort Order" integers 2 | // embedded in strings are compared by value. 3 | // 4 | // References: 5 | // https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/ 6 | package natsort 7 | 8 | import ( 9 | "sort" 10 | ) 11 | 12 | // Strings sorts the given slice of strings in natural order. 13 | func Strings(a []string) { 14 | sort.Sort(Order(a)) 15 | } 16 | 17 | // Order implements sort.Interface to sort strings in natural order. This means 18 | // that e.g. "abc2" < "abc12". 19 | // 20 | // Non-digit sequences and numbers are compared separately. The former are 21 | // compared bytewise, while the latter are compared numerically (except that 22 | // the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02") 23 | // 24 | // Limitation: only ASCII digits (0-9) are considered. 25 | type Order []string 26 | 27 | func (n Order) Len() int { return len(n) } 28 | func (n Order) Swap(i, j int) { n[i], n[j] = n[j], n[i] } 29 | func (n Order) Less(i, j int) bool { return Less(n[i], n[j]) } 30 | 31 | // isdigit reports whether the given byte is a decimal digit. 32 | func isdigit(b byte) bool { 33 | return '0' <= b && b <= '9' 34 | } 35 | 36 | // Less compares two strings using natural ordering. This means that e.g. "abc2" 37 | // < "abc12". 38 | // 39 | // Non-digit sequences and numbers are compared separately. The former are 40 | // compared bytewise, while the latter are compared numerically (except that 41 | // the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02") 42 | // 43 | // Limitation: only ASCII digits (0-9) are considered. 44 | func Less(str1, str2 string) bool { 45 | idx1, idx2 := 0, 0 46 | for idx1 < len(str1) && idx2 < len(str2) { 47 | c1, c2 := str1[idx1], str2[idx2] 48 | dig1, dig2 := isdigit(c1), isdigit(c2) 49 | switch { 50 | case dig1 && dig2: // Digits 51 | // Eat zeros. 52 | for ; idx1 < len(str1) && str1[idx1] == '0'; idx1++ { 53 | } 54 | for ; idx2 < len(str2) && str2[idx2] == '0'; idx2++ { 55 | } 56 | // Eat all digits. 57 | nonZero1, nonZero2 := idx1, idx2 58 | for ; idx1 < len(str1) && isdigit(str1[idx1]); idx1++ { 59 | } 60 | for ; idx2 < len(str2) && isdigit(str2[idx2]); idx2++ { 61 | } 62 | // If lengths of numbers with non-zero prefix differ, the shorter 63 | // one is less. 64 | if len1, len2 := idx1-nonZero1, idx2-nonZero2; len1 != len2 { 65 | return len1 < len2 66 | } 67 | // If they're equal, string comparison is correct. 68 | if nr1, nr2 := str1[nonZero1:idx1], str2[nonZero2:idx2]; nr1 != nr2 { 69 | return nr1 < nr2 70 | } 71 | // Otherwise, the one with less zeros is less. 72 | // Because everything up to the number is equal, comparing the index 73 | // after the zeros is sufficient. 74 | if nonZero1 != nonZero2 { 75 | return nonZero1 < nonZero2 76 | } 77 | default: // non-digit characters 78 | // UTF-8 compares bytewise-lexicographically, no need to decode 79 | // codepoints. 80 | if c1 != c2 { 81 | return c1 < c2 82 | } 83 | idx1++ 84 | idx2++ 85 | } 86 | // They're identical so far, so continue comparing. 87 | } 88 | // So far they are identical. At least one is ended. If the other continues, 89 | // it sorts last. 90 | return len(str1) < len(str2) 91 | } 92 | -------------------------------------------------------------------------------- /internal/natsort/natsort_test.go: -------------------------------------------------------------------------------- 1 | package natsort 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | "sort" 7 | "strconv" 8 | "testing" 9 | ) 10 | 11 | func TestStrings(t *testing.T) { 12 | golden := []struct { 13 | in []string 14 | want []string 15 | }{ 16 | { 17 | in: []string{"abc5", "abc1", "abc01", "ab", "abc10", "abc2"}, 18 | want: []string{ 19 | "ab", 20 | "abc1", 21 | "abc01", 22 | "abc2", 23 | "abc5", 24 | "abc10", 25 | }, 26 | }, 27 | { 28 | in: []string{"foo20", "foo.bar", "foo2", "foo.10", "foo.1", "foo.20", "foo.11", "foo1", "foobar", "foo21", "foo10", "foo11", "foo.21", "foo.2"}, 29 | want: []string{ 30 | "foo.1", 31 | "foo.2", 32 | "foo.10", 33 | "foo.11", 34 | "foo.20", 35 | "foo.21", 36 | "foo.bar", 37 | "foo1", 38 | "foo2", 39 | "foo10", 40 | "foo11", 41 | "foo20", 42 | "foo21", 43 | "foobar", 44 | }, 45 | }, 46 | } 47 | for _, g := range golden { 48 | Strings(g.in) 49 | if !reflect.DeepEqual(g.want, g.in) { 50 | t.Errorf("Error: sort failed, expected: %#q, got: %#q", g.want, g.in) 51 | } 52 | } 53 | } 54 | 55 | func TestLess(t *testing.T) { 56 | testset := []struct { 57 | s1, s2 string 58 | less bool 59 | }{ 60 | {"0", "00", true}, 61 | {"00", "0", false}, 62 | {"aa", "ab", true}, 63 | {"ab", "abc", true}, 64 | {"abc", "ad", true}, 65 | {"ab1", "ab2", true}, 66 | {"ab1c", "ab1c", false}, 67 | {"ab12", "abc", true}, 68 | {"ab2a", "ab10", true}, 69 | {"a0001", "a0000001", true}, 70 | {"a10", "abcdefgh2", true}, 71 | {"аб2аб", "аб10аб", true}, 72 | {"2аб", "3аб", true}, 73 | // 74 | {"a1b", "a01b", true}, 75 | {"a01b", "a1b", false}, 76 | {"ab01b", "ab010b", true}, 77 | {"ab010b", "ab01b", false}, 78 | {"a01b001", "a001b01", true}, 79 | {"a001b01", "a01b001", false}, 80 | {"a1", "a1x", true}, 81 | {"1ax", "1b", true}, 82 | {"1b", "1ax", false}, 83 | // 84 | {"082", "83", true}, 85 | // 86 | {"083a", "9a", false}, 87 | {"9a", "083a", true}, 88 | // 89 | {"foo.bar", "foo123", true}, 90 | {"foo123", "foo.bar", false}, 91 | } 92 | for _, v := range testset { 93 | if res := Less(v.s1, v.s2); res != v.less { 94 | t.Errorf("Compared %#q to %#q: expected %v, got %v", 95 | v.s1, v.s2, v.less, res) 96 | } 97 | } 98 | } 99 | 100 | func BenchmarkStdStrings(b *testing.B) { 101 | set := testSet(300) 102 | arr := make([]string, len(set[0])) 103 | b.ResetTimer() 104 | for i := 0; i < b.N; i++ { 105 | for _, list := range set { 106 | b.StopTimer() 107 | copy(arr, list) 108 | b.StartTimer() 109 | 110 | sort.Strings(arr) 111 | } 112 | } 113 | } 114 | 115 | func BenchmarkStrings(b *testing.B) { 116 | set := testSet(300) 117 | arr := make([]string, len(set[0])) 118 | b.ResetTimer() 119 | for i := 0; i < b.N; i++ { 120 | for _, list := range set { 121 | b.StopTimer() 122 | copy(arr, list) 123 | b.StartTimer() 124 | 125 | Strings(arr) 126 | } 127 | } 128 | } 129 | 130 | func BenchmarkStdLess(b *testing.B) { 131 | set := testSet(300) 132 | b.ResetTimer() 133 | for i := 0; i < b.N; i++ { 134 | for j := range set[0] { 135 | k := (j + 1) % len(set[0]) 136 | _ = set[0][j] < set[0][k] 137 | } 138 | } 139 | } 140 | 141 | func BenchmarkLess(b *testing.B) { 142 | set := testSet(300) 143 | b.ResetTimer() 144 | for i := 0; i < b.N; i++ { 145 | for j := range set[0] { 146 | k := (j + 1) % len(set[0]) 147 | _ = Less(set[0][j], set[0][k]) 148 | } 149 | } 150 | } 151 | 152 | // Get 1000 arrays of 10000-string-arrays (less if -short is specified). 153 | func testSet(seed int) [][]string { 154 | gen := &generator{ 155 | src: rand.New(rand.NewSource( 156 | int64(seed), 157 | )), 158 | } 159 | n := 1000 160 | if testing.Short() { 161 | n = 1 162 | } 163 | set := make([][]string, n) 164 | for i := range set { 165 | strings := make([]string, 10000) 166 | for idx := range strings { 167 | // Generate a random string 168 | strings[idx] = gen.NextString() 169 | } 170 | set[i] = strings 171 | } 172 | return set 173 | } 174 | 175 | type generator struct { 176 | src *rand.Rand 177 | } 178 | 179 | func (g *generator) NextInt(max int) int { 180 | return g.src.Intn(max) 181 | } 182 | 183 | // Gets random random-length alphanumeric string. 184 | func (g *generator) NextString() (str string) { 185 | // Random-length 3-8 chars part 186 | strlen := g.src.Intn(6) + 3 187 | // Random-length 1-3 num 188 | numlen := g.src.Intn(3) + 1 189 | // Random position for num in string 190 | numpos := g.src.Intn(strlen + 1) 191 | // Generate the number 192 | var num string 193 | for i := 0; i < numlen; i++ { 194 | num += strconv.Itoa(g.src.Intn(10)) 195 | } 196 | // Put it all together 197 | for i := 0; i < strlen+1; i++ { 198 | if i == numpos { 199 | str += num 200 | } else { 201 | str += string('a' + g.src.Intn(16)) 202 | } 203 | } 204 | return str 205 | } 206 | -------------------------------------------------------------------------------- /messagediff_test.go: -------------------------------------------------------------------------------- 1 | package messagediff 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/d4l3k/messagediff/testdata" 8 | ) 9 | 10 | type testStruct struct { 11 | A, b int 12 | C []int 13 | D [3]int 14 | } 15 | 16 | type RecursiveStruct struct { 17 | Key int 18 | Child *RecursiveStruct 19 | } 20 | 21 | func newRecursiveStruct(key int) *RecursiveStruct { 22 | a := &RecursiveStruct{ 23 | Key: key, 24 | } 25 | b := &RecursiveStruct{ 26 | Key: key, 27 | Child: a, 28 | } 29 | a.Child = b 30 | return a 31 | } 32 | 33 | type testCase struct { 34 | a, b interface{} 35 | diff string 36 | equal bool 37 | } 38 | 39 | func checkTestCases(t *testing.T, testData []testCase) { 40 | for i, td := range testData { 41 | diff, equal := PrettyDiff(td.a, td.b) 42 | if diff != td.diff { 43 | t.Errorf("%d. PrettyDiff(%#v, %#v) diff = %#v; not %#v", i, td.a, td.b, diff, td.diff) 44 | } 45 | if equal != td.equal { 46 | t.Errorf("%d. PrettyDiff(%#v, %#v) equal = %#v; not %#v", i, td.a, td.b, equal, td.equal) 47 | } 48 | } 49 | } 50 | 51 | func TestPrettyDiff(t *testing.T) { 52 | testData := []testCase{ 53 | { 54 | true, 55 | false, 56 | "modified: = false\n", 57 | false, 58 | }, 59 | { 60 | true, 61 | 0, 62 | "modified: = 0\n", 63 | false, 64 | }, 65 | { 66 | []int{0, 1, 2}, 67 | []int{0, 1, 2, 3}, 68 | "added: [3] = 3\n", 69 | false, 70 | }, 71 | { 72 | []int{0, 1, 2, 3}, 73 | []int{0, 1, 2}, 74 | "removed: [3] = 3\n", 75 | false, 76 | }, 77 | { 78 | []int{0}, 79 | []int{1}, 80 | "modified: [0] = 1\n", 81 | false, 82 | }, 83 | { 84 | &[]int{0}, 85 | &[]int{1}, 86 | "modified: [0] = 1\n", 87 | false, 88 | }, 89 | { 90 | map[string]int{"a": 1, "b": 2}, 91 | map[string]int{"b": 4, "c": 3}, 92 | "added: [\"c\"] = 3\nmodified: [\"b\"] = 4\nremoved: [\"a\"] = 1\n", 93 | false, 94 | }, 95 | { 96 | testStruct{1, 2, []int{1}, [3]int{4, 5, 6}}, 97 | testStruct{1, 3, []int{1, 2}, [3]int{4, 5, 6}}, 98 | "added: .C[1] = 2\nmodified: .b = 3\n", 99 | false, 100 | }, 101 | { 102 | testStruct{1, 3, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, [3]int{4, 5, 6}}, 103 | testStruct{1, 3, []int{42, 43, 44, 3, 4, 5, 6, 7, 8, 9, 45, 46, 12}, [3]int{4, 5, 6}}, 104 | "modified: .C[0] = 42\nmodified: .C[1] = 43\nmodified: .C[2] = 44\nmodified: .C[10] = 45\nmodified: .C[11] = 46\n", 105 | false, 106 | }, 107 | { 108 | nil, 109 | nil, 110 | "", 111 | true, 112 | }, 113 | { 114 | &struct{}{}, 115 | nil, 116 | "modified: = \n", 117 | false, 118 | }, 119 | { 120 | nil, 121 | &struct{}{}, 122 | "modified: = &struct {}{}\n", 123 | false, 124 | }, 125 | { 126 | time.Time{}, 127 | time.Time{}, 128 | "", 129 | true, 130 | }, 131 | { 132 | testdata.MakeTest(10, "duck"), 133 | testdata.MakeTest(20, "foo"), 134 | "modified: .a = 20\nmodified: .b = \"foo\"\n", 135 | false, 136 | }, 137 | { 138 | time.Date(2018, 7, 24, 14, 06, 59, 0, &time.Location{}), 139 | time.Date(2018, 7, 24, 14, 06, 59, 0, time.UTC), 140 | "", 141 | true, 142 | }, 143 | { 144 | time.Date(2017, 1, 1, 0, 0, 0, 0, &time.Location{}), 145 | time.Date(2018, 7, 24, 14, 06, 59, 0, time.UTC), 146 | "modified: = \"2018-07-24 14:06:59 +0000 UTC\"\n", 147 | false, 148 | }, 149 | } 150 | checkTestCases(t, testData) 151 | } 152 | 153 | func TestPrettyDiffRecursive(t *testing.T) { 154 | testData := []testCase{ 155 | { 156 | newRecursiveStruct(1), 157 | newRecursiveStruct(1), 158 | "", 159 | true, 160 | }, 161 | { 162 | newRecursiveStruct(1), 163 | newRecursiveStruct(2), 164 | "modified: .Child.Key = 2\nmodified: .Key = 2\n", 165 | false, 166 | }, 167 | } 168 | checkTestCases(t, testData) 169 | } 170 | 171 | func TestPathString(t *testing.T) { 172 | testData := []struct { 173 | in Path 174 | want string 175 | }{{ 176 | Path{StructField("test"), SliceIndex(1), MapKey{"blue"}, MapKey{12.3}}, 177 | ".test[1][\"blue\"][12.3]", 178 | }} 179 | for i, td := range testData { 180 | if out := td.in.String(); out != td.want { 181 | t.Errorf("%d. %#v.String() = %#v; not %#v", i, td.in, out, td.want) 182 | } 183 | } 184 | } 185 | 186 | type ignoreStruct struct { 187 | A int `testdiff:"ignore"` 188 | a int 189 | B [3]int `testdiff:"ignore"` 190 | b [3]int 191 | } 192 | 193 | func TestIgnoreTag(t *testing.T) { 194 | s1 := ignoreStruct{1, 1, [3]int{1, 2, 3}, [3]int{4, 5, 6}} 195 | s2 := ignoreStruct{2, 1, [3]int{1, 8, 3}, [3]int{4, 5, 6}} 196 | 197 | diff, equal := PrettyDiff(s1, s2) 198 | if !equal { 199 | t.Errorf("Expected structs to be equal. Diff:\n%s", diff) 200 | } 201 | 202 | s2 = ignoreStruct{2, 2, [3]int{1, 8, 3}, [3]int{4, 9, 6}} 203 | diff, equal = PrettyDiff(s1, s2) 204 | if equal { 205 | t.Errorf("Expected structs NOT to be equal.") 206 | } 207 | expect := "modified: .a = 2\nmodified: .b[1] = 9\n" 208 | if diff != expect { 209 | t.Errorf("Expected diff to be:\n%v\nbut got:\n%v", expect, diff) 210 | } 211 | } 212 | 213 | func TestIgnoreStructFieldOption(t *testing.T) { 214 | a := struct { 215 | X string 216 | Y string 217 | }{ 218 | X: "x", 219 | Y: "y", 220 | } 221 | b := struct { 222 | X string 223 | Y string 224 | }{ 225 | X: "xx", 226 | Y: "y", 227 | } 228 | 229 | diff, equal := PrettyDiff(a, b, IgnoreStructField("X")) 230 | if !equal { 231 | t.Errorf("Expected structs to be equal. Diff:\n%s", diff) 232 | } 233 | 234 | diff, equal = PrettyDiff(a, b, IgnoreStructField("Y")) 235 | if equal { 236 | t.Errorf("Expected structs NOT to be equal.") 237 | } 238 | expect := "modified: .X = \"xx\"\n" 239 | if diff != expect { 240 | t.Errorf("Expected diff to be:\n%v\nbut got:\n%v", expect, diff) 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /bypass.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Dave Collins 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // NOTE: Due to the following build constraints, this file will only be compiled 16 | // when the code is not running on Google App Engine and "-tags disableunsafe" 17 | // is not added to the go build command line. 18 | // +build !appengine,!disableunsafe 19 | 20 | package messagediff 21 | 22 | import ( 23 | "reflect" 24 | "unsafe" 25 | ) 26 | 27 | const ( 28 | // UnsafeDisabled is a build-time constant which specifies whether or 29 | // not access to the unsafe package is available. 30 | UnsafeDisabled = false 31 | 32 | // ptrSize is the size of a pointer on the current arch. 33 | ptrSize = unsafe.Sizeof((*byte)(nil)) 34 | ) 35 | 36 | var ( 37 | // offsetPtr, offsetScalar, and offsetFlag are the offsets for the 38 | // internal reflect.Value fields. These values are valid before golang 39 | // commit ecccf07e7f9d which changed the format. The are also valid 40 | // after commit 82f48826c6c7 which changed the format again to mirror 41 | // the original format. Code in the init function updates these offsets 42 | // as necessary. 43 | offsetPtr = uintptr(ptrSize) 44 | offsetScalar = uintptr(0) 45 | offsetFlag = uintptr(ptrSize * 2) 46 | 47 | // flagKindWidth and flagKindShift indicate various bits that the 48 | // reflect package uses internally to track kind information. 49 | // 50 | // flagRO indicates whether or not the value field of a reflect.Value is 51 | // read-only. 52 | // 53 | // flagIndir indicates whether the value field of a reflect.Value is 54 | // the actual data or a pointer to the data. 55 | // 56 | // These values are valid before golang commit 90a7c3c86944 which 57 | // changed their positions. Code in the init function updates these 58 | // flags as necessary. 59 | flagKindWidth = uintptr(5) 60 | flagKindShift = uintptr(flagKindWidth - 1) 61 | flagRO = uintptr(1 << 0) 62 | flagIndir = uintptr(1 << 1) 63 | ) 64 | 65 | func init() { 66 | // Older versions of reflect.Value stored small integers directly in the 67 | // ptr field (which is named val in the older versions). Versions 68 | // between commits ecccf07e7f9d and 82f48826c6c7 added a new field named 69 | // scalar for this purpose which unfortunately came before the flag 70 | // field, so the offset of the flag field is different for those 71 | // versions. 72 | // 73 | // This code constructs a new reflect.Value from a known small integer 74 | // and checks if the size of the reflect.Value struct indicates it has 75 | // the scalar field. When it does, the offsets are updated accordingly. 76 | vv := reflect.ValueOf(0xf00) 77 | if unsafe.Sizeof(vv) == (ptrSize * 4) { 78 | offsetScalar = ptrSize * 2 79 | offsetFlag = ptrSize * 3 80 | } 81 | 82 | // Commit 90a7c3c86944 changed the flag positions such that the low 83 | // order bits are the kind. This code extracts the kind from the flags 84 | // field and ensures it's the correct type. When it's not, the flag 85 | // order has been changed to the newer format, so the flags are updated 86 | // accordingly. 87 | upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag) 88 | upfv := *(*uintptr)(upf) 89 | flagKindMask := uintptr((1<>flagKindShift != uintptr(reflect.Int) { 91 | flagKindShift = 0 92 | flagRO = 1 << 5 93 | flagIndir = 1 << 6 94 | 95 | // Commit adf9b30e5594 modified the flags to separate the 96 | // flagRO flag into two bits which specifies whether or not the 97 | // field is embedded. This causes flagIndir to move over a bit 98 | // and means that flagRO is the combination of either of the 99 | // original flagRO bit and the new bit. 100 | // 101 | // This code detects the change by extracting what used to be 102 | // the indirect bit to ensure it's set. When it's not, the flag 103 | // order has been changed to the newer format, so the flags are 104 | // updated accordingly. 105 | if upfv&flagIndir == 0 { 106 | flagRO = 3 << 5 107 | flagIndir = 1 << 7 108 | } 109 | } 110 | } 111 | 112 | // unsafeReflectValue converts the passed reflect.Value into a one that bypasses 113 | // the typical safety restrictions preventing access to unaddressable and 114 | // unexported data. It works by digging the raw pointer to the underlying 115 | // value out of the protected value and generating a new unprotected (unsafe) 116 | // reflect.Value to it. 117 | // 118 | // This allows us to check for implementations of the Stringer and error 119 | // interfaces to be used for pretty printing ordinarily unaddressable and 120 | // inaccessible values such as unexported struct fields. 121 | func unsafeReflectValue(v reflect.Value) (rv reflect.Value) { 122 | indirects := 1 123 | vt := v.Type() 124 | upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr) 125 | rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag)) 126 | if rvf&flagIndir != 0 { 127 | vt = reflect.PtrTo(v.Type()) 128 | indirects++ 129 | } else if offsetScalar != 0 { 130 | // The value is in the scalar field when it's not one of the 131 | // reference types. 132 | switch vt.Kind() { 133 | case reflect.Uintptr: 134 | case reflect.Chan: 135 | case reflect.Func: 136 | case reflect.Map: 137 | case reflect.Ptr: 138 | case reflect.UnsafePointer: 139 | default: 140 | upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + 141 | offsetScalar) 142 | } 143 | } 144 | 145 | pv := reflect.NewAt(vt, upv) 146 | rv = pv 147 | for i := 0; i < indirects; i++ { 148 | rv = rv.Elem() 149 | } 150 | return rv 151 | } 152 | -------------------------------------------------------------------------------- /messagediff.go: -------------------------------------------------------------------------------- 1 | package messagediff 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "time" 8 | "unsafe" 9 | 10 | "github.com/d4l3k/messagediff/internal/natsort" 11 | ) 12 | 13 | // PrettyDiff does a deep comparison and returns the nicely formated results. 14 | // See DeepDiff for more details. 15 | func PrettyDiff(a, b interface{}, options ...Option) (string, bool) { 16 | d, equal := DeepDiff(a, b, options...) 17 | var dstr []string 18 | for path, added := range d.Added { 19 | dstr = append(dstr, fmt.Sprintf("added: %s = %#v\n", path.String(), added)) 20 | } 21 | for path, removed := range d.Removed { 22 | dstr = append(dstr, fmt.Sprintf("removed: %s = %#v\n", path.String(), removed)) 23 | } 24 | for path, modified := range d.Modified { 25 | dstr = append(dstr, fmt.Sprintf("modified: %s = %#v\n", path.String(), modified.New)) 26 | } 27 | natsort.Strings(dstr) 28 | return strings.Join(dstr, ""), equal 29 | } 30 | 31 | // DeepDiff does a deep comparison and returns the results. 32 | // If the field is time.Time, use Equal to compare 33 | func DeepDiff(a, b interface{}, options ...Option) (*Diff, bool) { 34 | d := newDiff() 35 | opts := &opts{} 36 | for _, o := range options { 37 | o.apply(opts) 38 | } 39 | return d, d.diff(reflect.ValueOf(a), reflect.ValueOf(b), nil, opts) 40 | } 41 | 42 | func newDiff() *Diff { 43 | return &Diff{ 44 | Added: make(map[*Path]interface{}), 45 | Removed: make(map[*Path]interface{}), 46 | Modified: make(map[*Path]Update), 47 | visited: make(map[visit]bool), 48 | } 49 | } 50 | 51 | // Update records the old and new value of a modified field. 52 | type Update struct { 53 | Old interface{} 54 | New interface{} 55 | } 56 | 57 | func (d *Diff) diff(aVal, bVal reflect.Value, path Path, opts *opts) bool { 58 | // The array underlying `path` could be modified in subsequent 59 | // calls. Make sure we have a local copy. 60 | localPath := make(Path, len(path)) 61 | copy(localPath, path) 62 | 63 | // Validity checks. Should only trigger if nil is one of the original arguments. 64 | if !aVal.IsValid() && !bVal.IsValid() { 65 | return true 66 | } 67 | if !bVal.IsValid() { 68 | d.Modified[&localPath] = Update{Old: aVal.Interface(), New: nil} 69 | return false 70 | } else if !aVal.IsValid() { 71 | d.Modified[&localPath] = Update{Old: nil, New: bVal.Interface()} 72 | return false 73 | } 74 | 75 | if aVal.Type() != bVal.Type() { 76 | d.Modified[&localPath] = Update{Old: aVal.Interface(), New: bVal.Interface()} 77 | return false 78 | } 79 | kind := aVal.Kind() 80 | 81 | // Borrowed from the reflect package to handle recursive data structures. 82 | hard := func(k reflect.Kind) bool { 83 | switch k { 84 | case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct: 85 | return true 86 | } 87 | return false 88 | } 89 | 90 | if aVal.CanAddr() && bVal.CanAddr() && hard(kind) { 91 | addr1 := unsafe.Pointer(aVal.UnsafeAddr()) 92 | addr2 := unsafe.Pointer(bVal.UnsafeAddr()) 93 | if uintptr(addr1) > uintptr(addr2) { 94 | // Canonicalize order to reduce number of entries in visited. 95 | // Assumes non-moving garbage collector. 96 | addr1, addr2 = addr2, addr1 97 | } 98 | 99 | // Short circuit if references are already seen. 100 | typ := aVal.Type() 101 | v := visit{addr1, addr2, typ} 102 | if d.visited[v] { 103 | return true 104 | } 105 | 106 | // Remember for later. 107 | d.visited[v] = true 108 | } 109 | // End of borrowed code. 110 | 111 | equal := true 112 | switch kind { 113 | case reflect.Map, reflect.Ptr, reflect.Func, reflect.Chan, reflect.Slice: 114 | if aVal.IsNil() && bVal.IsNil() { 115 | return true 116 | } 117 | if aVal.IsNil() || bVal.IsNil() { 118 | d.Modified[&localPath] = Update{Old: aVal.Interface(), New: bVal.Interface()} 119 | return false 120 | } 121 | } 122 | 123 | switch kind { 124 | case reflect.Array, reflect.Slice: 125 | aLen := aVal.Len() 126 | bLen := bVal.Len() 127 | for i := 0; i < min(aLen, bLen); i++ { 128 | localPath := append(localPath, SliceIndex(i)) 129 | if eq := d.diff(aVal.Index(i), bVal.Index(i), localPath, opts); !eq { 130 | equal = false 131 | } 132 | } 133 | if aLen > bLen { 134 | for i := bLen; i < aLen; i++ { 135 | localPath := append(localPath, SliceIndex(i)) 136 | d.Removed[&localPath] = aVal.Index(i).Interface() 137 | equal = false 138 | } 139 | } else if aLen < bLen { 140 | for i := aLen; i < bLen; i++ { 141 | localPath := append(localPath, SliceIndex(i)) 142 | d.Added[&localPath] = bVal.Index(i).Interface() 143 | equal = false 144 | } 145 | } 146 | case reflect.Map: 147 | for _, key := range aVal.MapKeys() { 148 | aI := aVal.MapIndex(key) 149 | bI := bVal.MapIndex(key) 150 | localPath := append(localPath, MapKey{key.Interface()}) 151 | if !bI.IsValid() { 152 | d.Removed[&localPath] = aI.Interface() 153 | equal = false 154 | } else if eq := d.diff(aI, bI, localPath, opts); !eq { 155 | equal = false 156 | } 157 | } 158 | for _, key := range bVal.MapKeys() { 159 | aI := aVal.MapIndex(key) 160 | if !aI.IsValid() { 161 | bI := bVal.MapIndex(key) 162 | localPath := append(localPath, MapKey{key.Interface()}) 163 | d.Added[&localPath] = bI.Interface() 164 | equal = false 165 | } 166 | } 167 | case reflect.Struct: 168 | typ := aVal.Type() 169 | // If the field is time.Time, use Equal to compare 170 | if typ.String() == "time.Time" { 171 | aTime := aVal.Interface().(time.Time) 172 | bTime := bVal.Interface().(time.Time) 173 | if !aTime.Equal(bTime) { 174 | d.Modified[&localPath] = Update{Old: aTime.String(), New: bTime.String()} 175 | equal = false 176 | } 177 | } else { 178 | for i := 0; i < typ.NumField(); i++ { 179 | index := []int{i} 180 | field := typ.FieldByIndex(index) 181 | if field.Tag.Get("testdiff") == "ignore" { // skip fields marked to be ignored 182 | continue 183 | } 184 | if _, skip := opts.ignoreField[field.Name]; skip { 185 | continue 186 | } 187 | localPath := append(localPath, StructField(field.Name)) 188 | aI := unsafeReflectValue(aVal.FieldByIndex(index)) 189 | bI := unsafeReflectValue(bVal.FieldByIndex(index)) 190 | if eq := d.diff(aI, bI, localPath, opts); !eq { 191 | equal = false 192 | } 193 | } 194 | } 195 | case reflect.Ptr: 196 | equal = d.diff(aVal.Elem(), bVal.Elem(), localPath, opts) 197 | default: 198 | if reflect.DeepEqual(aVal.Interface(), bVal.Interface()) { 199 | equal = true 200 | } else { 201 | d.Modified[&localPath] = Update{Old: aVal.Interface(), New: bVal.Interface()} 202 | equal = false 203 | } 204 | } 205 | return equal 206 | } 207 | 208 | func min(a, b int) int { 209 | if a < b { 210 | return a 211 | } 212 | return b 213 | } 214 | 215 | // During deepValueEqual, must keep track of checks that are 216 | // in progress. The comparison algorithm assumes that all 217 | // checks in progress are true when it reencounters them. 218 | // Visited comparisons are stored in a map indexed by visit. 219 | // This is borrowed from the reflect package. 220 | type visit struct { 221 | a1 unsafe.Pointer 222 | a2 unsafe.Pointer 223 | typ reflect.Type 224 | } 225 | 226 | // Diff represents a change in a struct. 227 | type Diff struct { 228 | Added, Removed map[*Path]interface{} 229 | Modified map[*Path]Update 230 | visited map[visit]bool 231 | } 232 | 233 | // Path represents a path to a changed datum. 234 | type Path []PathNode 235 | 236 | func (p Path) String() string { 237 | var out string 238 | for _, n := range p { 239 | out += n.String() 240 | } 241 | return out 242 | } 243 | 244 | // PathNode represents one step in the path. 245 | type PathNode interface { 246 | String() string 247 | } 248 | 249 | // StructField is a path element representing a field of a struct. 250 | type StructField string 251 | 252 | func (n StructField) String() string { 253 | return fmt.Sprintf(".%s", string(n)) 254 | } 255 | 256 | // MapKey is a path element representing a key of a map. 257 | type MapKey struct { 258 | Key interface{} 259 | } 260 | 261 | func (n MapKey) String() string { 262 | return fmt.Sprintf("[%#v]", n.Key) 263 | } 264 | 265 | // SliceIndex is a path element representing a index of a slice. 266 | type SliceIndex int 267 | 268 | func (n SliceIndex) String() string { 269 | return fmt.Sprintf("[%d]", n) 270 | } 271 | 272 | type opts struct { 273 | ignoreField map[string]struct{} 274 | } 275 | 276 | // Option is an option to specify in diff 277 | type Option interface { 278 | apply(*opts) 279 | } 280 | 281 | // IgnoreStructField return an option of IgnoreFieldOption 282 | func IgnoreStructField(field string) Option { 283 | return IgnoreFieldOption{ 284 | Field: field, 285 | } 286 | } 287 | 288 | // IgnoreFieldOption is an option for specifying a field that does not diff 289 | type IgnoreFieldOption struct { 290 | Field string 291 | } 292 | 293 | func (i IgnoreFieldOption) apply(opts *opts) { 294 | if opts.ignoreField == nil { 295 | opts.ignoreField = map[string]struct{}{} 296 | } 297 | opts.ignoreField[i.Field] = struct{}{} 298 | } 299 | --------------------------------------------------------------------------------