github.com/integration-system/go-cmp@v0.0.0-20190131081942-ac5582987a2f/cmp/internal/diff/diff_test.go (about) 1 // Copyright 2017, The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE.md file. 4 5 package diff 6 7 import ( 8 "fmt" 9 "math/rand" 10 "strings" 11 "testing" 12 "unicode" 13 ) 14 15 func TestDifference(t *testing.T) { 16 tests := []struct { 17 // Before passing x and y to Difference, we strip all spaces so that 18 // they can be used by the test author to indicate a missing symbol 19 // in one of the lists. 20 x, y string 21 want string 22 }{{ 23 x: "", 24 y: "", 25 want: "", 26 }, { 27 x: "#", 28 y: "#", 29 want: ".", 30 }, { 31 x: "##", 32 y: "# ", 33 want: ".X", 34 }, { 35 x: "a#", 36 y: "A ", 37 want: "MX", 38 }, { 39 x: "#a", 40 y: " A", 41 want: "XM", 42 }, { 43 x: "# ", 44 y: "##", 45 want: ".Y", 46 }, { 47 x: " #", 48 y: "@#", 49 want: "Y.", 50 }, { 51 x: "@#", 52 y: " #", 53 want: "X.", 54 }, { 55 x: "##########0123456789", 56 y: " 0123456789", 57 want: "XXXXXXXXXX..........", 58 }, { 59 x: " 0123456789", 60 y: "##########0123456789", 61 want: "YYYYYYYYYY..........", 62 }, { 63 x: "#####0123456789#####", 64 y: " 0123456789 ", 65 want: "XXXXX..........XXXXX", 66 }, { 67 x: " 0123456789 ", 68 y: "#####0123456789#####", 69 want: "YYYYY..........YYYYY", 70 }, { 71 x: "01234##########56789", 72 y: "01234 56789", 73 want: ".....XXXXXXXXXX.....", 74 }, { 75 x: "01234 56789", 76 y: "01234##########56789", 77 want: ".....YYYYYYYYYY.....", 78 }, { 79 x: "0123456789##########", 80 y: "0123456789 ", 81 want: "..........XXXXXXXXXX", 82 }, { 83 x: "0123456789 ", 84 y: "0123456789##########", 85 want: "..........YYYYYYYYYY", 86 }, { 87 x: "abcdefghij0123456789", 88 y: "ABCDEFGHIJ0123456789", 89 want: "MMMMMMMMMM..........", 90 }, { 91 x: "ABCDEFGHIJ0123456789", 92 y: "abcdefghij0123456789", 93 want: "MMMMMMMMMM..........", 94 }, { 95 x: "01234abcdefghij56789", 96 y: "01234ABCDEFGHIJ56789", 97 want: ".....MMMMMMMMMM.....", 98 }, { 99 x: "01234ABCDEFGHIJ56789", 100 y: "01234abcdefghij56789", 101 want: ".....MMMMMMMMMM.....", 102 }, { 103 x: "0123456789abcdefghij", 104 y: "0123456789ABCDEFGHIJ", 105 want: "..........MMMMMMMMMM", 106 }, { 107 x: "0123456789ABCDEFGHIJ", 108 y: "0123456789abcdefghij", 109 want: "..........MMMMMMMMMM", 110 }, { 111 x: "ABCDEFGHIJ0123456789 ", 112 y: " 0123456789abcdefghij", 113 want: "XXXXXXXXXX..........YYYYYYYYYY", 114 }, { 115 x: " 0123456789abcdefghij", 116 y: "ABCDEFGHIJ0123456789 ", 117 want: "YYYYYYYYYY..........XXXXXXXXXX", 118 }, { 119 x: "ABCDE0123456789 FGHIJ", 120 y: " 0123456789abcdefghij", 121 want: "XXXXX..........YYYYYMMMMM", 122 }, { 123 x: " 0123456789abcdefghij", 124 y: "ABCDE0123456789 FGHIJ", 125 want: "YYYYY..........XXXXXMMMMM", 126 }, { 127 x: "ABCDE01234F G H I J 56789 ", 128 y: " 01234 a b c d e56789fghij", 129 want: "XXXXX.....XYXYXYXYXY.....YYYYY", 130 }, { 131 x: " 01234a b c d e 56789fghij", 132 y: "ABCDE01234 F G H I J56789 ", 133 want: "YYYYY.....XYXYXYXYXY.....XXXXX", 134 }, { 135 x: "FGHIJ01234ABCDE56789 ", 136 y: " 01234abcde56789fghij", 137 want: "XXXXX.....MMMMM.....YYYYY", 138 }, { 139 x: " 01234abcde56789fghij", 140 y: "FGHIJ01234ABCDE56789 ", 141 want: "YYYYY.....MMMMM.....XXXXX", 142 }, { 143 x: "ABCAB BA ", 144 y: " C BABAC", 145 want: "XX.X.Y..Y", 146 }, { 147 x: "# #### ###", 148 y: "#y####yy###", 149 want: ".Y....YY...", 150 }, { 151 x: "# #### # ##x#x", 152 y: "#y####y y## # ", 153 want: ".Y....YXY..X.X", 154 }, { 155 x: "###z#z###### x #", 156 y: "#y##Z#Z###### yy#", 157 want: ".Y..M.M......XYY.", 158 }, { 159 x: "0 12z3x 456789 x x 0", 160 y: "0y12Z3 y456789y y y0", 161 want: ".Y..M.XY......YXYXY.", 162 }, { 163 x: "0 2 4 6 8 ..................abXXcdEXF.ghXi", 164 y: " 1 3 5 7 9..................AB CDE F.GH I", 165 want: "XYXYXYXYXY..................MMXXMM.X..MMXM", 166 }, { 167 x: "I HG.F EDC BA..................9 7 5 3 1 ", 168 y: "iXhg.FXEdcXXba.................. 8 6 4 2 0", 169 want: "MYMM..Y.MMYYMM..................XYXYXYXYXY", 170 }, { 171 x: "x1234", 172 y: " 1234", 173 want: "X....", 174 }, { 175 x: "x123x4", 176 y: " 123 4", 177 want: "X...X.", 178 }, { 179 x: "x1234x56", 180 y: " 1234 ", 181 want: "X....XXX", 182 }, { 183 x: "x1234xxx56", 184 y: " 1234 56", 185 want: "X....XXX..", 186 }, { 187 x: ".1234...ab", 188 y: " 1234 AB", 189 want: "X....XXXMM", 190 }, { 191 x: "x1234xxab.", 192 y: " 1234 AB ", 193 want: "X....XXMMX", 194 }, { 195 x: " 0123456789", 196 y: "9012345678 ", 197 want: "Y.........X", 198 }, { 199 x: " 0123456789", 200 y: "8901234567 ", 201 want: "YY........XX", 202 }, { 203 x: " 0123456789", 204 y: "7890123456 ", 205 want: "YYY.......XXX", 206 }, { 207 x: " 0123456789", 208 y: "6789012345 ", 209 want: "YYYY......XXXX", 210 }, { 211 x: "0123456789 ", 212 y: " 5678901234", 213 want: "XXXXX.....YYYYY", 214 }, { 215 x: "0123456789 ", 216 y: " 4567890123", 217 want: "XXXX......YYYY", 218 }, { 219 x: "0123456789 ", 220 y: " 3456789012", 221 want: "XXX.......YYY", 222 }, { 223 x: "0123456789 ", 224 y: " 2345678901", 225 want: "XX........YY", 226 }, { 227 x: "0123456789 ", 228 y: " 1234567890", 229 want: "X.........Y", 230 }, { 231 x: "0 1 2 3 45 6 7 8 9 ", 232 y: " 9 8 7 6 54 3 2 1 0", 233 want: "XYXYXYXYX.YXYXYXYXY", 234 }, { 235 x: "0 1 2345678 9 ", 236 y: " 6 72 5 819034", 237 want: "XYXY.XX.XX.Y.YYY", 238 }, { 239 x: "F B Q M O I G T L N72X90 E 4S P 651HKRJU DA 83CVZW", 240 y: " 5 W H XO10R9IV K ZLCTAJ8P3N SEQM4 7 2G6 UBD F ", 241 want: "XYXYXYXY.YYYY.YXYXY.YYYYYYY.XXXXXY.YY.XYXYY.XXXXXX.Y.XYXXXXXX", 242 }} 243 244 for _, tt := range tests { 245 t.Run("", func(t *testing.T) { 246 x := strings.Replace(tt.x, " ", "", -1) 247 y := strings.Replace(tt.y, " ", "", -1) 248 es := testStrings(t, x, y) 249 if got := es.String(); got != tt.want { 250 t.Errorf("Difference(%s, %s):\ngot %s\nwant %s", x, y, got, tt.want) 251 } 252 }) 253 } 254 } 255 256 func TestDifferenceFuzz(t *testing.T) { 257 tests := []struct{ px, py, pm float32 }{ 258 {px: 0.0, py: 0.0, pm: 0.1}, 259 {px: 0.0, py: 0.1, pm: 0.0}, 260 {px: 0.1, py: 0.0, pm: 0.0}, 261 {px: 0.0, py: 0.1, pm: 0.1}, 262 {px: 0.1, py: 0.0, pm: 0.1}, 263 {px: 0.2, py: 0.2, pm: 0.2}, 264 {px: 0.3, py: 0.1, pm: 0.2}, 265 {px: 0.1, py: 0.3, pm: 0.2}, 266 {px: 0.2, py: 0.2, pm: 0.2}, 267 {px: 0.3, py: 0.3, pm: 0.3}, 268 {px: 0.1, py: 0.1, pm: 0.5}, 269 {px: 0.4, py: 0.1, pm: 0.5}, 270 {px: 0.3, py: 0.2, pm: 0.5}, 271 {px: 0.2, py: 0.3, pm: 0.5}, 272 {px: 0.1, py: 0.4, pm: 0.5}, 273 } 274 275 for i, tt := range tests { 276 t.Run(fmt.Sprintf("P%d", i), func(t *testing.T) { 277 // Sweep from 1B to 1KiB. 278 for n := 1; n <= 1024; n <<= 1 { 279 t.Run(fmt.Sprintf("N%d", n), func(t *testing.T) { 280 for j := 0; j < 10; j++ { 281 x, y := generateStrings(n, tt.px, tt.py, tt.pm, int64(j)) 282 testStrings(t, x, y) 283 } 284 }) 285 } 286 }) 287 } 288 } 289 290 func BenchmarkDifference(b *testing.B) { 291 for n := 1 << 10; n <= 1<<20; n <<= 2 { 292 b.Run(fmt.Sprintf("N%d", n), func(b *testing.B) { 293 x, y := generateStrings(n, 0.05, 0.05, 0.10, 0) 294 b.ReportAllocs() 295 b.SetBytes(int64(len(x) + len(y))) 296 for i := 0; i < b.N; i++ { 297 Difference(len(x), len(y), func(ix, iy int) Result { 298 return compareByte(x[ix], y[iy]) 299 }) 300 } 301 }) 302 } 303 } 304 305 func generateStrings(n int, px, py, pm float32, seed int64) (string, string) { 306 if px+py+pm > 1.0 { 307 panic("invalid probabilities") 308 } 309 py += px 310 pm += py 311 312 b := make([]byte, n) 313 r := rand.New(rand.NewSource(seed)) 314 r.Read(b) 315 316 var x, y []byte 317 for len(b) > 0 { 318 switch p := r.Float32(); { 319 case p < px: // UniqueX 320 x = append(x, b[0]) 321 case p < py: // UniqueY 322 y = append(y, b[0]) 323 case p < pm: // Modified 324 x = append(x, 'A'+(b[0]%26)) 325 y = append(y, 'a'+(b[0]%26)) 326 default: // Identity 327 x = append(x, b[0]) 328 y = append(y, b[0]) 329 } 330 b = b[1:] 331 } 332 return string(x), string(y) 333 } 334 335 func testStrings(t *testing.T, x, y string) EditScript { 336 es := Difference(len(x), len(y), func(ix, iy int) Result { 337 return compareByte(x[ix], y[iy]) 338 }) 339 if es.LenX() != len(x) { 340 t.Errorf("es.LenX = %d, want %d", es.LenX(), len(x)) 341 } 342 if es.LenY() != len(y) { 343 t.Errorf("es.LenY = %d, want %d", es.LenY(), len(y)) 344 } 345 if !validateScript(x, y, es) { 346 t.Errorf("invalid edit script: %v", es) 347 } 348 return es 349 } 350 351 func validateScript(x, y string, es EditScript) bool { 352 var bx, by []byte 353 for _, e := range es { 354 switch e { 355 case Identity: 356 if !compareByte(x[len(bx)], y[len(by)]).Equal() { 357 return false 358 } 359 bx = append(bx, x[len(bx)]) 360 by = append(by, y[len(by)]) 361 case UniqueX: 362 bx = append(bx, x[len(bx)]) 363 case UniqueY: 364 by = append(by, y[len(by)]) 365 case Modified: 366 if !compareByte(x[len(bx)], y[len(by)]).Similar() { 367 return false 368 } 369 bx = append(bx, x[len(bx)]) 370 by = append(by, y[len(by)]) 371 } 372 } 373 return string(bx) == x && string(by) == y 374 } 375 376 // compareByte returns a Result where the result is Equal if x == y, 377 // similar if x and y differ only in casing, and different otherwise. 378 func compareByte(x, y byte) (r Result) { 379 switch { 380 case x == y: 381 return equalResult // Identity 382 case unicode.ToUpper(rune(x)) == unicode.ToUpper(rune(y)): 383 return similarResult // Modified 384 default: 385 return differentResult // UniqueX or UniqueY 386 } 387 } 388 389 var ( 390 equalResult = Result{NDiff: 0} 391 similarResult = Result{NDiff: 1} 392 differentResult = Result{NDiff: 2} 393 ) 394 395 func TestResult(t *testing.T) { 396 tests := []struct { 397 result Result 398 wantEqual bool 399 wantSimilar bool 400 }{ 401 // equalResult is equal since NDiff == 0, by definition of Equal method. 402 {equalResult, true, true}, 403 // similarResult is similar since it is a binary result where only one 404 // element was compared (i.e., Either NSame==1 or NDiff==1). 405 {similarResult, false, true}, 406 // differentResult is different since there are enough differences that 407 // it isn't even considered similar. 408 {differentResult, false, false}, 409 410 // Zero value is always equal. 411 {Result{NSame: 0, NDiff: 0}, true, true}, 412 413 // Binary comparisons (where NSame+NDiff == 1) are always similar. 414 {Result{NSame: 1, NDiff: 0}, true, true}, 415 {Result{NSame: 0, NDiff: 1}, false, true}, 416 417 // More complex ratios. The exact ratio for similarity may change, 418 // and may require updates to these test cases. 419 {Result{NSame: 1, NDiff: 1}, false, true}, 420 {Result{NSame: 1, NDiff: 2}, false, true}, 421 {Result{NSame: 1, NDiff: 3}, false, false}, 422 {Result{NSame: 2, NDiff: 1}, false, true}, 423 {Result{NSame: 2, NDiff: 2}, false, true}, 424 {Result{NSame: 2, NDiff: 3}, false, true}, 425 {Result{NSame: 3, NDiff: 1}, false, true}, 426 {Result{NSame: 3, NDiff: 2}, false, true}, 427 {Result{NSame: 3, NDiff: 3}, false, true}, 428 {Result{NSame: 1000, NDiff: 0}, true, true}, 429 {Result{NSame: 1000, NDiff: 1}, false, true}, 430 {Result{NSame: 1000, NDiff: 2}, false, true}, 431 {Result{NSame: 0, NDiff: 1000}, false, false}, 432 {Result{NSame: 1, NDiff: 1000}, false, false}, 433 {Result{NSame: 2, NDiff: 1000}, false, false}, 434 } 435 436 for _, tt := range tests { 437 if got := tt.result.Equal(); got != tt.wantEqual { 438 t.Errorf("%#v.Equal() = %v, want %v", tt.result, got, tt.wantEqual) 439 } 440 if got := tt.result.Similar(); got != tt.wantSimilar { 441 t.Errorf("%#v.Similar() = %v, want %v", tt.result, got, tt.wantSimilar) 442 } 443 } 444 }