aboutsummaryrefslogblamecommitdiff
path: root/src/google/protobuf/compiler/command_line_interface_unittest.cc
blob: 0784e7817f531ad0ec0902bba395fbe4f0067857 (plain) (tree)
1
2
3
4
5
6
7
8
                                                      
                                                   

                                     


                                                                         
  








                                                                         
  










                                                                        




                                                







                      

                 
                                          




                                                            
                                        









                                               








                       















                                                                       
                          











                                                                         



                                                                         









































                                                                             


                                                            
















                                                                              
                                                    



































                                                                              


















                                                                          















































































                                                                             








                                                       




























































                                                                             











                                                                



























































































































                                                                               

































                                                                       

















                                                                             

                                                 











































































































































































                                                                                











































                                                                      

                                                                




                                                        


























































































































































































































































































                                                                               




















































                                                                         





                                                                      
                                           





                                                   
                                    


























                                                                           
                                           





                                                   
                                     




























                                                                           































































































































































                                                                            




                         
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// http://code.google.com/p/protobuf/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// Author: kenton@google.com (Kenton Varda)
//  Based on original Protocol Buffers design by
//  Sanjay Ghemawat, Jeff Dean, and others.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef _MSC_VER
#include <io.h>
#else
#include <unistd.h>
#endif
#include <vector>

#include <google/protobuf/descriptor.pb.h>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/io/zero_copy_stream.h>
#include <google/protobuf/compiler/command_line_interface.h>
#include <google/protobuf/compiler/code_generator.h>
#include <google/protobuf/io/printer.h>
#include <google/protobuf/unittest.pb.h>
#include <google/protobuf/testing/file.h>
#include <google/protobuf/stubs/strutil.h>

#include <google/protobuf/testing/googletest.h>
#include <gtest/gtest.h>

namespace google {
namespace protobuf {
namespace compiler {

#if defined(_WIN32)
#ifndef STDIN_FILENO
#define STDIN_FILENO 0
#endif
#ifndef STDOUT_FILENO
#define STDOUT_FILENO 1
#endif
#endif

namespace {

class CommandLineInterfaceTest : public testing::Test {
 protected:
  virtual void SetUp();
  virtual void TearDown();

  // Runs the CommandLineInterface with the given command line.  The
  // command is automatically split on spaces, and the string "$tmpdir"
  // is replaced with TestTempDir().
  void Run(const string& command);

  // -----------------------------------------------------------------
  // Methods to set up the test (called before Run()).

  class MockCodeGenerator;
  class NullCodeGenerator;

  // Registers a MockCodeGenerator with the given name.
  MockCodeGenerator* RegisterGenerator(const string& generator_name,
                                       const string& flag_name,
                                       const string& filename,
                                       const string& help_text);
  MockCodeGenerator* RegisterErrorGenerator(const string& generator_name,
                                            const string& error_text,
                                            const string& flag_name,
                                            const string& filename,
                                            const string& help_text);

  // Registers a CodeGenerator which will not actually generate anything,
  // but records the parameter passed to the generator.
  NullCodeGenerator* RegisterNullGenerator(const string& flag_name);

  // Create a temp file within temp_directory_ with the given name.
  // The containing directory is also created if necessary.
  void CreateTempFile(const string& name, const string& contents);

  void SetInputsAreProtoPathRelative(bool enable) {
    cli_.SetInputsAreProtoPathRelative(enable);
  }

  // -----------------------------------------------------------------
  // Methods to check the test results (called after Run()).

  // Checks that no text was written to stderr during Run(), and Run()
  // returned 0.
  void ExpectNoErrors();

  // Checks that Run() returned non-zero and the stderr output is exactly
  // the text given.  expected_test may contain references to "$tmpdir",
  // which will be replaced by the temporary directory path.
  void ExpectErrorText(const string& expected_text);

  // Checks that Run() returned non-zero and the stderr contains the given
  // substring.
  void ExpectErrorSubstring(const string& expected_substring);

  // Returns true if ExpectErrorSubstring(expected_substring) would pass, but
  // does not fail otherwise.
  bool HasAlternateErrorSubstring(const string& expected_substring);

  // Checks that MockCodeGenerator::Generate() was called in the given
  // context.  That is, this tests if the generator with the given name
  // was called with the given parameter and proto file and produced the
  // given output file.  This is checked by reading the output file and
  // checking that it contains the content that MockCodeGenerator would
  // generate given these inputs.  message_name is the name of the first
  // message that appeared in the proto file; this is just to make extra
  // sure that the correct file was parsed.
  void ExpectGenerated(const string& generator_name,
                       const string& parameter,
                       const string& proto_name,
                       const string& message_name,
                       const string& output_file);

  void ReadDescriptorSet(const string& filename,
                         FileDescriptorSet* descriptor_set);

 private:
  // The object we are testing.
  CommandLineInterface cli_;

