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 insertion_list = Split(insertions, ",", true);
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) + ".pb.meta",
170 &meta_content, true));
171 GeneratedCodeInfo annotations;
172 GOOGLE_CHECK(TextFormat::ParseFromString(meta_content, &annotations));
173 ASSERT_EQ(7, annotations.annotation_size());
174
175 CheckSingleAnnotation("first_annotation", "first", file_content,
176 annotations.annotation(0));
177 CheckSingleAnnotation("first_path",
178 "test_generator: first_insert,\n foo.proto,\n "
179 "MockCodeGenerator_Annotate,\n foo.proto\n",
180 file_content, annotations.annotation(1));
181 CheckSingleAnnotation("first_path",
182 "test_plugin: first_insert,\n foo.proto,\n "
183 "MockCodeGenerator_Annotate,\n foo.proto\n",
184 file_content, annotations.annotation(2));
185 CheckSingleAnnotation("second_annotation", "second", file_content,
186 annotations.annotation(3));
187 // This annotated text has changed because it was inserted at an indented
188 // insertion point.
189 CheckSingleAnnotation("second_path",
190 "test_generator: second_insert,\n foo.proto,\n "
191 "MockCodeGenerator_Annotate,\n foo.proto\n",
192 file_content, annotations.annotation(4));
193 CheckSingleAnnotation("second_path",
194 "test_plugin: second_insert,\n foo.proto,\n "
195 "MockCodeGenerator_Annotate,\n foo.proto\n",
196 file_content, annotations.annotation(5));
197 CheckSingleAnnotation("third_annotation", "third", file_content,
198 annotations.annotation(6));
199 }
200
Generate(const FileDescriptor * file,const std::string & parameter,GeneratorContext * context,std::string * error) const201 bool MockCodeGenerator::Generate(const FileDescriptor* file,
202 const std::string& parameter,
203 GeneratorContext* context,
204 std::string* error) const {
205 bool annotate = false;
206 for (int i = 0; i < file->message_type_count(); i++) {
207 if (HasPrefixString(file->message_type(i)->name(), "MockCodeGenerator_")) {
208 std::string command = StripPrefixString(
209 file->message_type(i)->name(), "MockCodeGenerator_");
210 if (command == "Error") {
211 *error = "Saw message type MockCodeGenerator_Error.";
212 return false;
213 } else if (command == "Exit") {
214 std::cerr << "Saw message type MockCodeGenerator_Exit." << std::endl;
215 exit(123);
216 } else if (command == "Abort") {
217 std::cerr << "Saw message type MockCodeGenerator_Abort." << std::endl;
218 abort();
219 } else if (command == "HasSourceCodeInfo") {
220 FileDescriptorProto file_descriptor_proto;
221 file->CopySourceCodeInfoTo(&file_descriptor_proto);
222 bool has_source_code_info =
223 file_descriptor_proto.has_source_code_info() &&
224 file_descriptor_proto.source_code_info().location_size() > 0;
225 std::cerr << "Saw message type MockCodeGenerator_HasSourceCodeInfo: "
226 << has_source_code_info << "." << std::endl;
227 abort();
228 } else if (command == "HasJsonName") {
229 FieldDescriptorProto field_descriptor_proto;
230 file->message_type(i)->field(0)->CopyTo(&field_descriptor_proto);
231 std::cerr << "Saw json_name: " << field_descriptor_proto.has_json_name()
232 << std::endl;
233 abort();
234 } else if (command == "Annotate") {
235 annotate = true;
236 } else if (command == "ShowVersionNumber") {
237 Version compiler_version;
238 context->GetCompilerVersion(&compiler_version);
239 std::cerr << "Saw compiler_version: "
240 << compiler_version.major() * 1000000 +
241 compiler_version.minor() * 1000 +
242 compiler_version.patch()
243 << " " << compiler_version.suffix() << std::endl;
244 abort();
245 } else {
246 GOOGLE_LOG(FATAL) << "Unknown MockCodeGenerator command: " << command;
247 }
248 }
249 }
250
251 bool insert_endlines = HasPrefixString(parameter, "insert_endlines=");
252 if (insert_endlines || HasPrefixString(parameter, "insert=")) {
253 std::vector<std::string> insert_into = Split(
254 StripPrefixString(
255 parameter, insert_endlines ? "insert_endlines=" : "insert="),
256 ",", true);
257
258 for (size_t i = 0; i < insert_into.size(); i++) {
259 {
260 google::protobuf::GeneratedCodeInfo info;
261 std::string content =
262 GetOutputFileContent(name_, "first_insert", file, context);
263 if (insert_endlines) {
264 GlobalReplaceSubstring(",", ",\n", &content);
265 }
266 if (annotate) {
267 auto* annotation = info.add_annotation();
268 annotation->set_begin(0);
269 annotation->set_end(content.size());
270 annotation->set_source_file("first_path");
271 }
272 std::unique_ptr<io::ZeroCopyOutputStream> output(
273 context->OpenForInsertWithGeneratedCodeInfo(
274 GetOutputFileName(insert_into[i], file),
275 kFirstInsertionPointName, info));
276 io::Printer printer(output.get(), '$');
277 printer.PrintRaw(content);
278 if (printer.failed()) {
279 *error = "MockCodeGenerator detected write error.";
280 return false;
281 }
282 }
283
284 {
285 google::protobuf::GeneratedCodeInfo info;
286 std::string content =
287 GetOutputFileContent(name_, "second_insert", file, context);
288 if (insert_endlines) {
289 GlobalReplaceSubstring(",", ",\n", &content);
290 }
291 if (annotate) {
292 auto* annotation = info.add_annotation();
293 annotation->set_begin(0);
294 annotation->set_end(content.size());
295 annotation->set_source_file("second_path");
296 }
297 std::unique_ptr<io::ZeroCopyOutputStream> output(
298 context->OpenForInsertWithGeneratedCodeInfo(
299 GetOutputFileName(insert_into[i], file),
300 kSecondInsertionPointName, info));
301 io::Printer printer(output.get(), '$');
302 printer.PrintRaw(content);
303 if (printer.failed()) {
304 *error = "MockCodeGenerator detected write error.";
305 return false;
306 }
307 }
308 }
309 } else {
310 std::unique_ptr<io::ZeroCopyOutputStream> output(
311 context->Open(GetOutputFileName(name_, file)));
312
313 GeneratedCodeInfo annotations;
314 io::AnnotationProtoCollector<GeneratedCodeInfo> annotation_collector(
315 &annotations);
316 io::Printer printer(output.get(), '$',
317 annotate ? &annotation_collector : NULL);
318 printer.PrintRaw(GetOutputFileContent(name_, parameter, file, context));
319 std::string annotate_suffix = "_annotation";
320 if (annotate) {
321 printer.Print("$p$\n", "p", "first");
322 printer.Annotate("p", "first" + annotate_suffix);
323 }
324 printer.PrintRaw(kFirstInsertionPoint);
325 if (annotate) {
326 printer.Print("$p$\n", "p", "second");
327 printer.Annotate("p", "second" + annotate_suffix);
328 }
329 printer.PrintRaw(kSecondInsertionPoint);
330 if (annotate) {
331 printer.Print("$p$\n", "p", "third");
332 printer.Annotate("p", "third" + annotate_suffix);
333 }
334
335 if (printer.failed()) {
336 *error = "MockCodeGenerator detected write error.";
337 return false;
338 }
339 if (annotate) {
340 std::unique_ptr<io::ZeroCopyOutputStream> meta_output(
341 context->Open(GetOutputFileName(name_, file) + ".pb.meta"));
342 if (!TextFormat::Print(annotations, meta_output.get())) {
343 *error = "MockCodeGenerator couldn't write .pb.meta";
344 return false;
345 }
346 }
347 }
348
349 return true;
350 }
351
GetOutputFileName(const std::string & generator_name,const FileDescriptor * file)352 std::string MockCodeGenerator::GetOutputFileName(
353 const std::string& generator_name, const FileDescriptor* file) {
354 return GetOutputFileName(generator_name, file->name());
355 }
356
GetOutputFileName(const std::string & generator_name,const std::string & file)357 std::string MockCodeGenerator::GetOutputFileName(
358 const std::string& generator_name, const std::string& file) {
359 return file + ".MockCodeGenerator." + generator_name;
360 }
361
GetOutputFileContent(const std::string & generator_name,const std::string & parameter,const FileDescriptor * file,GeneratorContext * context)362 std::string MockCodeGenerator::GetOutputFileContent(
363 const std::string& generator_name, const std::string& parameter,
364 const FileDescriptor* file, GeneratorContext* context) {
365 std::vector<const FileDescriptor*> all_files;
366 context->ListParsedFiles(&all_files);
367 return GetOutputFileContent(
368 generator_name, parameter, file->name(), CommaSeparatedList(all_files),
369 file->message_type_count() > 0 ? file->message_type(0)->name()
370 : "(none)");
371 }
372
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)373 std::string MockCodeGenerator::GetOutputFileContent(
374 const std::string& generator_name, const std::string& parameter,
375 const std::string& file, const std::string& parsed_file_list,
376 const std::string& first_message_name) {
377 return strings::Substitute("$0: $1, $2, $3, $4\n", generator_name, parameter,
378 file, first_message_name, parsed_file_list);
379 }
380
381 } // namespace compiler
382 } // namespace protobuf
383 } // namespace google
384