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