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 // Based on original Protocol Buffers design by
33 // Sanjay Ghemawat, Jeff Dean, and others.
34
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <fcntl.h>
38 #ifdef _MSC_VER
39 #include <io.h>
40 #else
41 #include <unistd.h>
42 #endif
43 #include <memory>
44 #include <vector>
45
46 #include <google/protobuf/descriptor.pb.h>
47 #include <google/protobuf/descriptor.h>
48 #include <google/protobuf/io/zero_copy_stream.h>
49 #include <google/protobuf/compiler/command_line_interface.h>
50 #include <google/protobuf/compiler/code_generator.h>
51 #include <google/protobuf/compiler/mock_code_generator.h>
52 #include <google/protobuf/compiler/subprocess.h>
53 #include <google/protobuf/io/printer.h>
54 #include <google/protobuf/unittest.pb.h>
55 #include <google/protobuf/testing/file.h>
56 #include <google/protobuf/stubs/strutil.h>
57 #include <google/protobuf/stubs/substitute.h>
58
59 #include <google/protobuf/testing/googletest.h>
60 #include <gtest/gtest.h>
61
62 namespace google {
63 namespace protobuf {
64 namespace compiler {
65
66 #if defined(_WIN32)
67 #ifndef STDIN_FILENO
68 #define STDIN_FILENO 0
69 #endif
70 #ifndef STDOUT_FILENO
71 #define STDOUT_FILENO 1
72 #endif
73 #ifndef F_OK
74 #define F_OK 00 // not defined by MSVC for whatever reason
75 #endif
76 #endif
77
78 namespace {
79
80 class CommandLineInterfaceTest : public testing::Test {
81 protected:
82 virtual void SetUp();
83 virtual void TearDown();
84
85 // Runs the CommandLineInterface with the given command line. The
86 // command is automatically split on spaces, and the string "$tmpdir"
87 // is replaced with TestTempDir().
88 void Run(const string& command);
89
90 // -----------------------------------------------------------------
91 // Methods to set up the test (called before Run()).
92
93 class NullCodeGenerator;
94
95 // Normally plugins are allowed for all tests. Call this to explicitly
96 // disable them.
DisallowPlugins()97 void DisallowPlugins() { disallow_plugins_ = true; }
98
99 // Create a temp file within temp_directory_ with the given name.
100 // The containing directory is also created if necessary.
101 void CreateTempFile(const string& name, const string& contents);
102
103 // Create a subdirectory within temp_directory_.
104 void CreateTempDir(const string& name);
105
SetInputsAreProtoPathRelative(bool enable)106 void SetInputsAreProtoPathRelative(bool enable) {
107 cli_.SetInputsAreProtoPathRelative(enable);
108 }
109
110 // -----------------------------------------------------------------
111 // Methods to check the test results (called after Run()).
112
113 // Checks that no text was written to stderr during Run(), and Run()
114 // returned 0.
115 void ExpectNoErrors();
116
117 // Checks that Run() returned non-zero and the stderr output is exactly
118 // the text given. expected_test may contain references to "$tmpdir",
119 // which will be replaced by the temporary directory path.
120 void ExpectErrorText(const string& expected_text);
121
122 // Checks that Run() returned non-zero and the stderr contains the given
123 // substring.
124 void ExpectErrorSubstring(const string& expected_substring);
125
126 // Like ExpectErrorSubstring, but checks that Run() returned zero.
127 void ExpectErrorSubstringWithZeroReturnCode(
128 const string& expected_substring);
129
130 // Checks that the captured stdout is the same as the expected_text.
131 void ExpectCapturedStdout(const string& expected_text);
132
133 // Returns true if ExpectErrorSubstring(expected_substring) would pass, but
134 // does not fail otherwise.
135 bool HasAlternateErrorSubstring(const string& expected_substring);
136
137 // Checks that MockCodeGenerator::Generate() was called in the given
138 // context (or the generator in test_plugin.cc, which produces the same
139 // output). That is, this tests if the generator with the given name
140 // was called with the given parameter and proto file and produced the
141 // given output file. This is checked by reading the output file and
142 // checking that it contains the content that MockCodeGenerator would
143 // generate given these inputs. message_name is the name of the first
144 // message that appeared in the proto file; this is just to make extra
145 // sure that the correct file was parsed.
146 void ExpectGenerated(const string& generator_name,
147 const string& parameter,
148 const string& proto_name,
149 const string& message_name);
150 void ExpectGenerated(const string& generator_name,
151 const string& parameter,
152 const string& proto_name,
153 const string& message_name,
154 const string& output_directory);
155 void ExpectGeneratedWithMultipleInputs(const string& generator_name,
156 const string& all_proto_names,
157 const string& proto_name,
158 const string& message_name);
159 void ExpectGeneratedWithInsertions(const string& generator_name,
160 const string& parameter,
161 const string& insertions,
162 const string& proto_name,
163 const string& message_name);
164
165 void ExpectNullCodeGeneratorCalled(const string& parameter);
166
167 void ReadDescriptorSet(const string& filename,
168 FileDescriptorSet* descriptor_set);
169
170 private:
171 // The object we are testing.
172 CommandLineInterface cli_;
173
174 // Was DisallowPlugins() called?
175 bool disallow_plugins_;
176
177 // We create a directory within TestTempDir() in order to add extra
178 // protection against accidentally deleting user files (since we recursively
179 // delete this directory during the test). This is the full path of that
180 // directory.
181 string temp_directory_;
182
183 // The result of Run().
184 int return_code_;
185
186 // The captured stderr output.
187 string error_text_;
188
189 // The captured stdout.
190 string captured_stdout_;
191
192 // Pointers which need to be deleted later.
193 vector<CodeGenerator*> mock_generators_to_delete_;
194
195 NullCodeGenerator* null_generator_;
196 };
197
198 class CommandLineInterfaceTest::NullCodeGenerator : public CodeGenerator {
199 public:
NullCodeGenerator()200 NullCodeGenerator() : called_(false) {}
~NullCodeGenerator()201 ~NullCodeGenerator() {}
202
203 mutable bool called_;
204 mutable string parameter_;
205
206 // implements CodeGenerator ----------------------------------------
Generate(const FileDescriptor * file,const string & parameter,GeneratorContext * context,string * error) const207 bool Generate(const FileDescriptor* file,
208 const string& parameter,
209 GeneratorContext* context,
210 string* error) const {
211 called_ = true;
212 parameter_ = parameter;
213 return true;
214 }
215 };
216
217 // ===================================================================
218
SetUp()219 void CommandLineInterfaceTest::SetUp() {
220 // Most of these tests were written before this option was added, so we
221 // run with the option on (which used to be the only way) except in certain
222 // tests where we turn it off.
223 cli_.SetInputsAreProtoPathRelative(true);
224
225 temp_directory_ = TestTempDir() + "/proto2_cli_test_temp";
226
227 // If the temp directory already exists, it must be left over from a
228 // previous run. Delete it.
229 if (File::Exists(temp_directory_)) {
230 File::DeleteRecursively(temp_directory_, NULL, NULL);
231 }
232
233 // Create the temp directory.
234 GOOGLE_CHECK_OK(File::CreateDir(temp_directory_, 0777));
235
236 // Register generators.
237 CodeGenerator* generator = new MockCodeGenerator("test_generator");
238 mock_generators_to_delete_.push_back(generator);
239 cli_.RegisterGenerator("--test_out", "--test_opt", generator, "Test output.");
240 cli_.RegisterGenerator("-t", generator, "Test output.");
241
242 generator = new MockCodeGenerator("alt_generator");
243 mock_generators_to_delete_.push_back(generator);
244 cli_.RegisterGenerator("--alt_out", generator, "Alt output.");
245
246 generator = null_generator_ = new NullCodeGenerator();
247 mock_generators_to_delete_.push_back(generator);
248 cli_.RegisterGenerator("--null_out", generator, "Null output.");
249
250 disallow_plugins_ = false;
251 }
252
TearDown()253 void CommandLineInterfaceTest::TearDown() {
254 // Delete the temp directory.
255 File::DeleteRecursively(temp_directory_, NULL, NULL);
256
257 // Delete all the MockCodeGenerators.
258 for (int i = 0; i < mock_generators_to_delete_.size(); i++) {
259 delete mock_generators_to_delete_[i];
260 }
261 mock_generators_to_delete_.clear();
262 }
263
Run(const string & command)264 void CommandLineInterfaceTest::Run(const string& command) {
265 vector<string> args = Split(command, " ", true);
266
267 if (!disallow_plugins_) {
268 cli_.AllowPlugins("prefix-");
269 const char* possible_paths[] = {
270 // When building with shared libraries, libtool hides the real executable
271 // in .libs and puts a fake wrapper in the current directory.
272 // Unfortunately, due to an apparent bug on Cygwin/MinGW, if one program
273 // wrapped in this way (e.g. protobuf-tests.exe) tries to execute another
274 // program wrapped in this way (e.g. test_plugin.exe), the latter fails
275 // with error code 127 and no explanation message. Presumably the problem
276 // is that the wrapper for protobuf-tests.exe set some environment
277 // variables that confuse the wrapper for test_plugin.exe. Luckily, it
278 // turns out that if we simply invoke the wrapped test_plugin.exe
279 // directly, it works -- I guess the environment variables set by the
280 // protobuf-tests.exe wrapper happen to be correct for it too. So we do
281 // that.
282 ".libs/test_plugin.exe", // Win32 w/autotool (Cygwin / MinGW)
283 "test_plugin.exe", // Other Win32 (MSVC)
284 "test_plugin", // Unix
285 };
286
287 string plugin_path;
288
289 for (int i = 0; i < GOOGLE_ARRAYSIZE(possible_paths); i++) {
290 if (access(possible_paths[i], F_OK) == 0) {
291 plugin_path = possible_paths[i];
292 break;
293 }
294 }
295
296 if (plugin_path.empty()) {
297 GOOGLE_LOG(ERROR)
298 << "Plugin executable not found. Plugin tests are likely to fail.";
299 } else {
300 args.push_back("--plugin=prefix-gen-plug=" + plugin_path);
301 }
302 }
303
304 scoped_array<const char*> argv(new const char* [args.size()]);
305
306 for (int i = 0; i < args.size(); i++) {
307 args[i] = StringReplace(args[i], "$tmpdir", temp_directory_, true);
308 argv[i] = args[i].c_str();
309 }
310
311 // TODO(jieluo): Cygwin doesn't work well if we try to capture stderr and
312 // stdout at the same time. Need to figure out why and add this capture back
313 // for Cygwin.
314 #if !defined(__CYGWIN__)
315 CaptureTestStdout();
316 #endif
317 CaptureTestStderr();
318
319 return_code_ = cli_.Run(args.size(), argv.get());
320
321 error_text_ = GetCapturedTestStderr();
322 #if !defined(__CYGWIN__)
323 captured_stdout_ = GetCapturedTestStdout();
324 #endif
325 }
326
327 // -------------------------------------------------------------------
328
CreateTempFile(const string & name,const string & contents)329 void CommandLineInterfaceTest::CreateTempFile(
330 const string& name,
331 const string& contents) {
332 // Create parent directory, if necessary.
333 string::size_type slash_pos = name.find_last_of('/');
334 if (slash_pos != string::npos) {
335 string dir = name.substr(0, slash_pos);
336 if (!File::Exists(temp_directory_ + "/" + dir)) {
337 GOOGLE_CHECK_OK(File::RecursivelyCreateDir(temp_directory_ + "/" + dir,
338 0777));
339 }
340 }
341
342 // Write file.
343 string full_name = temp_directory_ + "/" + name;
344 GOOGLE_CHECK_OK(File::SetContents(full_name, contents, true));
345 }
346
CreateTempDir(const string & name)347 void CommandLineInterfaceTest::CreateTempDir(const string& name) {
348 GOOGLE_CHECK_OK(File::RecursivelyCreateDir(temp_directory_ + "/" + name,
349 0777));
350 }
351
352 // -------------------------------------------------------------------
353
ExpectNoErrors()354 void CommandLineInterfaceTest::ExpectNoErrors() {
355 EXPECT_EQ(0, return_code_);
356 EXPECT_EQ("", error_text_);
357 }
358
ExpectErrorText(const string & expected_text)359 void CommandLineInterfaceTest::ExpectErrorText(const string& expected_text) {
360 EXPECT_NE(0, return_code_);
361 EXPECT_EQ(StringReplace(expected_text, "$tmpdir", temp_directory_, true),
362 error_text_);
363 }
364
ExpectErrorSubstring(const string & expected_substring)365 void CommandLineInterfaceTest::ExpectErrorSubstring(
366 const string& expected_substring) {
367 EXPECT_NE(0, return_code_);
368 EXPECT_PRED_FORMAT2(testing::IsSubstring, expected_substring, error_text_);
369 }
370
ExpectErrorSubstringWithZeroReturnCode(const string & expected_substring)371 void CommandLineInterfaceTest::ExpectErrorSubstringWithZeroReturnCode(
372 const string& expected_substring) {
373 EXPECT_EQ(0, return_code_);
374 EXPECT_PRED_FORMAT2(testing::IsSubstring, expected_substring, error_text_);
375 }
376
HasAlternateErrorSubstring(const string & expected_substring)377 bool CommandLineInterfaceTest::HasAlternateErrorSubstring(
378 const string& expected_substring) {
379 EXPECT_NE(0, return_code_);
380 return error_text_.find(expected_substring) != string::npos;
381 }
382
ExpectGenerated(const string & generator_name,const string & parameter,const string & proto_name,const string & message_name)383 void CommandLineInterfaceTest::ExpectGenerated(
384 const string& generator_name,
385 const string& parameter,
386 const string& proto_name,
387 const string& message_name) {
388 MockCodeGenerator::ExpectGenerated(
389 generator_name, parameter, "", proto_name, message_name, proto_name,
390 temp_directory_);
391 }
392
ExpectGenerated(const string & generator_name,const string & parameter,const string & proto_name,const string & message_name,const string & output_directory)393 void CommandLineInterfaceTest::ExpectGenerated(
394 const string& generator_name,
395 const string& parameter,
396 const string& proto_name,
397 const string& message_name,
398 const string& output_directory) {
399 MockCodeGenerator::ExpectGenerated(
400 generator_name, parameter, "", proto_name, message_name, proto_name,
401 temp_directory_ + "/" + output_directory);
402 }
403
ExpectGeneratedWithMultipleInputs(const string & generator_name,const string & all_proto_names,const string & proto_name,const string & message_name)404 void CommandLineInterfaceTest::ExpectGeneratedWithMultipleInputs(
405 const string& generator_name,
406 const string& all_proto_names,
407 const string& proto_name,
408 const string& message_name) {
409 MockCodeGenerator::ExpectGenerated(
410 generator_name, "", "", proto_name, message_name,
411 all_proto_names,
412 temp_directory_);
413 }
414
ExpectGeneratedWithInsertions(const string & generator_name,const string & parameter,const string & insertions,const string & proto_name,const string & message_name)415 void CommandLineInterfaceTest::ExpectGeneratedWithInsertions(
416 const string& generator_name,
417 const string& parameter,
418 const string& insertions,
419 const string& proto_name,
420 const string& message_name) {
421 MockCodeGenerator::ExpectGenerated(
422 generator_name, parameter, insertions, proto_name, message_name,
423 proto_name, temp_directory_);
424 }
425
ExpectNullCodeGeneratorCalled(const string & parameter)426 void CommandLineInterfaceTest::ExpectNullCodeGeneratorCalled(
427 const string& parameter) {
428 EXPECT_TRUE(null_generator_->called_);
429 EXPECT_EQ(parameter, null_generator_->parameter_);
430 }
431
ReadDescriptorSet(const string & filename,FileDescriptorSet * descriptor_set)432 void CommandLineInterfaceTest::ReadDescriptorSet(
433 const string& filename, FileDescriptorSet* descriptor_set) {
434 string path = temp_directory_ + "/" + filename;
435 string file_contents;
436 GOOGLE_CHECK_OK(File::GetContents(path, &file_contents, true));
437
438 if (!descriptor_set->ParseFromString(file_contents)) {
439 FAIL() << "Could not parse file contents: " << path;
440 }
441 }
442
ExpectCapturedStdout(const string & expected_text)443 void CommandLineInterfaceTest::ExpectCapturedStdout(
444 const string& expected_text) {
445 EXPECT_EQ(expected_text, captured_stdout_);
446 }
447
448 // ===================================================================
449
TEST_F(CommandLineInterfaceTest,BasicOutput)450 TEST_F(CommandLineInterfaceTest, BasicOutput) {
451 // Test that the common case works.
452
453 CreateTempFile("foo.proto",
454 "syntax = \"proto2\";\n"
455 "message Foo {}\n");
456
457 Run("protocol_compiler --test_out=$tmpdir "
458 "--proto_path=$tmpdir foo.proto");
459
460 ExpectNoErrors();
461 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
462 }
463
TEST_F(CommandLineInterfaceTest,BasicPlugin)464 TEST_F(CommandLineInterfaceTest, BasicPlugin) {
465 // Test that basic plugins work.
466
467 CreateTempFile("foo.proto",
468 "syntax = \"proto2\";\n"
469 "message Foo {}\n");
470
471 Run("protocol_compiler --plug_out=$tmpdir "
472 "--proto_path=$tmpdir foo.proto");
473
474 ExpectNoErrors();
475 ExpectGenerated("test_plugin", "", "foo.proto", "Foo");
476 }
477
TEST_F(CommandLineInterfaceTest,GeneratorAndPlugin)478 TEST_F(CommandLineInterfaceTest, GeneratorAndPlugin) {
479 // Invoke a generator and a plugin at the same time.
480
481 CreateTempFile("foo.proto",
482 "syntax = \"proto2\";\n"
483 "message Foo {}\n");
484
485 Run("protocol_compiler --test_out=$tmpdir --plug_out=$tmpdir "
486 "--proto_path=$tmpdir foo.proto");
487
488 ExpectNoErrors();
489 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
490 ExpectGenerated("test_plugin", "", "foo.proto", "Foo");
491 }
492
TEST_F(CommandLineInterfaceTest,MultipleInputs)493 TEST_F(CommandLineInterfaceTest, MultipleInputs) {
494 // Test parsing multiple input files.
495
496 CreateTempFile("foo.proto",
497 "syntax = \"proto2\";\n"
498 "message Foo {}\n");
499 CreateTempFile("bar.proto",
500 "syntax = \"proto2\";\n"
501 "message Bar {}\n");
502
503 Run("protocol_compiler --test_out=$tmpdir --plug_out=$tmpdir "
504 "--proto_path=$tmpdir foo.proto bar.proto");
505
506 ExpectNoErrors();
507 ExpectGeneratedWithMultipleInputs("test_generator", "foo.proto,bar.proto",
508 "foo.proto", "Foo");
509 ExpectGeneratedWithMultipleInputs("test_generator", "foo.proto,bar.proto",
510 "bar.proto", "Bar");
511 ExpectGeneratedWithMultipleInputs("test_plugin", "foo.proto,bar.proto",
512 "foo.proto", "Foo");
513 ExpectGeneratedWithMultipleInputs("test_plugin", "foo.proto,bar.proto",
514 "bar.proto", "Bar");
515 }
516
TEST_F(CommandLineInterfaceTest,MultipleInputsWithImport)517 TEST_F(CommandLineInterfaceTest, MultipleInputsWithImport) {
518 // Test parsing multiple input files with an import of a separate file.
519
520 CreateTempFile("foo.proto",
521 "syntax = \"proto2\";\n"
522 "message Foo {}\n");
523 CreateTempFile("bar.proto",
524 "syntax = \"proto2\";\n"
525 "import \"baz.proto\";\n"
526 "message Bar {\n"
527 " optional Baz a = 1;\n"
528 "}\n");
529 CreateTempFile("baz.proto",
530 "syntax = \"proto2\";\n"
531 "message Baz {}\n");
532
533 Run("protocol_compiler --test_out=$tmpdir --plug_out=$tmpdir "
534 "--proto_path=$tmpdir foo.proto bar.proto");
535
536 ExpectNoErrors();
537 ExpectGeneratedWithMultipleInputs("test_generator", "foo.proto,bar.proto",
538 "foo.proto", "Foo");
539 ExpectGeneratedWithMultipleInputs("test_generator", "foo.proto,bar.proto",
540 "bar.proto", "Bar");
541 ExpectGeneratedWithMultipleInputs("test_plugin", "foo.proto,bar.proto",
542 "foo.proto", "Foo");
543 ExpectGeneratedWithMultipleInputs("test_plugin", "foo.proto,bar.proto",
544 "bar.proto", "Bar");
545 }
546
TEST_F(CommandLineInterfaceTest,CreateDirectory)547 TEST_F(CommandLineInterfaceTest, CreateDirectory) {
548 // Test that when we output to a sub-directory, it is created.
549
550 CreateTempFile("bar/baz/foo.proto",
551 "syntax = \"proto2\";\n"
552 "message Foo {}\n");
553 CreateTempDir("out");
554 CreateTempDir("plugout");
555
556 Run("protocol_compiler --test_out=$tmpdir/out --plug_out=$tmpdir/plugout "
557 "--proto_path=$tmpdir bar/baz/foo.proto");
558
559 ExpectNoErrors();
560 ExpectGenerated("test_generator", "", "bar/baz/foo.proto", "Foo", "out");
561 ExpectGenerated("test_plugin", "", "bar/baz/foo.proto", "Foo", "plugout");
562 }
563
TEST_F(CommandLineInterfaceTest,GeneratorParameters)564 TEST_F(CommandLineInterfaceTest, GeneratorParameters) {
565 // Test that generator parameters are correctly parsed from the command line.
566
567 CreateTempFile("foo.proto",
568 "syntax = \"proto2\";\n"
569 "message Foo {}\n");
570
571 Run("protocol_compiler --test_out=TestParameter:$tmpdir "
572 "--plug_out=TestPluginParameter:$tmpdir "
573 "--proto_path=$tmpdir foo.proto");
574
575 ExpectNoErrors();
576 ExpectGenerated("test_generator", "TestParameter", "foo.proto", "Foo");
577 ExpectGenerated("test_plugin", "TestPluginParameter", "foo.proto", "Foo");
578 }
579
TEST_F(CommandLineInterfaceTest,ExtraGeneratorParameters)580 TEST_F(CommandLineInterfaceTest, ExtraGeneratorParameters) {
581 // Test that generator parameters specified with the option flag are
582 // correctly passed to the code generator.
583
584 CreateTempFile("foo.proto",
585 "syntax = \"proto2\";\n"
586 "message Foo {}\n");
587 // Create the "a" and "b" sub-directories.
588 CreateTempDir("a");
589 CreateTempDir("b");
590
591 Run("protocol_compiler "
592 "--test_opt=foo1 "
593 "--test_out=bar:$tmpdir/a "
594 "--test_opt=foo2 "
595 "--test_out=baz:$tmpdir/b "
596 "--test_opt=foo3 "
597 "--proto_path=$tmpdir foo.proto");
598
599 ExpectNoErrors();
600 ExpectGenerated(
601 "test_generator", "bar,foo1,foo2,foo3", "foo.proto", "Foo", "a");
602 ExpectGenerated(
603 "test_generator", "baz,foo1,foo2,foo3", "foo.proto", "Foo", "b");
604 }
605
TEST_F(CommandLineInterfaceTest,Insert)606 TEST_F(CommandLineInterfaceTest, Insert) {
607 // Test running a generator that inserts code into another's output.
608
609 CreateTempFile("foo.proto",
610 "syntax = \"proto2\";\n"
611 "message Foo {}\n");
612
613 Run("protocol_compiler "
614 "--test_out=TestParameter:$tmpdir "
615 "--plug_out=TestPluginParameter:$tmpdir "
616 "--test_out=insert=test_generator,test_plugin:$tmpdir "
617 "--plug_out=insert=test_generator,test_plugin:$tmpdir "
618 "--proto_path=$tmpdir foo.proto");
619
620 ExpectNoErrors();
621 ExpectGeneratedWithInsertions(
622 "test_generator", "TestParameter", "test_generator,test_plugin",
623 "foo.proto", "Foo");
624 ExpectGeneratedWithInsertions(
625 "test_plugin", "TestPluginParameter", "test_generator,test_plugin",
626 "foo.proto", "Foo");
627 }
628
629 #if defined(_WIN32)
630
TEST_F(CommandLineInterfaceTest,WindowsOutputPath)631 TEST_F(CommandLineInterfaceTest, WindowsOutputPath) {
632 // Test that the output path can be a Windows-style path.
633
634 CreateTempFile("foo.proto",
635 "syntax = \"proto2\";\n");
636
637 Run("protocol_compiler --null_out=C:\\ "
638 "--proto_path=$tmpdir foo.proto");
639
640 ExpectNoErrors();
641 ExpectNullCodeGeneratorCalled("");
642 }
643
TEST_F(CommandLineInterfaceTest,WindowsOutputPathAndParameter)644 TEST_F(CommandLineInterfaceTest, WindowsOutputPathAndParameter) {
645 // Test that we can have a windows-style output path and a parameter.
646
647 CreateTempFile("foo.proto",
648 "syntax = \"proto2\";\n");
649
650 Run("protocol_compiler --null_out=bar:C:\\ "
651 "--proto_path=$tmpdir foo.proto");
652
653 ExpectNoErrors();
654 ExpectNullCodeGeneratorCalled("bar");
655 }
656
TEST_F(CommandLineInterfaceTest,TrailingBackslash)657 TEST_F(CommandLineInterfaceTest, TrailingBackslash) {
658 // Test that the directories can end in backslashes. Some users claim this
659 // doesn't work on their system.
660
661 CreateTempFile("foo.proto",
662 "syntax = \"proto2\";\n"
663 "message Foo {}\n");
664
665 Run("protocol_compiler --test_out=$tmpdir\\ "
666 "--proto_path=$tmpdir\\ foo.proto");
667
668 ExpectNoErrors();
669 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
670 }
671
672 #endif // defined(_WIN32) || defined(__CYGWIN__)
673
TEST_F(CommandLineInterfaceTest,PathLookup)674 TEST_F(CommandLineInterfaceTest, PathLookup) {
675 // Test that specifying multiple directories in the proto search path works.
676
677 CreateTempFile("b/bar.proto",
678 "syntax = \"proto2\";\n"
679 "message Bar {}\n");
680 CreateTempFile("a/foo.proto",
681 "syntax = \"proto2\";\n"
682 "import \"bar.proto\";\n"
683 "message Foo {\n"
684 " optional Bar a = 1;\n"
685 "}\n");
686 CreateTempFile("b/foo.proto", "this should not be parsed\n");
687
688 Run("protocol_compiler --test_out=$tmpdir "
689 "--proto_path=$tmpdir/a --proto_path=$tmpdir/b foo.proto");
690
691 ExpectNoErrors();
692 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
693 }
694
TEST_F(CommandLineInterfaceTest,ColonDelimitedPath)695 TEST_F(CommandLineInterfaceTest, ColonDelimitedPath) {
696 // Same as PathLookup, but we provide the proto_path in a single flag.
697
698 CreateTempFile("b/bar.proto",
699 "syntax = \"proto2\";\n"
700 "message Bar {}\n");
701 CreateTempFile("a/foo.proto",
702 "syntax = \"proto2\";\n"
703 "import \"bar.proto\";\n"
704 "message Foo {\n"
705 " optional Bar a = 1;\n"
706 "}\n");
707 CreateTempFile("b/foo.proto", "this should not be parsed\n");
708
709 #undef PATH_SEPARATOR
710 #if defined(_WIN32)
711 #define PATH_SEPARATOR ";"
712 #else
713 #define PATH_SEPARATOR ":"
714 #endif
715
716 Run("protocol_compiler --test_out=$tmpdir "
717 "--proto_path=$tmpdir/a"PATH_SEPARATOR"$tmpdir/b foo.proto");
718
719 #undef PATH_SEPARATOR
720
721 ExpectNoErrors();
722 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
723 }
724
TEST_F(CommandLineInterfaceTest,NonRootMapping)725 TEST_F(CommandLineInterfaceTest, NonRootMapping) {
726 // Test setting up a search path mapping a directory to a non-root location.
727
728 CreateTempFile("foo.proto",
729 "syntax = \"proto2\";\n"
730 "message Foo {}\n");
731
732 Run("protocol_compiler --test_out=$tmpdir "
733 "--proto_path=bar=$tmpdir bar/foo.proto");
734
735 ExpectNoErrors();
736 ExpectGenerated("test_generator", "", "bar/foo.proto", "Foo");
737 }
738
TEST_F(CommandLineInterfaceTest,MultipleGenerators)739 TEST_F(CommandLineInterfaceTest, MultipleGenerators) {
740 // Test that we can have multiple generators and use both in one invocation,
741 // each with a different output directory.
742
743 CreateTempFile("foo.proto",
744 "syntax = \"proto2\";\n"
745 "message Foo {}\n");
746 // Create the "a" and "b" sub-directories.
747 CreateTempDir("a");
748 CreateTempDir("b");
749
750 Run("protocol_compiler "
751 "--test_out=$tmpdir/a "
752 "--alt_out=$tmpdir/b "
753 "--proto_path=$tmpdir foo.proto");
754
755 ExpectNoErrors();
756 ExpectGenerated("test_generator", "", "foo.proto", "Foo", "a");
757 ExpectGenerated("alt_generator", "", "foo.proto", "Foo", "b");
758 }
759
TEST_F(CommandLineInterfaceTest,DisallowServicesNoServices)760 TEST_F(CommandLineInterfaceTest, DisallowServicesNoServices) {
761 // Test that --disallow_services doesn't cause a problem when there are no
762 // services.
763
764 CreateTempFile("foo.proto",
765 "syntax = \"proto2\";\n"
766 "message Foo {}\n");
767
768 Run("protocol_compiler --disallow_services --test_out=$tmpdir "
769 "--proto_path=$tmpdir foo.proto");
770
771 ExpectNoErrors();
772 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
773 }
774
TEST_F(CommandLineInterfaceTest,DisallowServicesHasService)775 TEST_F(CommandLineInterfaceTest, DisallowServicesHasService) {
776 // Test that --disallow_services produces an error when there are services.
777
778 CreateTempFile("foo.proto",
779 "syntax = \"proto2\";\n"
780 "message Foo {}\n"
781 "service Bar {}\n");
782
783 Run("protocol_compiler --disallow_services --test_out=$tmpdir "
784 "--proto_path=$tmpdir foo.proto");
785
786 ExpectErrorSubstring("foo.proto: This file contains services");
787 }
788
TEST_F(CommandLineInterfaceTest,AllowServicesHasService)789 TEST_F(CommandLineInterfaceTest, AllowServicesHasService) {
790 // Test that services work fine as long as --disallow_services is not used.
791
792 CreateTempFile("foo.proto",
793 "syntax = \"proto2\";\n"
794 "message Foo {}\n"
795 "service Bar {}\n");
796
797 Run("protocol_compiler --test_out=$tmpdir "
798 "--proto_path=$tmpdir foo.proto");
799
800 ExpectNoErrors();
801 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
802 }
803
TEST_F(CommandLineInterfaceTest,CwdRelativeInputs)804 TEST_F(CommandLineInterfaceTest, CwdRelativeInputs) {
805 // Test that we can accept working-directory-relative input files.
806
807 SetInputsAreProtoPathRelative(false);
808
809 CreateTempFile("foo.proto",
810 "syntax = \"proto2\";\n"
811 "message Foo {}\n");
812
813 Run("protocol_compiler --test_out=$tmpdir "
814 "--proto_path=$tmpdir $tmpdir/foo.proto");
815
816 ExpectNoErrors();
817 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
818 }
819
TEST_F(CommandLineInterfaceTest,WriteDescriptorSet)820 TEST_F(CommandLineInterfaceTest, WriteDescriptorSet) {
821 CreateTempFile("foo.proto",
822 "syntax = \"proto2\";\n"
823 "message Foo {}\n");
824 CreateTempFile("bar.proto",
825 "syntax = \"proto2\";\n"
826 "import \"foo.proto\";\n"
827 "message Bar {\n"
828 " optional Foo foo = 1;\n"
829 "}\n");
830
831 Run("protocol_compiler --descriptor_set_out=$tmpdir/descriptor_set "
832 "--proto_path=$tmpdir bar.proto");
833
834 ExpectNoErrors();
835
836 FileDescriptorSet descriptor_set;
837 ReadDescriptorSet("descriptor_set", &descriptor_set);
838 if (HasFatalFailure()) return;
839 EXPECT_EQ(1, descriptor_set.file_size());
840 EXPECT_EQ("bar.proto", descriptor_set.file(0).name());
841 // Descriptor set should not have source code info.
842 EXPECT_FALSE(descriptor_set.file(0).has_source_code_info());
843 }
844
TEST_F(CommandLineInterfaceTest,WriteDescriptorSetWithSourceInfo)845 TEST_F(CommandLineInterfaceTest, WriteDescriptorSetWithSourceInfo) {
846 CreateTempFile("foo.proto",
847 "syntax = \"proto2\";\n"
848 "message Foo {}\n");
849 CreateTempFile("bar.proto",
850 "syntax = \"proto2\";\n"
851 "import \"foo.proto\";\n"
852 "message Bar {\n"
853 " optional Foo foo = 1;\n"
854 "}\n");
855
856 Run("protocol_compiler --descriptor_set_out=$tmpdir/descriptor_set "
857 "--include_source_info --proto_path=$tmpdir bar.proto");
858
859 ExpectNoErrors();
860
861 FileDescriptorSet descriptor_set;
862 ReadDescriptorSet("descriptor_set", &descriptor_set);
863 if (HasFatalFailure()) return;
864 EXPECT_EQ(1, descriptor_set.file_size());
865 EXPECT_EQ("bar.proto", descriptor_set.file(0).name());
866 // Source code info included.
867 EXPECT_TRUE(descriptor_set.file(0).has_source_code_info());
868 }
869
TEST_F(CommandLineInterfaceTest,WriteTransitiveDescriptorSet)870 TEST_F(CommandLineInterfaceTest, WriteTransitiveDescriptorSet) {
871 CreateTempFile("foo.proto",
872 "syntax = \"proto2\";\n"
873 "message Foo {}\n");
874 CreateTempFile("bar.proto",
875 "syntax = \"proto2\";\n"
876 "import \"foo.proto\";\n"
877 "message Bar {\n"
878 " optional Foo foo = 1;\n"
879 "}\n");
880
881 Run("protocol_compiler --descriptor_set_out=$tmpdir/descriptor_set "
882 "--include_imports --proto_path=$tmpdir bar.proto");
883
884 ExpectNoErrors();
885
886 FileDescriptorSet descriptor_set;
887 ReadDescriptorSet("descriptor_set", &descriptor_set);
888 if (HasFatalFailure()) return;
889 EXPECT_EQ(2, descriptor_set.file_size());
890 if (descriptor_set.file(0).name() == "bar.proto") {
891 std::swap(descriptor_set.mutable_file()->mutable_data()[0],
892 descriptor_set.mutable_file()->mutable_data()[1]);
893 }
894 EXPECT_EQ("foo.proto", descriptor_set.file(0).name());
895 EXPECT_EQ("bar.proto", descriptor_set.file(1).name());
896 // Descriptor set should not have source code info.
897 EXPECT_FALSE(descriptor_set.file(0).has_source_code_info());
898 EXPECT_FALSE(descriptor_set.file(1).has_source_code_info());
899 }
900
TEST_F(CommandLineInterfaceTest,WriteTransitiveDescriptorSetWithSourceInfo)901 TEST_F(CommandLineInterfaceTest, WriteTransitiveDescriptorSetWithSourceInfo) {
902 CreateTempFile("foo.proto",
903 "syntax = \"proto2\";\n"
904 "message Foo {}\n");
905 CreateTempFile("bar.proto",
906 "syntax = \"proto2\";\n"
907 "import \"foo.proto\";\n"
908 "message Bar {\n"
909 " optional Foo foo = 1;\n"
910 "}\n");
911
912 Run("protocol_compiler --descriptor_set_out=$tmpdir/descriptor_set "
913 "--include_imports --include_source_info --proto_path=$tmpdir bar.proto");
914
915 ExpectNoErrors();
916
917 FileDescriptorSet descriptor_set;
918 ReadDescriptorSet("descriptor_set", &descriptor_set);
919 if (HasFatalFailure()) return;
920 EXPECT_EQ(2, descriptor_set.file_size());
921 if (descriptor_set.file(0).name() == "bar.proto") {
922 std::swap(descriptor_set.mutable_file()->mutable_data()[0],
923 descriptor_set.mutable_file()->mutable_data()[1]);
924 }
925 EXPECT_EQ("foo.proto", descriptor_set.file(0).name());
926 EXPECT_EQ("bar.proto", descriptor_set.file(1).name());
927 // Source code info included.
928 EXPECT_TRUE(descriptor_set.file(0).has_source_code_info());
929 EXPECT_TRUE(descriptor_set.file(1).has_source_code_info());
930 }
931
932 // -------------------------------------------------------------------
933
TEST_F(CommandLineInterfaceTest,ParseErrors)934 TEST_F(CommandLineInterfaceTest, ParseErrors) {
935 // Test that parse errors are reported.
936
937 CreateTempFile("foo.proto",
938 "syntax = \"proto2\";\n"
939 "badsyntax\n");
940
941 Run("protocol_compiler --test_out=$tmpdir "
942 "--proto_path=$tmpdir foo.proto");
943
944 ExpectErrorText(
945 "foo.proto:2:1: Expected top-level statement (e.g. \"message\").\n");
946 }
947
TEST_F(CommandLineInterfaceTest,ParseErrorsMultipleFiles)948 TEST_F(CommandLineInterfaceTest, ParseErrorsMultipleFiles) {
949 // Test that parse errors are reported from multiple files.
950
951 // We set up files such that foo.proto actually depends on bar.proto in
952 // two ways: Directly and through baz.proto. bar.proto's errors should
953 // only be reported once.
954 CreateTempFile("bar.proto",
955 "syntax = \"proto2\";\n"
956 "badsyntax\n");
957 CreateTempFile("baz.proto",
958 "syntax = \"proto2\";\n"
959 "import \"bar.proto\";\n");
960 CreateTempFile("foo.proto",
961 "syntax = \"proto2\";\n"
962 "import \"bar.proto\";\n"
963 "import \"baz.proto\";\n");
964
965 Run("protocol_compiler --test_out=$tmpdir "
966 "--proto_path=$tmpdir foo.proto");
967
968 ExpectErrorText(
969 "bar.proto:2:1: Expected top-level statement (e.g. \"message\").\n"
970 "baz.proto: Import \"bar.proto\" was not found or had errors.\n"
971 "foo.proto: Import \"bar.proto\" was not found or had errors.\n"
972 "foo.proto: Import \"baz.proto\" was not found or had errors.\n");
973 }
974
TEST_F(CommandLineInterfaceTest,InputNotFoundError)975 TEST_F(CommandLineInterfaceTest, InputNotFoundError) {
976 // Test what happens if the input file is not found.
977
978 Run("protocol_compiler --test_out=$tmpdir "
979 "--proto_path=$tmpdir foo.proto");
980
981 ExpectErrorText(
982 "foo.proto: File not found.\n");
983 }
984
TEST_F(CommandLineInterfaceTest,CwdRelativeInputNotFoundError)985 TEST_F(CommandLineInterfaceTest, CwdRelativeInputNotFoundError) {
986 // Test what happens when a working-directory-relative input file is not
987 // found.
988
989 SetInputsAreProtoPathRelative(false);
990
991 Run("protocol_compiler --test_out=$tmpdir "
992 "--proto_path=$tmpdir $tmpdir/foo.proto");
993
994 ExpectErrorText(
995 "$tmpdir/foo.proto: No such file or directory\n");
996 }
997
TEST_F(CommandLineInterfaceTest,CwdRelativeInputNotMappedError)998 TEST_F(CommandLineInterfaceTest, CwdRelativeInputNotMappedError) {
999 // Test what happens when a working-directory-relative input file is not
1000 // mapped to a virtual path.
1001
1002 SetInputsAreProtoPathRelative(false);
1003
1004 CreateTempFile("foo.proto",
1005 "syntax = \"proto2\";\n"
1006 "message Foo {}\n");
1007
1008 // Create a directory called "bar" so that we can point --proto_path at it.
1009 CreateTempFile("bar/dummy", "");
1010
1011 Run("protocol_compiler --test_out=$tmpdir "
1012 "--proto_path=$tmpdir/bar $tmpdir/foo.proto");
1013
1014 ExpectErrorText(
1015 "$tmpdir/foo.proto: File does not reside within any path "
1016 "specified using --proto_path (or -I). You must specify a "
1017 "--proto_path which encompasses this file. Note that the "
1018 "proto_path must be an exact prefix of the .proto file "
1019 "names -- protoc is too dumb to figure out when two paths "
1020 "(e.g. absolute and relative) are equivalent (it's harder "
1021 "than you think).\n");
1022 }
1023
TEST_F(CommandLineInterfaceTest,CwdRelativeInputNotFoundAndNotMappedError)1024 TEST_F(CommandLineInterfaceTest, CwdRelativeInputNotFoundAndNotMappedError) {
1025 // Check what happens if the input file is not found *and* is not mapped
1026 // in the proto_path.
1027
1028 SetInputsAreProtoPathRelative(false);
1029
1030 // Create a directory called "bar" so that we can point --proto_path at it.
1031 CreateTempFile("bar/dummy", "");
1032
1033 Run("protocol_compiler --test_out=$tmpdir "
1034 "--proto_path=$tmpdir/bar $tmpdir/foo.proto");
1035
1036 ExpectErrorText(
1037 "$tmpdir/foo.proto: No such file or directory\n");
1038 }
1039
TEST_F(CommandLineInterfaceTest,CwdRelativeInputShadowedError)1040 TEST_F(CommandLineInterfaceTest, CwdRelativeInputShadowedError) {
1041 // Test what happens when a working-directory-relative input file is shadowed
1042 // by another file in the virtual path.
1043
1044 SetInputsAreProtoPathRelative(false);
1045
1046 CreateTempFile("foo/foo.proto",
1047 "syntax = \"proto2\";\n"
1048 "message Foo {}\n");
1049 CreateTempFile("bar/foo.proto",
1050 "syntax = \"proto2\";\n"
1051 "message Bar {}\n");
1052
1053 Run("protocol_compiler --test_out=$tmpdir "
1054 "--proto_path=$tmpdir/foo --proto_path=$tmpdir/bar "
1055 "$tmpdir/bar/foo.proto");
1056
1057 ExpectErrorText(
1058 "$tmpdir/bar/foo.proto: Input is shadowed in the --proto_path "
1059 "by \"$tmpdir/foo/foo.proto\". Either use the latter "
1060 "file as your input or reorder the --proto_path so that the "
1061 "former file's location comes first.\n");
1062 }
1063
TEST_F(CommandLineInterfaceTest,ProtoPathNotFoundError)1064 TEST_F(CommandLineInterfaceTest, ProtoPathNotFoundError) {
1065 // Test what happens if the input file is not found.
1066
1067 Run("protocol_compiler --test_out=$tmpdir "
1068 "--proto_path=$tmpdir/foo foo.proto");
1069
1070 ExpectErrorText(
1071 "$tmpdir/foo: warning: directory does not exist.\n"
1072 "foo.proto: File not found.\n");
1073 }
1074
TEST_F(CommandLineInterfaceTest,MissingInputError)1075 TEST_F(CommandLineInterfaceTest, MissingInputError) {
1076 // Test that we get an error if no inputs are given.
1077
1078 Run("protocol_compiler --test_out=$tmpdir "
1079 "--proto_path=$tmpdir");
1080
1081 ExpectErrorText("Missing input file.\n");
1082 }
1083
TEST_F(CommandLineInterfaceTest,MissingOutputError)1084 TEST_F(CommandLineInterfaceTest, MissingOutputError) {
1085 CreateTempFile("foo.proto",
1086 "syntax = \"proto2\";\n"
1087 "message Foo {}\n");
1088
1089 Run("protocol_compiler --proto_path=$tmpdir foo.proto");
1090
1091 ExpectErrorText("Missing output directives.\n");
1092 }
1093
TEST_F(CommandLineInterfaceTest,OutputWriteError)1094 TEST_F(CommandLineInterfaceTest, OutputWriteError) {
1095 CreateTempFile("foo.proto",
1096 "syntax = \"proto2\";\n"
1097 "message Foo {}\n");
1098
1099 string output_file =
1100 MockCodeGenerator::GetOutputFileName("test_generator", "foo.proto");
1101
1102 // Create a directory blocking our output location.
1103 CreateTempDir(output_file);
1104
1105 Run("protocol_compiler --test_out=$tmpdir "
1106 "--proto_path=$tmpdir foo.proto");
1107
1108 // MockCodeGenerator no longer detects an error because we actually write to
1109 // an in-memory location first, then dump to disk at the end. This is no
1110 // big deal.
1111 // ExpectErrorSubstring("MockCodeGenerator detected write error.");
1112
1113 #if defined(_WIN32) && !defined(__CYGWIN__)
1114 // Windows with MSVCRT.dll produces EPERM instead of EISDIR.
1115 if (HasAlternateErrorSubstring(output_file + ": Permission denied")) {
1116 return;
1117 }
1118 #endif
1119
1120 ExpectErrorSubstring(output_file + ": Is a directory");
1121 }
1122
TEST_F(CommandLineInterfaceTest,PluginOutputWriteError)1123 TEST_F(CommandLineInterfaceTest, PluginOutputWriteError) {
1124 CreateTempFile("foo.proto",
1125 "syntax = \"proto2\";\n"
1126 "message Foo {}\n");
1127
1128 string output_file =
1129 MockCodeGenerator::GetOutputFileName("test_plugin", "foo.proto");
1130
1131 // Create a directory blocking our output location.
1132 CreateTempDir(output_file);
1133
1134 Run("protocol_compiler --plug_out=$tmpdir "
1135 "--proto_path=$tmpdir foo.proto");
1136
1137 #if defined(_WIN32) && !defined(__CYGWIN__)
1138 // Windows with MSVCRT.dll produces EPERM instead of EISDIR.
1139 if (HasAlternateErrorSubstring(output_file + ": Permission denied")) {
1140 return;
1141 }
1142 #endif
1143
1144 ExpectErrorSubstring(output_file + ": Is a directory");
1145 }
1146
TEST_F(CommandLineInterfaceTest,OutputDirectoryNotFoundError)1147 TEST_F(CommandLineInterfaceTest, OutputDirectoryNotFoundError) {
1148 CreateTempFile("foo.proto",
1149 "syntax = \"proto2\";\n"
1150 "message Foo {}\n");
1151
1152 Run("protocol_compiler --test_out=$tmpdir/nosuchdir "
1153 "--proto_path=$tmpdir foo.proto");
1154
1155 ExpectErrorSubstring("nosuchdir/: No such file or directory");
1156 }
1157
TEST_F(CommandLineInterfaceTest,PluginOutputDirectoryNotFoundError)1158 TEST_F(CommandLineInterfaceTest, PluginOutputDirectoryNotFoundError) {
1159 CreateTempFile("foo.proto",
1160 "syntax = \"proto2\";\n"
1161 "message Foo {}\n");
1162
1163 Run("protocol_compiler --plug_out=$tmpdir/nosuchdir "
1164 "--proto_path=$tmpdir foo.proto");
1165
1166 ExpectErrorSubstring("nosuchdir/: No such file or directory");
1167 }
1168
TEST_F(CommandLineInterfaceTest,OutputDirectoryIsFileError)1169 TEST_F(CommandLineInterfaceTest, OutputDirectoryIsFileError) {
1170 CreateTempFile("foo.proto",
1171 "syntax = \"proto2\";\n"
1172 "message Foo {}\n");
1173
1174 Run("protocol_compiler --test_out=$tmpdir/foo.proto "
1175 "--proto_path=$tmpdir foo.proto");
1176
1177 #if defined(_WIN32) && !defined(__CYGWIN__)
1178 // Windows with MSVCRT.dll produces EINVAL instead of ENOTDIR.
1179 if (HasAlternateErrorSubstring("foo.proto/: Invalid argument")) {
1180 return;
1181 }
1182 #endif
1183
1184 ExpectErrorSubstring("foo.proto/: Not a directory");
1185 }
1186
TEST_F(CommandLineInterfaceTest,GeneratorError)1187 TEST_F(CommandLineInterfaceTest, GeneratorError) {
1188 CreateTempFile("foo.proto",
1189 "syntax = \"proto2\";\n"
1190 "message MockCodeGenerator_Error {}\n");
1191
1192 Run("protocol_compiler --test_out=$tmpdir "
1193 "--proto_path=$tmpdir foo.proto");
1194
1195 ExpectErrorSubstring(
1196 "--test_out: foo.proto: Saw message type MockCodeGenerator_Error.");
1197 }
1198
TEST_F(CommandLineInterfaceTest,GeneratorPluginError)1199 TEST_F(CommandLineInterfaceTest, GeneratorPluginError) {
1200 // Test a generator plugin that returns an error.
1201
1202 CreateTempFile("foo.proto",
1203 "syntax = \"proto2\";\n"
1204 "message MockCodeGenerator_Error {}\n");
1205
1206 Run("protocol_compiler --plug_out=TestParameter:$tmpdir "
1207 "--proto_path=$tmpdir foo.proto");
1208
1209 ExpectErrorSubstring(
1210 "--plug_out: foo.proto: Saw message type MockCodeGenerator_Error.");
1211 }
1212
TEST_F(CommandLineInterfaceTest,GeneratorPluginFail)1213 TEST_F(CommandLineInterfaceTest, GeneratorPluginFail) {
1214 // Test a generator plugin that exits with an error code.
1215
1216 CreateTempFile("foo.proto",
1217 "syntax = \"proto2\";\n"
1218 "message MockCodeGenerator_Exit {}\n");
1219
1220 Run("protocol_compiler --plug_out=TestParameter:$tmpdir "
1221 "--proto_path=$tmpdir foo.proto");
1222
1223 ExpectErrorSubstring("Saw message type MockCodeGenerator_Exit.");
1224 ExpectErrorSubstring(
1225 "--plug_out: prefix-gen-plug: Plugin failed with status code 123.");
1226 }
1227
TEST_F(CommandLineInterfaceTest,GeneratorPluginCrash)1228 TEST_F(CommandLineInterfaceTest, GeneratorPluginCrash) {
1229 // Test a generator plugin that crashes.
1230
1231 CreateTempFile("foo.proto",
1232 "syntax = \"proto2\";\n"
1233 "message MockCodeGenerator_Abort {}\n");
1234
1235 Run("protocol_compiler --plug_out=TestParameter:$tmpdir "
1236 "--proto_path=$tmpdir foo.proto");
1237
1238 ExpectErrorSubstring("Saw message type MockCodeGenerator_Abort.");
1239
1240 #ifdef _WIN32
1241 // Windows doesn't have signals. It looks like abort()ing causes the process
1242 // to exit with status code 3, but let's not depend on the exact number here.
1243 ExpectErrorSubstring(
1244 "--plug_out: prefix-gen-plug: Plugin failed with status code");
1245 #else
1246 // Don't depend on the exact signal number.
1247 ExpectErrorSubstring(
1248 "--plug_out: prefix-gen-plug: Plugin killed by signal");
1249 #endif
1250 }
1251
TEST_F(CommandLineInterfaceTest,PluginReceivesSourceCodeInfo)1252 TEST_F(CommandLineInterfaceTest, PluginReceivesSourceCodeInfo) {
1253 CreateTempFile("foo.proto",
1254 "syntax = \"proto2\";\n"
1255 "message MockCodeGenerator_HasSourceCodeInfo {}\n");
1256
1257 Run("protocol_compiler --plug_out=$tmpdir --proto_path=$tmpdir foo.proto");
1258
1259 ExpectErrorSubstring(
1260 "Saw message type MockCodeGenerator_HasSourceCodeInfo: 1.");
1261 }
1262
TEST_F(CommandLineInterfaceTest,GeneratorPluginNotFound)1263 TEST_F(CommandLineInterfaceTest, GeneratorPluginNotFound) {
1264 // Test what happens if the plugin isn't found.
1265
1266 CreateTempFile("error.proto",
1267 "syntax = \"proto2\";\n"
1268 "message Foo {}\n");
1269
1270 Run("protocol_compiler --badplug_out=TestParameter:$tmpdir "
1271 "--plugin=prefix-gen-badplug=no_such_file "
1272 "--proto_path=$tmpdir error.proto");
1273
1274 #ifdef _WIN32
1275 ExpectErrorSubstring("--badplug_out: prefix-gen-badplug: " +
1276 Subprocess::Win32ErrorMessage(ERROR_FILE_NOT_FOUND));
1277 #else
1278 // Error written to stdout by child process after exec() fails.
1279 ExpectErrorSubstring(
1280 "no_such_file: program not found or is not executable");
1281
1282 // Error written by parent process when child fails.
1283 ExpectErrorSubstring(
1284 "--badplug_out: prefix-gen-badplug: Plugin failed with status code 1.");
1285 #endif
1286 }
1287
TEST_F(CommandLineInterfaceTest,GeneratorPluginNotAllowed)1288 TEST_F(CommandLineInterfaceTest, GeneratorPluginNotAllowed) {
1289 // Test what happens if plugins aren't allowed.
1290
1291 CreateTempFile("error.proto",
1292 "syntax = \"proto2\";\n"
1293 "message Foo {}\n");
1294
1295 DisallowPlugins();
1296 Run("protocol_compiler --plug_out=TestParameter:$tmpdir "
1297 "--proto_path=$tmpdir error.proto");
1298
1299 ExpectErrorSubstring("Unknown flag: --plug_out");
1300 }
1301
TEST_F(CommandLineInterfaceTest,HelpText)1302 TEST_F(CommandLineInterfaceTest, HelpText) {
1303 Run("test_exec_name --help");
1304
1305 ExpectErrorSubstringWithZeroReturnCode("Usage: test_exec_name ");
1306 ExpectErrorSubstringWithZeroReturnCode("--test_out=OUT_DIR");
1307 ExpectErrorSubstringWithZeroReturnCode("Test output.");
1308 ExpectErrorSubstringWithZeroReturnCode("--alt_out=OUT_DIR");
1309 ExpectErrorSubstringWithZeroReturnCode("Alt output.");
1310 }
1311
TEST_F(CommandLineInterfaceTest,GccFormatErrors)1312 TEST_F(CommandLineInterfaceTest, GccFormatErrors) {
1313 // Test --error_format=gcc (which is the default, but we want to verify
1314 // that it can be set explicitly).
1315
1316 CreateTempFile("foo.proto",
1317 "syntax = \"proto2\";\n"
1318 "badsyntax\n");
1319
1320 Run("protocol_compiler --test_out=$tmpdir "
1321 "--proto_path=$tmpdir --error_format=gcc foo.proto");
1322
1323 ExpectErrorText(
1324 "foo.proto:2:1: Expected top-level statement (e.g. \"message\").\n");
1325 }
1326
TEST_F(CommandLineInterfaceTest,MsvsFormatErrors)1327 TEST_F(CommandLineInterfaceTest, MsvsFormatErrors) {
1328 // Test --error_format=msvs
1329
1330 CreateTempFile("foo.proto",
1331 "syntax = \"proto2\";\n"
1332 "badsyntax\n");
1333
1334 Run("protocol_compiler --test_out=$tmpdir "
1335 "--proto_path=$tmpdir --error_format=msvs foo.proto");
1336
1337 ExpectErrorText(
1338 "$tmpdir/foo.proto(2) : error in column=1: Expected top-level statement "
1339 "(e.g. \"message\").\n");
1340 }
1341
TEST_F(CommandLineInterfaceTest,InvalidErrorFormat)1342 TEST_F(CommandLineInterfaceTest, InvalidErrorFormat) {
1343 // Test --error_format=msvs
1344
1345 CreateTempFile("foo.proto",
1346 "syntax = \"proto2\";\n"
1347 "badsyntax\n");
1348
1349 Run("protocol_compiler --test_out=$tmpdir "
1350 "--proto_path=$tmpdir --error_format=invalid foo.proto");
1351
1352 ExpectErrorText(
1353 "Unknown error format: invalid\n");
1354 }
1355
1356 // -------------------------------------------------------------------
1357 // Flag parsing tests
1358
TEST_F(CommandLineInterfaceTest,ParseSingleCharacterFlag)1359 TEST_F(CommandLineInterfaceTest, ParseSingleCharacterFlag) {
1360 // Test that a single-character flag works.
1361
1362 CreateTempFile("foo.proto",
1363 "syntax = \"proto2\";\n"
1364 "message Foo {}\n");
1365
1366 Run("protocol_compiler -t$tmpdir "
1367 "--proto_path=$tmpdir foo.proto");
1368
1369 ExpectNoErrors();
1370 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
1371 }
1372
TEST_F(CommandLineInterfaceTest,ParseSpaceDelimitedValue)1373 TEST_F(CommandLineInterfaceTest, ParseSpaceDelimitedValue) {
1374 // Test that separating the flag value with a space works.
1375
1376 CreateTempFile("foo.proto",
1377 "syntax = \"proto2\";\n"
1378 "message Foo {}\n");
1379
1380 Run("protocol_compiler --test_out $tmpdir "
1381 "--proto_path=$tmpdir foo.proto");
1382
1383 ExpectNoErrors();
1384 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
1385 }
1386
TEST_F(CommandLineInterfaceTest,ParseSingleCharacterSpaceDelimitedValue)1387 TEST_F(CommandLineInterfaceTest, ParseSingleCharacterSpaceDelimitedValue) {
1388 // Test that separating the flag value with a space works for
1389 // single-character flags.
1390
1391 CreateTempFile("foo.proto",
1392 "syntax = \"proto2\";\n"
1393 "message Foo {}\n");
1394
1395 Run("protocol_compiler -t $tmpdir "
1396 "--proto_path=$tmpdir foo.proto");
1397
1398 ExpectNoErrors();
1399 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
1400 }
1401
TEST_F(CommandLineInterfaceTest,MissingValueError)1402 TEST_F(CommandLineInterfaceTest, MissingValueError) {
1403 // Test that we get an error if a flag is missing its value.
1404
1405 Run("protocol_compiler --test_out --proto_path=$tmpdir foo.proto");
1406
1407 ExpectErrorText("Missing value for flag: --test_out\n");
1408 }
1409
TEST_F(CommandLineInterfaceTest,MissingValueAtEndError)1410 TEST_F(CommandLineInterfaceTest, MissingValueAtEndError) {
1411 // Test that we get an error if the last argument is a flag requiring a
1412 // value.
1413
1414 Run("protocol_compiler --test_out");
1415
1416 ExpectErrorText("Missing value for flag: --test_out\n");
1417 }
1418
TEST_F(CommandLineInterfaceTest,PrintFreeFieldNumbers)1419 TEST_F(CommandLineInterfaceTest, PrintFreeFieldNumbers) {
1420 CreateTempFile(
1421 "foo.proto",
1422 "syntax = \"proto2\";\n"
1423 "package foo;\n"
1424 "message Foo {\n"
1425 " optional int32 a = 2;\n"
1426 " optional string b = 4;\n"
1427 " optional string c = 5;\n"
1428 " optional int64 d = 8;\n"
1429 " optional double e = 10;\n"
1430 "}\n");
1431 CreateTempFile(
1432 "bar.proto",
1433 "syntax = \"proto2\";\n"
1434 "message Bar {\n"
1435 " optional int32 a = 2;\n"
1436 " extensions 4 to 5;\n"
1437 " optional int64 d = 8;\n"
1438 " extensions 10;\n"
1439 "}\n");
1440 CreateTempFile(
1441 "baz.proto",
1442 "syntax = \"proto2\";\n"
1443 "message Baz {\n"
1444 " optional int32 a = 2;\n"
1445 " optional int64 d = 8;\n"
1446 " extensions 15 to max;\n" // unordered.
1447 " extensions 13;\n"
1448 " extensions 10 to 12;\n"
1449 " extensions 5;\n"
1450 " extensions 4;\n"
1451 "}\n");
1452 CreateTempFile(
1453 "quz.proto",
1454 "syntax = \"proto2\";\n"
1455 "message Quz {\n"
1456 " message Foo {}\n" // nested message
1457 " optional int32 a = 2;\n"
1458 " optional group C = 4 {\n"
1459 " optional int32 d = 5;\n"
1460 " }\n"
1461 " extensions 8 to 10;\n"
1462 " optional group E = 11 {\n"
1463 " optional int32 f = 9;\n" // explicitly reuse extension range 8-10
1464 " optional group G = 15 {\n" // nested group
1465 " message Foo {}\n" // nested message inside nested group
1466 " }\n"
1467 " }\n"
1468 "}\n");
1469
1470 Run("protocol_compiler --print_free_field_numbers --proto_path=$tmpdir "
1471 "foo.proto bar.proto baz.proto quz.proto");
1472
1473 ExpectNoErrors();
1474
1475 // TODO(jieluo): Cygwin doesn't work well if we try to capture stderr and
1476 // stdout at the same time. Need to figure out why and add this test back
1477 // for Cygwin.
1478 #if !defined(__CYGWIN__)
1479 ExpectCapturedStdout(
1480 "foo.Foo free: 1 3 6-7 9 11-INF\n"
1481 "Bar free: 1 3 6-7 9 11-INF\n"
1482 "Baz free: 1 3 6-7 9 14\n"
1483 "Quz.Foo free: 1-INF\n"
1484 "Quz.E.G.Foo free: 1-INF\n"
1485 "Quz free: 1 3 6-7 12-14 16-INF\n");
1486 #endif
1487 }
1488
1489 // ===================================================================
1490
1491 // Test for --encode and --decode. Note that it would be easier to do this
1492 // test as a shell script, but we'd like to be able to run the test on
1493 // platforms that don't have a Bourne-compatible shell available (especially
1494 // Windows/MSVC).
1495 class EncodeDecodeTest : public testing::Test {
1496 protected:
SetUp()1497 virtual void SetUp() {
1498 duped_stdin_ = dup(STDIN_FILENO);
1499 }
1500
TearDown()1501 virtual void TearDown() {
1502 dup2(duped_stdin_, STDIN_FILENO);
1503 close(duped_stdin_);
1504 }
1505
RedirectStdinFromText(const string & input)1506 void RedirectStdinFromText(const string& input) {
1507 string filename = TestTempDir() + "/test_stdin";
1508 GOOGLE_CHECK_OK(File::SetContents(filename, input, true));
1509 GOOGLE_CHECK(RedirectStdinFromFile(filename));
1510 }
1511
RedirectStdinFromFile(const string & filename)1512 bool RedirectStdinFromFile(const string& filename) {
1513 int fd = open(filename.c_str(), O_RDONLY);
1514 if (fd < 0) return false;
1515 dup2(fd, STDIN_FILENO);
1516 close(fd);
1517 return true;
1518 }
1519
1520 // Remove '\r' characters from text.
StripCR(const string & text)1521 string StripCR(const string& text) {
1522 string result;
1523
1524 for (int i = 0; i < text.size(); i++) {
1525 if (text[i] != '\r') {
1526 result.push_back(text[i]);
1527 }
1528 }
1529
1530 return result;
1531 }
1532
1533 enum Type { TEXT, BINARY };
1534 enum ReturnCode { SUCCESS, ERROR };
1535
Run(const string & command)1536 bool Run(const string& command) {
1537 vector<string> args;
1538 args.push_back("protoc");
1539 SplitStringUsing(command, " ", &args);
1540 args.push_back("--proto_path=" + TestSourceDir());
1541
1542 scoped_array<const char*> argv(new const char* [args.size()]);
1543 for (int i = 0; i < args.size(); i++) {
1544 argv[i] = args[i].c_str();
1545 }
1546
1547 CommandLineInterface cli;
1548 cli.SetInputsAreProtoPathRelative(true);
1549
1550 CaptureTestStdout();
1551 CaptureTestStderr();
1552
1553 int result = cli.Run(args.size(), argv.get());
1554
1555 captured_stdout_ = GetCapturedTestStdout();
1556 captured_stderr_ = GetCapturedTestStderr();
1557
1558 return result == 0;
1559 }
1560
ExpectStdoutMatchesBinaryFile(const string & filename)1561 void ExpectStdoutMatchesBinaryFile(const string& filename) {
1562 string expected_output;
1563 GOOGLE_CHECK_OK(File::GetContents(filename, &expected_output, true));
1564
1565 // Don't use EXPECT_EQ because we don't want to print raw binary data to
1566 // stdout on failure.
1567 EXPECT_TRUE(captured_stdout_ == expected_output);
1568 }
1569
ExpectStdoutMatchesTextFile(const string & filename)1570 void ExpectStdoutMatchesTextFile(const string& filename) {
1571 string expected_output;
1572 GOOGLE_CHECK_OK(File::GetContents(filename, &expected_output, true));
1573
1574 ExpectStdoutMatchesText(expected_output);
1575 }
1576
ExpectStdoutMatchesText(const string & expected_text)1577 void ExpectStdoutMatchesText(const string& expected_text) {
1578 EXPECT_EQ(StripCR(expected_text), StripCR(captured_stdout_));
1579 }
1580
ExpectStderrMatchesText(const string & expected_text)1581 void ExpectStderrMatchesText(const string& expected_text) {
1582 EXPECT_EQ(StripCR(expected_text), StripCR(captured_stderr_));
1583 }
1584
1585 private:
1586 int duped_stdin_;
1587 string captured_stdout_;
1588 string captured_stderr_;
1589 };
1590
TEST_F(EncodeDecodeTest,Encode)1591 TEST_F(EncodeDecodeTest, Encode) {
1592 RedirectStdinFromFile(TestSourceDir() + "/google/protobuf/"
1593 "testdata/text_format_unittest_data_oneof_implemented.txt");
1594 EXPECT_TRUE(Run("google/protobuf/unittest.proto "
1595 "--encode=protobuf_unittest.TestAllTypes"));
1596 ExpectStdoutMatchesBinaryFile(TestSourceDir() +
1597 "/google/protobuf/testdata/golden_message_oneof_implemented");
1598 ExpectStderrMatchesText("");
1599 }
1600
TEST_F(EncodeDecodeTest,Decode)1601 TEST_F(EncodeDecodeTest, Decode) {
1602 RedirectStdinFromFile(TestSourceDir() +
1603 "/google/protobuf/testdata/golden_message_oneof_implemented");
1604 EXPECT_TRUE(Run("google/protobuf/unittest.proto "
1605 "--decode=protobuf_unittest.TestAllTypes"));
1606 ExpectStdoutMatchesTextFile(TestSourceDir() +
1607 "/google/protobuf/"
1608 "testdata/text_format_unittest_data_oneof_implemented.txt");
1609 ExpectStderrMatchesText("");
1610 }
1611
TEST_F(EncodeDecodeTest,Partial)1612 TEST_F(EncodeDecodeTest, Partial) {
1613 RedirectStdinFromText("");
1614 EXPECT_TRUE(Run("google/protobuf/unittest.proto "
1615 "--encode=protobuf_unittest.TestRequired"));
1616 ExpectStdoutMatchesText("");
1617 ExpectStderrMatchesText(
1618 "warning: Input message is missing required fields: a, b, c\n");
1619 }
1620
TEST_F(EncodeDecodeTest,DecodeRaw)1621 TEST_F(EncodeDecodeTest, DecodeRaw) {
1622 protobuf_unittest::TestAllTypes message;
1623 message.set_optional_int32(123);
1624 message.set_optional_string("foo");
1625 string data;
1626 message.SerializeToString(&data);
1627
1628 RedirectStdinFromText(data);
1629 EXPECT_TRUE(Run("--decode_raw"));
1630 ExpectStdoutMatchesText("1: 123\n"
1631 "14: \"foo\"\n");
1632 ExpectStderrMatchesText("");
1633 }
1634
TEST_F(EncodeDecodeTest,UnknownType)1635 TEST_F(EncodeDecodeTest, UnknownType) {
1636 EXPECT_FALSE(Run("google/protobuf/unittest.proto "
1637 "--encode=NoSuchType"));
1638 ExpectStdoutMatchesText("");
1639 ExpectStderrMatchesText("Type not defined: NoSuchType\n");
1640 }
1641
TEST_F(EncodeDecodeTest,ProtoParseError)1642 TEST_F(EncodeDecodeTest, ProtoParseError) {
1643 EXPECT_FALSE(Run("google/protobuf/no_such_file.proto "
1644 "--encode=NoSuchType"));
1645 ExpectStdoutMatchesText("");
1646 ExpectStderrMatchesText(
1647 "google/protobuf/no_such_file.proto: File not found.\n");
1648 }
1649
1650 } // anonymous namespace
1651
1652 } // namespace compiler
1653 } // namespace protobuf
1654 } // namespace google
1655