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