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