  // We create a directory within TestTempDir() in order to add extra
  // protection against accidentally deleting user files (since we recursively
  // delete this directory during the test).  This is the full path of that
  // directory.
  string temp_directory_;

  // The result of Run().
  int return_code_;

  // The captured stderr output.
  string error_text_;

  // Pointers which need to be deleted later.
  vector<CodeGenerator*> mock_generators_to_delete_;
};

// A mock CodeGenerator which outputs information about the context in which
// it was called, which can then be checked.  Output is written to a filename
// constructed by concatenating the filename_prefix (given to the constructor)
// with the proto file name, separated by a '.'.
class CommandLineInterfaceTest::MockCodeGenerator : public CodeGenerator {
 public:
  // Create a MockCodeGenerator whose Generate() method returns true.
  MockCodeGenerator(const string& name, const string& filename_prefix);

  // Create a MockCodeGenerator whose Generate() method returns false
  // and sets the error string to the given string.
  MockCodeGenerator(const string& name, const string& filename_prefix,
                    const string& error);

  ~MockCodeGenerator();

  void set_expect_write_error(bool value) {
    expect_write_error_ = value;
  }

  // implements CodeGenerator ----------------------------------------
  bool Generate(const FileDescriptor* file,
                const string& parameter,
                OutputDirectory* output_directory,
                string* error) const;

 private:
  string name_;
  string filename_prefix_;
  bool return_error_;
  string error_;
  bool expect_write_error_;
};

class CommandLineInterfaceTest::NullCodeGenerator : public CodeGenerator {
 public:
  NullCodeGenerator() : called_(false) {}
  ~NullCodeGenerator() {}

  mutable bool called_;
  mutable string parameter_;

