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