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