  // implements CodeGenerator ----------------------------------------
  bool Generate(const FileDescriptor* file,
                const string& parameter,
                OutputDirectory* output_directory,
                string* error) const {
    called_ = true;
    parameter_ = parameter;
    return true;
  }
};

// ===================================================================

void CommandLineInterfaceTest::SetUp() {
  // Most of these tests were written before this option was added, so we
  // run with the option on (which used to be the only way) except in certain
  // tests where we turn it off.
  cli_.SetInputsAreProtoPathRelative(true);

  temp_directory_ = TestTempDir() + "/proto2_cli_test_temp";

  // If the temp directory already exists, it must be left over from a
  // previous run.  Delete it.
  if (File::Exists(temp_directory_)) {
    File::DeleteRecursively(temp_directory_, NULL, NULL);
  }

  // Create the temp directory.
  GOOGLE_CHECK(File::CreateDir(temp_directory_.c_str(), DEFAULT_FILE_MODE));
}

void CommandLineInterfaceTest::TearDown() {
  // Delete the temp directory.
  File::DeleteRecursively(temp_directory_, NULL, NULL);

  // Delete all the MockCodeGenerators.
  for (int i = 0; i < mock_generators_to_delete_.size(); i++) {
    delete mock_generators_to_delete_[i];
  }
  mock_generators_to_delete_.clear();
}

void CommandLineInterfaceTest::Run(const string& command) {
  vector<string> args;
  SplitStringUsing(command, " ", &args);

  scoped_array<const char*> argv(new const char*[args.size()]);

  for (int i = 0; i < args.size(); i++) {
    args[i] = StringReplace(args[i], "$tmpdir", temp_directory_, true);
    argv[i] = args[i].c_str();
  }

  CaptureTestStderr();

  return_code_ = cli_.Run(args.size(), argv.get());

  error_text_ = GetCapturedTestStderr();
}

// -------------------------------------------------------------------

CommandLineInterfaceTest::MockCodeGenerator*
CommandLineInterfaceTest::RegisterGenerator(
    const string& generator_name,
    const string& flag_name,
    const string& filename,
    const string& help_text) {
  MockCodeGenerator* generator =
    new MockCodeGenerator(generator_name, filename);
  mock_generators_to_delete_.push_back(generator);

  cli_.RegisterGenerator(flag_name, generator, help_text);
  return generator;
}

CommandLineInterfaceTest::MockCodeGenerator*
CommandLineInterfaceTest::RegisterErrorGenerator(
    const string& generator_name,
    const string& error_text,
    const string& flag_name,
    const string& filename_prefix,
    const string& help_text) {
  MockCodeGenerator* generator =
    new MockCodeGenerator(generator_name, filename_prefix, error_text);
  mock_generators_to_delete_.push_back(generator);

  cli_.RegisterGenerator(flag_name, generator, help_text);
  return generator;
}

CommandLineInterfaceTest::NullCodeGenerator*
CommandLineInterfaceTest::RegisterNullGenerator(
    const string& flag_name) {
  NullCodeGenerator* generator = new NullCodeGenerator;
  mock_generators_to_delete_.push_back(generator);
  cli_.RegisterGenerator(flag_name, generator, "");
  return generator;
}

void CommandLineInterfaceTest::CreateTempFile(
    const string& name,
    const string& contents) {
  // Create parent directory, if necessary.
  string::size_type slash_pos = name.find_last_of('/');
  if (slash_pos != string::npos) {
    string dir = name.substr(0, slash_pos);
    File::RecursivelyCreateDir(temp_directory_ + "/" + dir, 0777);
  }

  // Write file.
  string full_name = temp_directory_ + "/" + name;
  File::WriteStringToFileOrDie(contents, full_name);
}

// -------------------------------------------------------------------

void CommandLineInterfaceTest::ExpectNoErrors() {
  EXPECT_EQ(0, return_code_);
  EXPECT_EQ("", error_text_);
}

void CommandLineInterfaceTest::ExpectErrorText(const string& expected_text) {
  EXPECT_NE(0, return_code_);
  EXPECT_EQ(StringReplace(expected_text, "$tmpdir", temp_directory_, true),
            error_text_);
}

void CommandLineInterfaceTest::ExpectErrorSubstring(
    const string& expected_substring) {
  EXPECT_NE(0, return_code_);
  EXPECT_PRED_FORMAT2(testing::IsSubstring, expected_substring, error_text_);
}

bool CommandLineInterfaceTest::HasAlternateErrorSubstring(
    const string& expected_substring) {
  EXPECT_NE(0, return_code_);
  return error_text_.find(expected_substring) != string::npos;
}

void CommandLineInterfaceTest::ExpectGenerated(
    const string& generator_name,
    const string& parameter,
    const string& proto_name,
    const string& message_name,
    const string& output_file_prefix) {
  // Open and read the file.
  string output_file = output_file_prefix + "." + proto_name;
  string file_contents;
  ASSERT_TRUE(File::ReadFileToString(temp_directory_ + "/" + output_file,
                                     &file_contents))
    << "Failed to open file: " + output_file;

  // Check that the contents are as we expect.
  string expected_contents =
    generator_name + ": " + parameter + ", " + proto_name + ", " +
    message_name + "\n";
  EXPECT_EQ(expected_contents, file_contents)
    << "Output file did not have expected contents: " + output_file;
}

void CommandLineInterfaceTest::ReadDescriptorSet(
    const string& filename, FileDescriptorSet* descriptor_set) {
  string path = temp_directory_ + "/" + filename;
  string file_contents;
  if (!File::ReadFileToString(path, &file_contents)) {
    FAIL() << "File not found: " << path;
  }
  if (!descriptor_set->ParseFromString(file_contents)) {
    FAIL() << "Could not parse file contents: " << path;
  }
}

// ===================================================================

CommandLineInterfaceTest::MockCodeGenerator::MockCodeGenerator(
    const string& name, const string& filename_prefix)
  : name_(name),
    filename_prefix_(filename_prefix),
    return_error_(false),
    expect_write_error_(false) {
}

CommandLineInterfaceTest::MockCodeGenerator::MockCodeGenerator(
    const string& name, const string& filename_prefix, const string& error)
  : name_(name),
    filename_prefix_(filename_prefix),
    return_error_(true),
    error_(error),
    expect_write_error_(false) {
}

CommandLineInterfaceTest::MockCodeGenerator::~MockCodeGenerator() {}

bool CommandLineInterfaceTest::MockCodeGenerator::Generate(
    const FileDescriptor* file,
    const string& parameter,
    OutputDirectory* output_directory,
    string* error) const {
  scoped_ptr<io::ZeroCopyOutputStream> output(
    output_directory->Open(filename_prefix_ + "." + file->name()));
  io::Printer printer(output.get(), '$');
  map<string, string> vars;
  vars["name"] = name_;
  vars["parameter"] = parameter;
  vars["proto_name"] = file->name();
  vars["message_name"] = file->message_type_count() > 0 ?
    file->message_type(0)->full_name().c_str() : "(none)";

  printer.Print(vars, "$name$: $parameter$, $proto_name$, $message_name$\n");

  if (expect_write_error_) {
    EXPECT_TRUE(printer.failed());
  } else {
    EXPECT_FALSE(printer.failed());
  }

  *error = error_;
  return !return_error_;
}

// ===================================================================

TEST_F(CommandLineInterfaceTest, BasicOutput) {
  // Test that the common case works.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir foo.proto");

  ExpectNoErrors();
  ExpectGenerated("test_generator", "", "foo.proto", "Foo", "output.test");
}

TEST_F(CommandLineInterfaceTest, MultipleInputs) {
  // Test parsing multiple input files.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");
  CreateTempFile("bar.proto",
    "syntax = \"proto2\";\n"
    "message Bar {}\n");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir foo.proto bar.proto");

  ExpectNoErrors();
  ExpectGenerated("test_generator", "", "foo.proto", "Foo", "output.test");
  ExpectGenerated("test_generator", "", "bar.proto", "Bar", "output.test");
}

TEST_F(CommandLineInterfaceTest, CreateDirectory) {
  // Test that when we output to a sub-directory, it is created.

  RegisterGenerator("test_generator", "--test_out",
                    "bar/baz/output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir foo.proto");

  ExpectNoErrors();
  ExpectGenerated("test_generator", "",
                  "foo.proto", "Foo", "bar/baz/output.test");
}

TEST_F(CommandLineInterfaceTest, GeneratorParameters) {
  // Test that generator parameters are correctly parsed from the command line.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  Run("protocol_compiler --test_out=TestParameter:$tmpdir "
      "--proto_path=$tmpdir foo.proto");

