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