kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/cxx/extractor/testdata/claim_pragma_test.cc (about)

     1  #include <cstddef>
     2  #include <memory>
     3  #include <string>
     4  #include <unordered_map>
     5  #include <unordered_set>
     6  #include <vector>
     7  
     8  #include "absl/log/check.h"
     9  #include "gmock/gmock.h"
    10  #include "google/protobuf/descriptor.h"
    11  #include "google/protobuf/text_format.h"
    12  #include "google/protobuf/util/field_comparator.h"
    13  #include "google/protobuf/util/message_differencer.h"
    14  #include "gtest/gtest.h"
    15  #include "kythe/cxx/extractor/cxx_extractor.h"
    16  #include "kythe/cxx/extractor/language.h"
    17  #include "kythe/proto/analysis.pb.h"
    18  #include "kythe/proto/filecontext.pb.h"
    19  
    20  namespace kythe {
    21  namespace {
    22  
    23  using ::google::protobuf::FieldDescriptor;
    24  using ::google::protobuf::Message;
    25  using ::google::protobuf::TextFormat;
    26  using ::google::protobuf::util::MessageDifferencer;
    27  using ::google::protobuf::util::SimpleFieldComparator;
    28  using ::testing::ElementsAre;
    29  
    30  constexpr char kExpectedContents[] = R"(
    31  v_name {
    32    language: "c++"
    33  }
    34  required_input {
    35    v_name {
    36      path: "kythe/cxx/extractor/testdata/claim_main.cc"
    37    }
    38    info {
    39      path: "kythe/cxx/extractor/testdata/claim_main.cc"
    40      digest: "4b19bb44ad66bc14a2b29694604420990d94e2b27bb55d10ce9ad5a93f6a6bde"
    41    }
    42    details {
    43      [type.googleapis.com/kythe.proto.ContextDependentVersion] {
    44        row {
    45          source_context: "hash0"
    46          column {
    47            offset: 12
    48            linked_context: "hash1"
    49          }
    50          column {
    51            offset: 33
    52            linked_context: "hash1"
    53          }
    54          always_process: true
    55        }
    56      }
    57    }
    58  }
    59  required_input {
    60    v_name {
    61      path: "kythe/cxx/extractor/testdata/claim_b.h"
    62    }
    63    info {
    64      path: "kythe/cxx/extractor/testdata/claim_b.h"
    65      digest: "a3d03965930673eced0d8ad50753f1933013a27a06b8be57443781275f1f937f"
    66    }
    67    details {
    68      [type.googleapis.com/kythe.proto.ContextDependentVersion] {
    69        row {
    70          source_context: "hash1"
    71          always_process: true
    72        }
    73      }
    74    }
    75  }
    76  required_input {
    77    v_name {
    78      path: "kythe/cxx/extractor/testdata/claim_a.h"
    79    }
    80    info {
    81      path: "kythe/cxx/extractor/testdata/claim_a.h"
    82      digest: "2c339c36aa02459955c6d5be9e73ebe030baf3b74dc1123439af8613844d0b1f"
    83    }
    84    details {
    85      [type.googleapis.com/kythe.proto.ContextDependentVersion] {
    86        row {
    87          source_context: "hash1"
    88        }
    89      }
    90    }
    91  }
    92  argument: "./kythe/cxx/extractor/cxx_extractor"
    93  argument: "-target"
    94  argument: "{ignored target}"
    95  argument: "-DKYTHE_IS_RUNNING=1"
    96  argument: "-resource-dir"
    97  argument: "/kythe_builtins"
    98  argument: "--driver-mode=g++"
    99  argument: "-I./kythe/cxx/extractor/testdata"
   100  argument: "./kythe/cxx/extractor/testdata/claim_main.cc"
   101  argument: "-fsyntax-only"
   102  source_file: "kythe/cxx/extractor/testdata/claim_main.cc"
   103  entry_context: "hash0"
   104  )";
   105  
   106  // Returns the `FieldDescriptor*` for the nested field found
   107  // by following the provided path components or null, e.g.
   108  //   auto pb = Parse(R"(
   109  //     message {
   110  //       inner {
   111  //         field: 10
   112  //       }
   113  //     })");
   114  //   const FieldDescriptor* message_inner_field =
   115  //       FindFieldByLowercasePath(pb.GetDescriptor(),
   116  //                                {"message", "inner", "field"});
   117  const FieldDescriptor* FindNestedFieldByLowercasePath(
   118      const google::protobuf::Descriptor* descriptor,
   119      const std::vector<std::string>& field_names) {
   120    const FieldDescriptor* field = nullptr;
   121    for (const auto& name : field_names) {
   122      if (descriptor == nullptr) return nullptr;
   123      field = descriptor->FindFieldByLowercaseName(name);
   124      if (field == nullptr) return nullptr;
   125      descriptor = field->message_type();
   126    }
   127    return field;
   128  }
   129  
   130  std::vector<const FieldDescriptor*> CanonicalizedHashFields(
   131      const kythe::proto::CompilationUnit& unit,
   132      const kythe::proto::ContextDependentVersion& context) {
   133    const auto* unit_desc = unit.GetDescriptor();
   134    const auto* context_desc = context.GetDescriptor();
   135    return {
   136        FindNestedFieldByLowercasePath(unit_desc, {"entry_context"}),
   137        FindNestedFieldByLowercasePath(context_desc, {"row", "source_context"}),
   138        FindNestedFieldByLowercasePath(context_desc,
   139                                       {"row", "column", "linked_context"}),
   140    };
   141  }
   142  
   143  class CanonicalHashComparator : public SimpleFieldComparator {
   144   public:
   145    bool CanonicalizeHashField(const FieldDescriptor* field) {
   146      CHECK(field && field->cpp_type() == FieldDescriptor::CPPTYPE_STRING)
   147          << "Field must be bytes or string.";
   148      auto result = canonical_fields_.insert(field);
   149      return result.second;
   150    }
   151  
   152   private:
   153    using HashMap = std::unordered_map<std::string, size_t>;
   154  
   155    ComparisonResult Compare(
   156        const Message& message_1, const Message& message_2,
   157        const FieldDescriptor* field, int index_1, int index_2,
   158        const google::protobuf::util::FieldContext* field_context) override {
   159      // Fall back to the default if this isn't a canonicalized field.
   160      if (canonical_fields_.find(field) == canonical_fields_.end()) {
   161        return SimpleCompare(message_1, message_2, field, index_1, index_2,
   162                             field_context);
   163      }
   164      const auto* reflection_1 = message_1.GetReflection();
   165      const auto* reflection_2 = message_2.GetReflection();
   166      if (field->is_repeated()) {
   167        // Allocate scratch strings to store the result if a conversion is
   168        // needed.
   169        std::string scratch1;
   170        std::string scratch2;
   171        return CompareCanonicalHash(reflection_1->GetRepeatedStringReference(
   172                                        message_1, field, index_1, &scratch1),
   173                                    reflection_2->GetRepeatedStringReference(
   174                                        message_2, field, index_2, &scratch2));
   175      } else {
   176        // Allocate scratch strings to store the result if a conversion is
   177        // needed.
   178        std::string scratch1;
   179        std::string scratch2;
   180        return CompareCanonicalHash(
   181            reflection_1->GetStringReference(message_1, field, &scratch1),
   182            reflection_2->GetStringReference(message_2, field, &scratch2));
   183      }
   184    }
   185  
   186    ComparisonResult CompareCanonicalHash(const std::string& string_1,
   187                                          const std::string& string_2) {
   188      return HashIndex(&left_message_hashes_, string_1) ==
   189                     HashIndex(&right_message_hashes_, string_2)
   190                 ? SAME
   191                 : DIFFERENT;
   192    }
   193  
   194    static size_t HashIndex(HashMap* canonical_map, const std::string& hash) {
   195      size_t index = canonical_map->size();
   196      // We use an index equivalent to the visitation order of the hashes.
   197      // This is potentially fragile as we really only care if a protocol buffer
   198      // is self-consistent, but will suffice for unit tests.
   199      auto result = canonical_map->insert({hash, index});
   200      return result.first->second;
   201    }
   202  
   203    std::unordered_set<const FieldDescriptor*> canonical_fields_;
   204    HashMap left_message_hashes_;
   205    HashMap right_message_hashes_;
   206  };
   207  
   208  class FakeCompilationWriterSink : public kythe::CompilationWriterSink {
   209   public:
   210    explicit FakeCompilationWriterSink(int* call_count)
   211        : call_count_(call_count) {}
   212  
   213   private:
   214    void WriteFileContent(const kythe::proto::FileData&) override {}
   215    void OpenIndex(const std::string&) override {}
   216    void WriteHeader(const kythe::proto::CompilationUnit& unit) override {
   217      (*call_count_)++;
   218  
   219      const auto& desc = *unit.GetDescriptor();
   220  
   221      kythe::proto::ContextDependentVersion context;
   222      CanonicalHashComparator hash_compare;
   223      for (const auto* field : CanonicalizedHashFields(unit, context)) {
   224        hash_compare.CanonicalizeHashField(field);
   225      }
   226      MessageDifferencer diff;
   227      diff.set_message_field_comparison(MessageDifferencer::EQUIVALENT);
   228      diff.set_scope(MessageDifferencer::PARTIAL);
   229      diff.set_field_comparator(&hash_compare);
   230      diff.TreatAsSet(desc.FindFieldByLowercaseName("required_input"));
   231      // We need to ignore the value of the '-target' argument, so
   232      // do the comparison separately.
   233      diff.IgnoreField(desc.FindFieldByLowercaseName("argument"));
   234  
   235      kythe::proto::CompilationUnit expected;
   236      ASSERT_TRUE(TextFormat::ParseFromString(kExpectedContents, &expected));
   237      std::string diffs;
   238      diff.ReportDifferencesToString(&diffs);
   239      EXPECT_TRUE(diff.Compare(expected, unit)) << diffs;
   240  
   241      EXPECT_THAT(
   242          unit.argument(),
   243          ElementsAre("/dummy/path/to/g++", "-target", ::testing::_,
   244                      "-DKYTHE_IS_RUNNING=1", "-resource-dir", "/kythe_builtins",
   245                      "--driver-mode=g++", "-I./kythe/cxx/extractor/testdata",
   246                      "./kythe/cxx/extractor/testdata/claim_main.cc",
   247                      "-fsyntax-only"));
   248    }
   249  
   250    int* call_count_;
   251  };
   252  
   253  TEST(ClaimPragmaTest, ClaimPragmaIsSupported) {
   254    kythe::ExtractorConfiguration extractor;
   255    extractor.SetArgs({
   256        "dummy-executable",
   257        "--with_executable",
   258        "/dummy/path/to/g++",
   259        "-I./kythe/cxx/extractor/testdata",
   260        "./kythe/cxx/extractor/testdata/claim_main.cc",
   261    });
   262  
   263    int call_count = 0;
   264    EXPECT_TRUE(extractor.Extract(
   265        supported_language::Language::kCpp,
   266        std::make_unique<FakeCompilationWriterSink>(&call_count)));
   267    EXPECT_EQ(call_count, 1);
   268  }
   269  
   270  }  // namespace
   271  }  // namespace kythe