kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/cxx/extractor/bazel_artifact_selector_test.cc (about) 1 /* 2 * Copyright 2020 The Kythe Authors. All rights reserved. 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 #include "kythe/cxx/extractor/bazel_artifact_selector.h" 17 18 #include <algorithm> 19 #include <functional> 20 #include <optional> 21 #include <string> 22 #include <tuple> 23 #include <vector> 24 25 #include "absl/container/flat_hash_map.h" 26 #include "absl/container/flat_hash_set.h" 27 #include "absl/functional/any_invocable.h" 28 #include "absl/log/check.h" 29 #include "absl/status/status.h" 30 #include "absl/strings/str_cat.h" 31 #include "absl/strings/string_view.h" 32 #include "absl/types/span.h" 33 #include "gmock/gmock.h" 34 #include "google/protobuf/any.pb.h" 35 #include "google/protobuf/io/zero_copy_stream_impl_lite.h" 36 #include "google/protobuf/text_format.h" 37 #include "gtest/gtest.h" 38 #include "kythe/cxx/extractor/bazel_artifact.h" 39 #include "kythe/proto/bazel_artifact_selector_v2.pb.h" 40 #include "protobuf-matchers/protocol-buffer-matchers.h" 41 #include "re2/re2.h" 42 #include "third_party/bazel/src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.pb.h" 43 44 namespace kythe { 45 namespace { 46 using ::protobuf_matchers::EqualsProto; 47 using ::testing::Eq; 48 using ::testing::FieldsAre; 49 using ::testing::IsEmpty; 50 using ::testing::Optional; 51 using ::testing::SizeIs; 52 using ::testing::UnorderedElementsAre; 53 54 struct FileSet { 55 std::vector<std::string> files; 56 std::vector<std::string> file_sets; 57 }; 58 59 using IdGenerator = absl::AnyInvocable<std::string()>; 60 61 IdGenerator NumericIdGenerator() { 62 return [i = 0]() mutable { return absl::StrCat(i++); }; 63 } 64 65 IdGenerator AlphaIdGenerator() { 66 return [i = 0]() mutable { 67 std::string result = absl::StrCat(i++); 68 for (char& ch : result) { 69 ch = (ch - '0') + 'a'; 70 } 71 return result; 72 }; 73 } 74 75 IdGenerator MixedIdGenerator() { 76 return [numeric = NumericIdGenerator(), alpha = AlphaIdGenerator(), 77 i = 0]() mutable { return (i++ % 2) ? numeric() : alpha(); }; 78 } 79 80 absl::flat_hash_map<std::string, FileSet> GenerateFileSets( 81 int count, IdGenerator& next_id) { 82 absl::flat_hash_map<std::string, FileSet> result; 83 for (int i = 0; i < count; ++i) { 84 auto id = next_id(); 85 result[id].files = {absl::StrCat("path/to/file/", id, ".kzip")}; 86 } 87 return result; 88 } 89 90 absl::flat_hash_map<std::string, FileSet> GenerateFileSets( 91 int count, IdGenerator&& next_id) { 92 return GenerateFileSets(count, next_id); 93 } 94 95 void ToNamedSetOfFilesEvents( 96 const absl::flat_hash_map<std::string, FileSet>& file_sets, 97 std::vector<build_event_stream::BuildEvent>& result) { 98 result.reserve(result.size() + file_sets.size()); 99 for (const auto& [id, file_set] : file_sets) { 100 auto& event = result.emplace_back(); 101 event.mutable_id()->mutable_named_set()->set_id(id); 102 for (const auto& path : file_set.files) { 103 auto* file = event.mutable_named_set_of_files()->add_files(); 104 file->set_name(path); 105 file->set_uri(absl::StrCat("file:///", path)); 106 } 107 for (const auto& child_id : file_set.file_sets) { 108 event.mutable_named_set_of_files()->add_file_sets()->set_id(child_id); 109 } 110 } 111 } 112 113 std::vector<build_event_stream::BuildEvent> ToNamedSetOfFilesEvents( 114 const absl::flat_hash_map<std::string, FileSet>& file_sets) { 115 std::vector<build_event_stream::BuildEvent> result; 116 ToNamedSetOfFilesEvents(file_sets, result); 117 return result; 118 } 119 120 // The TargetCompleted event will always come after the NamedSetOfFiles events. 121 void ToTargetCompletedBuildEvents( 122 absl::string_view label, 123 const absl::flat_hash_map<std::string, FileSet>& file_sets, 124 std::vector<build_event_stream::BuildEvent>& result) { 125 ToNamedSetOfFilesEvents(file_sets, result); 126 127 auto& event = result.emplace_back(); 128 event.mutable_id()->mutable_target_completed()->set_label(label); 129 event.mutable_completed()->set_success(true); 130 auto* output_group = event.mutable_completed()->add_output_group(); 131 for (const auto& [id, unused] : file_sets) { 132 output_group->add_file_sets()->set_id(id); 133 } 134 } 135 136 struct BuildEventOptions { 137 int target_count = 10; 138 int files_per_target = 2; 139 int common_file_count = 2; 140 }; 141 142 std::vector<build_event_stream::BuildEvent> GenerateBuildEvents( 143 const BuildEventOptions& options = {}, 144 IdGenerator next_id = MixedIdGenerator()) { 145 absl::flat_hash_map<std::string, FileSet> common = 146 GenerateFileSets(options.common_file_count, next_id); 147 148 std::vector<build_event_stream::BuildEvent> events; 149 ToNamedSetOfFilesEvents(common, events); 150 for (int i = 0; i < options.target_count; ++i) { 151 absl::flat_hash_map<std::string, FileSet> files = 152 GenerateFileSets(options.files_per_target, next_id); 153 for (auto& [unused_id, file_set] : files) { 154 for (const auto& [id, unused] : common) { 155 file_set.file_sets.push_back(id); 156 } 157 } 158 ToTargetCompletedBuildEvents(absl::StrCat("//path/to/target:", i), files, 159 events); 160 } 161 return events; 162 } 163 164 AspectArtifactSelector::Options DefaultOptions() { 165 return { 166 .file_name_allowlist = RegexSet::Build({R"(\.kzip$)"}).value(), 167 .output_group_allowlist = RegexSet::Build({".*"}).value(), 168 .target_aspect_allowlist = RegexSet::Build({".*"}).value(), 169 }; 170 } 171 172 build_event_stream::BuildEvent ParseEventOrDie(absl::string_view text) { 173 build_event_stream::BuildEvent result; 174 175 google::protobuf::io::ArrayInputStream input(text.data(), text.size()); 176 CHECK(google::protobuf::TextFormat::Parse(&input, &result)); 177 return result; 178 } 179 180 // Verify that we can find artifacts when the fileset comes after the target. 181 TEST(AspectArtifactSelectorTest, SelectsOutOfOrderFileSets) { 182 AspectArtifactSelector selector(DefaultOptions()); 183 184 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 185 id { 186 target_completed { 187 label: "//path/to/target:name" 188 aspect: "//aspect:file.bzl%name" 189 } 190 } 191 completed { 192 success: true 193 output_group { 194 name: "kythe_compilation_unit" 195 file_sets { id: "1" } 196 } 197 })pb")), 198 Eq(std::nullopt)); 199 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 200 id { named_set { id: "1" } } 201 named_set_of_files { 202 files { name: "path/to/file.kzip" uri: "file:///path/to/file.kzip" } 203 })pb")), 204 Eq(BazelArtifact{ 205 .label = "//path/to/target:name", 206 .files = {{ 207 .local_path = "path/to/file.kzip", 208 .uri = "file:///path/to/file.kzip", 209 }}, 210 })); 211 } 212 213 TEST(AspectArtifactSelectorTest, SelectsMatchingTargetsOnce) { 214 AspectArtifactSelector selector(DefaultOptions()); 215 216 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 217 id { named_set { id: "1" } } 218 named_set_of_files { 219 files { name: "path/to/file.kzip" uri: "file:///path/to/file.kzip" } 220 })pb")), 221 Eq(std::nullopt)); 222 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 223 id { 224 target_completed { 225 label: "//path/to/target:name" 226 aspect: "//aspect:file.bzl%name" 227 } 228 } 229 completed { 230 success: true 231 output_group { 232 name: "kythe_compilation_unit" 233 file_sets { id: "1" } 234 } 235 })pb")), 236 Eq(BazelArtifact{ 237 .label = "//path/to/target:name", 238 .files = {{ 239 .local_path = "path/to/file.kzip", 240 .uri = "file:///path/to/file.kzip", 241 }}, 242 })); 243 244 // Don't select the same fileset a second time, even for a different target. 245 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 246 id { 247 target_completed { 248 label: "//path/to/new/target:name" 249 aspect: "//aspect:file.bzl%name" 250 } 251 } 252 completed { 253 success: true 254 output_group { 255 name: "kythe_compilation_unit" 256 file_sets { id: "1" } 257 } 258 })pb")), 259 Eq(std::nullopt)); 260 } 261 262 // Verify that we can find artifacts even if the target failed to build. 263 TEST(AspectArtifactSelectorTest, SelectsFailedTargets) { 264 AspectArtifactSelector selector(DefaultOptions()); 265 266 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 267 id { 268 target_completed { 269 label: "//path/to/target:name" 270 aspect: "//aspect:file.bzl%name" 271 } 272 } 273 completed { 274 success: false 275 output_group { 276 name: "kythe_compilation_unit" 277 file_sets { id: "1" } 278 } 279 })pb")), 280 Eq(std::nullopt)); 281 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 282 id { named_set { id: "1" } } 283 named_set_of_files { 284 files { name: "path/to/file.kzip" uri: "file:///path/to/file.kzip" } 285 })pb")), 286 Eq(BazelArtifact{ 287 .label = "//path/to/target:name", 288 .files = {{ 289 .local_path = "path/to/file.kzip", 290 .uri = "file:///path/to/file.kzip", 291 }}, 292 })); 293 } 294 295 // Verify that we can find artifacts even if they were part of an unselected 296 // file group. 297 TEST(AspectArtifactSelectorTest, SelectsDuplicatedFileSets) { 298 auto options = DefaultOptions(); 299 options.output_group_allowlist = 300 RegexSet::Build({"kythe_compilation_unit"}).value(); 301 AspectArtifactSelector selector(options); 302 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 303 id { named_set { id: "1" } } 304 named_set_of_files { 305 files { name: "path/to/file.kzip" uri: "file:///path/to/file.kzip" } 306 })pb")), 307 Eq(std::nullopt)); 308 309 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 310 id { 311 target_completed { 312 label: "//path/to/target:name" 313 aspect: "//aspect:file.bzl%name" 314 } 315 } 316 completed { 317 output_group { 318 name: "unselected" 319 file_sets { id: "1" } 320 } 321 output_group { 322 name: "kythe_compilation_unit" 323 file_sets { id: "1" } 324 } 325 })pb")), 326 Eq(BazelArtifact{ 327 .label = "//path/to/target:name", 328 .files = {{ 329 .local_path = "path/to/file.kzip", 330 .uri = "file:///path/to/file.kzip", 331 }}, 332 })); 333 } 334 335 // Verify that we can find artifacts even if they were part of an unselected 336 // file group. 337 TEST(AspectArtifactSelectorTest, SelectsDuplicatedFileSetsWhenPruned) { 338 auto options = DefaultOptions(); 339 options.output_group_allowlist = 340 RegexSet::Build({"kythe_compilation_unit"}).value(); 341 options.dispose_unselected_output_groups = true; 342 AspectArtifactSelector selector(options); 343 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 344 id { named_set { id: "1" } } 345 named_set_of_files { 346 files { name: "path/to/file.kzip" uri: "file:///path/to/file.kzip" } 347 })pb")), 348 Eq(std::nullopt)); 349 350 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 351 id { 352 target_completed { 353 label: "//path/to/target:name" 354 aspect: "//aspect:file.bzl%name" 355 } 356 } 357 completed { 358 output_group { 359 name: "unselected" 360 file_sets { id: "1" } 361 } 362 output_group { 363 name: "kythe_compilation_unit" 364 file_sets { id: "1" } 365 } 366 })pb")), 367 Eq(BazelArtifact{ 368 .label = "//path/to/target:name", 369 .files = {{ 370 .local_path = "path/to/file.kzip", 371 .uri = "file:///path/to/file.kzip", 372 }}, 373 })); 374 } 375 376 TEST(AspectArtifactSelectorTest, SelectsSubsequentDuplicatedFileSets) { 377 auto options = DefaultOptions(); 378 options.output_group_allowlist = 379 RegexSet::Build({"kythe_compilation_unit"}).value(); 380 AspectArtifactSelector selector(options); 381 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 382 id { named_set { id: "1" } } 383 named_set_of_files { 384 files { name: "path/to/file.kzip" uri: "file:///path/to/file.kzip" } 385 })pb")), 386 Eq(std::nullopt)); 387 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 388 id { 389 target_completed { 390 label: "//path/to/some/target:name" 391 aspect: "//aspect:file.bzl%name" 392 } 393 } 394 completed { 395 output_group { 396 name: "unselected" 397 file_sets { id: "1" } 398 } 399 })pb")), 400 Eq(std::nullopt)); 401 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 402 id { 403 target_completed { 404 label: "//path/to/target:name" 405 aspect: "//aspect:file.bzl%name" 406 } 407 } 408 completed { 409 output_group { 410 name: "kythe_compilation_unit" 411 file_sets { id: "1" } 412 } 413 })pb")), 414 Eq(BazelArtifact{ 415 .label = "//path/to/target:name", 416 .files = {{ 417 .local_path = "path/to/file.kzip", 418 .uri = "file:///path/to/file.kzip", 419 }}, 420 })); 421 } 422 423 TEST(AspectArtifactSelectorTest, SkipsSubsequentPrunedFileSets) { 424 auto options = DefaultOptions(); 425 options.output_group_allowlist = 426 RegexSet::Build({"kythe_compilation_unit"}).value(); 427 options.dispose_unselected_output_groups = true; 428 AspectArtifactSelector selector(options); 429 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 430 id { named_set { id: "1" } } 431 named_set_of_files { 432 files { name: "path/to/file.kzip" uri: "file:///path/to/file.kzip" } 433 })pb")), 434 Eq(std::nullopt)); 435 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 436 id { 437 target_completed { 438 label: "//path/to/some/target:name" 439 aspect: "//aspect:file.bzl%name" 440 } 441 } 442 completed { 443 output_group { 444 name: "unselected" 445 file_sets { id: "1" } 446 } 447 })pb")), 448 Eq(std::nullopt)); 449 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 450 id { 451 target_completed { 452 label: "//path/to/target:name" 453 aspect: "//aspect:file.bzl%name" 454 } 455 } 456 completed { 457 output_group { 458 name: "kythe_compilation_unit" 459 file_sets { id: "1" } 460 } 461 })pb")), 462 Eq(std::nullopt)); 463 } 464 465 TEST(AspectArtifactSelectorTest, SkipsPrunedPendingFileSets) { 466 auto options = DefaultOptions(); 467 options.output_group_allowlist = 468 RegexSet::Build({"kythe_compilation_unit"}).value(); 469 options.dispose_unselected_output_groups = true; 470 AspectArtifactSelector selector(options); 471 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 472 id { 473 target_completed { 474 label: "//path/to/some/target:name" 475 aspect: "//aspect:file.bzl%name" 476 } 477 } 478 completed { 479 output_group { 480 name: "unselected" 481 file_sets { id: "1" } 482 } 483 })pb")), 484 Eq(std::nullopt)); 485 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 486 id { named_set { id: "1" } } 487 named_set_of_files { 488 files { name: "path/to/file.kzip" uri: "file:///path/to/file.kzip" } 489 })pb")), 490 Eq(std::nullopt)); 491 } 492 493 TEST(AspectArtifactSelectorTest, SkipsSubsequentPrunedPendingFileSets) { 494 auto options = DefaultOptions(); 495 options.output_group_allowlist = 496 RegexSet::Build({"kythe_compilation_unit"}).value(); 497 options.dispose_unselected_output_groups = true; 498 AspectArtifactSelector selector(options); 499 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 500 id { 501 target_completed { 502 label: "//path/to/some/target:name" 503 aspect: "//aspect:file.bzl%name" 504 } 505 } 506 completed { 507 output_group { 508 name: "unselected" 509 file_sets { id: "1" } 510 } 511 })pb")), 512 Eq(std::nullopt)); 513 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 514 id { 515 target_completed { 516 label: "//path/to/target:name" 517 aspect: "//aspect:file.bzl%name" 518 } 519 } 520 completed { 521 output_group { 522 name: "kythe_compilation_unit" 523 file_sets { id: "1" } 524 } 525 })pb")), 526 Eq(std::nullopt)); 527 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 528 id { named_set { id: "1" } } 529 named_set_of_files { 530 files { name: "path/to/file.kzip" uri: "file:///path/to/file.kzip" } 531 })pb")), 532 Eq(std::nullopt)); 533 } 534 535 TEST(AspectArtifactSelectorTest, SelectsSubsequentPrunedPendingFileSets) { 536 auto options = DefaultOptions(); 537 options.output_group_allowlist = 538 RegexSet::Build({"kythe_compilation_unit"}).value(); 539 options.dispose_unselected_output_groups = true; 540 AspectArtifactSelector selector(options); 541 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 542 id { 543 target_completed { 544 label: "//path/to/target:name" 545 aspect: "//aspect:file.bzl%name" 546 } 547 } 548 completed { 549 output_group { 550 name: "kythe_compilation_unit" 551 file_sets { id: "1" } 552 } 553 })pb")), 554 Eq(std::nullopt)); 555 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 556 id { 557 target_completed { 558 label: "//path/to/some/target:name" 559 aspect: "//aspect:file.bzl%name" 560 } 561 } 562 completed { 563 output_group { 564 name: "unselected" 565 file_sets { id: "1" } 566 } 567 })pb")), 568 Eq(std::nullopt)); 569 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 570 id { named_set { id: "1" } } 571 named_set_of_files { 572 files { name: "path/to/file.kzip" uri: "file:///path/to/file.kzip" } 573 })pb")), 574 Eq(BazelArtifact{ 575 .label = "//path/to/target:name", 576 .files = {{ 577 .local_path = "path/to/file.kzip", 578 .uri = "file:///path/to/file.kzip", 579 }}, 580 })); 581 } 582 583 TEST(AspectArtifactSelectorTest, SkipsNonMatchingOutputGroups) { 584 auto options = DefaultOptions(); 585 options.output_group_allowlist = 586 RegexSet::Build({"kythe_compilation_unit"}).value(); 587 AspectArtifactSelector selector(options); 588 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 589 id { named_set { id: "1" } } 590 named_set_of_files { 591 files { name: "path/to/file.kzip" uri: "file:///path/to/file.kzip" } 592 })pb")), 593 Eq(std::nullopt)); 594 595 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 596 id { 597 target_completed { 598 label: "//path/to/target:name" 599 aspect: "//aspect:file.bzl%name" 600 } 601 } 602 completed { 603 output_group { 604 name: "unselected" 605 file_sets { id: "1" } 606 } 607 })pb")), 608 Eq(std::nullopt)); 609 } 610 611 // Verifies that serializing integral ids in V2 format 612 // doesn't use the file_set_ids field. 613 TEST(AspectArtifactSelectorTest, SerializationOptimizesIntegers) { 614 AspectArtifactSelector selector(DefaultOptions()); 615 616 std::vector<build_event_stream::BuildEvent> events = 617 ToNamedSetOfFilesEvents(GenerateFileSets(5, NumericIdGenerator())); 618 for (const auto& event : events) { 619 ASSERT_EQ(selector.Select(event), std::nullopt); 620 } 621 google::protobuf::Any any; 622 ASSERT_TRUE(selector.SerializeInto(any)); 623 kythe::proto::BazelAspectArtifactSelectorStateV2 state; 624 ASSERT_TRUE(any.UnpackTo(&state)); 625 EXPECT_THAT(state.file_set_ids(), IsEmpty()); 626 } 627 628 struct StressTestCase { 629 enum class NamedSetIdStyle { 630 kIntegral, 631 kAlpha, 632 kMixed, 633 }; 634 NamedSetIdStyle id_style = NamedSetIdStyle::kMixed; 635 bool reversed = false; 636 AspectArtifactSelectorSerializationFormat serialization_format = 637 AspectArtifactSelectorSerializationFormat::kV2; 638 639 using TupleType = std::tuple<NamedSetIdStyle, bool, 640 AspectArtifactSelectorSerializationFormat>; 641 explicit StressTestCase(const TupleType& t) 642 : id_style(std::get<0>(t)), 643 reversed(std::get<1>(t)), 644 serialization_format(std::get<2>(t)) {} 645 }; 646 647 class AspectArtifactSelectorStressTest 648 : public testing::TestWithParam<StressTestCase> { 649 public: 650 std::vector<build_event_stream::BuildEvent> GenerateTestBuildEvents( 651 const BuildEventOptions& options) { 652 std::vector<build_event_stream::BuildEvent> events = 653 GenerateBuildEvents(options, MakeIdGenerator()); 654 if (GetParam().reversed) { 655 std::reverse(events.begin(), events.end()); 656 } 657 return events; 658 } 659 660 IdGenerator MakeIdGenerator() { 661 switch (GetParam().id_style) { 662 case StressTestCase::NamedSetIdStyle::kIntegral: 663 return NumericIdGenerator(); 664 case StressTestCase::NamedSetIdStyle::kAlpha: 665 return AlphaIdGenerator(); 666 case StressTestCase::NamedSetIdStyle::kMixed: 667 return MixedIdGenerator(); 668 } 669 } 670 }; 671 672 INSTANTIATE_TEST_SUITE_P( 673 AspectArtifactSelectorStressTest, AspectArtifactSelectorStressTest, 674 testing::ConvertGenerator<StressTestCase::TupleType>(testing::Combine( 675 testing::Values(StressTestCase::NamedSetIdStyle::kIntegral, 676 StressTestCase::NamedSetIdStyle::kAlpha, 677 StressTestCase::NamedSetIdStyle::kMixed), 678 testing::Bool(), 679 testing::Values(AspectArtifactSelectorSerializationFormat::kV1, 680 AspectArtifactSelectorSerializationFormat::kV2))), 681 [](const testing::TestParamInfo< 682 AspectArtifactSelectorStressTest::ParamType>& info) { 683 std::string id_style = [&] { 684 switch (info.param.id_style) { 685 case StressTestCase::NamedSetIdStyle::kAlpha: 686 return "Alphabetic"; 687 case StressTestCase::NamedSetIdStyle::kIntegral: 688 return "Integral"; 689 case StressTestCase::NamedSetIdStyle::kMixed: 690 return "Mixed"; 691 } 692 }(); 693 std::string format = [&] { 694 switch (info.param.serialization_format) { 695 case AspectArtifactSelectorSerializationFormat::kV1: 696 return "V1"; 697 case AspectArtifactSelectorSerializationFormat::kV2: 698 return "V2"; 699 } 700 }(); 701 return absl::StrCat(info.param.reversed ? "Reversed" : "Ordered", "_", 702 id_style, "_", format); 703 }); 704 705 // Verify that the selector selects the expected number of files 706 // distributed across several targets. 707 TEST_P(AspectArtifactSelectorStressTest, SelectsExpectedTargetFiles) { 708 std::vector<build_event_stream::BuildEvent> events = GenerateTestBuildEvents( 709 {.target_count = 10, .files_per_target = 2, .common_file_count = 2}); 710 711 AspectArtifactSelector selector(DefaultOptions()); 712 absl::flat_hash_set<std::string> targets; 713 absl::flat_hash_set<BazelArtifactFile> files; 714 for (const auto& event : events) { 715 if (auto artifact = selector.Select(event)) { 716 targets.insert(artifact->label); 717 files.insert(artifact->files.begin(), artifact->files.end()); 718 } 719 } 720 EXPECT_THAT(targets, SizeIs(10)); 721 EXPECT_THAT(files, SizeIs(10 * 2 + 2)); 722 } 723 724 // Verify that the selector selects the expected number of files 725 // distributed across several targets, after deserialization into 726 // a freshly constructed selector. 727 TEST_P(AspectArtifactSelectorStressTest, 728 SelectsExpectedTargetFilesWhenFreshlyDeserialized) { 729 std::vector<build_event_stream::BuildEvent> events = GenerateTestBuildEvents( 730 {.target_count = 10, .files_per_target = 2, .common_file_count = 2}); 731 732 absl::flat_hash_set<std::string> targets; 733 absl::flat_hash_set<BazelArtifactFile> files; 734 735 std::optional<google::protobuf::Any> state; 736 for (const auto& event : events) { 737 AspectArtifactSelector selector(DefaultOptions()); 738 if (state.has_value()) { 739 ASSERT_EQ(selector.DeserializeFrom(*state), absl::OkStatus()) 740 << absl::StrCat(*state); 741 } 742 743 if (auto artifact = selector.Select(event)) { 744 targets.insert(artifact->label); 745 files.insert(artifact->files.begin(), artifact->files.end()); 746 } 747 748 ASSERT_TRUE(selector.SerializeInto(state.emplace())); 749 } 750 EXPECT_THAT(targets, SizeIs(10)); 751 EXPECT_THAT(files, SizeIs(10 * 2 + 2)); 752 } 753 754 // Verify that the selector selects the expected number of files 755 // distributed across several targets, after deserialization. 756 TEST_P(AspectArtifactSelectorStressTest, 757 SelectsExpectedTargetFilesWhenDeserialized) { 758 std::vector<build_event_stream::BuildEvent> events = GenerateTestBuildEvents( 759 {.target_count = 10, .files_per_target = 2, .common_file_count = 2}); 760 761 absl::flat_hash_set<std::string> targets; 762 absl::flat_hash_set<BazelArtifactFile> files; 763 764 std::optional<google::protobuf::Any> state; 765 AspectArtifactSelector selector(DefaultOptions()); 766 for (const auto& event : events) { 767 if (state.has_value()) { 768 ASSERT_EQ(selector.DeserializeFrom(*state), absl::OkStatus()) 769 << absl::StrCat(*state); 770 } 771 772 if (auto artifact = selector.Select(event)) { 773 targets.insert(artifact->label); 774 files.insert(artifact->files.begin(), artifact->files.end()); 775 } 776 777 ASSERT_TRUE(selector.SerializeInto(state.emplace())); 778 } 779 EXPECT_THAT(targets, SizeIs(10)); 780 EXPECT_THAT(files, SizeIs(10 * 2 + 2)); 781 } 782 783 TEST(AspectArtifactSelectorTest, SerializationRoundTrips) { 784 AspectArtifactSelector selector(DefaultOptions()); 785 786 // Pending. 787 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 788 id { 789 target_completed { 790 label: "//path/to/target:name" 791 aspect: "//aspect:file.bzl%name" 792 } 793 } 794 completed { 795 success: true 796 output_group { 797 name: "kythe_compilation_unit" 798 file_sets { id: "1" } 799 } 800 })pb")), 801 Eq(std::nullopt)); 802 // Active. 803 ASSERT_THAT(selector.Select(ParseEventOrDie(R"pb( 804 id { named_set { id: "2" } } 805 named_set_of_files { 806 files { name: "path/to/1/file.kzip" uri: "file:///path/to/1/file.kzip" } 807 files { name: "path/to/2/file.kzip" uri: "file:///path/to/2/file.kzip" } 808 })pb")), 809 Eq(std::nullopt)); 810 811 // Active => Disposed. 812 ASSERT_THAT(selector.Select(ParseEventOrDie(R"pb( 813 id { named_set { id: "3" } } 814 named_set_of_files { 815 files { name: "path/to/1/file.kzip" uri: "file:///path/to/1/file.kzip" } 816 files { name: "path/to/3/file.kzip" uri: "file:///path/to/3/file.kzip" } 817 })pb")), 818 Eq(std::nullopt)); 819 EXPECT_THAT( 820 selector.Select(ParseEventOrDie(R"pb( 821 id { 822 target_completed { 823 label: "//path/to/disposed/target:name" 824 aspect: "//aspect:file.bzl%name" 825 } 826 } 827 completed { 828 success: true 829 output_group { 830 name: "kythe_compilation_unit" 831 file_sets { id: "3" } 832 } 833 })pb")), 834 Optional(FieldsAre( 835 "//path/to/disposed/target:name", 836 UnorderedElementsAre( 837 FieldsAre("path/to/1/file.kzip", "file:///path/to/1/file.kzip"), 838 FieldsAre("path/to/3/file.kzip", 839 "file:///path/to/3/file.kzip"))))); 840 841 google::protobuf::Any initial; 842 ASSERT_TRUE(selector.SerializeInto(initial)); 843 { 844 // The original selector round trips. 845 ASSERT_THAT(selector.DeserializeFrom(initial), absl::OkStatus()); 846 847 google::protobuf::Any deserialized; 848 ASSERT_TRUE(selector.SerializeInto(deserialized)); 849 850 EXPECT_THAT(deserialized, EqualsProto(initial)); 851 } 852 853 { 854 // A freshly constructed selector round trips. 855 AspectArtifactSelector empty_selector(DefaultOptions()); 856 ASSERT_THAT(empty_selector.DeserializeFrom(initial), absl::OkStatus()); 857 858 google::protobuf::Any deserialized; 859 ASSERT_TRUE(empty_selector.SerializeInto(deserialized)); 860 861 EXPECT_THAT(deserialized, EqualsProto(initial)); 862 } 863 } 864 865 TEST(AspectArtifactSelectorTest, CompatibleWithAny) { 866 // Just ensures that AspectArtifactSelector can be assigned to an Any. 867 AnyArtifactSelector unused = AspectArtifactSelector(DefaultOptions()); 868 } 869 870 TEST(AspectArtifactSelectorTest, SerializationResumesSelection) { 871 google::protobuf::Any state; 872 { 873 AspectArtifactSelector selector(DefaultOptions()); 874 875 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 876 id { 877 target_completed { 878 label: "//path/to/target:name" 879 aspect: "//aspect:file.bzl%name" 880 } 881 } 882 completed { 883 success: true 884 output_group { 885 name: "kythe_compilation_unit" 886 file_sets { id: "1" } 887 } 888 })pb")), 889 Eq(std::nullopt)); 890 ASSERT_THAT(selector.SerializeInto(state), true); 891 } 892 893 { 894 AspectArtifactSelector selector(DefaultOptions()); 895 ASSERT_THAT(selector.Deserialize({state}), absl::OkStatus()); 896 897 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 898 id { named_set { id: "1" } } 899 named_set_of_files { 900 files { name: "path/to/file.kzip" uri: "file:///path/to/file.kzip" } 901 })pb")), 902 Eq(BazelArtifact{ 903 .label = "//path/to/target:name", 904 .files = {{ 905 .local_path = "path/to/file.kzip", 906 .uri = "file:///path/to/file.kzip", 907 }}, 908 })); 909 } 910 { 911 AspectArtifactSelector selector(DefaultOptions()); 912 ASSERT_THAT(selector.Deserialize({&state}), absl::OkStatus()); 913 914 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 915 id { named_set { id: "1" } } 916 named_set_of_files { 917 files { name: "path/to/file.kzip" uri: "file:///path/to/file.kzip" } 918 })pb")), 919 Eq(BazelArtifact{ 920 .label = "//path/to/target:name", 921 .files = {{ 922 .local_path = "path/to/file.kzip", 923 .uri = "file:///path/to/file.kzip", 924 }}, 925 })); 926 } 927 } 928 929 class MockArtifactSelector : public BazelArtifactSelector { 930 public: 931 MockArtifactSelector() = default; 932 MOCK_METHOD(std::optional<BazelArtifact>, Select, 933 (const build_event_stream::BuildEvent&), (override)); 934 MOCK_METHOD(bool, SerializeInto, (google::protobuf::Any&), (const override)); 935 MOCK_METHOD(absl::Status, DeserializeFrom, (const google::protobuf::Any&), 936 (override)); 937 }; 938 939 TEST(BazelArtifactSelectorTest, DeserializationProtocol) { 940 using ::testing::Ref; 941 using ::testing::Return; 942 943 MockArtifactSelector mock; 944 945 google::protobuf::Any any; 946 947 EXPECT_CALL(mock, DeserializeFrom(Ref(any))) 948 .WillOnce(Return(absl::OkStatus())) 949 .WillOnce(Return(absl::UnimplementedError(""))) 950 .WillOnce(Return(absl::InvalidArgumentError(""))) 951 .WillOnce(Return(absl::FailedPreconditionError(""))); 952 953 EXPECT_THAT(mock.Deserialize({&any}), absl::OkStatus()); 954 EXPECT_THAT(mock.Deserialize({&any}), absl::OkStatus()); 955 EXPECT_THAT(mock.Deserialize({&any}), absl::InvalidArgumentError("")); 956 EXPECT_THAT(mock.Deserialize({&any}), 957 absl::NotFoundError("No state found: FAILED_PRECONDITION: ")); 958 959 EXPECT_CALL(mock, DeserializeFrom(Ref(any))) 960 .WillOnce(Return(absl::FailedPreconditionError(""))) 961 .WillOnce(Return(absl::FailedPreconditionError(""))) 962 .WillOnce(Return(absl::OkStatus())); 963 964 EXPECT_THAT(mock.Deserialize({&any, &any, &any}), absl::OkStatus()); 965 } 966 967 TEST(AnyArtifactSelectorTest, ForwardsFunctions) { 968 using ::testing::_; 969 using ::testing::Ref; 970 using ::testing::Return; 971 972 MockArtifactSelector mock; 973 google::protobuf::Any dummy; 974 975 EXPECT_CALL(mock, Select(_)).WillOnce(Return(std::nullopt)); 976 EXPECT_CALL(mock, SerializeInto(Ref(dummy))).WillOnce(Return(false)); 977 EXPECT_CALL(mock, DeserializeFrom(Ref(dummy))) 978 .WillOnce(Return(absl::UnimplementedError(""))); 979 980 AnyArtifactSelector selector = std::ref(mock); 981 EXPECT_THAT(selector.Select(build_event_stream::BuildEvent()), 982 Eq(std::nullopt)); 983 EXPECT_THAT(selector.SerializeInto(dummy), false); 984 EXPECT_THAT(selector.DeserializeFrom(dummy), absl::UnimplementedError("")); 985 } 986 987 TEST(ExtraActionSelector, SelectsAllByDefault) { 988 ExtraActionSelector selector; 989 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 990 id { 991 action_completed { 992 primary_output: "path/to/file/dummy.kzip" 993 label: "//kythe/cxx/extractor:bazel_artifact_selector" 994 configuration { id: "hash0" } 995 } 996 } 997 action { 998 success: true 999 label: "//kythe/cxx/extractor:bazel_artifact_selector" 1000 primary_output { uri: "file:///home/path/to/file/dummy.kzip" } 1001 configuration { id: "hash0" } 1002 type: "extract_kzip_cxx_extra_action" 1003 } 1004 )pb")), 1005 Eq(BazelArtifact{ 1006 .label = "//kythe/cxx/extractor:bazel_artifact_selector", 1007 .files = {{ 1008 .local_path = "path/to/file/dummy.kzip", 1009 .uri = "file:///home/path/to/file/dummy.kzip", 1010 }}, 1011 })); 1012 } 1013 1014 TEST(ExtraActionSelector, SelectsFromList) { 1015 ExtraActionSelector selector({"matching_action_type"}); 1016 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 1017 id { 1018 action_completed { 1019 primary_output: "path/to/file/dummy.kzip" 1020 label: "//kythe/cxx/extractor:bazel_artifact_selector" 1021 configuration { id: "hash0" } 1022 } 1023 } 1024 action { 1025 success: true 1026 label: "//kythe/cxx/extractor:bazel_artifact_selector" 1027 primary_output { uri: "file:///home/path/to/file/dummy.kzip" } 1028 configuration { id: "hash0" } 1029 type: "matching_action_type" 1030 } 1031 )pb")), 1032 Eq(BazelArtifact{ 1033 .label = "//kythe/cxx/extractor:bazel_artifact_selector", 1034 .files = {{ 1035 .local_path = "path/to/file/dummy.kzip", 1036 .uri = "file:///home/path/to/file/dummy.kzip", 1037 }}, 1038 })); 1039 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 1040 id { 1041 action_completed { 1042 primary_output: "path/to/file/dummy.kzip" 1043 label: "//kythe/cxx/extractor:bazel_artifact_selector" 1044 configuration { id: "hash0" } 1045 } 1046 } 1047 action { 1048 success: true 1049 label: "//kythe/cxx/extractor:bazel_artifact_selector" 1050 primary_output { uri: "file:///home/path/to/file/dummy.kzip" } 1051 configuration { id: "hash0" } 1052 type: "another_action_type" 1053 } 1054 )pb")), 1055 Eq(std::nullopt)); 1056 } 1057 1058 TEST(ExtraActionSelector, SelectsFromPattern) { 1059 const RE2 pattern("matching_action_type"); 1060 ExtraActionSelector selector(&pattern); 1061 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 1062 id { 1063 action_completed { 1064 primary_output: "path/to/file/dummy.kzip" 1065 label: "//kythe/cxx/extractor:bazel_artifact_selector" 1066 configuration { id: "hash0" } 1067 } 1068 } 1069 action { 1070 success: true 1071 label: "//kythe/cxx/extractor:bazel_artifact_selector" 1072 primary_output { uri: "file:///home/path/to/file/dummy.kzip" } 1073 configuration { id: "hash0" } 1074 type: "matching_action_type" 1075 } 1076 )pb")), 1077 Eq(BazelArtifact{ 1078 .label = "//kythe/cxx/extractor:bazel_artifact_selector", 1079 .files = {{ 1080 .local_path = "path/to/file/dummy.kzip", 1081 .uri = "file:///home/path/to/file/dummy.kzip", 1082 }}, 1083 })); 1084 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 1085 id { 1086 action_completed { 1087 primary_output: "path/to/file/dummy.kzip" 1088 label: "//kythe/cxx/extractor:bazel_artifact_selector" 1089 configuration { id: "hash0" } 1090 } 1091 } 1092 action { 1093 success: true 1094 label: "//kythe/cxx/extractor:bazel_artifact_selector" 1095 primary_output { uri: "file:///home/path/to/file/dummy.kzip" } 1096 configuration { id: "hash0" } 1097 type: "another_action_type" 1098 } 1099 )pb")), 1100 Eq(std::nullopt)); 1101 } 1102 1103 TEST(ExtraActionSelector, SelectsNoneWithEmptyPattern) { 1104 const RE2 pattern(""); 1105 ExtraActionSelector selector(&pattern); 1106 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 1107 id { 1108 action_completed { 1109 primary_output: "path/to/file/dummy.kzip" 1110 label: "//kythe/cxx/extractor:bazel_artifact_selector" 1111 configuration { id: "hash0" } 1112 } 1113 } 1114 action { 1115 success: true 1116 label: "//kythe/cxx/extractor:bazel_artifact_selector" 1117 primary_output { uri: "file:///home/path/to/file/dummy.kzip" } 1118 configuration { id: "hash0" } 1119 type: "another_action_type" 1120 } 1121 )pb")), 1122 Eq(std::nullopt)); 1123 } 1124 1125 TEST(ExtraActionSelector, SelectsNoneWithNullPattern) { 1126 ExtraActionSelector selector(nullptr); 1127 EXPECT_THAT(selector.Select(ParseEventOrDie(R"pb( 1128 id { 1129 action_completed { 1130 primary_output: "path/to/file/dummy.kzip" 1131 label: "//kythe/cxx/extractor:bazel_artifact_selector" 1132 configuration { id: "hash0" } 1133 } 1134 } 1135 action { 1136 success: true 1137 label: "//kythe/cxx/extractor:bazel_artifact_selector" 1138 primary_output { uri: "file:///home/path/to/file/dummy.kzip" } 1139 configuration { id: "hash0" } 1140 type: "another_action_type" 1141 } 1142 )pb")), 1143 Eq(std::nullopt)); 1144 } 1145 1146 } // namespace 1147 } // namespace kythe