├── godiff ├── .gitignore └── godiff.go ├── .gitignore ├── go.mod ├── doc.go ├── strings.go ├── bytes.go ├── interface.go ├── delta_test.go ├── strings_test.go ├── bytes_test.go ├── example_test.go ├── delta.go ├── LICENSE ├── bench_test.go ├── README.md ├── lcs.go └── diff_test.go /godiff/.gitignore: -------------------------------------------------------------------------------- 1 | godiff 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | .idea 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PieterD/diff 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package diff is able to show the difference between two sets of data. 2 | package diff 3 | -------------------------------------------------------------------------------- /strings.go: -------------------------------------------------------------------------------- 1 | package diff 2 | 3 | // Holds two string lists to diff. 4 | type Strings struct { 5 | Left, Right []string 6 | } 7 | 8 | func (str Strings) Equal(left, right int) bool { 9 | return str.Left[left] == str.Right[right] 10 | } 11 | 12 | func (str Strings) Length() (int, int) { 13 | return len(str.Left), len(str.Right) 14 | } 15 | -------------------------------------------------------------------------------- /bytes.go: -------------------------------------------------------------------------------- 1 | package diff 2 | 3 | import "bytes" 4 | 5 | // Holds two string lists to diff. 6 | type Bytes struct { 7 | Left, Right [][]byte 8 | } 9 | 10 | func (str Bytes) Equal(left, right int) bool { 11 | return bytes.Equal(str.Left[left], str.Right[right]) 12 | } 13 | 14 | func (str Bytes) Length() (int, int) { 15 | return len(str.Left), len(str.Right) 16 | } 17 | -------------------------------------------------------------------------------- /interface.go: -------------------------------------------------------------------------------- 1 | package diff 2 | 3 | // Wrap your data in one of these to diff it. 4 | // It should hold two collections, the Left (or old) one and the Right (or new) one. 5 | type Interface interface { 6 | // Return true if the elements at the given indices in Left and Right are equal. 7 | Equal(left, right int) (isEqual bool) 8 | // Return the sizes of the Left and Right collections. 9 | Length() (left int, right int) 10 | } 11 | -------------------------------------------------------------------------------- /delta_test.go: -------------------------------------------------------------------------------- 1 | package diff_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/PieterD/diff" 7 | ) 8 | 9 | func TestDeltaString(t *testing.T) { 10 | if diff.Left.String() != "Left" { 11 | t.Fatalf("Expected Left, got %s", diff.Left.String()) 12 | } 13 | if diff.Right.String() != "Right" { 14 | t.Fatalf("Expected Right, got %s", diff.Right.String()) 15 | } 16 | if diff.Both.String() != "Both" { 17 | t.Fatalf("Expected Both, got %s", diff.Both.String()) 18 | } 19 | if diff.Delta(55).String() != "unknown" { 20 | t.Fatalf("Expected unknown, got %s", diff.Delta(55).String()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /strings_test.go: -------------------------------------------------------------------------------- 1 | package diff_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/PieterD/diff" 7 | ) 8 | 9 | func TestStrings(t *testing.T) { 10 | s := diff.Strings{ 11 | Left: []string{"hello", "world"}, 12 | Right: []string{"its", "my", "world"}, 13 | } 14 | l, r := s.Length() 15 | if l != 2 { 16 | t.Fatalf("Wrong left length, expected 2, got %d", l) 17 | } 18 | if r != 3 { 19 | t.Fatalf("Wrong right length, expected 3, got %d", r) 20 | } 21 | 22 | if s.Equal(0, 0) { 23 | t.Fatalf("Did not expect equal") 24 | } 25 | if s.Equal(1, 1) { 26 | t.Fatalf("Did not expect equal") 27 | } 28 | if !s.Equal(1, 2) { 29 | t.Fatalf("Expected equal") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bytes_test.go: -------------------------------------------------------------------------------- 1 | package diff_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/PieterD/diff" 7 | ) 8 | 9 | func TestBytes(t *testing.T) { 10 | s := diff.Bytes{ 11 | Left: [][]byte{[]byte("hello"), []byte("world")}, 12 | Right: [][]byte{[]byte("its"), []byte("my"), []byte("world")}, 13 | } 14 | l, r := s.Length() 15 | if l != 2 { 16 | t.Fatalf("Wrong left length, expected 2, got %d", l) 17 | } 18 | if r != 3 { 19 | t.Fatalf("Wrong right length, expected 3, got %d", r) 20 | } 21 | 22 | if s.Equal(0, 0) { 23 | t.Fatalf("Did not expect equal") 24 | } 25 | if s.Equal(1, 1) { 26 | t.Fatalf("Did not expect equal") 27 | } 28 | if !s.Equal(1, 2) { 29 | t.Fatalf("Expected equal") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package diff_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/PieterD/diff" 7 | ) 8 | 9 | func ExampleNew() { 10 | // Data 11 | l := []string{ 12 | "linea", 13 | "lineb", 14 | "linec", 15 | } 16 | r := []string{ 17 | "linea", 18 | "lineQ", 19 | "linec", 20 | } 21 | // Diff l and r using Strings 22 | d := diff.New(diff.Strings{ 23 | Left: l, 24 | Right: r, 25 | }) 26 | // Print the diff 27 | for i := range d { 28 | switch d[i].Delta { 29 | case diff.Both: 30 | fmt.Printf(" %s\n", l[d[i].Index]) 31 | case diff.Left: 32 | fmt.Printf("- %s\n", l[d[i].Index]) 33 | case diff.Right: 34 | fmt.Printf("+ %s\n", r[d[i].Index]) 35 | } 36 | } 37 | // Output: 38 | // linea 39 | // - lineb 40 | // + lineQ 41 | // linec 42 | } 43 | -------------------------------------------------------------------------------- /delta.go: -------------------------------------------------------------------------------- 1 | package diff 2 | 3 | // Describe in which collection the element occurs; Left, Right or Both. 4 | type Delta int 5 | 6 | const ( 7 | // Element is present in both Left and Right collections. 8 | // Index uses the Left collection. 9 | Both Delta = iota 10 | // Element is present only in the Left collection. 11 | // Index uses the Left collection. 12 | Left 13 | // Element is present only in the Right collection. 14 | // Index uses the Right collection. 15 | Right 16 | ) 17 | 18 | func (delta Delta) String() string { 19 | switch delta { 20 | case Both: 21 | return "Both" 22 | case Left: 23 | return "Left" 24 | case Right: 25 | return "Right" 26 | } 27 | return "unknown" 28 | } 29 | 30 | // One Diff record per element. 31 | // If Delta is Left or Both, Index is for the left collection. 32 | // If Delta is Right, Index is for the right collection. 33 | type Diff struct { 34 | Delta Delta 35 | Index int 36 | } 37 | -------------------------------------------------------------------------------- /godiff/godiff.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | 10 | "github.com/PieterD/diff" 11 | ) 12 | 13 | var ( 14 | plus = []byte("+ ") 15 | minus = []byte("- ") 16 | blank = []byte(" ") 17 | ) 18 | 19 | func main() { 20 | flag.Parse() 21 | args := flag.Args() 22 | if len(args) != 2 { 23 | fmt.Fprintf(os.Stderr, "Usage: godiff \n") 24 | os.Exit(1) 25 | } 26 | left := read(args[0]) 27 | right := read(args[1]) 28 | d := diff.New(diff.Bytes{ 29 | Left: left, 30 | Right: right, 31 | }) 32 | 33 | for i := range d { 34 | var pfx, str []byte 35 | switch d[i].Delta { 36 | case diff.Both: 37 | pfx = blank 38 | str = left[d[i].Index] 39 | case diff.Left: 40 | pfx = minus 41 | str = left[d[i].Index] 42 | case diff.Right: 43 | pfx = plus 44 | str = right[d[i].Index] 45 | } 46 | fmt.Printf("%s%s\n", pfx, str) 47 | } 48 | } 49 | 50 | func read(filename string) [][]byte { 51 | b, err := ioutil.ReadFile(filename) 52 | if err != nil { 53 | panic(err) 54 | } 55 | return bytes.Split(b, []byte("\n")) 56 | } 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Pieter Droogendijk 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 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | package diff_test 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "github.com/PieterD/diff" 8 | ) 9 | 10 | func genStr(size int, ratio float64) []byte { 11 | b := make([]byte, size) 12 | for i := range b { 13 | c := byte('a') 14 | if rand.Float64() < ratio { 15 | c = 'b' 16 | } 17 | b[i] = c 18 | } 19 | return b 20 | } 21 | 22 | var str = genStr(500, 0.5) 23 | 24 | func strPart(b []byte) []byte { 25 | i := rand.Int31n(int32(len(b))) 26 | j := i + rand.Int31n(int32(len(b))-i) 27 | return b[i:j] 28 | } 29 | 30 | func BenchmarkSame(b *testing.B) { 31 | for i := 0; i < b.N; i++ { 32 | diff.New(Bytes{[]byte(str), []byte(str)}) 33 | } 34 | } 35 | 36 | func BenchmarkNotRandom(b *testing.B) { 37 | for i := 0; i < b.N; i++ { 38 | diff.New(Bytes{[]byte("ababaaabababbababbaabbaabababaa"), []byte("aaababbbababaabababababbabababababababababababbabbbabababaaabbb")}) 39 | } 40 | } 41 | 42 | func BenchmarkRandom(b *testing.B) { 43 | for i := 0; i < b.N; i++ { 44 | a := strPart(str) 45 | b := strPart(str) 46 | diff.New(Bytes{a, b}) 47 | } 48 | } 49 | 50 | func BenchmarkLeft(b *testing.B) { 51 | for i := 0; i < b.N; i++ { 52 | left := str[250:] 53 | right := str 54 | diff.New(Bytes{left, right}) 55 | } 56 | } 57 | 58 | func BenchmarkRight(b *testing.B) { 59 | for i := 0; i < b.N; i++ { 60 | left := str 61 | right := str[250:] 62 | diff.New(Bytes{left, right}) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | diff 2 | ==== 3 | 4 | [![Coverage](http://gocover.io/_badge/github.com/PieterD/diff)](http://gocover.io/github.com/PieterD/diff) 5 | [![GoDoc](https://godoc.org/github.com/PieterD/diff?status.svg)](https://godoc.org/github.com/PieterD/diff) 6 | 7 | A small library to diff data. 8 | 9 | It will work on any kind of data, as long as it can be expressed using the following interface: 10 | 11 | type Interface interface { 12 | // Return true if the elements at the given indices in Left and Right are equal. 13 | Equal(left, right int) (isEqual bool) 14 | // Return the sizes of the Left and Right collections. 15 | Length() (left int, right int) 16 | } 17 | 18 | Pre-made implementations for strings and byte slices already exist. A simple example: 19 | 20 | // Data 21 | l := []string{ 22 | "linea", 23 | "lineb", 24 | "linec", 25 | } 26 | r := []string{ 27 | "linea", 28 | "lineQ", 29 | "linec", 30 | } 31 | // Diff l and r using Strings 32 | d := diff.New(diff.Strings{ 33 | Left: l, 34 | Right: r, 35 | }) 36 | // Print the diff 37 | for i := range d { 38 | switch d[i].Delta { 39 | case diff.Both: 40 | fmt.Printf(" %s\n", l[d[i].Index]) 41 | case diff.Left: 42 | fmt.Printf("- %s\n", l[d[i].Index]) 43 | case diff.Right: 44 | fmt.Printf("+ %s\n", r[d[i].Index]) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lcs.go: -------------------------------------------------------------------------------- 1 | package diff 2 | 3 | // Runs a diff on the given Interface. 4 | // Returns the results as a slice of Diff. 5 | func New(iface Interface) []Diff { 6 | l, r := iface.Length() 7 | diff := make([]Diff, 0, l+r) 8 | diff = snipEnd(iface, l, r, diff) 9 | l -= len(diff) 10 | r -= len(diff) 11 | if l != 0 && r != 0 { 12 | table := lcs(iface, l, r) 13 | diff = walk(iface, l, r, table, diff) 14 | } else if l != 0 { 15 | diff = remainingOneSide(iface, l, Left, diff) 16 | } else if r != 0 { 17 | diff = remainingOneSide(iface, r, Right, diff) 18 | } 19 | reverse(diff) 20 | return diff 21 | } 22 | 23 | // Handle the identical Left and Right tails. 24 | func snipEnd(iface Interface, l, r int, diff []Diff) []Diff { 25 | min := l 26 | if r < min { 27 | min = r 28 | } 29 | for i := 0; i < min; i++ { 30 | if iface.Equal(l-1-i, r-1-i) { 31 | diff = append(diff, Diff{Delta: Both, Index: l - 1 - i}) 32 | } else { 33 | break 34 | } 35 | } 36 | return diff 37 | } 38 | 39 | // Handle the rest of the diff, if one of the two collections is exhausted after snipEnd. 40 | func remainingOneSide(iface Interface, idx int, delta Delta, diff []Diff) []Diff { 41 | for i := 0; i < idx; i++ { 42 | diff = append(diff, Diff{Delta: delta, Index: idx - 1 - i}) 43 | } 44 | return diff 45 | } 46 | 47 | // Constructs a LCSLength table 48 | // http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS 49 | func lcs(iface Interface, l, r int) [][]int { 50 | lnum, rnum := l, r 51 | rows, cols := lnum+1, rnum+1 52 | table := make([][]int, rows) 53 | cels := make([]int, rows*cols) 54 | for i := 0; i < rows; i++ { 55 | table[i] = cels[:cols] 56 | cels = cels[cols:] 57 | } 58 | 59 | for i := 1; i < rows; i++ { 60 | for j := 1; j < cols; j++ { 61 | if iface.Equal(i-1, j-1) { 62 | table[i][j] = table[i-1][j-1] + 1 63 | } else { 64 | a := table[i-1][j] 65 | b := table[i][j-1] 66 | if b > a { 67 | a = b 68 | } 69 | table[i][j] = a 70 | } 71 | } 72 | } 73 | return table 74 | } 75 | 76 | // Walk the lcs table 77 | // http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Example 78 | func walk(iface Interface, l, r int, table [][]int, diff []Diff) []Diff { 79 | i, j := l, r 80 | for { 81 | if i == 0 && j == 0 { 82 | return diff 83 | } else if i == 0 { 84 | j-- 85 | diff = append(diff, Diff{Delta: Right, Index: j}) 86 | } else if j == 0 { 87 | i-- 88 | diff = append(diff, Diff{Delta: Left, Index: i}) 89 | } else { 90 | if iface.Equal(i-1, j-1) { 91 | i-- 92 | j-- 93 | diff = append(diff, Diff{Delta: Both, Index: i}) 94 | } else { 95 | if table[i-1][j] > table[i][j-1] { 96 | i-- 97 | diff = append(diff, Diff{Delta: Left, Index: i}) 98 | } else { 99 | j-- 100 | diff = append(diff, Diff{Delta: Right, Index: j}) 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | func reverse(diff []Diff) { 108 | i := 0 109 | j := len(diff) - 1 110 | for i < j { 111 | diff[i], diff[j] = diff[j], diff[i] 112 | i++ 113 | j-- 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /diff_test.go: -------------------------------------------------------------------------------- 1 | package diff_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/PieterD/diff" 7 | ) 8 | 9 | // Holds byte slices to diff, one character at a time. 10 | type Bytes struct { 11 | Left, Right []byte 12 | } 13 | 14 | func (b Bytes) Equal(left, right int) bool { 15 | return b.Left[left] == b.Right[right] 16 | } 17 | 18 | func (b Bytes) Length() (int, int) { 19 | return len(b.Left), len(b.Right) 20 | } 21 | 22 | func bytesFromString(l, r string) Bytes { 23 | return Bytes{ 24 | Left: []byte(l), 25 | Right: []byte(r), 26 | } 27 | } 28 | 29 | type testStruct struct { 30 | t *testing.T 31 | d []diff.Diff 32 | p int 33 | } 34 | 35 | func newTestStruct(t *testing.T, d []diff.Diff, size int) testStruct { 36 | if len(d) != size { 37 | t.Fatalf("Wrong size: expected %d, got %d", size, len(d)) 38 | } 39 | ts := testStruct{t, d, 0} 40 | return ts 41 | } 42 | 43 | func (ts *testStruct) expect(delta diff.Delta, index int) { 44 | if ts.p >= len(ts.d) { 45 | ts.t.Fatalf("Unexpected end") 46 | } 47 | if ts.d[ts.p].Delta != delta { 48 | ts.t.Fatalf("Wrong Delta idx %d: expected %d, got %d", ts.p, delta, ts.d[ts.p].Delta) 49 | } 50 | if ts.d[ts.p].Index != index { 51 | ts.t.Fatalf("Wrong Index idx %d: expected %d, got %d", ts.p, index, ts.d[ts.p].Index) 52 | } 53 | ts.p++ 54 | } 55 | 56 | func (ts *testStruct) end() { 57 | if ts.p < len(ts.d) { 58 | ts.t.Fatalf("Expected end") 59 | } 60 | } 61 | 62 | func TestSimple(t *testing.T) { 63 | d := diff.New(bytesFromString("abc", "b")) 64 | ts := newTestStruct(t, d, 3) 65 | ts.expect(diff.Left, 0) 66 | ts.expect(diff.Both, 1) 67 | ts.expect(diff.Left, 2) 68 | ts.end() 69 | } 70 | 71 | func TestSimple2(t *testing.T) { 72 | d := diff.New(bytesFromString("abc", "bcde")) 73 | ts := newTestStruct(t, d, 5) 74 | ts.expect(diff.Left, 0) 75 | ts.expect(diff.Both, 1) 76 | ts.expect(diff.Both, 2) 77 | ts.expect(diff.Right, 2) 78 | ts.expect(diff.Right, 3) 79 | ts.end() 80 | } 81 | 82 | func TestSimple3(t *testing.T) { 83 | d := diff.New(bytesFromString("abcxyz", "bcdeyz")) 84 | ts := newTestStruct(t, d, 8) 85 | ts.expect(diff.Left, 0) 86 | ts.expect(diff.Both, 1) 87 | ts.expect(diff.Both, 2) 88 | ts.expect(diff.Left, 3) 89 | ts.expect(diff.Right, 2) 90 | ts.expect(diff.Right, 3) 91 | ts.expect(diff.Both, 4) 92 | ts.expect(diff.Both, 5) 93 | ts.end() 94 | } 95 | 96 | func TestLeft(t *testing.T) { 97 | d := diff.New(bytesFromString("aaaaaaa", "aaaaaaaaab")) 98 | ts := newTestStruct(t, d, 10) 99 | ts.expect(diff.Right, 0) 100 | ts.expect(diff.Right, 1) 101 | ts.expect(diff.Both, 0) 102 | ts.expect(diff.Both, 1) 103 | ts.expect(diff.Both, 2) 104 | ts.expect(diff.Both, 3) 105 | ts.expect(diff.Both, 4) 106 | ts.expect(diff.Both, 5) 107 | ts.expect(diff.Both, 6) 108 | ts.expect(diff.Right, 9) 109 | ts.end() 110 | } 111 | 112 | func TestRight(t *testing.T) { 113 | d := diff.New(bytesFromString("aaaaaaaaab", "aaaaaaa")) 114 | ts := newTestStruct(t, d, 10) 115 | ts.expect(diff.Left, 0) 116 | ts.expect(diff.Left, 1) 117 | ts.expect(diff.Both, 2) 118 | ts.expect(diff.Both, 3) 119 | ts.expect(diff.Both, 4) 120 | ts.expect(diff.Both, 5) 121 | ts.expect(diff.Both, 6) 122 | ts.expect(diff.Both, 7) 123 | ts.expect(diff.Both, 8) 124 | ts.expect(diff.Left, 9) 125 | ts.end() 126 | } 127 | 128 | func TestBackLeft(t *testing.T) { 129 | d := diff.New(bytesFromString("!!!!!!aaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaa")) 130 | ts := newTestStruct(t, d, 21) 131 | ts.expect(diff.Left, 0) 132 | ts.expect(diff.Left, 1) 133 | ts.expect(diff.Left, 2) 134 | ts.expect(diff.Left, 3) 135 | ts.expect(diff.Left, 4) 136 | ts.expect(diff.Left, 5) 137 | ts.expect(diff.Both, 6) 138 | ts.expect(diff.Both, 7) 139 | ts.expect(diff.Both, 8) 140 | ts.expect(diff.Both, 9) 141 | ts.expect(diff.Both, 10) 142 | ts.expect(diff.Both, 11) 143 | ts.expect(diff.Both, 12) 144 | ts.expect(diff.Both, 13) 145 | ts.expect(diff.Both, 14) 146 | ts.expect(diff.Both, 15) 147 | ts.expect(diff.Both, 16) 148 | ts.expect(diff.Both, 17) 149 | ts.expect(diff.Both, 18) 150 | ts.expect(diff.Both, 19) 151 | ts.expect(diff.Both, 20) 152 | ts.end() 153 | } 154 | func TestBackRight(t *testing.T) { 155 | d := diff.New(bytesFromString("aaaaaaaaaaaaaaa", "!!!!!!aaaaaaaaaaaaaaa")) 156 | ts := newTestStruct(t, d, 21) 157 | ts.expect(diff.Right, 0) 158 | ts.expect(diff.Right, 1) 159 | ts.expect(diff.Right, 2) 160 | ts.expect(diff.Right, 3) 161 | ts.expect(diff.Right, 4) 162 | ts.expect(diff.Right, 5) 163 | ts.expect(diff.Both, 0) 164 | ts.expect(diff.Both, 1) 165 | ts.expect(diff.Both, 2) 166 | ts.expect(diff.Both, 3) 167 | ts.expect(diff.Both, 4) 168 | ts.expect(diff.Both, 5) 169 | ts.expect(diff.Both, 6) 170 | ts.expect(diff.Both, 7) 171 | ts.expect(diff.Both, 8) 172 | ts.expect(diff.Both, 9) 173 | ts.expect(diff.Both, 10) 174 | ts.expect(diff.Both, 11) 175 | ts.expect(diff.Both, 12) 176 | ts.expect(diff.Both, 13) 177 | ts.expect(diff.Both, 14) 178 | ts.end() 179 | } 180 | --------------------------------------------------------------------------------