  ExpectNoErrors();
  ExpectGenerated("test_generator", "TestParameter",
                  "foo.proto", "Foo", "output.test");
}

#if defined(_WIN32) || defined(__CYGWIN__)

TEST_F(CommandLineInterfaceTest, WindowsOutputPath) {
  // Test that the output path can be a Windows-style path.

  NullCodeGenerator* generator = RegisterNullGenerator("--test_out");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n");

  Run("protocol_compiler --test_out=C:\\ "
      "--proto_path=$tmpdir foo.proto");

  ExpectNoErrors();
  EXPECT_TRUE(generator->called_);
  EXPECT_EQ("", generator->parameter_);
}

TEST_F(CommandLineInterfaceTest, WindowsOutputPathAndParameter) {
  // Test that we can have a windows-style output path and a parameter.

  NullCodeGenerator* generator = RegisterNullGenerator("--test_out");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n");

  Run("protocol_compiler --test_out=bar:C:\\ "
      "--proto_path=$tmpdir foo.proto");

  ExpectNoErrors();
  EXPECT_TRUE(generator->called_);
  EXPECT_EQ("bar", generator->parameter_);
}

TEST_F(CommandLineInterfaceTest, TrailingBackslash) {
  // Test that the directories can end in backslashes.  Some users claim this
  // doesn't work on their system.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  Run("protocol_compiler --test_out=$tmpdir\\ "
      "--proto_path=$tmpdir\\ foo.proto");

  ExpectNoErrors();
  ExpectGenerated("test_generator", "", "foo.proto", "Foo", "output.test");
}

#endif  // defined(_WIN32) || defined(__CYGWIN__)

TEST_F(CommandLineInterfaceTest, PathLookup) {
  // Test that specifying multiple directories in the proto search path works.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("b/bar.proto",
    "syntax = \"proto2\";\n"
    "message Bar {}\n");
  CreateTempFile("a/foo.proto",
    "syntax = \"proto2\";\n"
    "import \"bar.proto\";\n"
    "message Foo {\n"
    "  optional Bar a = 1;\n"
    "}\n");
  CreateTempFile("b/foo.proto", "this should not be parsed\n");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir/a --proto_path=$tmpdir/b foo.proto");

  ExpectNoErrors();
  ExpectGenerated("test_generator", "", "foo.proto", "Foo", "output.test");
}

TEST_F(CommandLineInterfaceTest, ColonDelimitedPath) {
  // Same as PathLookup, but we provide the proto_path in a single flag.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("b/bar.proto",
    "syntax = \"proto2\";\n"
    "message Bar {}\n");
  CreateTempFile("a/foo.proto",
    "syntax = \"proto2\";\n"
    "import \"bar.proto\";\n"
    "message Foo {\n"
    "  optional Bar a = 1;\n"
    "}\n");
  CreateTempFile("b/foo.proto", "this should not be parsed\n");

#undef PATH_SEPARATOR
#if defined(_WIN32)
#define PATH_SEPARATOR ";"
#else
#define PATH_SEPARATOR ":"
#endif

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir/a"PATH_SEPARATOR"$tmpdir/b foo.proto");

#undef PATH_SEPARATOR

  ExpectNoErrors();
  ExpectGenerated("test_generator", "", "foo.proto", "Foo", "output.test");
}

TEST_F(CommandLineInterfaceTest, NonRootMapping) {
  // Test setting up a search path mapping a directory to a non-root location.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=bar=$tmpdir bar/foo.proto");

  ExpectNoErrors();
  ExpectGenerated("test_generator", "", "bar/foo.proto", "Foo", "output.test");
}

