github.com/GoogleCloudPlatform/testgrid@v0.0.174/cmd/state_comparer/main_test.go (about) 1 /* 2 Copyright 2021 The TestGrid Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // A script to quickly check two TestGrid state.protos do not wildly differ. 18 // Assume that if the column and row names are approx. equivalent, the state 19 // is probably reasonable. 20 21 package main 22 23 import ( 24 "context" 25 "strings" 26 "testing" 27 28 "github.com/GoogleCloudPlatform/testgrid/util/gcs" 29 30 statepb "github.com/GoogleCloudPlatform/testgrid/pb/state" 31 ) 32 33 func newPathOrDie(s string) gcs.Path { 34 p, err := gcs.NewPath(s) 35 if err != nil { 36 panic(err) 37 } 38 return *p 39 } 40 41 func TestValidate(t *testing.T) { 42 cases := []struct { 43 name string 44 first gcs.Path 45 second gcs.Path 46 diffRatioOK float64 47 err bool 48 }{ 49 { 50 name: "empty paths, error", 51 first: newPathOrDie(""), 52 second: newPathOrDie(""), 53 err: true, 54 }, 55 { 56 name: "empty first path, error", 57 first: newPathOrDie(""), 58 second: newPathOrDie("gs://path/to/second"), 59 err: true, 60 }, 61 { 62 name: "empty second path, error", 63 first: newPathOrDie("gs://path/to/first"), 64 second: newPathOrDie(""), 65 err: true, 66 }, 67 { 68 name: "paths basically work", 69 first: newPathOrDie("gs://path/to/first"), 70 second: newPathOrDie("gs://path/to/second"), 71 }, 72 { 73 name: "reject negative ratio", 74 first: newPathOrDie("gs://path/to/first"), 75 second: newPathOrDie("gs://path/to/second"), 76 diffRatioOK: -0.5, 77 err: true, 78 }, 79 { 80 name: "reject ratio over 1.0", 81 first: newPathOrDie("gs://path/to/first"), 82 second: newPathOrDie("gs://path/to/second"), 83 diffRatioOK: 1.5, 84 err: true, 85 }, 86 { 87 name: "ratio basically works", 88 first: newPathOrDie("gs://path/to/first"), 89 second: newPathOrDie("gs://path/to/second"), 90 diffRatioOK: 0.5, 91 }, 92 } 93 94 for _, tc := range cases { 95 t.Run(tc.name, func(t *testing.T) { 96 opt := options{ 97 first: tc.first, 98 second: tc.second, 99 diffRatioOK: tc.diffRatioOK, 100 } 101 err := opt.validate() 102 if tc.err && err == nil { 103 t.Fatalf("validate() (%v), expected error but got none", opt) 104 } 105 if !tc.err { 106 if err != nil { 107 t.Fatalf("validate() (%v) got unexpected error %v", opt, err) 108 } 109 // Also check that paths end in a '/'. 110 if !strings.HasSuffix(opt.first.String(), "/") { 111 t.Errorf("first path %q should end in '/'.", tc.first.String()) 112 } 113 if !strings.HasSuffix(opt.second.String(), "/") { 114 t.Errorf("second path %q should end in '/'.", tc.second.String()) 115 } 116 } 117 }) 118 } 119 } 120 121 func TestCompare(t *testing.T) { 122 cases := []struct { 123 name string 124 first *statepb.Grid 125 second *statepb.Grid 126 diffRatioOK float64 127 diffed bool 128 }{ 129 { 130 name: "nil grids, same", 131 diffed: false, 132 }, 133 { 134 name: "empty grids, same", 135 first: &statepb.Grid{}, 136 second: &statepb.Grid{}, 137 diffed: false, 138 }, 139 { 140 name: "basic grids, same", 141 first: &statepb.Grid{ 142 Rows: []*statepb.Row{ 143 {Name: "row1"}, 144 {Name: "row2"}, 145 }, 146 Columns: []*statepb.Column{ 147 {Build: "col1"}, 148 {Build: "col2"}, 149 }, 150 }, 151 second: &statepb.Grid{ 152 Rows: []*statepb.Row{ 153 {Name: "row1"}, 154 {Name: "row2"}, 155 }, 156 Columns: []*statepb.Column{ 157 {Build: "col1"}, 158 {Build: "col2"}, 159 }, 160 }, 161 diffed: false, 162 }, 163 { 164 name: "different rows, diff", 165 first: &statepb.Grid{ 166 Rows: []*statepb.Row{ 167 {Name: "row1"}, 168 }, 169 Columns: []*statepb.Column{ 170 {Build: "col1"}, 171 {Build: "col2"}, 172 }, 173 }, 174 second: &statepb.Grid{ 175 Rows: []*statepb.Row{ 176 {Name: "row2"}, 177 }, 178 Columns: []*statepb.Column{ 179 {Build: "col1"}, 180 {Build: "col2"}, 181 }, 182 }, 183 diffed: true, 184 }, 185 { 186 name: "different rows lower than ratio, same", 187 first: &statepb.Grid{ 188 Rows: []*statepb.Row{ 189 {Name: "row1"}, 190 {Name: "row2"}, 191 }, 192 Columns: []*statepb.Column{ 193 {Build: "col1"}, 194 {Build: "col2"}, 195 }, 196 }, 197 second: &statepb.Grid{ 198 Rows: []*statepb.Row{ 199 {Name: "row1"}, 200 }, 201 Columns: []*statepb.Column{ 202 {Build: "col1"}, 203 {Build: "col2"}, 204 }, 205 }, 206 diffRatioOK: 0.6, 207 }, 208 { 209 name: "different columns, diff", 210 first: &statepb.Grid{ 211 Rows: []*statepb.Row{ 212 {Name: "row1"}, 213 {Name: "row2"}, 214 }, 215 Columns: []*statepb.Column{ 216 {Build: "col1"}, 217 {Build: "col2"}, 218 }, 219 }, 220 second: &statepb.Grid{ 221 Rows: []*statepb.Row{ 222 {Name: "row1"}, 223 {Name: "row2"}, 224 }, 225 Columns: []*statepb.Column{ 226 {Build: "col3"}, 227 {Build: "col4"}, 228 }, 229 }, 230 diffed: true, 231 }, 232 { 233 name: "different columns lower than ratio, same", 234 first: &statepb.Grid{ 235 Rows: []*statepb.Row{ 236 {Name: "row1"}, 237 {Name: "row2"}, 238 }, 239 Columns: []*statepb.Column{ 240 {Build: "col1"}, 241 {Build: "col2"}, 242 }, 243 }, 244 second: &statepb.Grid{ 245 Rows: []*statepb.Row{ 246 {Name: "row1"}, 247 {Name: "row2"}, 248 }, 249 Columns: []*statepb.Column{ 250 {Build: "col1"}, 251 }, 252 }, 253 diffRatioOK: 0.6, 254 diffed: false, 255 }, 256 { 257 name: "different grids, diff", 258 first: &statepb.Grid{ 259 Rows: []*statepb.Row{ 260 {Name: "row1"}, 261 {Name: "row2"}, 262 }, 263 Columns: []*statepb.Column{ 264 {Build: "col1"}, 265 {Build: "col2"}, 266 }, 267 }, 268 second: &statepb.Grid{ 269 Rows: []*statepb.Row{ 270 {Name: "row1"}, 271 }, 272 Columns: []*statepb.Column{ 273 {Build: "col1"}, 274 }, 275 }, 276 diffed: true, 277 }, 278 { 279 name: "different grids lower than ratio, same", 280 first: &statepb.Grid{ 281 Rows: []*statepb.Row{ 282 {Name: "row1"}, 283 {Name: "row2"}, 284 }, 285 Columns: []*statepb.Column{ 286 {Build: "col1"}, 287 {Build: "col2"}, 288 }, 289 }, 290 second: &statepb.Grid{ 291 Rows: []*statepb.Row{ 292 {Name: "row1"}, 293 }, 294 Columns: []*statepb.Column{ 295 {Build: "col1"}, 296 }, 297 }, 298 diffRatioOK: 0.6, 299 diffed: false, 300 }, 301 { 302 name: "diff cols but empty rows, same", 303 first: &statepb.Grid{ 304 Rows: []*statepb.Row{}, 305 Columns: []*statepb.Column{ 306 {Build: "col1"}, 307 }, 308 }, 309 second: &statepb.Grid{ 310 Rows: []*statepb.Row{}, 311 Columns: []*statepb.Column{ 312 {Build: "col1"}, 313 {Build: "col2"}, 314 }, 315 }, 316 diffed: false, 317 }, 318 { 319 name: "diff rows but empty cols, same", 320 first: &statepb.Grid{ 321 Rows: []*statepb.Row{ 322 {Name: "row1"}, 323 {Name: "row2"}, 324 }, 325 Columns: []*statepb.Column{}, 326 }, 327 second: &statepb.Grid{ 328 Rows: []*statepb.Row{ 329 {Name: "row1"}, 330 }, 331 Columns: []*statepb.Column{}, 332 }, 333 diffed: false, 334 }, 335 { 336 name: "first empty, second has one column, same", 337 first: &statepb.Grid{ 338 Rows: []*statepb.Row{}, 339 Columns: []*statepb.Column{}, 340 }, 341 second: &statepb.Grid{ 342 Rows: []*statepb.Row{ 343 {Name: "row1"}, 344 {Name: "row2"}, 345 {Name: "row3"}, 346 }, 347 Columns: []*statepb.Column{ 348 {Build: "col1"}, 349 }, 350 }, 351 diffed: false, 352 }, 353 } 354 355 for _, tc := range cases { 356 t.Run(tc.name, func(t *testing.T) { 357 ctx := context.Background() 358 if diffed, _, _ := compare(ctx, tc.first, tc.second, tc.diffRatioOK, 5); diffed != tc.diffed { 359 t.Errorf("compare(%s, %s) not as expected; got %t, want %t", tc.first, tc.second, diffed, tc.diffed) 360 } 361 }) 362 } 363 } 364 365 func TestReportDiff(t *testing.T) { 366 cases := []struct { 367 name string 368 first map[string]int 369 second map[string]int 370 diffRatioOK float64 371 diffed bool 372 reasons diffReasons 373 }{ 374 { 375 name: "nil", 376 diffed: false, 377 }, 378 { 379 name: "same", 380 first: map[string]int{ 381 "some": 1, 382 "more": 2, 383 }, 384 second: map[string]int{ 385 "some": 1, 386 "more": 2, 387 }, 388 diffed: false, 389 }, 390 { 391 name: "different", 392 first: map[string]int{ 393 "some": 1, 394 "more": 1, 395 }, 396 second: map[string]int{ 397 "hi": 1, 398 "hello": 1, 399 "yay": 1, 400 }, 401 diffed: true, 402 reasons: diffReasons{other: true}, 403 }, 404 { 405 name: "different above ratio", 406 first: map[string]int{ 407 "some": 1, 408 "more": 1, 409 }, 410 second: map[string]int{ 411 "some": 1, 412 "more": 1, 413 "hi": 1, 414 }, 415 diffRatioOK: 0.3, 416 diffed: true, 417 reasons: diffReasons{other: true}, 418 }, 419 { 420 name: "different below ratio", 421 first: map[string]int{ 422 "some": 1, 423 "more": 1, 424 }, 425 second: map[string]int{ 426 "some": 1, 427 "more": 1, 428 "hi": 1, 429 }, 430 diffRatioOK: 0.6, 431 diffed: false, 432 reasons: diffReasons{}, 433 }, 434 { 435 name: "first has duplicates", 436 first: map[string]int{ 437 "some": 3, 438 "more": 5, 439 }, 440 second: map[string]int{ 441 "some": 1, 442 "more": 1, 443 }, 444 diffed: true, 445 reasons: diffReasons{firstHasDuplicates: true}, 446 }, 447 { 448 name: "second has duplicates", 449 first: map[string]int{ 450 "some": 1, 451 "more": 1, 452 }, 453 second: map[string]int{ 454 "some": 3, 455 "more": 5, 456 }, 457 diffed: true, 458 reasons: diffReasons{secondHasDuplicates: true}, 459 }, 460 { 461 name: "first has duplicates below ratio", 462 first: map[string]int{ 463 "some": 1, 464 "more": 2, 465 }, 466 second: map[string]int{ 467 "some": 1, 468 "more": 1, 469 }, 470 diffRatioOK: 0.6, 471 diffed: false, 472 reasons: diffReasons{}, 473 }, 474 { 475 name: "second has duplicates below ratio", 476 first: map[string]int{ 477 "some": 1, 478 "more": 1, 479 }, 480 second: map[string]int{ 481 "some": 1, 482 "more": 2, 483 }, 484 diffRatioOK: 0.6, 485 diffed: false, 486 reasons: diffReasons{}, 487 }, 488 { 489 name: "first has duplicates above ratio", 490 first: map[string]int{ 491 "some": 2, 492 "more": 2, 493 }, 494 second: map[string]int{ 495 "some": 1, 496 "more": 1, 497 }, 498 diffRatioOK: 0.3, 499 diffed: true, 500 reasons: diffReasons{firstHasDuplicates: true}, 501 }, 502 { 503 name: "second has duplicates above ratio", 504 first: map[string]int{ 505 "some": 1, 506 "more": 1, 507 }, 508 second: map[string]int{ 509 "some": 2, 510 "more": 2, 511 }, 512 diffRatioOK: 0.3, 513 diffed: true, 514 reasons: diffReasons{secondHasDuplicates: true}, 515 }, 516 { 517 name: "second has duplicates and differences above ratio", 518 first: map[string]int{ 519 "some": 1, 520 "more": 1, 521 }, 522 second: map[string]int{ 523 "some": 2, 524 "more": 2, 525 "hi": 5, 526 }, 527 diffRatioOK: 0.3, 528 diffed: true, 529 reasons: diffReasons{other: true}, 530 }, 531 } 532 533 for _, tc := range cases { 534 t.Run(tc.name, func(t *testing.T) { 535 diffed, reasons := reportDiff(tc.first, tc.second, "thing", tc.diffRatioOK) 536 if diffed != tc.diffed { 537 t.Errorf("reportDiff(%v, %v, %f) diffed wrong; got %t, want %t", tc.first, tc.second, tc.diffRatioOK, diffed, tc.diffed) 538 } 539 if reasons != tc.reasons { 540 t.Errorf("reportDiff(%v, %v, %f) has wrong diff reasons; got %t, want %t", tc.first, tc.second, tc.diffRatioOK, reasons, tc.reasons) 541 } 542 }) 543 } 544 } 545 546 func TestCompareKeys(t *testing.T) { 547 cases := []struct { 548 name string 549 first map[string]int 550 second map[string]int 551 diffRatioOK float64 552 diffed bool 553 }{ 554 { 555 name: "nil", 556 diffed: false, 557 }, 558 { 559 name: "empty", 560 first: map[string]int{}, 561 second: map[string]int{}, 562 diffed: false, 563 }, 564 { 565 name: "maps match", 566 first: map[string]int{ 567 "some": 1, 568 "more": 2, 569 }, 570 second: map[string]int{ 571 "some": 1, 572 "more": 2, 573 }, 574 diffed: false, 575 }, 576 { 577 name: "non-matching keys", 578 first: map[string]int{ 579 "some": 5, 580 "more": 3, 581 }, 582 second: map[string]int{ 583 "hi": 1, 584 "hello": 1, 585 "other": 1, 586 }, 587 diffed: true, 588 }, 589 { 590 name: "duplicate keys", 591 first: map[string]int{ 592 "some": 5, 593 "more": 3, 594 }, 595 second: map[string]int{ 596 "some": 1, 597 "more": 1, 598 }, 599 diffed: false, 600 }, 601 { 602 name: "mostly duplicate keys below ratio", 603 first: map[string]int{ 604 "some": 5, 605 "more": 3, 606 }, 607 second: map[string]int{ 608 "some": 1, 609 "more": 1, 610 "hi": 1, 611 }, 612 diffRatioOK: 0.6, 613 diffed: false, 614 }, 615 { 616 name: "mostly duplicate keys above ratio", 617 first: map[string]int{ 618 "some": 5, 619 "more": 3, 620 }, 621 second: map[string]int{ 622 "some": 1, 623 "more": 1, 624 "hi": 1, 625 }, 626 diffRatioOK: 0.3, 627 diffed: true, 628 }, 629 } 630 for _, tc := range cases { 631 t.Run(tc.name, func(t *testing.T) { 632 if diffed := compareKeys(tc.first, tc.second, tc.diffRatioOK); diffed != tc.diffed { 633 t.Errorf("compareKeys(%v, %v, %f) not as expected; got %t, want %t", tc.first, tc.second, tc.diffRatioOK, diffed, tc.diffed) 634 } 635 }) 636 } 637 }