Home | History | Annotate | Download | only in compiler
      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 (at) 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.
     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 
    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:
    184   NullCodeGenerator() : called_(false) {}
    185   ~NullCodeGenerator() {}
    186 
    187   mutable bool called_;
    188   mutable string parameter_;
    189 
    190   // implements CodeGenerator ----------------------------------------
    191   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 
    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 
    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 
    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 
    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 
    321 void CommandLineInterfaceTest::CreateTempDir(const string& name) {
    322   File::RecursivelyCreateDir(temp_directory_ + "/" + name, 0777);
    323 }
    324 
    325 // -------------------------------------------------------------------
    326 
    327 void CommandLineInterfaceTest::ExpectNoErrors() {
    328   EXPECT_EQ(0, return_code_);
    329   EXPECT_EQ("", error_text_);
    330 }
    331 
    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 
    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 
    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 
    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 
    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 
    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 
    381 void CommandLineInterfaceTest::ExpectNullCodeGeneratorCalled(
    382     const string& parameter) {
    383   EXPECT_TRUE(null_generator_->called_);
    384   EXPECT_EQ(parameter, null_generator_->parameter_);
    385 }
    386 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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:
   1245   virtual void SetUp() {
   1246     duped_stdin_ = dup(STDIN_FILENO);
   1247   }
   1248 
   1249   virtual void TearDown() {
   1250     dup2(duped_stdin_, STDIN_FILENO);
   1251     close(duped_stdin_);
   1252   }
   1253 
   1254   void RedirectStdinFromText(const string& input) {
   1255     string filename = TestTempDir() + "/test_stdin";
   1256     File::WriteStringToFileOrDie(input, filename);
   1257     GOOGLE_CHECK(RedirectStdinFromFile(filename));
   1258   }
   1259 
   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.
   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 
   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 
   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 
   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 
   1325   void ExpectStdoutMatchesText(const string& expected_text) {
   1326     EXPECT_EQ(StripCR(expected_text), StripCR(captured_stdout_));
   1327   }
   1328 
   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 
   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 
   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 
   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 
   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 
   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 
   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