// 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 #include #include #ifdef _MSC_VER #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include 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 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 args; SplitStringUsing(command, " ", &args); scoped_array 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 output( output_directory->Open(filename_prefix_ + "." + file->name())); io::Printer printer(output.get(), '$'); map 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") { 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 args; args.push_back("protoc"); SplitStringUsing(command, " ", &args); args.push_back("--proto_path=" + TestSourceDir()); scoped_array 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