TEST_F(CommandLineInterfaceTest, MultipleGenerators) {
  // Test that we can have multiple generators and use both in one invocation,
  // each with a different output directory.

  RegisterGenerator("test_generator_1", "--test1_out",
                    "output1.test", "Test output 1.");
  RegisterGenerator("test_generator_2", "--test2_out",
                    "output2.test", "Test output 2.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");
  // Create the "a" and "b" sub-directories.
  CreateTempFile("a/dummy", "");
  CreateTempFile("b/dummy", "");

  Run("protocol_compiler "
      "--test1_out=$tmpdir/a "
      "--test2_out=$tmpdir/b "
      "--proto_path=$tmpdir foo.proto");

  ExpectNoErrors();
  ExpectGenerated("test_generator_1", "", "foo.proto", "Foo", "a/output1.test");
  ExpectGenerated("test_generator_2", "", "foo.proto", "Foo", "b/output2.test");
}

TEST_F(CommandLineInterfaceTest, DisallowServicesNoServices) {
  // Test that --disallow_services doesn't cause a problem when there are no
  // services.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  Run("protocol_compiler --disallow_services --test_out=$tmpdir "
      "--proto_path=$tmpdir foo.proto");

  ExpectNoErrors();
  ExpectGenerated("test_generator", "", "foo.proto", "Foo", "output.test");
}

TEST_F(CommandLineInterfaceTest, DisallowServicesHasService) {
  // Test that --disallow_services produces an error when there are services.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n"
    "service Bar {}\n");

  Run("protocol_compiler --disallow_services --test_out=$tmpdir "
      "--proto_path=$tmpdir foo.proto");

  ExpectErrorSubstring("foo.proto: This file contains services");
}

TEST_F(CommandLineInterfaceTest, AllowServicesHasService) {
  // Test that services work fine as long as --disallow_services is not used.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n"
    "service Bar {}\n");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir foo.proto");

  ExpectNoErrors();
  ExpectGenerated("test_generator", "", "foo.proto", "Foo", "output.test");
}

TEST_F(CommandLineInterfaceTest, CwdRelativeInputs) {
  // Test that we can accept working-directory-relative input files.

  SetInputsAreProtoPathRelative(false);

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir $tmpdir/foo.proto");

  ExpectNoErrors();
  ExpectGenerated("test_generator", "", "foo.proto", "Foo", "output.test");
}

TEST_F(CommandLineInterfaceTest, WriteDescriptorSet) {
  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");
  CreateTempFile("bar.proto",
    "syntax = \"proto2\";\n"
    "import \"foo.proto\";\n"
    "message Bar {\n"
    "  optional Foo foo = 1;\n"
    "}\n");

  Run("protocol_compiler --descriptor_set_out=$tmpdir/descriptor_set "
      "--proto_path=$tmpdir bar.proto");

  ExpectNoErrors();

  FileDescriptorSet descriptor_set;
  ReadDescriptorSet("descriptor_set", &descriptor_set);
  if (HasFatalFailure()) return;
  ASSERT_EQ(1, descriptor_set.file_size());
  EXPECT_EQ("bar.proto", descriptor_set.file(0).name());
}

TEST_F(CommandLineInterfaceTest, WriteTransitiveDescriptorSet) {
  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");
  CreateTempFile("bar.proto",
    "syntax = \"proto2\";\n"
    "import \"foo.proto\";\n"
    "message Bar {\n"
    "  optional Foo foo = 1;\n"
    "}\n");

  Run("protocol_compiler --descriptor_set_out=$tmpdir/descriptor_set "
      "--include_imports --proto_path=$tmpdir bar.proto");

  ExpectNoErrors();

  FileDescriptorSet descriptor_set;
  ReadDescriptorSet("descriptor_set", &descriptor_set);
  if (HasFatalFailure()) return;
  ASSERT_EQ(2, descriptor_set.file_size());
  if (descriptor_set.file(0).name() == "bar.proto") {
    std::swap(descriptor_set.mutable_file()->mutable_data()[0],
              descriptor_set.mutable_file()->mutable_data()[1]);
  }
  EXPECT_EQ("foo.proto", descriptor_set.file(0).name());
  EXPECT_EQ("bar.proto", descriptor_set.file(1).name());
}

// -------------------------------------------------------------------

TEST_F(CommandLineInterfaceTest, ParseErrors) {
  // Test that parse errors are reported.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "badsyntax\n");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir foo.proto");

  ExpectErrorText(
    "foo.proto:2:1: Expected top-level statement (e.g. \"message\").\n");
}

TEST_F(CommandLineInterfaceTest, ParseErrorsMultipleFiles) {
  // Test that parse errors are reported from multiple files.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  // We set up files such that foo.proto actually depends on bar.proto in
  // two ways:  Directly and through baz.proto.  bar.proto's errors should
  // only be reported once.
  CreateTempFile("bar.proto",
    "syntax = \"proto2\";\n"
    "badsyntax\n");
  CreateTempFile("baz.proto",
    "syntax = \"proto2\";\n"
    "import \"bar.proto\";\n");
  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "import \"bar.proto\";\n"
    "import \"baz.proto\";\n");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir foo.proto");

  ExpectErrorText(
    "bar.proto:2:1: Expected top-level statement (e.g. \"message\").\n"
    "baz.proto: Import \"bar.proto\" was not found or had errors.\n"
    "foo.proto: Import \"bar.proto\" was not found or had errors.\n"
    "foo.proto: Import \"baz.proto\" was not found or had errors.\n");
}

