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