command_line_interface_unittest.cc revision d0332953cda33fb4f8e24ebff9c49159b69c43d6
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