TEST_F(CommandLineInterfaceTest, InputNotFoundError) {
  // Test what happens if the input file is not found.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir foo.proto");

  ExpectErrorText(
    "foo.proto: File not found.\n");
}

TEST_F(CommandLineInterfaceTest, CwdRelativeInputNotFoundError) {
  // Test what happens when a working-directory-relative input file is not
  // found.

  SetInputsAreProtoPathRelative(false);

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir $tmpdir/foo.proto");

  ExpectErrorText(
    "$tmpdir/foo.proto: No such file or directory\n");
}

TEST_F(CommandLineInterfaceTest, CwdRelativeInputNotMappedError) {
  // Test what happens when a working-directory-relative input file is not
  // mapped to a virtual path.

  SetInputsAreProtoPathRelative(false);

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  // Create a directory called "bar" so that we can point --proto_path at it.
  CreateTempFile("bar/dummy", "");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir/bar $tmpdir/foo.proto");

  ExpectErrorText(
    "$tmpdir/foo.proto: File does not reside within any path "
      "specified using --proto_path (or -I).  You must specify a "
      "--proto_path which encompasses this file.\n");
}

TEST_F(CommandLineInterfaceTest, CwdRelativeInputNotFoundAndNotMappedError) {
  // Check what happens if the input file is not found *and* is not mapped
  // in the proto_path.

  SetInputsAreProtoPathRelative(false);

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  // Create a directory called "bar" so that we can point --proto_path at it.
  CreateTempFile("bar/dummy", "");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir/bar $tmpdir/foo.proto");

  ExpectErrorText(
    "$tmpdir/foo.proto: No such file or directory\n");
}

TEST_F(CommandLineInterfaceTest, CwdRelativeInputShadowedError) {
  // Test what happens when a working-directory-relative input file is shadowed
  // by another file in the virtual path.

  SetInputsAreProtoPathRelative(false);

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo/foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");
  CreateTempFile("bar/foo.proto",
    "syntax = \"proto2\";\n"
    "message Bar {}\n");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir/foo --proto_path=$tmpdir/bar "
      "$tmpdir/bar/foo.proto");

  ExpectErrorText(
    "$tmpdir/bar/foo.proto: Input is shadowed in the --proto_path "
    "by \"$tmpdir/foo/foo.proto\".  Either use the latter "
    "file as your input or reorder the --proto_path so that the "
    "former file's location comes first.\n");
}

TEST_F(CommandLineInterfaceTest, ProtoPathNotFoundError) {
  // Test what happens if the input file is not found.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir/foo foo.proto");

  ExpectErrorText(
    "$tmpdir/foo: warning: directory does not exist.\n"
    "foo.proto: File not found.\n");
}

TEST_F(CommandLineInterfaceTest, MissingInputError) {
  // Test that we get an error if no inputs are given.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir");

  ExpectErrorText("Missing input file.\n");
}

TEST_F(CommandLineInterfaceTest, MissingOutputError) {
  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  Run("protocol_compiler --proto_path=$tmpdir foo.proto");

  ExpectErrorText("Missing output directives.\n");
}

TEST_F(CommandLineInterfaceTest, OutputWriteError) {
  MockCodeGenerator* generator =
    RegisterGenerator("test_generator", "--test_out",
                      "output.test", "Test output.");
  generator->set_expect_write_error(true);

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  // Create a directory blocking our output location.
  CreateTempFile("output.test.foo.proto/foo", "");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir foo.proto");

#if defined(_WIN32) && !defined(__CYGWIN__)
  // Windows with MSVCRT.dll produces EPERM instead of EISDIR.
  if (HasAlternateErrorSubstring("output.test.foo.proto: Permission denied")) {
    return;
  }
#endif

  ExpectErrorSubstring("output.test.foo.proto: Is a directory");
}

TEST_F(CommandLineInterfaceTest, OutputDirectoryNotFoundError) {
  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  Run("protocol_compiler --test_out=$tmpdir/nosuchdir "
      "--proto_path=$tmpdir foo.proto");

  ExpectErrorSubstring("nosuchdir/: "
                       "No such file or directory");
}

TEST_F(CommandLineInterfaceTest, OutputDirectoryIsFileError) {
  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  Run("protocol_compiler --test_out=$tmpdir/foo.proto "
      "--proto_path=$tmpdir foo.proto");

#if defined(_WIN32) && !defined(__CYGWIN__)
  // Windows with MSVCRT.dll produces EINVAL instead of ENOTDIR.
  if (HasAlternateErrorSubstring("foo.proto/: Invalid argument")) {
    return;
  }
#endif

  ExpectErrorSubstring("foo.proto/: Not a directory");
}

