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