github.com/GoogleCloudPlatform/testgrid@v0.0.174/pkg/api/v1/state_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 package v1 18 19 import ( 20 "context" 21 "reflect" 22 "testing" 23 24 apipb "github.com/GoogleCloudPlatform/testgrid/pb/api/v1" 25 26 pb "github.com/GoogleCloudPlatform/testgrid/pb/config" 27 28 statepb "github.com/GoogleCloudPlatform/testgrid/pb/state" 29 "github.com/google/go-cmp/cmp" 30 "google.golang.org/protobuf/testing/protocmp" 31 "google.golang.org/protobuf/types/known/timestamppb" 32 ) 33 34 func TestDecodeRLE(t *testing.T) { 35 tests := []struct { 36 name string 37 encodedData []int32 38 expected []int32 39 }{ 40 { 41 name: "returns empty result if empty encoded data", 42 encodedData: []int32{}, 43 expected: []int32{}, 44 }, 45 { 46 name: "returns empty result if not valid encoded data", 47 encodedData: []int32{1, 3, 4}, 48 expected: []int32{}, 49 }, 50 { 51 name: "returns correct decoded result if valid encoded data", 52 encodedData: []int32{1, 2, 3, 4}, 53 expected: []int32{1, 1, 3, 3, 3, 3}, 54 }, 55 { 56 name: "returns empty result if null encoded data", 57 expected: []int32{}, 58 }, 59 } 60 61 for _, test := range tests { 62 t.Run(test.name, func(t *testing.T) { 63 result := decodeRLE(test.encodedData) 64 if (test.expected == nil && result != nil) || len(result) != len(test.expected) || (len(test.expected) != 0 && !reflect.DeepEqual(result, test.expected)) { 65 t.Errorf("Want %q, but got %q", test.expected, result) 66 } 67 }) 68 } 69 } 70 71 func TestListHeaders(t *testing.T) { 72 tests := []struct { 73 name string 74 config map[string]*pb.Configuration 75 grid map[string]*statepb.Grid 76 req *apipb.ListHeadersRequest 77 want *apipb.ListHeadersResponse 78 expectError bool 79 }{ 80 { 81 name: "Returns an error when there's no dashboard resource", 82 config: map[string]*pb.Configuration{ 83 "gs://default/config": {}, 84 }, 85 req: &apipb.ListHeadersRequest{ 86 Dashboard: "missing", 87 Tab: "some tab", 88 }, 89 expectError: true, 90 }, 91 { 92 name: "Returns an error when there's no tab resource", 93 config: map[string]*pb.Configuration{ 94 "gs://default/config": { 95 Dashboards: []*pb.Dashboard{ 96 { 97 Name: "Dashboard1", 98 DashboardTab: []*pb.DashboardTab{}, 99 }, 100 }, 101 }, 102 }, 103 req: &apipb.ListHeadersRequest{ 104 Dashboard: "dashboard1", 105 Tab: "missing", 106 }, 107 expectError: true, 108 }, 109 { 110 name: "Returns empty headers list from a tab", 111 config: map[string]*pb.Configuration{ 112 "gs://default/config": { 113 Dashboards: []*pb.Dashboard{ 114 { 115 Name: "Dashboard1", 116 DashboardTab: []*pb.DashboardTab{ 117 { 118 Name: "tab 1", 119 TestGroupName: "testgroupname", 120 }, 121 }, 122 }, 123 }, 124 }, 125 }, 126 grid: map[string]*statepb.Grid{ 127 "gs://default/tabs/Dashboard1/tab%201": {}, 128 }, 129 req: &apipb.ListHeadersRequest{ 130 Dashboard: "Dashboard1", 131 Tab: "tab 1", 132 }, 133 want: &apipb.ListHeadersResponse{}, 134 }, 135 { 136 name: "Returns correct headers from a tab", 137 config: map[string]*pb.Configuration{ 138 "gs://default/config": { 139 Dashboards: []*pb.Dashboard{ 140 { 141 Name: "Dashboard1", 142 DashboardTab: []*pb.DashboardTab{ 143 { 144 Name: "tab 1", 145 TestGroupName: "testgroupname", 146 }, 147 }, 148 }, 149 }, 150 }, 151 }, 152 grid: map[string]*statepb.Grid{ 153 "gs://default/tabs/Dashboard1/tab%201": { 154 Columns: []*statepb.Column{ 155 { 156 Build: "99", 157 Hint: "99", 158 Started: 1635693255000, // Milliseconds 159 Extra: []string{""}, 160 }, 161 { 162 Build: "80", 163 Hint: "80", 164 Started: 1635779655000, // Milliseconds 165 Extra: []string{"build80"}, 166 }, 167 }, 168 }, 169 }, 170 req: &apipb.ListHeadersRequest{ 171 Dashboard: "dashboard1", 172 Tab: "tab1", 173 }, 174 want: &apipb.ListHeadersResponse{ 175 Headers: []*apipb.ListHeadersResponse_Header{ 176 { 177 Build: "99", 178 Started: ×tamppb.Timestamp{Seconds: 1635693255}, 179 Extra: []string{""}, 180 }, 181 { 182 Build: "80", 183 Started: ×tamppb.Timestamp{Seconds: 1635779655}, 184 Extra: []string{"build80"}, 185 }, 186 }, 187 }, 188 }, 189 { 190 name: "Returns correct timestamps from a tab", 191 config: map[string]*pb.Configuration{ 192 "gs://default/config": { 193 Dashboards: []*pb.Dashboard{ 194 { 195 Name: "Dashboard1", 196 DashboardTab: []*pb.DashboardTab{ 197 { 198 Name: "tab 1", 199 TestGroupName: "testgroupname", 200 }, 201 }, 202 }, 203 }, 204 }, 205 }, 206 grid: map[string]*statepb.Grid{ 207 "gs://default/tabs/Dashboard1/tab%201": { 208 Columns: []*statepb.Column{ 209 { 210 Build: "99", 211 Hint: "99", 212 Started: 1, // Milliseconds 213 }, 214 { 215 Build: "80", 216 Hint: "80", 217 Started: 1635779655123, // Milliseconds 218 }, 219 }, 220 }, 221 }, 222 req: &apipb.ListHeadersRequest{ 223 Dashboard: "dashboard 1", 224 Tab: "tab 1", 225 }, 226 want: &apipb.ListHeadersResponse{ 227 Headers: []*apipb.ListHeadersResponse_Header{ 228 { 229 Build: "99", 230 Started: ×tamppb.Timestamp{Nanos: 1000000}, 231 }, 232 { 233 Build: "80", 234 Started: ×tamppb.Timestamp{Seconds: 1635779655, Nanos: 123000000}, 235 }, 236 }, 237 }, 238 }, 239 { 240 name: "Server error with unreadable config", 241 config: map[string]*pb.Configuration{ 242 "gs://default/config": {}, 243 }, 244 req: &apipb.ListHeadersRequest{ 245 Dashboard: "dashboard 1", 246 Tab: "tab 1", 247 }, 248 expectError: true, 249 }, 250 } 251 252 for _, tc := range tests { 253 t.Run(tc.name, func(t *testing.T) { 254 server := setupTestServer(t, tc.config, tc.grid, nil) 255 got, err := server.ListHeaders(context.Background(), tc.req) 256 switch { 257 case err != nil: 258 if !tc.expectError { 259 t.Errorf("got unexpected error: %v", err) 260 } 261 case tc.expectError: 262 t.Error("failed to receive an error") 263 default: 264 if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" { 265 t.Errorf("got unexpected diff (-want +got):\n%s", diff) 266 } 267 } 268 }) 269 270 } 271 272 } 273 274 func TestListRows(t *testing.T) { 275 tests := []struct { 276 name string 277 config map[string]*pb.Configuration 278 grid map[string]*statepb.Grid 279 patch func(*Server) 280 req *apipb.ListRowsRequest 281 want *apipb.ListRowsResponse 282 err bool 283 }{ 284 { 285 name: "Returns an error when there's no dashboard resource", 286 config: map[string]*pb.Configuration{ 287 "gs://default/config": {}, 288 }, 289 req: &apipb.ListRowsRequest{ 290 Scope: "gs://default", 291 Dashboard: "missing", 292 Tab: "irrelevant", 293 }, 294 err: true, 295 }, 296 { 297 name: "Returns an error when there's no tab resource", 298 config: map[string]*pb.Configuration{ 299 "gs://default/config": { 300 Dashboards: []*pb.Dashboard{ 301 { 302 Name: "Dashboard1", 303 DashboardTab: []*pb.DashboardTab{}, 304 }, 305 }, 306 }, 307 }, 308 req: &apipb.ListRowsRequest{ 309 Scope: "gs://default", 310 Dashboard: "Dashboard1", 311 Tab: "irrelevant", 312 }, 313 err: true, 314 }, 315 { 316 name: "Returns empty rows list from a tab", 317 config: map[string]*pb.Configuration{ 318 "gs://default/config": { 319 Dashboards: []*pb.Dashboard{ 320 { 321 Name: "Dashboard1", 322 DashboardTab: []*pb.DashboardTab{ 323 { 324 Name: "tab 1", 325 TestGroupName: "testgroupname", 326 }, 327 }, 328 }, 329 }, 330 }, 331 }, 332 grid: map[string]*statepb.Grid{ 333 "gs://default/tabs/Dashboard1/tab%201": {}, 334 }, 335 req: &apipb.ListRowsRequest{ 336 Scope: "gs://default", 337 Dashboard: "dashboard1", 338 Tab: "tab1", 339 }, 340 want: &apipb.ListRowsResponse{}, 341 }, 342 { 343 name: "Returns correct rows from a tab", 344 config: map[string]*pb.Configuration{ 345 "gs://default/config": { 346 Dashboards: []*pb.Dashboard{ 347 { 348 Name: "Dashboard1", 349 DashboardTab: []*pb.DashboardTab{ 350 { 351 Name: "tab 1", 352 TestGroupName: "testgroupname", 353 }, 354 }, 355 }, 356 }, 357 }, 358 }, 359 grid: map[string]*statepb.Grid{ 360 "gs://default/tabs/Dashboard1/tab%201": { 361 Rows: []*statepb.Row{ 362 { 363 Name: "tabrow1", 364 Id: "tabrow1", 365 Results: []int32{1, 2}, 366 CellIds: []string{"cell-1", "cell-2"}, 367 Messages: []string{"", "", "", ""}, 368 Icons: []string{"", "", "", ""}, 369 }, 370 }, 371 }, 372 }, 373 req: &apipb.ListRowsRequest{ 374 Scope: "gs://default", 375 Dashboard: "dashboard1", 376 Tab: "tab1", 377 }, 378 want: &apipb.ListRowsResponse{ 379 Rows: []*apipb.ListRowsResponse_Row{ 380 { 381 Name: "tabrow1", 382 Cells: []*apipb.ListRowsResponse_Cell{ 383 { 384 Result: 1, 385 CellId: "cell-1", 386 }, 387 { 388 Result: 1, 389 CellId: "cell-2", 390 }, 391 }, 392 }, 393 }, 394 }, 395 }, 396 { 397 name: "Returns correct rows with blank cells", 398 config: map[string]*pb.Configuration{ 399 "gs://default/config": { 400 Dashboards: []*pb.Dashboard{ 401 { 402 Name: "Dashboard1", 403 DashboardTab: []*pb.DashboardTab{ 404 { 405 Name: "tab 1", 406 TestGroupName: "testgroupname", 407 }, 408 }, 409 }, 410 }, 411 }, 412 }, 413 grid: map[string]*statepb.Grid{ 414 "gs://default/tabs/Dashboard1/tab%201": { 415 Rows: []*statepb.Row{ 416 { 417 Name: "tabrow1", 418 Id: "tabrow1", 419 Results: []int32{1, 2, 0, 2, 1, 1, 0, 3, 1, 1}, 420 CellIds: []string{"cell-1", "cell-2", "cell-5", "cell-9"}, 421 Messages: []string{"", "", "hello", "no"}, 422 Icons: []string{"", "", "H", "N"}, 423 }, 424 }, 425 }, 426 }, 427 req: &apipb.ListRowsRequest{ 428 Scope: "gs://default", 429 Dashboard: "dashboard1", 430 Tab: "tab1", 431 }, 432 want: &apipb.ListRowsResponse{ 433 Rows: []*apipb.ListRowsResponse_Row{ 434 { 435 Name: "tabrow1", 436 Cells: []*apipb.ListRowsResponse_Cell{ 437 { 438 Result: 1, 439 CellId: "cell-1", 440 }, 441 { 442 Result: 1, 443 CellId: "cell-2", 444 }, 445 {}, 446 {}, 447 { 448 Result: 1, 449 CellId: "cell-5", 450 Message: "hello", 451 Icon: "H", 452 }, 453 {}, 454 {}, 455 {}, 456 { 457 Result: 1, 458 CellId: "cell-9", 459 Message: "no", 460 Icon: "N", 461 }, 462 }, 463 }, 464 }, 465 }, 466 }, 467 { 468 name: "Returns tab from tab state", 469 config: map[string]*pb.Configuration{ 470 "gs://default/config": { 471 Dashboards: []*pb.Dashboard{ 472 { 473 Name: "Dashboard1", 474 DashboardTab: []*pb.DashboardTab{ 475 { 476 Name: "tab 1", 477 TestGroupName: "testgroupname", 478 }, 479 }, 480 }, 481 }, 482 }, 483 }, 484 grid: map[string]*statepb.Grid{ 485 "gs://default/look-ma-tabs/Dashboard1/tab%201": { 486 Rows: []*statepb.Row{ 487 { 488 Name: "tabrow1", 489 Id: "tabrow1", 490 Results: []int32{1, 2}, 491 CellIds: []string{"cell-1", "cell-2"}, 492 Messages: []string{"tab soda", "", "", ""}, 493 Icons: []string{"", "", "", ""}, 494 }, 495 }, 496 }, 497 }, 498 patch: func(s *Server) { 499 s.TabPathPrefix = "look-ma-tabs" 500 }, 501 req: &apipb.ListRowsRequest{ 502 Scope: "gs://default", 503 Dashboard: "dashboard1", 504 Tab: "tab1", 505 }, 506 want: &apipb.ListRowsResponse{ 507 Rows: []*apipb.ListRowsResponse_Row{ 508 { 509 Name: "tabrow1", 510 Cells: []*apipb.ListRowsResponse_Cell{ 511 { 512 Result: 1, 513 CellId: "cell-1", 514 Message: "tab soda", 515 }, 516 { 517 Result: 1, 518 CellId: "cell-2", 519 }, 520 }, 521 }, 522 }, 523 }, 524 }, 525 { 526 name: "Returns tab from state with row info missing", 527 config: map[string]*pb.Configuration{ 528 "gs://default/config": { 529 Dashboards: []*pb.Dashboard{ 530 { 531 Name: "Dashboard1", 532 DashboardTab: []*pb.DashboardTab{ 533 { 534 Name: "tab 1", 535 TestGroupName: "testgroupname", 536 }, 537 }, 538 }, 539 }, 540 }, 541 }, 542 grid: map[string]*statepb.Grid{ 543 "gs://default/look-ma-tabs/Dashboard1/tab%201": { 544 Rows: []*statepb.Row{ 545 { 546 Name: "tabrow1", 547 Id: "tabrow1", 548 Results: []int32{1, 2}, 549 CellIds: []string{"cell-1", "cell-2"}, 550 Messages: []string{"tab soda", ""}, 551 Icons: []string{"", ""}, 552 }, 553 { 554 Name: "tabrow1@TESTGRID@method-a", 555 Id: "tabrow1", 556 Results: []int32{1, 2}, 557 }, 558 }, 559 }, 560 }, 561 patch: func(s *Server) { 562 s.TabPathPrefix = "look-ma-tabs" 563 }, 564 req: &apipb.ListRowsRequest{ 565 Scope: "gs://default", 566 Dashboard: "dashboard1", 567 Tab: "tab1", 568 }, 569 want: &apipb.ListRowsResponse{ 570 Rows: []*apipb.ListRowsResponse_Row{ 571 { 572 Name: "tabrow1", 573 Cells: []*apipb.ListRowsResponse_Cell{ 574 { 575 Result: 1, 576 CellId: "cell-1", 577 Message: "tab soda", 578 }, 579 { 580 Result: 1, 581 CellId: "cell-2", 582 }, 583 }, 584 }, 585 { 586 Name: "tabrow1@TESTGRID@method-a", 587 Cells: []*apipb.ListRowsResponse_Cell{ 588 {Result: 1}, 589 {Result: 1}, 590 }, 591 }, 592 }, 593 }, 594 }, 595 } 596 for _, tc := range tests { 597 t.Run(tc.name, func(t *testing.T) { 598 server := setupTestServer(t, tc.config, tc.grid, nil) 599 if tc.patch != nil { 600 tc.patch(&server) 601 } 602 got, err := server.ListRows(context.Background(), tc.req) 603 switch { 604 case err != nil: 605 if !tc.err { 606 t.Errorf("ListRows() got unexpected error: %v", err) 607 } 608 case tc.err: 609 t.Error("ListRows() failed to receive an error") 610 default: 611 if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" { 612 t.Errorf("ListRows() got unexpected diff (-want +got):\n%s", diff) 613 } 614 } 615 }) 616 } 617 }