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