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