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