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