• 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 <iostream>
38 #include <memory>
39 #include <vector>
40 
41 #include <google/protobuf/stubs/strutil.h>
42 
43 #include <google/protobuf/stubs/logging.h>
44 #include <google/protobuf/stubs/common.h>
45 #include <google/protobuf/testing/file.h>
46 #include <google/protobuf/testing/file.h>
47 #include <google/protobuf/testing/file.h>
48 #include <google/protobuf/compiler/plugin.pb.h>
49 #include <google/protobuf/io/printer.h>
50 #include <google/protobuf/io/zero_copy_stream.h>
51 #include <google/protobuf/descriptor.pb.h>
52 #include <google/protobuf/descriptor.h>
53 #include <google/protobuf/text_format.h>
54 #include <gtest/gtest.h>
55 #include <google/protobuf/stubs/substitute.h>
56 
57 #ifdef major
58 #undef major
59 #endif
60 #ifdef minor
61 #undef minor
62 #endif
63 
64 namespace google {
65 namespace protobuf {
66 namespace compiler {
67 
68 // Returns the list of the names of files in all_files in the form of a
69 // comma-separated string.
CommaSeparatedList(const std::vector<const FileDescriptor * > & all_files)70 std::string CommaSeparatedList(
71     const std::vector<const FileDescriptor*>& all_files) {
72   std::vector<std::string> names;
73   for (size_t i = 0; i < all_files.size(); i++) {
74     names.push_back(all_files[i]->name());
75   }
76   return Join(names, ",");
77 }
78 
79 static const char* kFirstInsertionPointName = "first_mock_insertion_point";
80 static const char* kSecondInsertionPointName = "second_mock_insertion_point";
81 static const char* kFirstInsertionPoint =
82     "# @@protoc_insertion_point(first_mock_insertion_point) is here\n";
83 static const char* kSecondInsertionPoint =
84     "  # @@protoc_insertion_point(second_mock_insertion_point) is here\n";
85 
MockCodeGenerator(const std::string & name)86 MockCodeGenerator::MockCodeGenerator(const std::string& name) : name_(name) {}
87 
~MockCodeGenerator()88 MockCodeGenerator::~MockCodeGenerator() {}
89 
GetSupportedFeatures() const90 uint64_t MockCodeGenerator::GetSupportedFeatures() const {
91   uint64 all_features = CodeGenerator::FEATURE_PROTO3_OPTIONAL;
92   return all_features & ~suppressed_features_;
93 }
94 
SuppressFeatures(uint64 features)95 void MockCodeGenerator::SuppressFeatures(uint64 features) {
96   suppressed_features_ = features;
97 }
98 
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)99 void MockCodeGenerator::ExpectGenerated(
100     const std::string& name, const std::string& parameter,
101     const std::string& insertions, const std::string& file,
102     const std::string& first_message_name,
103     const std::string& first_parsed_file_name,
104     const std::string& output_directory) {
105   std::string content;
106   GOOGLE_CHECK_OK(
107       File::GetContents(output_directory + "/" + GetOutputFileName(name, file),
108                         &content, true));
109 
110   std::vector<std::string> lines =
111       Split(content, "\n", true);
112 
113   while (!lines.empty() && lines.back().empty()) {
114     lines.pop_back();
115   }
116   for (size_t i = 0; i < lines.size(); i++) {
117     lines[i] += "\n";
118   }
119 
120   std::vector<std::string> insertion_list;
121   if (!insertions.empty()) {
122     SplitStringUsing(insertions, ",", &insertion_list);
123   }
124 
125   EXPECT_EQ(lines.size(), 3 + insertion_list.size() * 2);
126   EXPECT_EQ(GetOutputFileContent(name, parameter, file, first_parsed_file_name,
127                                  first_message_name),
128             lines[0]);
129 
130   EXPECT_EQ(kFirstInsertionPoint, lines[1 + insertion_list.size()]);
131   EXPECT_EQ(kSecondInsertionPoint, lines[2 + insertion_list.size() * 2]);
132 
133   for (size_t i = 0; i < insertion_list.size(); i++) {
134     EXPECT_EQ(GetOutputFileContent(insertion_list[i], "first_insert", file,
135                                    file, first_message_name),
136               lines[1 + i]);
137     // Second insertion point is indented, so the inserted text should
138     // automatically be indented too.
139     EXPECT_EQ("  " + GetOutputFileContent(insertion_list[i], "second_insert",
140                                           file, file, first_message_name),
141               lines[2 + insertion_list.size() + i]);
142   }
143 }
144 
145 namespace {
CheckSingleAnnotation(const std::string & expected_file,const std::string & expected_text,const std::string & file_content,const GeneratedCodeInfo::Annotation & annotation)146 void CheckSingleAnnotation(const std::string& expected_file,
147                            const std::string& expected_text,
148                            const std::string& file_content,
149                            const GeneratedCodeInfo::Annotation& annotation) {
150   EXPECT_EQ(expected_file, annotation.source_file());
151   ASSERT_GE(file_content.size(), annotation.begin());
152   ASSERT_GE(file_content.size(), annotation.end());
153   ASSERT_LE(annotation.begin(), annotation.end());
154   EXPECT_EQ(expected_text.size(), annotation.end() - annotation.begin());
155   EXPECT_EQ(expected_text,
156             file_content.substr(annotation.begin(), expected_text.size()));
157 }
158 }  // anonymous namespace
159 
CheckGeneratedAnnotations(const std::string & name,const std::string & file,const std::string & output_directory)160 void MockCodeGenerator::CheckGeneratedAnnotations(
161     const std::string& name, const std::string& file,
162     const std::string& output_directory) {
163   std::string file_content;
164   GOOGLE_CHECK_OK(
165       File::GetContents(output_directory + "/" + GetOutputFileName(name, file),
166                         &file_content, true));
167   std::string meta_content;
168   GOOGLE_CHECK_OK(File::GetContents(
169       output_directory + "/" + GetOutputFileName(name, file) + ".meta",
170       &meta_content, true));
171   GeneratedCodeInfo annotations;
172   GOOGLE_CHECK(TextFormat::ParseFromString(meta_content, &annotations));
173   ASSERT_EQ(3, annotations.annotation_size());
174   CheckSingleAnnotation("first_annotation", "first", file_content,
175                         annotations.annotation(0));
176   CheckSingleAnnotation("second_annotation", "second", file_content,
177                         annotations.annotation(1));
178   CheckSingleAnnotation("third_annotation", "third", file_content,
179                         annotations.annotation(2));
180 }
181 
Generate(const FileDescriptor * file,const std::string & parameter,GeneratorContext * context,std::string * error) const182 bool MockCodeGenerator::Generate(const FileDescriptor* file,
183                                  const std::string& parameter,
184                                  GeneratorContext* context,
185                                  std::string* error) const {
186   bool annotate = false;
187   for (int i = 0; i < file->message_type_count(); i++) {
188     if (HasPrefixString(file->message_type(i)->name(), "MockCodeGenerator_")) {
189       std::string command = StripPrefixString(
190           file->message_type(i)->name(), "MockCodeGenerator_");
191       if (command == "Error") {
192         *error = "Saw message type MockCodeGenerator_Error.";
193         return false;
194       } else if (command == "Exit") {
195         std::cerr << "Saw message type MockCodeGenerator_Exit." << std::endl;
196         exit(123);
197       } else if (command == "Abort") {
198         std::cerr << "Saw message type MockCodeGenerator_Abort." << std::endl;
199         abort();
200       } else if (command == "HasSourceCodeInfo") {
201         FileDescriptorProto file_descriptor_proto;
202         file->CopySourceCodeInfoTo(&file_descriptor_proto);
203         bool has_source_code_info =
204             file_descriptor_proto.has_source_code_info() &&
205             file_descriptor_proto.source_code_info().location_size() > 0;
206         std::cerr << "Saw message type MockCodeGenerator_HasSourceCodeInfo: "
207                   << has_source_code_info << "." << std::endl;
208         abort();
209       } else if (command == "HasJsonName") {
210         FieldDescriptorProto field_descriptor_proto;
211         file->message_type(i)->field(0)->CopyTo(&field_descriptor_proto);
212         std::cerr << "Saw json_name: " << field_descriptor_proto.has_json_name()
213                   << std::endl;
214         abort();
215       } else if (command == "Annotate") {
216         annotate = true;
217       } else if (command == "ShowVersionNumber") {
218         Version compiler_version;
219         context->GetCompilerVersion(&compiler_version);
220         std::cerr << "Saw compiler_version: "
221                   << compiler_version.major() * 1000000 +
222                          compiler_version.minor() * 1000 +
223                          compiler_version.patch()
224                   << " " << compiler_version.suffix() << std::endl;
225         abort();
226       } else {
227         GOOGLE_LOG(FATAL) << "Unknown MockCodeGenerator command: " << command;
228       }
229     }
230   }
231 
232   if (HasPrefixString(parameter, "insert=")) {
233     std::vector<std::string> insert_into;
234     SplitStringUsing(StripPrefixString(parameter, "insert="), ",",
235                      &insert_into);
236 
237     for (size_t i = 0; i < insert_into.size(); i++) {
238       {
239         std::unique_ptr<io::ZeroCopyOutputStream> output(context->OpenForInsert(
240             GetOutputFileName(insert_into[i], file), kFirstInsertionPointName));
241         io::Printer printer(output.get(), '$');
242         printer.PrintRaw(
243             GetOutputFileContent(name_, "first_insert", file, context));
244         if (printer.failed()) {
245           *error = "MockCodeGenerator detected write error.";
246           return false;
247         }
248       }
249 
250       {
251         std::unique_ptr<io::ZeroCopyOutputStream> output(
252             context->OpenForInsert(GetOutputFileName(insert_into[i], file),
253                                    kSecondInsertionPointName));
254         io::Printer printer(output.get(), '$');
255         printer.PrintRaw(
256             GetOutputFileContent(name_, "second_insert", file, context));
257         if (printer.failed()) {
258           *error = "MockCodeGenerator detected write error.";
259           return false;
260         }
261       }
262     }
263   } else {
264     std::unique_ptr<io::ZeroCopyOutputStream> output(
265         context->Open(GetOutputFileName(name_, file)));
266 
267     GeneratedCodeInfo annotations;
268     io::AnnotationProtoCollector<GeneratedCodeInfo> annotation_collector(
269         &annotations);
270     io::Printer printer(output.get(), '$',
271                         annotate ? &annotation_collector : NULL);
272     printer.PrintRaw(GetOutputFileContent(name_, parameter, file, context));
273     std::string annotate_suffix = "_annotation";
274     if (annotate) {
275       printer.Print("$p$", "p", "first");
276       printer.Annotate("p", "first" + annotate_suffix);
277     }
278     printer.PrintRaw(kFirstInsertionPoint);
279     if (annotate) {
280       printer.Print("$p$", "p", "second");
281       printer.Annotate("p", "second" + annotate_suffix);
282     }
283     printer.PrintRaw(kSecondInsertionPoint);
284     if (annotate) {
285       printer.Print("$p$", "p", "third");
286       printer.Annotate("p", "third" + annotate_suffix);
287     }
288 
289     if (printer.failed()) {
290       *error = "MockCodeGenerator detected write error.";
291       return false;
292     }
293     if (annotate) {
294       std::unique_ptr<io::ZeroCopyOutputStream> meta_output(
295           context->Open(GetOutputFileName(name_, file) + ".meta"));
296       if (!TextFormat::Print(annotations, meta_output.get())) {
297         *error = "MockCodeGenerator couldn't write .meta";
298         return false;
299       }
300     }
301   }
302 
303   return true;
304 }
305 
GetOutputFileName(const std::string & generator_name,const FileDescriptor * file)306 std::string MockCodeGenerator::GetOutputFileName(
307     const std::string& generator_name, const FileDescriptor* file) {
308   return GetOutputFileName(generator_name, file->name());
309 }
310 
GetOutputFileName(const std::string & generator_name,const std::string & file)311 std::string MockCodeGenerator::GetOutputFileName(
312     const std::string& generator_name, const std::string& file) {
313   return file + ".MockCodeGenerator." + generator_name;
314 }
315 
GetOutputFileContent(const std::string & generator_name,const std::string & parameter,const FileDescriptor * file,GeneratorContext * context)316 std::string MockCodeGenerator::GetOutputFileContent(
317     const std::string& generator_name, const std::string& parameter,
318     const FileDescriptor* file, GeneratorContext* context) {
319   std::vector<const FileDescriptor*> all_files;
320   context->ListParsedFiles(&all_files);
321   return GetOutputFileContent(
322       generator_name, parameter, file->name(), CommaSeparatedList(all_files),
323       file->message_type_count() > 0 ? file->message_type(0)->name()
324                                      : "(none)");
325 }
326 
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)327 std::string MockCodeGenerator::GetOutputFileContent(
328     const std::string& generator_name, const std::string& parameter,
329     const std::string& file, const std::string& parsed_file_list,
330     const std::string& first_message_name) {
331   return strings::Substitute("$0: $1, $2, $3, $4\n", generator_name, parameter,
332                           file, first_message_name, parsed_file_list);
333 }
334 
335 }  // namespace compiler
336 }  // namespace protobuf
337 }  // namespace google
338