ast_to_proto_test.cpp 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. // Exceptions. See /LICENSE for license information.
  3. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. #include "executable_semantics/fuzzing/ast_to_proto.h"
  5. #include <gmock/gmock.h>
  6. #include <google/protobuf/descriptor.h>
  7. #include <gtest/gtest.h>
  8. #include <filesystem>
  9. #include <numeric>
  10. #include <set>
  11. #include <variant>
  12. #include "executable_semantics/syntax/parse.h"
  13. #include "llvm/Support/Error.h"
  14. namespace Carbon::Testing {
  15. namespace {
  16. using ::google::protobuf::Descriptor;
  17. using ::google::protobuf::FieldDescriptor;
  18. using ::google::protobuf::Message;
  19. using ::google::protobuf::Reflection;
  20. static std::vector<llvm::StringRef>* carbon_files = nullptr;
  21. // Concatenates message and field names.
  22. auto FieldName(const Descriptor& descriptor, const FieldDescriptor& field)
  23. -> std::string {
  24. return descriptor.full_name() + "." + field.name();
  25. }
  26. // Traverses the proto to find all unique messages and fields.
  27. auto CollectAllFields(const Descriptor& descriptor,
  28. std::set<std::string>& all_messages,
  29. std::set<std::string>& all_fields) -> void {
  30. all_messages.insert(descriptor.full_name());
  31. for (int i = 0; i < descriptor.field_count(); ++i) {
  32. const FieldDescriptor* field = descriptor.field(i);
  33. all_fields.insert(FieldName(descriptor, *field));
  34. if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE &&
  35. all_messages.find(field->message_type()->full_name()) ==
  36. all_messages.end()) {
  37. CollectAllFields(*field->message_type(), all_messages, all_fields);
  38. }
  39. }
  40. }
  41. // Traverses an instance of the proto to find all used fields.
  42. auto CollectUsedFields(const Message& message,
  43. std::set<std::string>& used_fields) -> void {
  44. const Descriptor* descriptor = message.GetDescriptor();
  45. const Reflection* reflection = message.GetReflection();
  46. for (int i = 0; i < descriptor->field_count(); ++i) {
  47. const FieldDescriptor* field = descriptor->field(i);
  48. if (!field->is_repeated()) {
  49. if (reflection->HasField(message, field)) {
  50. used_fields.insert(FieldName(*descriptor, *field));
  51. }
  52. } else {
  53. if (reflection->FieldSize(message, field) > 0) {
  54. used_fields.insert(FieldName(*descriptor, *field));
  55. }
  56. }
  57. if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
  58. if (!field->is_repeated()) {
  59. if (reflection->HasField(message, field)) {
  60. CollectUsedFields(reflection->GetMessage(message, field),
  61. used_fields);
  62. }
  63. } else {
  64. for (int i = 0; i < reflection->FieldSize(message, field); ++i) {
  65. CollectUsedFields(reflection->GetRepeatedMessage(message, field, i),
  66. used_fields);
  67. }
  68. }
  69. }
  70. }
  71. }
  72. // Determines which fields in the proto have not been used at all.
  73. auto GetUnusedFields(const Message& message) -> std::set<std::string> {
  74. std::set<std::string> all_messages;
  75. std::set<std::string> all_fields;
  76. CollectAllFields(*message.GetDescriptor(), all_messages, all_fields);
  77. std::set<std::string> used_fields;
  78. CollectUsedFields(message, used_fields);
  79. std::set<std::string> unused_fields;
  80. std::set_difference(all_fields.begin(), all_fields.end(), used_fields.begin(),
  81. used_fields.end(),
  82. std::inserter(unused_fields, unused_fields.begin()));
  83. return unused_fields;
  84. }
  85. // A 'smoke' test to check that each field present in `carbon.proto` is set at
  86. // least once after converting all Carbon test sources to proto represention.
  87. TEST(AstToProtoTest, SetsAllProtoFields) {
  88. Carbon::Fuzzing::CompilationUnit merged_proto;
  89. for (const llvm::StringRef f : *carbon_files) {
  90. Carbon::Arena arena;
  91. const ErrorOr<AST> ast = Carbon::Parse(&arena, f, /*trace=*/false);
  92. if (ast.ok()) {
  93. merged_proto.MergeFrom(AstToProto(*ast));
  94. }
  95. }
  96. std::set<std::string> unused_fields = GetUnusedFields(merged_proto);
  97. EXPECT_EQ(unused_fields.size(), 0)
  98. << "Unused fields"
  99. << std::accumulate(unused_fields.begin(), unused_fields.end(),
  100. std::string(),
  101. [](const std::string& a, const std::string& b) {
  102. return a + '\n' + b;
  103. });
  104. }
  105. } // namespace
  106. } // namespace Carbon::Testing
  107. int main(int argc, char** argv) {
  108. ::testing::InitGoogleTest(&argc, argv);
  109. // gtest should remove flags, leaving just input files.
  110. Carbon::Testing::carbon_files =
  111. new std::vector<llvm::StringRef>(&argv[1], &argv[argc]);
  112. return RUN_ALL_TESTS();
  113. }