github.com/grafana/pyroscope@v1.18.0/pkg/model/labels_test.go (about) 1 package model 2 3 import ( 4 "math/rand" 5 "sort" 6 "testing" 7 8 "github.com/stretchr/testify/assert" 9 10 typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" 11 ) 12 13 func TestLabelsUnique(t *testing.T) { 14 tests := []struct { 15 name string 16 input Labels 17 expected Labels 18 }{ 19 { 20 name: "Empty List", 21 input: Labels{}, 22 expected: Labels{}, 23 }, 24 { 25 name: "List with One Label", 26 input: Labels{ 27 {Name: "Name1", Value: "Value1"}, 28 }, 29 expected: Labels{ 30 {Name: "Name1", Value: "Value1"}, 31 }, 32 }, 33 { 34 name: "List with Duplicate Labels", 35 input: Labels{ 36 {Name: "Name1", Value: "Value1"}, 37 {Name: "Name1", Value: "Value2"}, 38 {Name: "Name2", Value: "Value3"}, 39 {Name: "Name3", Value: "Value4"}, 40 {Name: "Name3", Value: "Value5"}, 41 }, 42 expected: Labels{ 43 {Name: "Name1", Value: "Value1"}, 44 {Name: "Name2", Value: "Value3"}, 45 {Name: "Name3", Value: "Value4"}, 46 }, 47 }, 48 { 49 name: "List with No Duplicate Labels", 50 input: Labels{ 51 {Name: "Name1", Value: "Value1"}, 52 {Name: "Name2", Value: "Value2"}, 53 {Name: "Name3", Value: "Value3"}, 54 }, 55 expected: Labels{ 56 {Name: "Name1", Value: "Value1"}, 57 {Name: "Name2", Value: "Value2"}, 58 {Name: "Name3", Value: "Value3"}, 59 }, 60 }, 61 } 62 63 for _, test := range tests { 64 t.Run(test.name, func(t *testing.T) { 65 result := test.input.Unique() 66 assert.Equal(t, test.expected, result) 67 }) 68 } 69 } 70 71 func Test_LabelsBuilder_Unique(t *testing.T) { 72 tests := []struct { 73 name string 74 input Labels 75 add Labels 76 expected Labels 77 }{ 78 { 79 name: "duplicates in input are preserved", 80 input: Labels{ 81 {Name: "unique", Value: "yes"}, 82 {Name: "unique", Value: "no"}, 83 }, 84 add: Labels{ 85 {Name: "foo", Value: "bar"}, 86 {Name: "foo", Value: "baz"}, 87 }, 88 expected: Labels{ 89 {Name: "foo", Value: "baz"}, 90 {Name: "unique", Value: "yes"}, 91 {Name: "unique", Value: "no"}, 92 }, 93 }, 94 } 95 96 for _, test := range tests { 97 t.Run(test.name, func(t *testing.T) { 98 builder := NewLabelsBuilder(test.input) 99 for _, l := range test.add { 100 builder.Set(l.Name, l.Value) 101 } 102 assert.Equal(t, test.expected, builder.Labels()) 103 }) 104 } 105 } 106 107 func TestLabels_SessionID_Order(t *testing.T) { 108 input := []Labels{ 109 { 110 {Name: LabelNameSessionID, Value: "session-a"}, 111 {Name: LabelNameProfileType, Value: "cpu"}, 112 {Name: LabelNameServiceNamePrivate, Value: "service-name"}, 113 }, { 114 {Name: LabelNameSessionID, Value: "session-b"}, 115 {Name: LabelNameProfileType, Value: "cpu"}, 116 {Name: LabelNameServiceNamePrivate, Value: "service-name"}, 117 }, 118 } 119 120 for _, x := range input { 121 sort.Sort(LabelsEnforcedOrder(x)) 122 } 123 sort.Slice(input, func(i, j int) bool { 124 return CompareLabelPairs(input[i], input[j]) < 0 125 }) 126 127 expectedOrder := []Labels{ 128 { 129 {Name: LabelNameProfileType, Value: "cpu"}, 130 {Name: LabelNameServiceNamePrivate, Value: "service-name"}, 131 {Name: LabelNameSessionID, Value: "session-a"}, 132 }, { 133 {Name: LabelNameProfileType, Value: "cpu"}, 134 {Name: LabelNameServiceNamePrivate, Value: "service-name"}, 135 {Name: LabelNameSessionID, Value: "session-b"}, 136 }, 137 } 138 139 assert.Equal(t, expectedOrder, input) 140 } 141 142 func TestLabels_IsAllowedForIngestion(t *testing.T) { 143 type testCase struct { 144 labelName string 145 allowed bool 146 } 147 148 testCases := []testCase{ 149 {labelName: LabelNameSessionID, allowed: true}, 150 {labelName: "some_label", allowed: true}, 151 {labelName: LabelNameProfileType}, 152 } 153 154 for _, tc := range testCases { 155 allowed := IsLabelAllowedForIngestion(tc.labelName) 156 assert.Equalf(t, tc.allowed, allowed, "%q", tc.labelName) 157 } 158 } 159 160 func Test_SessionID_Parse(t *testing.T) { 161 n := rand.Uint64() 162 s := SessionID(n) 163 p, err := ParseSessionID(s.String()) 164 assert.NoError(t, err) 165 assert.Equal(t, SessionID(n), p) 166 167 _, err = ParseSessionID("not-a-session-id") // Matches expected length. 168 assert.NotNil(t, err) 169 170 _, err = ParseSessionID("not-a-session-id-either") 171 assert.NotNil(t, err) 172 } 173 174 func TestLabels_LabelsEnforcedOrder(t *testing.T) { 175 labels := []*typesv1.LabelPair{ 176 {Name: "foo", Value: "bar"}, 177 {Name: LabelNameProfileType, Value: "cpu"}, 178 {Name: "__request_id__", Value: "mess"}, 179 {Name: LabelNameServiceNamePrivate, Value: "service"}, 180 {Name: "Alarm", Value: "Order"}, 181 } 182 183 expected := Labels{ 184 {Name: LabelNameProfileType, Value: "cpu"}, 185 {Name: LabelNameServiceNamePrivate, Value: "service"}, 186 {Name: "Alarm", Value: "Order"}, 187 {Name: "__request_id__", Value: "mess"}, 188 {Name: "foo", Value: "bar"}, 189 } 190 191 permute(labels, func(x []*typesv1.LabelPair) { 192 sort.Sort(LabelsEnforcedOrder(x)) 193 assert.Equal(t, LabelPairsString(expected), LabelPairsString(labels)) 194 }) 195 } 196 197 func TestLabels_LabelsEnforcedOrder_BytesWithLabels(t *testing.T) { 198 labels := Labels{ 199 {Name: LabelNameProfileType, Value: "cpu"}, 200 {Name: LabelNameServiceNamePrivate, Value: "service"}, 201 {Name: "__request_id__", Value: "mess"}, 202 {Name: "A_label", Value: "bad"}, 203 {Name: "foo", Value: "bar"}, 204 } 205 sort.Sort(LabelsEnforcedOrder(labels)) 206 207 assert.NotEqual(t, 208 labels.BytesWithLabels(nil, "A_label"), 209 labels.BytesWithLabels(nil, "not_a_label"), 210 ) 211 212 assert.Equal(t, 213 labels.BytesWithLabels(nil, "A_label"), 214 Labels{{Name: "A_label", Value: "bad"}}.BytesWithLabels(nil, "A_label"), 215 ) 216 } 217 218 func permute[T any](s []T, f func([]T)) { 219 n := len(s) 220 stack := make([]int, n) 221 f(s) 222 i := 0 223 for i < n { 224 if stack[i] < i { 225 if i%2 == 0 { 226 s[0], s[i] = s[i], s[0] 227 } else { 228 s[stack[i]], s[i] = s[i], s[stack[i]] 229 } 230 f(s) 231 stack[i]++ 232 i = 0 233 } else { 234 stack[i] = 0 235 i++ 236 } 237 } 238 } 239 240 func TestInsert(t *testing.T) { 241 tests := []struct { 242 name string 243 labels Labels 244 insertName string 245 insertValue string 246 expected Labels 247 }{ 248 { 249 name: "Insert into empty slice", 250 labels: Labels{}, 251 insertName: "foo", 252 insertValue: "bar", 253 expected: Labels{ 254 {Name: "foo", Value: "bar"}, 255 }, 256 }, 257 { 258 name: "Insert at the beginning", 259 labels: Labels{ 260 {Name: "baz", Value: "qux"}, 261 {Name: "quux", Value: "corge"}, 262 }, 263 insertName: "alice", 264 insertValue: "bob", 265 expected: Labels{ 266 {Name: "alice", Value: "bob"}, 267 {Name: "baz", Value: "qux"}, 268 {Name: "quux", Value: "corge"}, 269 }, 270 }, 271 { 272 name: "Insert in the middle", 273 labels: Labels{ 274 {Name: "baz", Value: "qux"}, 275 {Name: "quux", Value: "corge"}, 276 }, 277 insertName: "foo", 278 insertValue: "bar", 279 expected: Labels{ 280 {Name: "baz", Value: "qux"}, 281 {Name: "foo", Value: "bar"}, 282 {Name: "quux", Value: "corge"}, 283 }, 284 }, 285 { 286 name: "Insert at the end", 287 labels: Labels{ 288 {Name: "baz", Value: "qux"}, 289 {Name: "quux", Value: "corge"}, 290 }, 291 insertName: "xyz", 292 insertValue: "123", 293 expected: Labels{ 294 {Name: "baz", Value: "qux"}, 295 {Name: "quux", Value: "corge"}, 296 {Name: "xyz", Value: "123"}, 297 }, 298 }, 299 { 300 name: "Update existing label", 301 labels: Labels{ 302 {Name: "baz", Value: "qux"}, 303 {Name: "quux", Value: "corge"}, 304 }, 305 insertName: "baz", 306 insertValue: "updated_value", 307 expected: Labels{ 308 {Name: "baz", Value: "updated_value"}, 309 {Name: "quux", Value: "corge"}, 310 }, 311 }, 312 } 313 314 for _, test := range tests { 315 t.Run(test.name, func(t *testing.T) { 316 assert.Equal(t, test.expected, test.labels.InsertSorted(test.insertName, test.insertValue)) 317 }) 318 } 319 } 320 321 func Test_ServiceVersionFromLabels(t *testing.T) { 322 tests := []struct { 323 name string 324 input Labels 325 expectedVersion ServiceVersion 326 expectedOk bool 327 }{ 328 { 329 name: "all present", 330 input: Labels{ 331 {Name: LabelNameServiceRepository, Value: "repo"}, 332 {Name: LabelNameServiceGitRef, Value: "ref"}, 333 {Name: LabelNameServiceRootPath, Value: "some-path"}, 334 {Name: "any-other-label", Value: "any-value"}, 335 }, 336 expectedVersion: ServiceVersion{ 337 Repository: "repo", 338 GitRef: "ref", 339 RootPath: "some-path", 340 }, 341 expectedOk: true, 342 }, 343 { 344 name: "some present", 345 input: Labels{ 346 {Name: LabelNameServiceRepository, Value: "repo"}, 347 {Name: LabelNameServiceRootPath, Value: "some-path"}, 348 {Name: "any-other-label", Value: "any-value"}, 349 }, 350 expectedVersion: ServiceVersion{ 351 Repository: "repo", 352 GitRef: "", 353 RootPath: "some-path", 354 }, 355 expectedOk: true, 356 }, 357 { 358 name: "none present", 359 input: Labels{ 360 {Name: "any-label", Value: "any-value"}, 361 }, 362 expectedVersion: ServiceVersion{ 363 Repository: "", 364 GitRef: "", 365 RootPath: "", 366 }, 367 expectedOk: false, 368 }, 369 } 370 for _, test := range tests { 371 t.Run(test.name, func(t *testing.T) { 372 version, ok := ServiceVersionFromLabels(test.input) 373 assert.Equal(t, test.expectedVersion, version) 374 assert.Equal(t, test.expectedOk, ok) 375 }) 376 } 377 } 378 379 func TestLabels_Subtract(t *testing.T) { 380 tests := []struct { 381 name string 382 labels Labels 383 input Labels 384 expected Labels 385 }{ 386 { 387 name: "Empty sets", 388 labels: Labels{}, 389 input: Labels{}, 390 expected: Labels{}, 391 }, 392 { 393 name: "Subtract from empty set", 394 labels: Labels{}, 395 input: Labels{ 396 {Name: "foo", Value: "bar"}, 397 {Name: "service", Value: "api"}, 398 }, 399 expected: Labels{}, 400 }, 401 { 402 name: "Subtract empty set", 403 labels: Labels{ 404 {Name: "foo", Value: "bar"}, 405 {Name: "service", Value: "api"}, 406 }, 407 input: Labels{}, 408 expected: Labels{ 409 {Name: "foo", Value: "bar"}, 410 {Name: "service", Value: "api"}, 411 }, 412 }, 413 { 414 name: "No overlap", 415 labels: Labels{ 416 {Name: "app", Value: "frontend"}, 417 {Name: "env", Value: "prod"}, 418 }, 419 input: Labels{ 420 {Name: "service", Value: "api"}, 421 {Name: "version", Value: "1.0"}, 422 }, 423 expected: Labels{ 424 {Name: "app", Value: "frontend"}, 425 {Name: "env", Value: "prod"}, 426 }, 427 }, 428 { 429 name: "Complete overlap", 430 labels: Labels{ 431 {Name: "app", Value: "frontend"}, 432 {Name: "env", Value: "prod"}, 433 }, 434 input: Labels{ 435 {Name: "app", Value: "frontend"}, 436 {Name: "env", Value: "prod"}, 437 }, 438 expected: Labels{}, 439 }, 440 { 441 name: "Partial overlap", 442 labels: Labels{ 443 {Name: "app", Value: "frontend"}, 444 {Name: "env", Value: "prod"}, 445 {Name: "version", Value: "2.0"}, 446 }, 447 input: Labels{ 448 {Name: "env", Value: "prod"}, 449 {Name: "service", Value: "api"}, 450 }, 451 expected: Labels{ 452 {Name: "app", Value: "frontend"}, 453 {Name: "version", Value: "2.0"}, 454 }, 455 }, 456 { 457 name: "Same name different value", 458 labels: Labels{ 459 {Name: "env", Value: "prod"}, 460 {Name: "service", Value: "frontend"}, 461 }, 462 input: Labels{ 463 {Name: "env", Value: "dev"}, 464 {Name: "service", Value: "api"}, 465 }, 466 expected: Labels{ 467 {Name: "env", Value: "prod"}, 468 {Name: "service", Value: "frontend"}, 469 }, 470 }, 471 { 472 name: "Single element sets", 473 labels: Labels{ 474 {Name: "foo", Value: "bar"}, 475 }, 476 input: Labels{ 477 {Name: "foo", Value: "bar"}, 478 }, 479 expected: Labels{}, 480 }, 481 { 482 name: "Larger set with multiple removals", 483 labels: Labels{ 484 {Name: "a", Value: "1"}, 485 {Name: "b", Value: "2"}, 486 {Name: "c", Value: "3"}, 487 {Name: "d", Value: "4"}, 488 {Name: "e", Value: "5"}, 489 }, 490 input: Labels{ 491 {Name: "b", Value: "2"}, 492 {Name: "d", Value: "4"}, 493 {Name: "f", Value: "6"}, 494 }, 495 expected: Labels{ 496 {Name: "a", Value: "1"}, 497 {Name: "c", Value: "3"}, 498 {Name: "e", Value: "5"}, 499 }, 500 }, 501 } 502 503 for _, test := range tests { 504 t.Run(test.name, func(t *testing.T) { 505 assert.Equal(t, test.expected, test.labels.Subtract(test.input)) 506 }) 507 } 508 } 509 510 func TestLabels_Intersect(t *testing.T) { 511 tests := []struct { 512 name string 513 labels Labels 514 input Labels 515 expected Labels 516 }{ 517 { 518 name: "Empty sets", 519 labels: Labels{}, 520 input: Labels{}, 521 expected: Labels{}, 522 }, 523 { 524 name: "Intersect with empty set", 525 labels: Labels{ 526 {Name: "foo", Value: "bar"}, 527 {Name: "service", Value: "api"}, 528 }, 529 input: Labels{}, 530 expected: Labels{}, 531 }, 532 { 533 name: "Intersect empty set", 534 labels: Labels{}, 535 input: Labels{ 536 {Name: "foo", Value: "bar"}, 537 {Name: "service", Value: "api"}, 538 }, 539 expected: Labels{}, 540 }, 541 { 542 name: "No overlap", 543 labels: Labels{ 544 {Name: "app", Value: "frontend"}, 545 {Name: "env", Value: "prod"}, 546 }, 547 input: Labels{ 548 {Name: "service", Value: "api"}, 549 {Name: "version", Value: "1.0"}, 550 }, 551 expected: Labels{}, 552 }, 553 { 554 name: "Complete overlap", 555 labels: Labels{ 556 {Name: "app", Value: "frontend"}, 557 {Name: "env", Value: "prod"}, 558 }, 559 input: Labels{ 560 {Name: "app", Value: "frontend"}, 561 {Name: "env", Value: "prod"}, 562 }, 563 expected: Labels{ 564 {Name: "app", Value: "frontend"}, 565 {Name: "env", Value: "prod"}, 566 }, 567 }, 568 { 569 name: "Partial overlap", 570 labels: Labels{ 571 {Name: "app", Value: "frontend"}, 572 {Name: "env", Value: "prod"}, 573 {Name: "version", Value: "2.0"}, 574 }, 575 input: Labels{ 576 {Name: "env", Value: "prod"}, 577 {Name: "service", Value: "api"}, 578 }, 579 expected: Labels{ 580 {Name: "env", Value: "prod"}, 581 }, 582 }, 583 { 584 name: "Same name different value", 585 labels: Labels{ 586 {Name: "env", Value: "prod"}, 587 {Name: "service", Value: "frontend"}, 588 }, 589 input: Labels{ 590 {Name: "env", Value: "dev"}, 591 {Name: "service", Value: "api"}, 592 }, 593 expected: Labels{}, 594 }, 595 { 596 name: "Single element intersection", 597 labels: Labels{ 598 {Name: "foo", Value: "bar"}, 599 }, 600 input: Labels{ 601 {Name: "foo", Value: "bar"}, 602 }, 603 expected: Labels{ 604 {Name: "foo", Value: "bar"}, 605 }, 606 }, 607 { 608 name: "Multiple intersections", 609 labels: Labels{ 610 {Name: "a", Value: "1"}, 611 {Name: "b", Value: "2"}, 612 {Name: "c", Value: "3"}, 613 {Name: "d", Value: "4"}, 614 {Name: "e", Value: "5"}, 615 }, 616 input: Labels{ 617 {Name: "b", Value: "2"}, 618 {Name: "c", Value: "3"}, 619 {Name: "f", Value: "6"}, 620 {Name: "g", Value: "7"}, 621 }, 622 expected: Labels{ 623 {Name: "b", Value: "2"}, 624 {Name: "c", Value: "3"}, 625 }, 626 }, 627 { 628 name: "First set is subset of second", 629 labels: Labels{ 630 {Name: "env", Value: "prod"}, 631 {Name: "version", Value: "1.0"}, 632 }, 633 input: Labels{ 634 {Name: "app", Value: "frontend"}, 635 {Name: "env", Value: "prod"}, 636 {Name: "service", Value: "api"}, 637 {Name: "version", Value: "1.0"}, 638 }, 639 expected: Labels{ 640 {Name: "env", Value: "prod"}, 641 {Name: "version", Value: "1.0"}, 642 }, 643 }, 644 { 645 name: "Second set is subset of first", 646 labels: Labels{ 647 {Name: "app", Value: "frontend"}, 648 {Name: "env", Value: "prod"}, 649 {Name: "service", Value: "api"}, 650 {Name: "version", Value: "1.0"}, 651 }, 652 input: Labels{ 653 {Name: "env", Value: "prod"}, 654 {Name: "version", Value: "1.0"}, 655 }, 656 expected: Labels{ 657 {Name: "env", Value: "prod"}, 658 {Name: "version", Value: "1.0"}, 659 }, 660 }, 661 } 662 663 for _, test := range tests { 664 t.Run(test.name, func(t *testing.T) { 665 assert.Equal(t, test.expected, test.labels.Intersect(test.input)) 666 }) 667 } 668 } 669 670 func TestLabels_IntersectAll(t *testing.T) { 671 tests := []struct { 672 name string 673 labelSets []Labels 674 expected Labels 675 }{ 676 { 677 name: "Empty input", 678 labelSets: []Labels{}, 679 expected: nil, 680 }, 681 { 682 name: "Single label set", 683 labelSets: []Labels{ 684 { 685 {Name: "env", Value: "prod"}, 686 {Name: "service", Value: "api"}, 687 }, 688 }, 689 expected: Labels{ 690 {Name: "env", Value: "prod"}, 691 {Name: "service", Value: "api"}, 692 }, 693 }, 694 { 695 name: "Two sets with full overlap", 696 labelSets: []Labels{ 697 { 698 {Name: "env", Value: "prod"}, 699 {Name: "service", Value: "api"}, 700 }, 701 { 702 {Name: "env", Value: "prod"}, 703 {Name: "service", Value: "api"}, 704 }, 705 }, 706 expected: Labels{ 707 {Name: "env", Value: "prod"}, 708 {Name: "service", Value: "api"}, 709 }, 710 }, 711 { 712 name: "Two sets with partial overlap", 713 labelSets: []Labels{ 714 { 715 {Name: "env", Value: "prod"}, 716 {Name: "pod", Value: "pod-1"}, 717 {Name: "region", Value: "us-east"}, 718 }, 719 { 720 {Name: "env", Value: "prod"}, 721 {Name: "pod", Value: "pod-2"}, 722 {Name: "region", Value: "us-east"}, 723 }, 724 }, 725 expected: Labels{ 726 {Name: "env", Value: "prod"}, 727 {Name: "region", Value: "us-east"}, 728 }, 729 }, 730 { 731 name: "No common labels", 732 labelSets: []Labels{ 733 { 734 {Name: "env", Value: "prod"}, 735 }, 736 { 737 {Name: "region", Value: "us-east"}, 738 }, 739 }, 740 expected: nil, 741 }, 742 } 743 744 for _, test := range tests { 745 t.Run(test.name, func(t *testing.T) { 746 result := IntersectAll(test.labelSets) 747 assert.Equal(t, test.expected, result) 748 }) 749 } 750 } 751 752 func TestLabels_WithoutLabels(t *testing.T) { 753 tests := []struct { 754 name string 755 labels Labels 756 remove []string 757 expected Labels 758 }{ 759 { 760 name: "Remove nothing", 761 labels: Labels{ 762 {Name: "env", Value: "prod"}, 763 {Name: "service", Value: "api"}, 764 }, 765 remove: []string{}, 766 expected: Labels{ 767 {Name: "env", Value: "prod"}, 768 {Name: "service", Value: "api"}, 769 }, 770 }, 771 { 772 name: "Remove single label", 773 labels: Labels{ 774 {Name: "env", Value: "prod"}, 775 {Name: "pod", Value: "pod-1"}, 776 {Name: "service", Value: "api"}, 777 }, 778 remove: []string{"pod"}, 779 expected: Labels{ 780 {Name: "env", Value: "prod"}, 781 {Name: "service", Value: "api"}, 782 }, 783 }, 784 { 785 name: "Remove multiple labels", 786 labels: Labels{ 787 {Name: "env", Value: "prod"}, 788 {Name: "pod", Value: "pod-1"}, 789 {Name: "region", Value: "us-east"}, 790 {Name: "service", Value: "api"}, 791 }, 792 remove: []string{"pod", "region"}, 793 expected: Labels{ 794 {Name: "env", Value: "prod"}, 795 {Name: "service", Value: "api"}, 796 }, 797 }, 798 { 799 name: "Remove non-existent label", 800 labels: Labels{ 801 {Name: "env", Value: "prod"}, 802 {Name: "service", Value: "api"}, 803 }, 804 remove: []string{"nonexistent"}, 805 expected: Labels{ 806 {Name: "env", Value: "prod"}, 807 {Name: "service", Value: "api"}, 808 }, 809 }, 810 { 811 name: "Remove all labels", 812 labels: Labels{ 813 {Name: "env", Value: "prod"}, 814 {Name: "service", Value: "api"}, 815 }, 816 remove: []string{"env", "service"}, 817 expected: Labels{}, 818 }, 819 { 820 name: "Empty labels", 821 labels: Labels{}, 822 remove: []string{"env"}, 823 expected: Labels{}, 824 }, 825 } 826 827 for _, test := range tests { 828 t.Run(test.name, func(t *testing.T) { 829 result := test.labels.WithoutLabels(test.remove...) 830 assert.Equal(t, test.expected, result) 831 }) 832 } 833 }