• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 //     * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 //     * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 //     * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 // Author: kenton@google.com (Kenton Varda)
32 
33 #include <google/protobuf/compiler/mock_code_generator.h>
34 
35 #include <stdlib.h>
36 
37 #include <cstdint>
38 #include <iostream>
39 #include <memory>
40 #include <vector>
41 
42 #include <google/protobuf/stubs/strutil.h>
43 
44 #include <google/protobuf/stubs/logging.h>
45 #include <google/protobuf/stubs/common.h>
46 #include <google/protobuf/testing/file.h>
47 #include <google/protobuf/testing/file.h>
48 #include <google/protobuf/testing/file.h>
49 #include <google/protobuf/compiler/plugin.pb.h>
50 #include <google/protobuf/descriptor.pb.h>
51 #include <gtest/gtest.h>
52 #include <google/protobuf/stubs/substitute.h>
53 #include <google/protobuf/descriptor.h>
54 #include <google/protobuf/io/printer.h>
55 #include <google/protobuf/io/zero_copy_stream.h>
56 #include <google/protobuf/text_format.h>
57 
58 #ifdef major
59 #undef major
60 #endif
61 #ifdef minor
62 #undef minor
63 #endif
64 
65 namespace google {
66 namespace protobuf {
67 namespace compiler {
68 
69 // Returns the list of the names of files in all_files in the form of a
70 // comma-separated string.
CommaSeparatedList(const std::vector<const FileDescriptor * > & all_files)71 std::string CommaSeparatedList(
72     const std::vector<const FileDescriptor*>& all_files) {
73   std::vector<std::string> names;
74   for (size_t i = 0; i < all_files.size(); i++) {
75     names.push_back(all_files[i]->name());
76   }
77   return Join(names, ",");
78 }
79 
80 static const char* kFirstInsertionPointName = "first_mock_insertion_point";
81 static const char* kSecondInsertionPointName = "second_mock_insertion_point";
82 static const char* kFirstInsertionPoint =
83     "# @@protoc_insertion_point(first_mock_insertion_point) is here\n";
84 static const char* kSecondInsertionPoint =
85     "  # @@protoc_insertion_point(second_mock_insertion_point) is here\n";
86 
MockCodeGenerator(const std::string & name)87 MockCodeGenerator::MockCodeGenerator(const std::string& name) : name_(name) {}
88 
~MockCodeGenerator()89 MockCodeGenerator::~MockCodeGenerator() {}
90 
GetSupportedFeatures() const91 uint64_t MockCodeGenerator::GetSupportedFeatures() const {
92   uint64_t all_features = CodeGenerator::FEATURE_PROTO3_OPTIONAL;
93   return all_features & ~suppressed_features_;
94 }
95 
SuppressFeatures(uint64_t features)96 void MockCodeGenerator::SuppressFeatures(uint64_t features) {
97   suppressed_features_ = features;
98 }
99 
ExpectGenerated(const std::string & name,const std::string & parameter,const std::string & insertions,const std::string & file,const std::string & first_message_name,const std::string & first_parsed_file_name,const std::string & output_directory)100 void MockCodeGenerator::ExpectGenerated(
101     const std::string& name, const std::string& parameter,
102     const std::string& insertions, const std::string& file,
103     const std::string& first_message_name,
104     const std::string& first_parsed_file_name,
105     const std::string& output_directory) {
106   std::string content;
107   GOOGLE_CHECK_OK(
108       File::GetContents(output_directory + "/" + GetOutputFileName(name, file),
109                         &content, true));
110 
111   std::vector<std::string> lines =
112       Split(content, "\n", true);
113 
114   while (!lines.empty() && lines.back().empty()) {
115     lines.pop_back();
116   }
117   for (size_t i = 0; i < lines.size(); i++) {
118     lines[i] += "\n";
119   }
120 
121   std::vector<std::string> insertion_list;
122   if (!insertions.empty()) {
123     insertion_list = Split(insertions, ",", true);
124   }
125 
126   EXPECT_EQ(lines.size(), 3 + insertion_list.size() * 2);
127   EXPECT_EQ(GetOutputFileContent(name, parameter, file, first_parsed_file_name,
128                                  first_message_name),
129             lines[0]);
130 
131   EXPECT_EQ(kFirstInsertionPoint, lines[1 + insertion_list.size()]);
132   EXPECT_EQ(kSecondInsertionPoint, lines[2 + insertion_list.size() * 2]);
133 
134   for (size_t i = 0; i < insertion_list.size(); i++) {
135     EXPECT_EQ(GetOutputFileContent(insertion_list[i], "first_insert", file,
136                                    file, first_message_name),
137               lines[1 + i]);
138     // Second insertion point is indented, so the inserted text should
139     // automatically be indented too.
140     EXPECT_EQ("  " + GetOutputFileContent(insertion_list[i], "second_insert",
141                                           file, file, first_message_name),
142               lines[2 + insertion_list.size() + i]);
143   }
144 }
145 
146 namespace {
CheckSingleAnnotation(const std::string & expected_file,const std::string & expected_text,const std::string & file_content,const GeneratedCodeInfo::Annotation & annotation)147 void CheckSingleAnnotation(const std::string& expected_file,
148                            const std::string& expected_text,
149                            const std::string& file_content,
150                            const GeneratedCodeInfo::Annotation& annotation) {
151   EXPECT_EQ(expected_file, annotation.source_file());
152   ASSERT_GE(file_content.size(), annotation.begin());
153   ASSERT_GE(file_content.size(), annotation.end());
154   ASSERT_LE(annotation.begin(), annotation.end());
155   EXPECT_EQ(expected_text.size(), annotation.end() - annotation.begin());
156   EXPECT_EQ(expected_text,
157             file_content.substr(annotation.begin(), expected_text.size()));
158 }
159 }  // anonymous namespace
160 
CheckGeneratedAnnotations(const std::string & name,const std::string & file,const std::string & output_directory)161 void MockCodeGenerator::CheckGeneratedAnnotations(
162     const std::string& name, const std::string& file,
163     const std::string& output_directory) {
164   std::string file_content;
165   GOOGLE_CHECK_OK(
166       File::GetContents(output_directory + "/" + GetOutputFileName(name, file),
167                         &file_content, true));
168   std::string meta_content;
169   GOOGLE_CHECK_OK(File::GetContents(
170       output_directory + "/" + GetOutputFileName(name, file) + ".pb.meta",
171       &meta_content, true));
172   GeneratedCodeInfo annotations;
173   GOOGLE_CHECK(TextFormat::ParseFromString(meta_content, &annotations));
174   ASSERT_EQ(7, annotations.annotation_size());
175 
176   CheckSingleAnnotation("first_annotation", "first", file_content,
177                         annotations.annotation(0));
178   CheckSingleAnnotation("first_path",
179                         "test_generator: first_insert,\n foo.proto,\n "
180                         "MockCodeGenerator_Annotate,\n foo.proto\n",
181                         file_content, annotations.annotation(1));
182   CheckSingleAnnotation("first_path",
183                         "test_plugin: first_insert,\n foo.proto,\n "
184                         "MockCodeGenerator_Annotate,\n foo.proto\n",
185                         file_content, annotations.annotation(2));
186   CheckSingleAnnotation("second_annotation", "second", file_content,
187                         annotations.annotation(3));
188   // This annotated text has changed because it was inserted at an indented
189   // insertion point.
190   CheckSingleAnnotation("second_path",
191                         "test_generator: second_insert,\n   foo.proto,\n   "
192                         "MockCodeGenerator_Annotate,\n   foo.proto\n",
193                         file_content, annotations.annotation(4));
194   CheckSingleAnnotation("second_path",
195                         "test_plugin: second_insert,\n   foo.proto,\n   "
196                         "MockCodeGenerator_Annotate,\n   foo.proto\n",
197                         file_content, annotations.annotation(5));
198   CheckSingleAnnotation("third_annotation", "third", file_content,
199                         annotations.annotation(6));
200 }
201 
Generate(const FileDescriptor * file,const std::string & parameter,GeneratorContext * context,std::string * error) const202 bool MockCodeGenerator::Generate(const FileDescriptor* file,
203                                  const std::string& parameter,
204                                  GeneratorContext* context,
205                                  std::string* error) const {
206   bool annotate = false;
207   for (int i = 0; i < file->message_type_count(); i++) {
208     if (HasPrefixString(file->message_type(i)->name(), "MockCodeGenerator_")) {
209       std::string command = StripPrefixString(
210           file->message_type(i)->name(), "MockCodeGenerator_");
211       if (command == "Error") {
212         *error = "Saw message type MockCodeGenerator_Error.";
213         return false;
214       } else if (command == "Exit") {
215         std::cerr << "Saw message type MockCodeGenerator_Exit." << std::endl;
216         exit(123);
217       } else if (command == "Abort") {
218         std::cerr << "Saw message type MockCodeGenerator_Abort." << std::endl;
219         abort();
220       } else if (command == "HasSourceCodeInfo") {
221         FileDescriptorProto file_descriptor_proto;
222         file->CopySourceCodeInfoTo(&file_descriptor_proto);
223         bool has_source_code_info =
224             file_descriptor_proto.has_source_code_info() &&
225             file_descriptor_proto.source_code_info().location_size() > 0;
226         std::cerr << "Saw message type MockCodeGenerator_HasSourceCodeInfo: "
227                   << has_source_code_info << "." << std::endl;
228         abort();
229       } else if (command == "HasJsonName") {
230         FieldDescriptorProto field_descriptor_proto;
231         file->message_type(i)->field(0)->CopyTo(&field_descriptor_proto);
232         std::cerr << "Saw json_name: " << field_descriptor_proto.has_json_name()
233                   << std::endl;
234         abort();
235       } else if (command == "Annotate") {
236         annotate = true;
237       } else if (command == "ShowVersionNumber") {
238         Version compiler_version;
239         context->GetCompilerVersion(&compiler_version);
240         std::cerr << "Saw compiler_version: "
241                   << compiler_version.major() * 1000000 +
242                          compiler_version.minor() * 1000 +
243                          compiler_version.patch()
244                   << " " << compiler_version.suffix() << std::endl;
245         abort();
246       } else {
247         GOOGLE_LOG(FATAL) << "Unknown MockCodeGenerator command: " << command;
248       }
249     }
250   }
251 
252   bool insert_endlines = HasPrefixString(parameter, "insert_endlines=");
253   if (insert_endlines || HasPrefixString(parameter, "insert=")) {
254     std::vector<std::string> insert_into = Split(
255         StripPrefixString(
256             parameter, insert_endlines ? "insert_endlines=" : "insert="),
257         ",", true);
258 
259     for (size_t i = 0; i < insert_into.size(); i++) {
260       {
261         google::protobuf::GeneratedCodeInfo info;
262         std::string content =
263             GetOutputFileContent(name_, "first_insert", file, context);
264         if (insert_endlines) {
265           GlobalReplaceSubstring(",", ",\n", &content);
266         }
267         if (annotate) {
268           auto* annotation = info.add_annotation();
269           annotation->set_begin(0);
270           annotation->set_end(content.size());
271           annotation->set_source_file("first_path");
272         }
273         std::unique_ptr<io::ZeroCopyOutputStream> output(
274             context->OpenForInsertWithGeneratedCodeInfo(
275                 GetOutputFileName(insert_into[i], file),
276                 kFirstInsertionPointName, info));
277         io::Printer printer(output.get(), '$');
278         printer.PrintRaw(content);
279         if (printer.failed()) {
280           *error = "MockCodeGenerator detected write error.";
281           return false;
282         }
283       }
284 
285       {
286         google::protobuf::GeneratedCodeInfo info;
287         std::string content =
288             GetOutputFileContent(name_, "second_insert", file, context);
289         if (insert_endlines) {
290           GlobalReplaceSubstring(",", ",\n", &content);
291         }
292         if (annotate) {
293           auto* annotation = info.add_annotation();
294           annotation->set_begin(0);
295           annotation->set_end(content.size());
296           annotation->set_source_file("second_path");
297         }
298         std::unique_ptr<io::ZeroCopyOutputStream> output(
299             context->OpenForInsertWithGeneratedCodeInfo(
300                 GetOutputFileName(insert_into[i], file),
301                 kSecondInsertionPointName, info));
302         io::Printer printer(output.get(), '$');
303         printer.PrintRaw(content);
304         if (printer.failed()) {
305           *error = "MockCodeGenerator detected write error.";
306           return false;
307         }
308       }
309     }
310   } else {
311     std::unique_ptr<io::ZeroCopyOutputStream> output(
312         context->Open(GetOutputFileName(name_, file)));
313 
314     GeneratedCodeInfo annotations;
315     io::AnnotationProtoCollector<GeneratedCodeInfo> annotation_collector(
316         &annotations);
317     io::Printer printer(output.get(), '$',
318                         annotate ? &annotation_collector : nullptr);
319     printer.PrintRaw(GetOutputFileContent(name_, parameter, file, context));
320     std::string annotate_suffix = "_annotation";
321     if (annotate) {
322       printer.Print("$p$\n", "p", "first");
323       printer.Annotate("p", "first" + annotate_suffix);
324     }
325     printer.PrintRaw(kFirstInsertionPoint);
326     if (annotate) {
327       printer.Print("$p$\n", "p", "second");
328       printer.Annotate("p", "second" + annotate_suffix);
329     }
330     printer.PrintRaw(kSecondInsertionPoint);
331     if (annotate) {
332       printer.Print("$p$\n", "p", "third");
333       printer.Annotate("p", "third" + annotate_suffix);
334     }
335 
336     if (printer.failed()) {
337       *error = "MockCodeGenerator detected write error.";
338       return false;
339     }
340     if (annotate) {
341       std::unique_ptr<io::ZeroCopyOutputStream> meta_output(
342           context->Open(GetOutputFileName(name_, file) + ".pb.meta"));
343       if (!TextFormat::Print(annotations, meta_output.get())) {
344         *error = "MockCodeGenerator couldn't write .pb.meta";
345         return false;
346       }
347     }
348   }
349 
350   return true;
351 }
352 
GetOutputFileName(const std::string & generator_name,const FileDescriptor * file)353 std::string MockCodeGenerator::GetOutputFileName(
354     const std::string& generator_name, const FileDescriptor* file) {
355   return GetOutputFileName(generator_name, file->name());
356 }
357 
GetOutputFileName(const std::string & generator_name,const std::string & file)358 std::string MockCodeGenerator::GetOutputFileName(
359     const std::string& generator_name, const std::string& file) {
360   return file + ".MockCodeGenerator." + generator_name;
361 }
362 
GetOutputFileContent(const std::string & generator_name,const std::string & parameter,const FileDescriptor * file,GeneratorContext * context)363 std::string MockCodeGenerator::GetOutputFileContent(
364     const std::string& generator_name, const std::string& parameter,
365     const FileDescriptor* file, GeneratorContext* context) {
366   std::vector<const FileDescriptor*> all_files;
367   context->ListParsedFiles(&all_files);
368   return GetOutputFileContent(
369       generator_name, parameter, file->name(), CommaSeparatedList(all_files),
370       file->message_type_count() > 0 ? file->message_type(0)->name()
371                                      : "(none)");
372 }
373 
GetOutputFileContent(const std::string & generator_name,const std::string & parameter,const std::string & file,const std::string & parsed_file_list,const std::string & first_message_name)374 std::string MockCodeGenerator::GetOutputFileContent(
375     const std::string& generator_name, const std::string& parameter,
376     const std::string& file, const std::string& parsed_file_list,
377     const std::string& first_message_name) {
378   return strings::Substitute("$0: $1, $2, $3, $4\n", generator_name, parameter,
379                           file, first_message_name, parsed_file_list);
380 }
381 
382 }  // namespace compiler
383 }  // namespace protobuf
384 }  // namespace google
385