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