TEST_F(CommandLineInterfaceTest, GeneratorError) {
  RegisterErrorGenerator("error_generator", "Test error message.",
                         "--error_out", "output.test", "Test error output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  Run("protocol_compiler --error_out=$tmpdir "
      "--proto_path=$tmpdir foo.proto");

  ExpectErrorSubstring("--error_out: Test error message.");
}

TEST_F(CommandLineInterfaceTest, HelpText) {
  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");
  RegisterErrorGenerator("error_generator", "Test error message.",
                         "--error_out", "output.test", "Test error output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  Run("test_exec_name --help");

  ExpectErrorSubstring("Usage: test_exec_name ");
  ExpectErrorSubstring("--test_out=OUT_DIR");
  ExpectErrorSubstring("Test output.");
  ExpectErrorSubstring("--error_out=OUT_DIR");
  ExpectErrorSubstring("Test error output.");
}

TEST_F(CommandLineInterfaceTest, GccFormatErrors) {
  // Test --error_format=gcc (which is the default, but we want to verify
  // that it can be set explicitly).

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "badsyntax\n");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir --error_format=gcc foo.proto");

  ExpectErrorText(
    "foo.proto:2:1: Expected top-level statement (e.g. \"message\").\n");
}

TEST_F(CommandLineInterfaceTest, MsvsFormatErrors) {
  // Test --error_format=msvs

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "badsyntax\n");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir --error_format=msvs foo.proto");

  ExpectErrorText(
    "foo.proto(2) : error in column=1: Expected top-level statement "
      "(e.g. \"message\").\n");
}

TEST_F(CommandLineInterfaceTest, InvalidErrorFormat) {
  // Test --error_format=msvs

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "badsyntax\n");

  Run("protocol_compiler --test_out=$tmpdir "
      "--proto_path=$tmpdir --error_format=invalid foo.proto");

  ExpectErrorText(
    "Unknown error format: invalid\n");
}

// -------------------------------------------------------------------
// Flag parsing tests

TEST_F(CommandLineInterfaceTest, ParseSingleCharacterFlag) {
  // Test that a single-character flag works.

  RegisterGenerator("test_generator", "-t",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  Run("protocol_compiler -t$tmpdir "
      "--proto_path=$tmpdir foo.proto");

  ExpectNoErrors();
  ExpectGenerated("test_generator", "", "foo.proto", "Foo", "output.test");
}

TEST_F(CommandLineInterfaceTest, ParseSpaceDelimitedValue) {
  // Test that separating the flag value with a space works.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  Run("protocol_compiler --test_out $tmpdir "
      "--proto_path=$tmpdir foo.proto");

  ExpectNoErrors();
  ExpectGenerated("test_generator", "", "foo.proto", "Foo", "output.test");
}

TEST_F(CommandLineInterfaceTest, ParseSingleCharacterSpaceDelimitedValue) {
  // Test that separating the flag value with a space works for
  // single-character flags.

  RegisterGenerator("test_generator", "-t",
                    "output.test", "Test output.");

  CreateTempFile("foo.proto",
    "syntax = \"proto2\";\n"
    "message Foo {}\n");

  Run("protocol_compiler -t $tmpdir "
      "--proto_path=$tmpdir foo.proto");

  ExpectNoErrors();
  ExpectGenerated("test_generator", "", "foo.proto", "Foo", "output.test");
}

TEST_F(CommandLineInterfaceTest, MissingValueError) {
  // Test that we get an error if a flag is missing its value.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  Run("protocol_compiler --test_out --proto_path=$tmpdir foo.proto");

  ExpectErrorText("Missing value for flag: --test_out\n");
}

TEST_F(CommandLineInterfaceTest, MissingValueAtEndError) {
  // Test that we get an error if the last argument is a flag requiring a
  // value.

  RegisterGenerator("test_generator", "--test_out",
                    "output.test", "Test output.");

  Run("protocol_compiler --test_out");

  ExpectErrorText("Missing value for flag: --test_out\n");
}

// ===================================================================

// Test for --encode and --decode.  Note that it would be easier to do this
// test as a shell script, but we'd like to be able to run the test on
// platforms that don't have a Bourne-compatible shell available (especially
// Windows/MSVC).
class EncodeDecodeTest : public testing::Test {
 protected:
  virtual void SetUp() {
    duped_stdin_ = dup(STDIN_FILENO);
  }

  virtual void TearDown() {
    dup2(duped_stdin_, STDIN_FILENO);
    close(duped_stdin_);
  }

  void RedirectStdinFromText(const string& input) {
    string filename = TestTempDir() + "/test_stdin";
    File::WriteStringToFileOrDie(input, filename);
    GOOGLE_CHECK(RedirectStdinFromFile(filename));
  }

