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