• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7 
8 // Author: kenton@google.com (Kenton Varda)
9 
10 #include "google/protobuf/compiler/mock_code_generator.h"
11 
12 #include <stdlib.h>
13 
14 #include <cstdint>
15 #include <iostream>
16 #include <memory>
17 #include <ostream>
18 #include <string>
19 #include <utility>
20 #include <vector>
21 
22 #include "google/protobuf/testing/file.h"
23 #include "google/protobuf/testing/file.h"
24 #include "google/protobuf/descriptor.pb.h"
25 #include <gtest/gtest.h>
26 #include "absl/log/absl_check.h"
27 #include "absl/log/absl_log.h"
28 #include "absl/strings/str_cat.h"
29 #include "absl/strings/str_join.h"
30 #include "absl/strings/str_replace.h"
31 #include "absl/strings/str_split.h"
32 #include "absl/strings/string_view.h"
33 #include "absl/strings/strip.h"
34 #include "absl/strings/substitute.h"
35 #include "google/protobuf/compiler/plugin.pb.h"
36 #include "google/protobuf/descriptor.h"
37 #include "google/protobuf/descriptor_visitor.h"
38 #include "google/protobuf/io/printer.h"
39 #include "google/protobuf/io/zero_copy_stream.h"
40 #include "google/protobuf/text_format.h"
41 #include "google/protobuf/unittest_features.pb.h"
42 
43 #ifdef major
44 #undef major
45 #endif
46 #ifdef minor
47 #undef minor
48 #endif
49 
50 namespace google {
51 namespace protobuf {
52 namespace compiler {
53 namespace {
54 
55 // Returns the list of the names of files in all_files in the form of a
56 // comma-separated string.
CommaSeparatedList(const std::vector<const FileDescriptor * > & all_files)57 std::string CommaSeparatedList(
58     const std::vector<const FileDescriptor*>& all_files) {
59   std::vector<absl::string_view> names;
60   for (size_t i = 0; i < all_files.size(); i++) {
61     names.push_back(all_files[i]->name());
62   }
63   return absl::StrJoin(names, ",");
64 }
65 
66 static constexpr absl::string_view kFirstInsertionPointName =
67     "first_mock_insertion_point";
68 static constexpr absl::string_view kSecondInsertionPointName =
69     "second_mock_insertion_point";
70 static constexpr absl::string_view kFirstInsertionPoint =
71     "# @@protoc_insertion_point(first_mock_insertion_point) is here\n";
72 static constexpr absl::string_view kSecondInsertionPoint =
73     "  # @@protoc_insertion_point(second_mock_insertion_point) is here\n";
74 
GetTestCase()75 absl::string_view GetTestCase() {
76   const char* c_key = getenv("TEST_CASE");
77   if (c_key == nullptr) {
78     // In Windows, setting 'TEST_CASE=' is equivalent to unsetting
79     // and therefore c_key can be nullptr
80     return "";
81   }
82   return c_key;
83 }
84 
85 }  // namespace
86 
MockCodeGenerator(absl::string_view name)87 MockCodeGenerator::MockCodeGenerator(absl::string_view name) : name_(name) {
88   absl::string_view key = GetTestCase();
89   if (key == "no_editions") {
90     suppressed_features_ |= CodeGenerator::FEATURE_SUPPORTS_EDITIONS;
91   } else if (key == "invalid_features") {
92     feature_extensions_ = {nullptr};
93   } else if (key == "no_feature_defaults") {
94     feature_extensions_ = {};
95   } else if (key == "high_maximum") {
96     maximum_edition_ = Edition::EDITION_99997_TEST_ONLY;
97   } else if (key == "low_minimum") {
98     maximum_edition_ = Edition::EDITION_1_TEST_ONLY;
99   }
100 }
101 
102 MockCodeGenerator::~MockCodeGenerator() = default;
103 
GetSupportedFeatures() const104 uint64_t MockCodeGenerator::GetSupportedFeatures() const {
105   uint64_t all_features = CodeGenerator::FEATURE_PROTO3_OPTIONAL |
106                           CodeGenerator::FEATURE_SUPPORTS_EDITIONS;
107   return all_features & ~suppressed_features_;
108 }
109 
SuppressFeatures(uint64_t features)110 void MockCodeGenerator::SuppressFeatures(uint64_t features) {
111   suppressed_features_ = features;
112 }
113 
ExpectGenerated(absl::string_view name,absl::string_view parameter,absl::string_view insertions,absl::string_view file,absl::string_view first_message_name,absl::string_view first_parsed_file_name,absl::string_view output_directory)114 void MockCodeGenerator::ExpectGenerated(
115     absl::string_view name, absl::string_view parameter,
116     absl::string_view insertions, absl::string_view file,
117     absl::string_view first_message_name,
118     absl::string_view first_parsed_file_name,
119     absl::string_view output_directory) {
120   std::string content;
121   ABSL_CHECK_OK(File::GetContents(
122       absl::StrCat(output_directory, "/", GetOutputFileName(name, file)),
123       &content, true));
124 
125   std::vector<std::string> lines =
126       absl::StrSplit(content, '\n', absl::SkipEmpty());
127 
128   while (!lines.empty() && lines.back().empty()) {
129     lines.pop_back();
130   }
131   for (size_t i = 0; i < lines.size(); i++) {
132     absl::StrAppend(&lines[i], "\n");
133   }
134 
135   std::vector<std::string> insertion_list;
136   if (!insertions.empty()) {
137     insertion_list = absl::StrSplit(insertions, ',', absl::SkipEmpty());
138   }
139 
140   EXPECT_EQ(lines.size(), 3 + insertion_list.size() * 2);
141   EXPECT_EQ(GetOutputFileContent(name, parameter, file, first_parsed_file_name,
142                                  first_message_name),
143             lines[0]);
144 
145   EXPECT_EQ(kFirstInsertionPoint, lines[1 + insertion_list.size()]);
146   EXPECT_EQ(kSecondInsertionPoint, lines[2 + insertion_list.size() * 2]);
147 
148   for (size_t i = 0; i < insertion_list.size(); i++) {
149     EXPECT_EQ(GetOutputFileContent(insertion_list[i], "first_insert", file,
150                                    file, first_message_name),
151               lines[1 + i]);
152     // Second insertion point is indented, so the inserted text should
153     // automatically be indented too.
154     EXPECT_EQ(absl::StrCat(
155                   "  ", GetOutputFileContent(insertion_list[i], "second_insert",
156                                              file, file, first_message_name)),
157               lines[2 + insertion_list.size() + i]);
158   }
159 }
160 
161 namespace {
CheckSingleAnnotation(absl::string_view expected_file,absl::string_view expected_text,absl::string_view file_content,const GeneratedCodeInfo::Annotation & annotation)162 void CheckSingleAnnotation(absl::string_view expected_file,
163                            absl::string_view expected_text,
164                            absl::string_view file_content,
165                            const GeneratedCodeInfo::Annotation& annotation) {
166   EXPECT_EQ(expected_file, annotation.source_file());
167   ASSERT_GE(file_content.size(), annotation.begin());
168   ASSERT_GE(file_content.size(), annotation.end());
169   ASSERT_LE(annotation.begin(), annotation.end());
170   EXPECT_EQ(expected_text.size(), annotation.end() - annotation.begin());
171   EXPECT_EQ(expected_text,
172             file_content.substr(annotation.begin(), expected_text.size()));
173 }
174 }  // anonymous namespace
175 
CheckGeneratedAnnotations(absl::string_view name,absl::string_view file,absl::string_view output_directory)176 void MockCodeGenerator::CheckGeneratedAnnotations(
177     absl::string_view name, absl::string_view file,
178     absl::string_view output_directory) {
179   std::string file_content;
180   ABSL_CHECK_OK(File::GetContents(
181       absl::StrCat(output_directory, "/", GetOutputFileName(name, file)),
182       &file_content, true));
183   std::string meta_content;
184   ABSL_CHECK_OK(
185       File::GetContents(absl::StrCat(output_directory, "/",
186                                      GetOutputFileName(name, file), ".pb.meta"),
187                         &meta_content, true));
188   GeneratedCodeInfo annotations;
189   ABSL_CHECK(TextFormat::ParseFromString(meta_content, &annotations));
190   ASSERT_EQ(7, annotations.annotation_size());
191 
192   CheckSingleAnnotation("first_annotation", "first", file_content,
193                         annotations.annotation(0));
194   CheckSingleAnnotation("first_path",
195                         "test_generator: first_insert,\n foo.proto,\n "
196                         "MockCodeGenerator_Annotate,\n foo.proto\n",
197                         file_content, annotations.annotation(1));
198   CheckSingleAnnotation("first_path",
199                         "test_plugin: first_insert,\n foo.proto,\n "
200                         "MockCodeGenerator_Annotate,\n foo.proto\n",
201                         file_content, annotations.annotation(2));
202   CheckSingleAnnotation("second_annotation", "second", file_content,
203                         annotations.annotation(3));
204   // This annotated text has changed because it was inserted at an indented
205   // insertion point.
206   CheckSingleAnnotation("second_path",
207                         "test_generator: second_insert,\n   foo.proto,\n   "
208                         "MockCodeGenerator_Annotate,\n   foo.proto\n",
209                         file_content, annotations.annotation(4));
210   CheckSingleAnnotation("second_path",
211                         "test_plugin: second_insert,\n   foo.proto,\n   "
212                         "MockCodeGenerator_Annotate,\n   foo.proto\n",
213                         file_content, annotations.annotation(5));
214   CheckSingleAnnotation("third_annotation", "third", file_content,
215                         annotations.annotation(6));
216 }
217 
Generate(const FileDescriptor * file,const std::string & parameter,GeneratorContext * context,std::string * error) const218 bool MockCodeGenerator::Generate(const FileDescriptor* file,
219                                  const std::string& parameter,
220                                  GeneratorContext* context,
221                                  std::string* error) const {
222   // Override minimum/maximum after generating the pool to simulate a plugin
223   // that "works" but doesn't advertise support of the current edition.
224   absl::string_view test_case = GetTestCase();
225   if (test_case == "high_minimum") {
226     minimum_edition_ = Edition::EDITION_99997_TEST_ONLY;
227   } else if (test_case == "low_maximum") {
228     maximum_edition_ = Edition::EDITION_PROTO2;
229   }
230 
231   if (GetEdition(*file) >= Edition::EDITION_2023 &&
232       (suppressed_features_ & CodeGenerator::FEATURE_SUPPORTS_EDITIONS) == 0) {
233     internal::VisitDescriptors(*file, [&](const auto& descriptor) {
234       const FeatureSet& features = GetResolvedSourceFeatures(descriptor);
235       ABSL_CHECK(features.HasExtension(pb::test))
236           << "Test features were not resolved properly";
237       ABSL_CHECK(features.GetExtension(pb::test).has_file_feature())
238           << "Test features were not resolved properly";
239       ABSL_CHECK(features.GetExtension(pb::test).has_source_feature())
240           << "Test features were not resolved properly";
241     });
242   }
243 
244   bool annotate = false;
245   for (int i = 0; i < file->message_type_count(); i++) {
246     if (absl::StartsWith(file->message_type(i)->name(), "MockCodeGenerator_")) {
247       absl::string_view command = absl::StripPrefix(
248           file->message_type(i)->name(), "MockCodeGenerator_");
249       if (command == "Error") {
250         *error = "Saw message type MockCodeGenerator_Error.";
251         return false;
252       }
253       if (command == "Exit") {
254         std::cerr << "Saw message type MockCodeGenerator_Exit." << std::endl;
255         exit(123);
256       }
257       ABSL_CHECK(command != "Abort")
258           << "Saw message type MockCodeGenerator_Abort.";
259       if (command == "HasSourceCodeInfo") {
260         FileDescriptorProto file_descriptor_proto;
261         file->CopySourceCodeInfoTo(&file_descriptor_proto);
262         bool has_source_code_info =
263             file_descriptor_proto.has_source_code_info() &&
264             file_descriptor_proto.source_code_info().location_size() > 0;
265         ABSL_LOG(FATAL)
266             << "Saw message type MockCodeGenerator_HasSourceCodeInfo: "
267             << has_source_code_info << ".";
268       } else if (command == "HasJsonName") {
269         FieldDescriptorProto field_descriptor_proto;
270         file->message_type(i)->field(0)->CopyTo(&field_descriptor_proto);
271         ABSL_LOG(FATAL) << "Saw json_name: "
272                         << field_descriptor_proto.has_json_name();
273       } else if (command == "Annotate") {
274         annotate = true;
275       } else if (command == "ShowVersionNumber") {
276         Version compiler_version;
277         context->GetCompilerVersion(&compiler_version);
278         ABSL_LOG(FATAL) << "Saw compiler_version: "
279                         << compiler_version.major() * 1000000 +
280                                compiler_version.minor() * 1000 +
281                                compiler_version.patch()
282                         << " " << compiler_version.suffix();
283       } else {
284         ABSL_LOG(FATAL) << "Unknown MockCodeGenerator command: " << command;
285       }
286     }
287   }
288 
289   bool insert_endlines = absl::StartsWith(parameter, "insert_endlines=");
290   if (insert_endlines || absl::StartsWith(parameter, "insert=")) {
291     std::vector<std::string> insert_into = absl::StrSplit(
292         absl::StripPrefix(parameter,
293                           insert_endlines ? "insert_endlines=" : "insert="),
294         ',', absl::SkipEmpty());
295 
296     for (size_t i = 0; i < insert_into.size(); i++) {
297       {
298         google::protobuf::GeneratedCodeInfo info;
299         std::string content =
300             GetOutputFileContent(name_, "first_insert", file, context);
301         if (insert_endlines) {
302           absl::StrReplaceAll({{",", ",\n"}}, &content);
303         }
304         if (annotate) {
305           auto* annotation = info.add_annotation();
306           annotation->set_begin(0);
307           annotation->set_end(content.size());
308           annotation->set_source_file("first_path");
309         }
310         std::unique_ptr<io::ZeroCopyOutputStream> output(
311             context->OpenForInsertWithGeneratedCodeInfo(
312                 GetOutputFileName(insert_into[i], file),
313                 std::string(kFirstInsertionPointName), info));
314         io::Printer printer(output.get(), '$');
315         printer.PrintRaw(content);
316         if (printer.failed()) {
317           *error = "MockCodeGenerator detected write error.";
318           return false;
319         }
320       }
321 
322       {
323         google::protobuf::GeneratedCodeInfo info;
324         std::string content =
325             GetOutputFileContent(name_, "second_insert", file, context);
326         if (insert_endlines) {
327           absl::StrReplaceAll({{",", ",\n"}}, &content);
328         }
329         if (annotate) {
330           auto* annotation = info.add_annotation();
331           annotation->set_begin(0);
332           annotation->set_end(content.size());
333           annotation->set_source_file("second_path");
334         }
335         std::unique_ptr<io::ZeroCopyOutputStream> output(
336             context->OpenForInsertWithGeneratedCodeInfo(
337                 GetOutputFileName(insert_into[i], file),
338                 std::string(kSecondInsertionPointName), info));
339         io::Printer printer(output.get(), '$');
340         printer.PrintRaw(content);
341         if (printer.failed()) {
342           *error = "MockCodeGenerator detected write error.";
343           return false;
344         }
345       }
346     }
347   } else {
348     std::unique_ptr<io::ZeroCopyOutputStream> output(
349         context->Open(GetOutputFileName(name_, file)));
350 
351     GeneratedCodeInfo annotations;
352     io::AnnotationProtoCollector<GeneratedCodeInfo> annotation_collector(
353         &annotations);
354     io::Printer printer(output.get(), '$',
355                         annotate ? &annotation_collector : nullptr);
356     printer.PrintRaw(GetOutputFileContent(name_, parameter, file, context));
357     std::string annotate_suffix = "_annotation";
358     if (annotate) {
359       printer.Print("$p$\n", "p", "first");
360       printer.Annotate("p", absl::StrCat("first", annotate_suffix));
361     }
362     printer.PrintRaw(kFirstInsertionPoint);
363     if (annotate) {
364       printer.Print("$p$\n", "p", "second");
365       printer.Annotate("p", absl::StrCat("second", annotate_suffix));
366     }
367     printer.PrintRaw(kSecondInsertionPoint);
368     if (annotate) {
369       printer.Print("$p$\n", "p", "third");
370       printer.Annotate("p", absl::StrCat("third", annotate_suffix));
371     }
372 
373     if (printer.failed()) {
374       *error = "MockCodeGenerator detected write error.";
375       return false;
376     }
377     if (annotate) {
378       std::unique_ptr<io::ZeroCopyOutputStream> meta_output(context->Open(
379           absl::StrCat(GetOutputFileName(name_, file), ".pb.meta")));
380       if (!TextFormat::Print(annotations, meta_output.get())) {
381         *error = "MockCodeGenerator couldn't write .pb.meta";
382         return false;
383       }
384     }
385   }
386 
387   return true;
388 }
389 
GetOutputFileName(absl::string_view generator_name,const FileDescriptor * file)390 std::string MockCodeGenerator::GetOutputFileName(
391     absl::string_view generator_name, const FileDescriptor* file) {
392   return GetOutputFileName(generator_name, file->name());
393 }
394 
GetOutputFileName(absl::string_view generator_name,absl::string_view file)395 std::string MockCodeGenerator::GetOutputFileName(
396     absl::string_view generator_name, absl::string_view file) {
397   return absl::StrCat(file, ".MockCodeGenerator.", generator_name);
398 }
399 
GetOutputFileContent(absl::string_view generator_name,absl::string_view parameter,const FileDescriptor * file,GeneratorContext * context)400 std::string MockCodeGenerator::GetOutputFileContent(
401     absl::string_view generator_name, absl::string_view parameter,
402     const FileDescriptor* file, GeneratorContext* context) {
403   std::vector<const FileDescriptor*> all_files;
404   context->ListParsedFiles(&all_files);
405   return GetOutputFileContent(
406       generator_name, parameter, file->name(), CommaSeparatedList(all_files),
407       file->message_type_count() > 0 ? file->message_type(0)->name()
408                                      : "(none)");
409 }
410 
GetOutputFileContent(absl::string_view generator_name,absl::string_view parameter,absl::string_view file,absl::string_view parsed_file_list,absl::string_view first_message_name)411 std::string MockCodeGenerator::GetOutputFileContent(
412     absl::string_view generator_name, absl::string_view parameter,
413     absl::string_view file, absl::string_view parsed_file_list,
414     absl::string_view first_message_name) {
415   return absl::Substitute("$0: $1, $2, $3, $4\n", generator_name, parameter,
416                           file, first_message_name, parsed_file_list);
417 }
418 
419 }  // namespace compiler
420 }  // namespace protobuf
421 }  // namespace google
422