  bool RedirectStdinFromFile(const string& filename) {
    int fd = open(filename.c_str(), O_RDONLY);
    if (fd < 0) return false;
    dup2(fd, STDIN_FILENO);
    close(fd);
    return true;
  }

  // Remove '\r' characters from text.
  string StripCR(const string& text) {
    string result;

    for (int i = 0; i < text.size(); i++) {
      if (text[i] != '\r') {
        result.push_back(text[i]);
      }
    }

    return result;
  }

  enum Type { TEXT, BINARY };
  enum ReturnCode { SUCCESS, ERROR };

  bool Run(const string& command) {
    vector<string> args;
    args.push_back("protoc");
    SplitStringUsing(command, " ", &args);
    args.push_back("--proto_path=" + TestSourceDir());

    scoped_array<const char*> argv(new const char*[args.size()]);
    for (int i = 0; i < args.size(); i++) {
      argv[i] = args[i].c_str();
    }

    CommandLineInterface cli;
    cli.SetInputsAreProtoPathRelative(true);

    CaptureTestStdout();
    CaptureTestStderr();

    int result = cli.Run(args.size(), argv.get());

    captured_stdout_ = GetCapturedTestStdout();
    captured_stderr_ = GetCapturedTestStderr();

    return result == 0;
  }

  void ExpectStdoutMatchesBinaryFile(const string& filename) {
    string expected_output;
    ASSERT_TRUE(File::ReadFileToString(filename, &expected_output));

    // Don't use EXPECT_EQ because we don't want to print raw binary data to
    // stdout on failure.
    EXPECT_TRUE(captured_stdout_ == expected_output);
  }

  void ExpectStdoutMatchesTextFile(const string& filename) {
    string expected_output;
    ASSERT_TRUE(File::ReadFileToString(filename, &expected_output));

    ExpectStdoutMatchesText(expected_output);
  }

  void ExpectStdoutMatchesText(const string& expected_text) {
    EXPECT_EQ(StripCR(expected_text), StripCR(captured_stdout_));
  }

  void ExpectStderrMatchesText(const string& expected_text) {
    EXPECT_EQ(StripCR(expected_text), StripCR(captured_stderr_));
  }

 private:
  int duped_stdin_;
  string captured_stdout_;
  string captured_stderr_;
};

TEST_F(EncodeDecodeTest, Encode) {
  RedirectStdinFromFile(TestSourceDir() +
    "/google/protobuf/testdata/text_format_unittest_data.txt");
  EXPECT_TRUE(Run("google/protobuf/unittest.proto "
                  "--encode=protobuf_unittest.TestAllTypes"));
  ExpectStdoutMatchesBinaryFile(TestSourceDir() +
    "/google/protobuf/testdata/golden_message");
  ExpectStderrMatchesText("");
}

TEST_F(EncodeDecodeTest, Decode) {
  RedirectStdinFromFile(TestSourceDir() +
    "/google/protobuf/testdata/golden_message");
  EXPECT_TRUE(Run("google/protobuf/unittest.proto "
                  "--decode=protobuf_unittest.TestAllTypes"));
  ExpectStdoutMatchesTextFile(TestSourceDir() +
    "/google/protobuf/testdata/text_format_unittest_data.txt");
  ExpectStderrMatchesText("");
}

TEST_F(EncodeDecodeTest, Partial) {
  RedirectStdinFromText("");
  EXPECT_TRUE(Run("google/protobuf/unittest.proto "
                  "--encode=protobuf_unittest.TestRequired"));
  ExpectStdoutMatchesText("");
  ExpectStderrMatchesText(
    "warning:  Input message is missing required fields:  a, b, c\n");
}

TEST_F(EncodeDecodeTest, DecodeRaw) {
  protobuf_unittest::TestAllTypes message;
  message.set_optional_int32(123);
  message.set_optional_string("foo");
  string data;
  message.SerializeToString(&data);

  RedirectStdinFromText(data);
  EXPECT_TRUE(Run("--decode_raw"));
  ExpectStdoutMatchesText("1: 123\n"
                          "14: \"foo\"\n");
  ExpectStderrMatchesText("");
}

TEST_F(EncodeDecodeTest, UnknownType) {
  EXPECT_FALSE(Run("google/protobuf/unittest.proto "
                   "--encode=NoSuchType"));
  ExpectStdoutMatchesText("");
  ExpectStderrMatchesText("Type not defined: NoSuchType\n");
}

TEST_F(EncodeDecodeTest, ProtoParseError) {
  EXPECT_FALSE(Run("google/protobuf/no_such_file.proto "
                   "--encode=NoSuchType"));
  ExpectStdoutMatchesText("");
  ExpectStderrMatchesText(
    "google/protobuf/no_such_file.proto: File not found.\n");
}

}  // anonymous namespace

}  // namespace compiler
}  // namespace protobuf
}  // namespace google