blob: 6634b6767a6db16b09510bcd6e6c5117f88eb462 [file] [log] [blame]
temporal40ee5512008-07-10 02:12:20 +00001// Protocol Buffers - Google's data interchange format
kenton@google.com24bf56f2008-09-24 20:31:01 +00002// Copyright 2008 Google Inc. All rights reserved.
temporal40ee5512008-07-10 02:12:20 +00003// http://code.google.com/p/protobuf/
4//
kenton@google.com24bf56f2008-09-24 20:31:01 +00005// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
temporal40ee5512008-07-10 02:12:20 +00008//
kenton@google.com24bf56f2008-09-24 20:31:01 +00009// * 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.
temporal40ee5512008-07-10 02:12:20 +000018//
kenton@google.com24bf56f2008-09-24 20:31:01 +000019// 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.
temporal40ee5512008-07-10 02:12:20 +000030
31// Author: kenton@google.com (Kenton Varda)
32// Based on original Protocol Buffers design by
33// Sanjay Ghemawat, Jeff Dean, and others.
34
temporal779f61c2008-08-13 03:15:00 +000035#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
temporal40ee5512008-07-10 02:12:20 +000043#include <vector>
44
temporal779f61c2008-08-13 03:15:00 +000045#include <google/protobuf/descriptor.pb.h>
temporal40ee5512008-07-10 02:12:20 +000046#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>
kenton@google.comfccb1462009-12-18 02:11:36 +000050#include <google/protobuf/compiler/mock_code_generator.h>
temporal40ee5512008-07-10 02:12:20 +000051#include <google/protobuf/io/printer.h>
temporal779f61c2008-08-13 03:15:00 +000052#include <google/protobuf/unittest.pb.h>
temporal40ee5512008-07-10 02:12:20 +000053#include <google/protobuf/testing/file.h>
54#include <google/protobuf/stubs/strutil.h>
kenton@google.comfccb1462009-12-18 02:11:36 +000055#include <google/protobuf/stubs/substitute.h>
temporal40ee5512008-07-10 02:12:20 +000056
57#include <google/protobuf/testing/googletest.h>
58#include <gtest/gtest.h>
59
60namespace google {
61namespace protobuf {
62namespace compiler {
63
temporal779f61c2008-08-13 03:15:00 +000064#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
kenton@google.comc0ee4d22009-12-22 02:05:33 +000071#ifndef F_OK
72#define F_OK 00 // not defined by MSVC for whatever reason
73#endif
temporal779f61c2008-08-13 03:15:00 +000074#endif
75
temporal40ee5512008-07-10 02:12:20 +000076namespace {
77
78class CommandLineInterfaceTest : public testing::Test {
79 protected:
80 virtual void SetUp();
81 virtual void TearDown();
82
83 // Runs the CommandLineInterface with the given command line. The
84 // command is automatically split on spaces, and the string "$tmpdir"
85 // is replaced with TestTempDir().
86 void Run(const string& command);
87
88 // -----------------------------------------------------------------
89 // Methods to set up the test (called before Run()).
90
temporalcc930432008-07-21 20:28:30 +000091 class NullCodeGenerator;
temporal40ee5512008-07-10 02:12:20 +000092
kenton@google.comfccb1462009-12-18 02:11:36 +000093 // Normally plugins are allowed for all tests. Call this to explicitly
94 // disable them.
95 void DisallowPlugins() { disallow_plugins_ = true; }
temporalcc930432008-07-21 20:28:30 +000096
temporal40ee5512008-07-10 02:12:20 +000097 // 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
kenton@google.comfccb1462009-12-18 02:11:36 +0000101 // Create a subdirectory within temp_directory_.
102 void CreateTempDir(const string& name);
103
temporal40ee5512008-07-10 02:12:20 +0000104 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
kenton@google.comfccb1462009-12-18 02:11:36 +0000129 // 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
temporal40ee5512008-07-10 02:12:20 +0000131 // 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,
kenton@google.comfccb1462009-12-18 02:11:36 +0000140 const string& message_name);
141 void ExpectGenerated(const string& generator_name,
142 const string& parameter,
143 const string& proto_name,
temporal40ee5512008-07-10 02:12:20 +0000144 const string& message_name,
kenton@google.comfccb1462009-12-18 02:11:36 +0000145 const string& output_directory);
liujisi@google.com33165fe2010-11-02 13:14:58 +0000146 void ExpectGeneratedWithMultipleInputs(const string& generator_name,
147 const string& all_proto_names,
148 const string& proto_name,
149 const string& message_name);
kenton@google.comfccb1462009-12-18 02:11:36 +0000150 void ExpectGeneratedWithInsertions(const string& generator_name,
151 const string& parameter,
152 const string& insertions,
153 const string& proto_name,
154 const string& message_name);
temporal40ee5512008-07-10 02:12:20 +0000155
kenton@google.com91218af2009-12-18 07:20:43 +0000156 void ExpectNullCodeGeneratorCalled(const string& parameter);
157
temporal779f61c2008-08-13 03:15:00 +0000158 void ReadDescriptorSet(const string& filename,
159 FileDescriptorSet* descriptor_set);
160
temporal40ee5512008-07-10 02:12:20 +0000161 private:
162 // The object we are testing.
163 CommandLineInterface cli_;
164
kenton@google.comfccb1462009-12-18 02:11:36 +0000165 // Was DisallowPlugins() called?
166 bool disallow_plugins_;
167
temporal40ee5512008-07-10 02:12:20 +0000168 // We create a directory within TestTempDir() in order to add extra
169 // protection against accidentally deleting user files (since we recursively
170 // delete this directory during the test). This is the full path of that
171 // directory.
172 string temp_directory_;
173
174 // The result of Run().
175 int return_code_;
176
177 // The captured stderr output.
178 string error_text_;
179
180 // Pointers which need to be deleted later.
temporalcc930432008-07-21 20:28:30 +0000181 vector<CodeGenerator*> mock_generators_to_delete_;
kenton@google.com91218af2009-12-18 07:20:43 +0000182
183 NullCodeGenerator* null_generator_;
temporal40ee5512008-07-10 02:12:20 +0000184};
185
temporalcc930432008-07-21 20:28:30 +0000186class CommandLineInterfaceTest::NullCodeGenerator : public CodeGenerator {
187 public:
188 NullCodeGenerator() : called_(false) {}
189 ~NullCodeGenerator() {}
190
191 mutable bool called_;
192 mutable string parameter_;
193
194 // implements CodeGenerator ----------------------------------------
195 bool Generate(const FileDescriptor* file,
196 const string& parameter,
liujisi@google.com33165fe2010-11-02 13:14:58 +0000197 GeneratorContext* context,
temporalcc930432008-07-21 20:28:30 +0000198 string* error) const {
199 called_ = true;
200 parameter_ = parameter;
201 return true;
202 }
203};
204
temporal40ee5512008-07-10 02:12:20 +0000205// ===================================================================
206
207void CommandLineInterfaceTest::SetUp() {
208 // Most of these tests were written before this option was added, so we
209 // run with the option on (which used to be the only way) except in certain
210 // tests where we turn it off.
211 cli_.SetInputsAreProtoPathRelative(true);
212
213 temp_directory_ = TestTempDir() + "/proto2_cli_test_temp";
214
215 // If the temp directory already exists, it must be left over from a
216 // previous run. Delete it.
217 if (File::Exists(temp_directory_)) {
218 File::DeleteRecursively(temp_directory_, NULL, NULL);
219 }
220
221 // Create the temp directory.
222 GOOGLE_CHECK(File::CreateDir(temp_directory_.c_str(), DEFAULT_FILE_MODE));
kenton@google.comfccb1462009-12-18 02:11:36 +0000223
224 // Register generators.
225 CodeGenerator* generator = new MockCodeGenerator("test_generator");
226 mock_generators_to_delete_.push_back(generator);
227 cli_.RegisterGenerator("--test_out", generator, "Test output.");
228 cli_.RegisterGenerator("-t", generator, "Test output.");
229
230 generator = new MockCodeGenerator("alt_generator");
231 mock_generators_to_delete_.push_back(generator);
232 cli_.RegisterGenerator("--alt_out", generator, "Alt output.");
233
kenton@google.com91218af2009-12-18 07:20:43 +0000234 generator = null_generator_ = new NullCodeGenerator();
kenton@google.comfccb1462009-12-18 02:11:36 +0000235 mock_generators_to_delete_.push_back(generator);
236 cli_.RegisterGenerator("--null_out", generator, "Null output.");
237
238 disallow_plugins_ = false;
temporal40ee5512008-07-10 02:12:20 +0000239}
240
241void CommandLineInterfaceTest::TearDown() {
242 // Delete the temp directory.
243 File::DeleteRecursively(temp_directory_, NULL, NULL);
244
245 // Delete all the MockCodeGenerators.
246 for (int i = 0; i < mock_generators_to_delete_.size(); i++) {
247 delete mock_generators_to_delete_[i];
248 }
249 mock_generators_to_delete_.clear();
250}
251
252void CommandLineInterfaceTest::Run(const string& command) {
253 vector<string> args;
254 SplitStringUsing(command, " ", &args);
255
kenton@google.comfccb1462009-12-18 02:11:36 +0000256 if (!disallow_plugins_) {
257 cli_.AllowPlugins("prefix-");
kenton@google.comc0ee4d22009-12-22 02:05:33 +0000258 const char* possible_paths[] = {
259 // When building with shared libraries, libtool hides the real executable
260 // in .libs and puts a fake wrapper in the current directory.
261 // Unfortunately, due to an apparent bug on Cygwin/MinGW, if one program
262 // wrapped in this way (e.g. protobuf-tests.exe) tries to execute another
263 // program wrapped in this way (e.g. test_plugin.exe), the latter fails
264 // with error code 127 and no explanation message. Presumably the problem
265 // is that the wrapper for protobuf-tests.exe set some environment
266 // variables that confuse the wrapper for test_plugin.exe. Luckily, it
267 // turns out that if we simply invoke the wrapped test_plugin.exe
268 // directly, it works -- I guess the environment variables set by the
269 // protobuf-tests.exe wrapper happen to be correct for it too. So we do
270 // that.
271 ".libs/test_plugin.exe", // Win32 w/autotool (Cygwin / MinGW)
272 "test_plugin.exe", // Other Win32 (MSVC)
273 "test_plugin", // Unix
274 };
275
276 string plugin_path;
277
278 for (int i = 0; i < GOOGLE_ARRAYSIZE(possible_paths); i++) {
279 if (access(possible_paths[i], F_OK) == 0) {
280 plugin_path = possible_paths[i];
281 break;
282 }
283 }
284
285 if (plugin_path.empty()) {
286 GOOGLE_LOG(ERROR)
287 << "Plugin executable not found. Plugin tests are likely to fail.";
288 } else {
289 args.push_back("--plugin=prefix-gen-plug=" + plugin_path);
290 }
kenton@google.comfccb1462009-12-18 02:11:36 +0000291 }
292
temporal40ee5512008-07-10 02:12:20 +0000293 scoped_array<const char*> argv(new const char*[args.size()]);
294
295 for (int i = 0; i < args.size(); i++) {
296 args[i] = StringReplace(args[i], "$tmpdir", temp_directory_, true);
297 argv[i] = args[i].c_str();
298 }
299
300 CaptureTestStderr();
301
302 return_code_ = cli_.Run(args.size(), argv.get());
303
304 error_text_ = GetCapturedTestStderr();
305}
306
307// -------------------------------------------------------------------
308
temporal40ee5512008-07-10 02:12:20 +0000309void CommandLineInterfaceTest::CreateTempFile(
310 const string& name,
311 const string& contents) {
312 // Create parent directory, if necessary.
313 string::size_type slash_pos = name.find_last_of('/');
314 if (slash_pos != string::npos) {
315 string dir = name.substr(0, slash_pos);
316 File::RecursivelyCreateDir(temp_directory_ + "/" + dir, 0777);
317 }
318
319 // Write file.
320 string full_name = temp_directory_ + "/" + name;
321 File::WriteStringToFileOrDie(contents, full_name);
322}
323
kenton@google.comfccb1462009-12-18 02:11:36 +0000324void CommandLineInterfaceTest::CreateTempDir(const string& name) {
325 File::RecursivelyCreateDir(temp_directory_ + "/" + name, 0777);
326}
327
temporal40ee5512008-07-10 02:12:20 +0000328// -------------------------------------------------------------------
329
330void CommandLineInterfaceTest::ExpectNoErrors() {
331 EXPECT_EQ(0, return_code_);
332 EXPECT_EQ("", error_text_);
333}
334
335void CommandLineInterfaceTest::ExpectErrorText(const string& expected_text) {
336 EXPECT_NE(0, return_code_);
337 EXPECT_EQ(StringReplace(expected_text, "$tmpdir", temp_directory_, true),
338 error_text_);
339}
340
341void CommandLineInterfaceTest::ExpectErrorSubstring(
342 const string& expected_substring) {
343 EXPECT_NE(0, return_code_);
344 EXPECT_PRED_FORMAT2(testing::IsSubstring, expected_substring, error_text_);
345}
346
347bool CommandLineInterfaceTest::HasAlternateErrorSubstring(
348 const string& expected_substring) {
349 EXPECT_NE(0, return_code_);
350 return error_text_.find(expected_substring) != string::npos;
351}
352
353void CommandLineInterfaceTest::ExpectGenerated(
354 const string& generator_name,
355 const string& parameter,
356 const string& proto_name,
kenton@google.comfccb1462009-12-18 02:11:36 +0000357 const string& message_name) {
358 MockCodeGenerator::ExpectGenerated(
liujisi@google.com33165fe2010-11-02 13:14:58 +0000359 generator_name, parameter, "", proto_name, message_name, proto_name,
360 temp_directory_);
kenton@google.comfccb1462009-12-18 02:11:36 +0000361}
temporal40ee5512008-07-10 02:12:20 +0000362
kenton@google.comfccb1462009-12-18 02:11:36 +0000363void CommandLineInterfaceTest::ExpectGenerated(
364 const string& generator_name,
365 const string& parameter,
366 const string& proto_name,
367 const string& message_name,
368 const string& output_directory) {
369 MockCodeGenerator::ExpectGenerated(
liujisi@google.com33165fe2010-11-02 13:14:58 +0000370 generator_name, parameter, "", proto_name, message_name, proto_name,
kenton@google.comfccb1462009-12-18 02:11:36 +0000371 temp_directory_ + "/" + output_directory);
372}
373
liujisi@google.com33165fe2010-11-02 13:14:58 +0000374void CommandLineInterfaceTest::ExpectGeneratedWithMultipleInputs(
375 const string& generator_name,
376 const string& all_proto_names,
377 const string& proto_name,
378 const string& message_name) {
379 MockCodeGenerator::ExpectGenerated(
380 generator_name, "", "", proto_name, message_name,
381 all_proto_names,
382 temp_directory_);
383}
384
kenton@google.comfccb1462009-12-18 02:11:36 +0000385void CommandLineInterfaceTest::ExpectGeneratedWithInsertions(
386 const string& generator_name,
387 const string& parameter,
388 const string& insertions,
389 const string& proto_name,
390 const string& message_name) {
391 MockCodeGenerator::ExpectGenerated(
392 generator_name, parameter, insertions, proto_name, message_name,
liujisi@google.com33165fe2010-11-02 13:14:58 +0000393 proto_name, temp_directory_);
temporal40ee5512008-07-10 02:12:20 +0000394}
395
kenton@google.com91218af2009-12-18 07:20:43 +0000396void CommandLineInterfaceTest::ExpectNullCodeGeneratorCalled(
397 const string& parameter) {
398 EXPECT_TRUE(null_generator_->called_);
399 EXPECT_EQ(parameter, null_generator_->parameter_);
400}
401
temporal779f61c2008-08-13 03:15:00 +0000402void CommandLineInterfaceTest::ReadDescriptorSet(
403 const string& filename, FileDescriptorSet* descriptor_set) {
404 string path = temp_directory_ + "/" + filename;
405 string file_contents;
406 if (!File::ReadFileToString(path, &file_contents)) {
407 FAIL() << "File not found: " << path;
408 }
409 if (!descriptor_set->ParseFromString(file_contents)) {
410 FAIL() << "Could not parse file contents: " << path;
411 }
412}
413
temporal40ee5512008-07-10 02:12:20 +0000414// ===================================================================
415
temporal40ee5512008-07-10 02:12:20 +0000416TEST_F(CommandLineInterfaceTest, BasicOutput) {
417 // Test that the common case works.
418
temporal40ee5512008-07-10 02:12:20 +0000419 CreateTempFile("foo.proto",
420 "syntax = \"proto2\";\n"
421 "message Foo {}\n");
422
423 Run("protocol_compiler --test_out=$tmpdir "
424 "--proto_path=$tmpdir foo.proto");
425
426 ExpectNoErrors();
kenton@google.comfccb1462009-12-18 02:11:36 +0000427 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
428}
429
430TEST_F(CommandLineInterfaceTest, BasicPlugin) {
431 // Test that basic plugins work.
432
433 CreateTempFile("foo.proto",
434 "syntax = \"proto2\";\n"
435 "message Foo {}\n");
436
437 Run("protocol_compiler --plug_out=$tmpdir "
438 "--proto_path=$tmpdir foo.proto");
439
440 ExpectNoErrors();
441 ExpectGenerated("test_plugin", "", "foo.proto", "Foo");
442}
443
444TEST_F(CommandLineInterfaceTest, GeneratorAndPlugin) {
445 // Invoke a generator and a plugin at the same time.
446
447 CreateTempFile("foo.proto",
448 "syntax = \"proto2\";\n"
449 "message Foo {}\n");
450
451 Run("protocol_compiler --test_out=$tmpdir --plug_out=$tmpdir "
452 "--proto_path=$tmpdir foo.proto");
453
454 ExpectNoErrors();
455 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
456 ExpectGenerated("test_plugin", "", "foo.proto", "Foo");
temporal40ee5512008-07-10 02:12:20 +0000457}
458
459TEST_F(CommandLineInterfaceTest, MultipleInputs) {
460 // Test parsing multiple input files.
461
temporal40ee5512008-07-10 02:12:20 +0000462 CreateTempFile("foo.proto",
463 "syntax = \"proto2\";\n"
464 "message Foo {}\n");
465 CreateTempFile("bar.proto",
466 "syntax = \"proto2\";\n"
467 "message Bar {}\n");
468
kenton@google.comfccb1462009-12-18 02:11:36 +0000469 Run("protocol_compiler --test_out=$tmpdir --plug_out=$tmpdir "
temporal40ee5512008-07-10 02:12:20 +0000470 "--proto_path=$tmpdir foo.proto bar.proto");
471
472 ExpectNoErrors();
liujisi@google.com33165fe2010-11-02 13:14:58 +0000473 ExpectGeneratedWithMultipleInputs("test_generator", "foo.proto,bar.proto",
474 "foo.proto", "Foo");
475 ExpectGeneratedWithMultipleInputs("test_generator", "foo.proto,bar.proto",
476 "bar.proto", "Bar");
477 ExpectGeneratedWithMultipleInputs("test_plugin", "foo.proto,bar.proto",
478 "foo.proto", "Foo");
479 ExpectGeneratedWithMultipleInputs("test_plugin", "foo.proto,bar.proto",
480 "bar.proto", "Bar");
481}
482
483TEST_F(CommandLineInterfaceTest, MultipleInputsWithImport) {
484 // Test parsing multiple input files with an import of a separate file.
485
486 CreateTempFile("foo.proto",
487 "syntax = \"proto2\";\n"
488 "message Foo {}\n");
489 CreateTempFile("bar.proto",
490 "syntax = \"proto2\";\n"
491 "import \"baz.proto\";\n"
492 "message Bar {\n"
493 " optional Baz a = 1;\n"
494 "}\n");
495 CreateTempFile("baz.proto",
496 "syntax = \"proto2\";\n"
497 "message Baz {}\n");
498
499 Run("protocol_compiler --test_out=$tmpdir --plug_out=$tmpdir "
500 "--proto_path=$tmpdir foo.proto bar.proto");
501
502 ExpectNoErrors();
503 ExpectGeneratedWithMultipleInputs("test_generator", "foo.proto,bar.proto",
504 "foo.proto", "Foo");
505 ExpectGeneratedWithMultipleInputs("test_generator", "foo.proto,bar.proto",
506 "bar.proto", "Bar");
507 ExpectGeneratedWithMultipleInputs("test_plugin", "foo.proto,bar.proto",
508 "foo.proto", "Foo");
509 ExpectGeneratedWithMultipleInputs("test_plugin", "foo.proto,bar.proto",
510 "bar.proto", "Bar");
temporal40ee5512008-07-10 02:12:20 +0000511}
512
513TEST_F(CommandLineInterfaceTest, CreateDirectory) {
514 // Test that when we output to a sub-directory, it is created.
515
kenton@google.comfccb1462009-12-18 02:11:36 +0000516 CreateTempFile("bar/baz/foo.proto",
temporal40ee5512008-07-10 02:12:20 +0000517 "syntax = \"proto2\";\n"
518 "message Foo {}\n");
kenton@google.comfccb1462009-12-18 02:11:36 +0000519 CreateTempDir("out");
520 CreateTempDir("plugout");
temporal40ee5512008-07-10 02:12:20 +0000521
kenton@google.comfccb1462009-12-18 02:11:36 +0000522 Run("protocol_compiler --test_out=$tmpdir/out --plug_out=$tmpdir/plugout "
523 "--proto_path=$tmpdir bar/baz/foo.proto");
temporal40ee5512008-07-10 02:12:20 +0000524
525 ExpectNoErrors();
kenton@google.comfccb1462009-12-18 02:11:36 +0000526 ExpectGenerated("test_generator", "", "bar/baz/foo.proto", "Foo", "out");
527 ExpectGenerated("test_plugin", "", "bar/baz/foo.proto", "Foo", "plugout");
temporal40ee5512008-07-10 02:12:20 +0000528}
529
530TEST_F(CommandLineInterfaceTest, GeneratorParameters) {
531 // Test that generator parameters are correctly parsed from the command line.
532
temporal40ee5512008-07-10 02:12:20 +0000533 CreateTempFile("foo.proto",
534 "syntax = \"proto2\";\n"
535 "message Foo {}\n");
536
537 Run("protocol_compiler --test_out=TestParameter:$tmpdir "
kenton@google.comfccb1462009-12-18 02:11:36 +0000538 "--plug_out=TestPluginParameter:$tmpdir "
temporal40ee5512008-07-10 02:12:20 +0000539 "--proto_path=$tmpdir foo.proto");
540
541 ExpectNoErrors();
kenton@google.comfccb1462009-12-18 02:11:36 +0000542 ExpectGenerated("test_generator", "TestParameter", "foo.proto", "Foo");
543 ExpectGenerated("test_plugin", "TestPluginParameter", "foo.proto", "Foo");
544}
545
546TEST_F(CommandLineInterfaceTest, Insert) {
547 // Test running a generator that inserts code into another's output.
548
549 CreateTempFile("foo.proto",
550 "syntax = \"proto2\";\n"
551 "message Foo {}\n");
552
553 Run("protocol_compiler "
554 "--test_out=TestParameter:$tmpdir "
555 "--plug_out=TestPluginParameter:$tmpdir "
556 "--test_out=insert=test_generator,test_plugin:$tmpdir "
557 "--plug_out=insert=test_generator,test_plugin:$tmpdir "
558 "--proto_path=$tmpdir foo.proto");
559
560 ExpectNoErrors();
561 ExpectGeneratedWithInsertions(
562 "test_generator", "TestParameter", "test_generator,test_plugin",
563 "foo.proto", "Foo");
564 ExpectGeneratedWithInsertions(
565 "test_plugin", "TestPluginParameter", "test_generator,test_plugin",
566 "foo.proto", "Foo");
temporal40ee5512008-07-10 02:12:20 +0000567}
568
temporalcc930432008-07-21 20:28:30 +0000569#if defined(_WIN32) || defined(__CYGWIN__)
570
571TEST_F(CommandLineInterfaceTest, WindowsOutputPath) {
572 // Test that the output path can be a Windows-style path.
573
temporalcc930432008-07-21 20:28:30 +0000574 CreateTempFile("foo.proto",
575 "syntax = \"proto2\";\n");
576
kenton@google.comfccb1462009-12-18 02:11:36 +0000577 Run("protocol_compiler --null_out=C:\\ "
temporalcc930432008-07-21 20:28:30 +0000578 "--proto_path=$tmpdir foo.proto");
579
580 ExpectNoErrors();
kenton@google.com91218af2009-12-18 07:20:43 +0000581 ExpectNullCodeGeneratorCalled("");
temporalcc930432008-07-21 20:28:30 +0000582}
583
584TEST_F(CommandLineInterfaceTest, WindowsOutputPathAndParameter) {
585 // Test that we can have a windows-style output path and a parameter.
586
temporalcc930432008-07-21 20:28:30 +0000587 CreateTempFile("foo.proto",
588 "syntax = \"proto2\";\n");
589
kenton@google.comfccb1462009-12-18 02:11:36 +0000590 Run("protocol_compiler --null_out=bar:C:\\ "
temporalcc930432008-07-21 20:28:30 +0000591 "--proto_path=$tmpdir foo.proto");
592
593 ExpectNoErrors();
kenton@google.com91218af2009-12-18 07:20:43 +0000594 ExpectNullCodeGeneratorCalled("bar");
temporalcc930432008-07-21 20:28:30 +0000595}
596
kenton@google.comef3730c2009-04-28 00:49:36 +0000597TEST_F(CommandLineInterfaceTest, TrailingBackslash) {
598 // Test that the directories can end in backslashes. Some users claim this
599 // doesn't work on their system.
600
kenton@google.comef3730c2009-04-28 00:49:36 +0000601 CreateTempFile("foo.proto",
602 "syntax = \"proto2\";\n"
603 "message Foo {}\n");
604
605 Run("protocol_compiler --test_out=$tmpdir\\ "
606 "--proto_path=$tmpdir\\ foo.proto");
607
608 ExpectNoErrors();
kenton@google.comfccb1462009-12-18 02:11:36 +0000609 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
kenton@google.comef3730c2009-04-28 00:49:36 +0000610}
611
temporalcc930432008-07-21 20:28:30 +0000612#endif // defined(_WIN32) || defined(__CYGWIN__)
613
temporal40ee5512008-07-10 02:12:20 +0000614TEST_F(CommandLineInterfaceTest, PathLookup) {
615 // Test that specifying multiple directories in the proto search path works.
616
temporal40ee5512008-07-10 02:12:20 +0000617 CreateTempFile("b/bar.proto",
618 "syntax = \"proto2\";\n"
619 "message Bar {}\n");
620 CreateTempFile("a/foo.proto",
621 "syntax = \"proto2\";\n"
622 "import \"bar.proto\";\n"
623 "message Foo {\n"
624 " optional Bar a = 1;\n"
625 "}\n");
626 CreateTempFile("b/foo.proto", "this should not be parsed\n");
627
628 Run("protocol_compiler --test_out=$tmpdir "
629 "--proto_path=$tmpdir/a --proto_path=$tmpdir/b foo.proto");
630
631 ExpectNoErrors();
kenton@google.comfccb1462009-12-18 02:11:36 +0000632 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
temporal40ee5512008-07-10 02:12:20 +0000633}
634
635TEST_F(CommandLineInterfaceTest, ColonDelimitedPath) {
636 // Same as PathLookup, but we provide the proto_path in a single flag.
637
temporal40ee5512008-07-10 02:12:20 +0000638 CreateTempFile("b/bar.proto",
639 "syntax = \"proto2\";\n"
640 "message Bar {}\n");
641 CreateTempFile("a/foo.proto",
642 "syntax = \"proto2\";\n"
643 "import \"bar.proto\";\n"
644 "message Foo {\n"
645 " optional Bar a = 1;\n"
646 "}\n");
647 CreateTempFile("b/foo.proto", "this should not be parsed\n");
648
649#undef PATH_SEPARATOR
650#if defined(_WIN32)
651#define PATH_SEPARATOR ";"
652#else
653#define PATH_SEPARATOR ":"
654#endif
655
656 Run("protocol_compiler --test_out=$tmpdir "
657 "--proto_path=$tmpdir/a"PATH_SEPARATOR"$tmpdir/b foo.proto");
658
659#undef PATH_SEPARATOR
660
661 ExpectNoErrors();
kenton@google.comfccb1462009-12-18 02:11:36 +0000662 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
temporal40ee5512008-07-10 02:12:20 +0000663}
664
665TEST_F(CommandLineInterfaceTest, NonRootMapping) {
666 // Test setting up a search path mapping a directory to a non-root location.
667
temporal40ee5512008-07-10 02:12:20 +0000668 CreateTempFile("foo.proto",
669 "syntax = \"proto2\";\n"
670 "message Foo {}\n");
671
672 Run("protocol_compiler --test_out=$tmpdir "
673 "--proto_path=bar=$tmpdir bar/foo.proto");
674
675 ExpectNoErrors();
kenton@google.comfccb1462009-12-18 02:11:36 +0000676 ExpectGenerated("test_generator", "", "bar/foo.proto", "Foo");
temporal40ee5512008-07-10 02:12:20 +0000677}
678
679TEST_F(CommandLineInterfaceTest, MultipleGenerators) {
680 // Test that we can have multiple generators and use both in one invocation,
681 // each with a different output directory.
682
temporal40ee5512008-07-10 02:12:20 +0000683 CreateTempFile("foo.proto",
684 "syntax = \"proto2\";\n"
685 "message Foo {}\n");
686 // Create the "a" and "b" sub-directories.
kenton@google.comfccb1462009-12-18 02:11:36 +0000687 CreateTempDir("a");
688 CreateTempDir("b");
temporal40ee5512008-07-10 02:12:20 +0000689
690 Run("protocol_compiler "
kenton@google.comfccb1462009-12-18 02:11:36 +0000691 "--test_out=$tmpdir/a "
692 "--alt_out=$tmpdir/b "
temporal40ee5512008-07-10 02:12:20 +0000693 "--proto_path=$tmpdir foo.proto");
694
695 ExpectNoErrors();
kenton@google.comfccb1462009-12-18 02:11:36 +0000696 ExpectGenerated("test_generator", "", "foo.proto", "Foo", "a");
697 ExpectGenerated("alt_generator", "", "foo.proto", "Foo", "b");
temporal40ee5512008-07-10 02:12:20 +0000698}
699
700TEST_F(CommandLineInterfaceTest, DisallowServicesNoServices) {
701 // Test that --disallow_services doesn't cause a problem when there are no
702 // services.
703
temporal40ee5512008-07-10 02:12:20 +0000704 CreateTempFile("foo.proto",
705 "syntax = \"proto2\";\n"
706 "message Foo {}\n");
707
708 Run("protocol_compiler --disallow_services --test_out=$tmpdir "
709 "--proto_path=$tmpdir foo.proto");
710
711 ExpectNoErrors();
kenton@google.comfccb1462009-12-18 02:11:36 +0000712 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
temporal40ee5512008-07-10 02:12:20 +0000713}
714
715TEST_F(CommandLineInterfaceTest, DisallowServicesHasService) {
716 // Test that --disallow_services produces an error when there are services.
717
temporal40ee5512008-07-10 02:12:20 +0000718 CreateTempFile("foo.proto",
719 "syntax = \"proto2\";\n"
720 "message Foo {}\n"
721 "service Bar {}\n");
722
723 Run("protocol_compiler --disallow_services --test_out=$tmpdir "
724 "--proto_path=$tmpdir foo.proto");
725
726 ExpectErrorSubstring("foo.proto: This file contains services");
727}
728
729TEST_F(CommandLineInterfaceTest, AllowServicesHasService) {
730 // Test that services work fine as long as --disallow_services is not used.
731
temporal40ee5512008-07-10 02:12:20 +0000732 CreateTempFile("foo.proto",
733 "syntax = \"proto2\";\n"
734 "message Foo {}\n"
735 "service Bar {}\n");
736
737 Run("protocol_compiler --test_out=$tmpdir "
738 "--proto_path=$tmpdir foo.proto");
739
740 ExpectNoErrors();
kenton@google.comfccb1462009-12-18 02:11:36 +0000741 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
temporal40ee5512008-07-10 02:12:20 +0000742}
743
744TEST_F(CommandLineInterfaceTest, CwdRelativeInputs) {
745 // Test that we can accept working-directory-relative input files.
746
747 SetInputsAreProtoPathRelative(false);
748
temporal40ee5512008-07-10 02:12:20 +0000749 CreateTempFile("foo.proto",
750 "syntax = \"proto2\";\n"
751 "message Foo {}\n");
752
753 Run("protocol_compiler --test_out=$tmpdir "
754 "--proto_path=$tmpdir $tmpdir/foo.proto");
755
756 ExpectNoErrors();
kenton@google.comfccb1462009-12-18 02:11:36 +0000757 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
temporal40ee5512008-07-10 02:12:20 +0000758}
759
temporal779f61c2008-08-13 03:15:00 +0000760TEST_F(CommandLineInterfaceTest, WriteDescriptorSet) {
761 CreateTempFile("foo.proto",
762 "syntax = \"proto2\";\n"
763 "message Foo {}\n");
764 CreateTempFile("bar.proto",
765 "syntax = \"proto2\";\n"
766 "import \"foo.proto\";\n"
767 "message Bar {\n"
768 " optional Foo foo = 1;\n"
769 "}\n");
770
771 Run("protocol_compiler --descriptor_set_out=$tmpdir/descriptor_set "
772 "--proto_path=$tmpdir bar.proto");
773
774 ExpectNoErrors();
775
776 FileDescriptorSet descriptor_set;
777 ReadDescriptorSet("descriptor_set", &descriptor_set);
778 if (HasFatalFailure()) return;
779 ASSERT_EQ(1, descriptor_set.file_size());
780 EXPECT_EQ("bar.proto", descriptor_set.file(0).name());
781}
782
783TEST_F(CommandLineInterfaceTest, WriteTransitiveDescriptorSet) {
784 CreateTempFile("foo.proto",
785 "syntax = \"proto2\";\n"
786 "message Foo {}\n");
787 CreateTempFile("bar.proto",
788 "syntax = \"proto2\";\n"
789 "import \"foo.proto\";\n"
790 "message Bar {\n"
791 " optional Foo foo = 1;\n"
792 "}\n");
793
794 Run("protocol_compiler --descriptor_set_out=$tmpdir/descriptor_set "
795 "--include_imports --proto_path=$tmpdir bar.proto");
796
797 ExpectNoErrors();
798
799 FileDescriptorSet descriptor_set;
800 ReadDescriptorSet("descriptor_set", &descriptor_set);
801 if (HasFatalFailure()) return;
802 ASSERT_EQ(2, descriptor_set.file_size());
803 if (descriptor_set.file(0).name() == "bar.proto") {
kenton@google.com7fb9ae92009-09-02 02:42:56 +0000804 std::swap(descriptor_set.mutable_file()->mutable_data()[0],
805 descriptor_set.mutable_file()->mutable_data()[1]);
temporal779f61c2008-08-13 03:15:00 +0000806 }
807 EXPECT_EQ("foo.proto", descriptor_set.file(0).name());
808 EXPECT_EQ("bar.proto", descriptor_set.file(1).name());
809}
810
temporal40ee5512008-07-10 02:12:20 +0000811// -------------------------------------------------------------------
812
813TEST_F(CommandLineInterfaceTest, ParseErrors) {
814 // Test that parse errors are reported.
815
temporal40ee5512008-07-10 02:12:20 +0000816 CreateTempFile("foo.proto",
817 "syntax = \"proto2\";\n"
818 "badsyntax\n");
819
820 Run("protocol_compiler --test_out=$tmpdir "
821 "--proto_path=$tmpdir foo.proto");
822
823 ExpectErrorText(
824 "foo.proto:2:1: Expected top-level statement (e.g. \"message\").\n");
825}
826
827TEST_F(CommandLineInterfaceTest, ParseErrorsMultipleFiles) {
828 // Test that parse errors are reported from multiple files.
829
temporal40ee5512008-07-10 02:12:20 +0000830 // We set up files such that foo.proto actually depends on bar.proto in
831 // two ways: Directly and through baz.proto. bar.proto's errors should
832 // only be reported once.
833 CreateTempFile("bar.proto",
834 "syntax = \"proto2\";\n"
835 "badsyntax\n");
836 CreateTempFile("baz.proto",
837 "syntax = \"proto2\";\n"
838 "import \"bar.proto\";\n");
839 CreateTempFile("foo.proto",
840 "syntax = \"proto2\";\n"
841 "import \"bar.proto\";\n"
842 "import \"baz.proto\";\n");
843
844 Run("protocol_compiler --test_out=$tmpdir "
845 "--proto_path=$tmpdir foo.proto");
846
847 ExpectErrorText(
848 "bar.proto:2:1: Expected top-level statement (e.g. \"message\").\n"
849 "baz.proto: Import \"bar.proto\" was not found or had errors.\n"
850 "foo.proto: Import \"bar.proto\" was not found or had errors.\n"
851 "foo.proto: Import \"baz.proto\" was not found or had errors.\n");
852}
853
854TEST_F(CommandLineInterfaceTest, InputNotFoundError) {
855 // Test what happens if the input file is not found.
856
temporal40ee5512008-07-10 02:12:20 +0000857 Run("protocol_compiler --test_out=$tmpdir "
858 "--proto_path=$tmpdir foo.proto");
859
860 ExpectErrorText(
861 "foo.proto: File not found.\n");
862}
863
864TEST_F(CommandLineInterfaceTest, CwdRelativeInputNotFoundError) {
865 // Test what happens when a working-directory-relative input file is not
866 // found.
867
868 SetInputsAreProtoPathRelative(false);
869
temporal40ee5512008-07-10 02:12:20 +0000870 Run("protocol_compiler --test_out=$tmpdir "
871 "--proto_path=$tmpdir $tmpdir/foo.proto");
872
873 ExpectErrorText(
874 "$tmpdir/foo.proto: No such file or directory\n");
875}
876
877TEST_F(CommandLineInterfaceTest, CwdRelativeInputNotMappedError) {
878 // Test what happens when a working-directory-relative input file is not
879 // mapped to a virtual path.
880
881 SetInputsAreProtoPathRelative(false);
882
temporal40ee5512008-07-10 02:12:20 +0000883 CreateTempFile("foo.proto",
884 "syntax = \"proto2\";\n"
885 "message Foo {}\n");
886
887 // Create a directory called "bar" so that we can point --proto_path at it.
888 CreateTempFile("bar/dummy", "");
889
890 Run("protocol_compiler --test_out=$tmpdir "
891 "--proto_path=$tmpdir/bar $tmpdir/foo.proto");
892
893 ExpectErrorText(
894 "$tmpdir/foo.proto: File does not reside within any path "
895 "specified using --proto_path (or -I). You must specify a "
kenton@google.com477f7992009-10-07 21:38:11 +0000896 "--proto_path which encompasses this file. Note that the "
897 "proto_path must be an exact prefix of the .proto file "
898 "names -- protoc is too dumb to figure out when two paths "
899 "(e.g. absolute and relative) are equivalent (it's harder "
900 "than you think).\n");
temporal40ee5512008-07-10 02:12:20 +0000901}
902
903TEST_F(CommandLineInterfaceTest, CwdRelativeInputNotFoundAndNotMappedError) {
904 // Check what happens if the input file is not found *and* is not mapped
905 // in the proto_path.
906
907 SetInputsAreProtoPathRelative(false);
908
temporal40ee5512008-07-10 02:12:20 +0000909 // Create a directory called "bar" so that we can point --proto_path at it.
910 CreateTempFile("bar/dummy", "");
911
912 Run("protocol_compiler --test_out=$tmpdir "
913 "--proto_path=$tmpdir/bar $tmpdir/foo.proto");
914
915 ExpectErrorText(
916 "$tmpdir/foo.proto: No such file or directory\n");
917}
918
919TEST_F(CommandLineInterfaceTest, CwdRelativeInputShadowedError) {
920 // Test what happens when a working-directory-relative input file is shadowed
921 // by another file in the virtual path.
922
923 SetInputsAreProtoPathRelative(false);
924
temporal40ee5512008-07-10 02:12:20 +0000925 CreateTempFile("foo/foo.proto",
926 "syntax = \"proto2\";\n"
927 "message Foo {}\n");
928 CreateTempFile("bar/foo.proto",
929 "syntax = \"proto2\";\n"
930 "message Bar {}\n");
931
932 Run("protocol_compiler --test_out=$tmpdir "
933 "--proto_path=$tmpdir/foo --proto_path=$tmpdir/bar "
934 "$tmpdir/bar/foo.proto");
935
936 ExpectErrorText(
937 "$tmpdir/bar/foo.proto: Input is shadowed in the --proto_path "
938 "by \"$tmpdir/foo/foo.proto\". Either use the latter "
939 "file as your input or reorder the --proto_path so that the "
940 "former file's location comes first.\n");
941}
942
943TEST_F(CommandLineInterfaceTest, ProtoPathNotFoundError) {
944 // Test what happens if the input file is not found.
945
temporal40ee5512008-07-10 02:12:20 +0000946 Run("protocol_compiler --test_out=$tmpdir "
947 "--proto_path=$tmpdir/foo foo.proto");
948
949 ExpectErrorText(
950 "$tmpdir/foo: warning: directory does not exist.\n"
951 "foo.proto: File not found.\n");
952}
953
954TEST_F(CommandLineInterfaceTest, MissingInputError) {
955 // Test that we get an error if no inputs are given.
956
temporal40ee5512008-07-10 02:12:20 +0000957 Run("protocol_compiler --test_out=$tmpdir "
958 "--proto_path=$tmpdir");
959
960 ExpectErrorText("Missing input file.\n");
961}
962
963TEST_F(CommandLineInterfaceTest, MissingOutputError) {
temporal40ee5512008-07-10 02:12:20 +0000964 CreateTempFile("foo.proto",
965 "syntax = \"proto2\";\n"
966 "message Foo {}\n");
967
968 Run("protocol_compiler --proto_path=$tmpdir foo.proto");
969
970 ExpectErrorText("Missing output directives.\n");
971}
972
973TEST_F(CommandLineInterfaceTest, OutputWriteError) {
temporal40ee5512008-07-10 02:12:20 +0000974 CreateTempFile("foo.proto",
975 "syntax = \"proto2\";\n"
976 "message Foo {}\n");
977
kenton@google.comfccb1462009-12-18 02:11:36 +0000978 string output_file =
979 MockCodeGenerator::GetOutputFileName("test_generator", "foo.proto");
980
temporal40ee5512008-07-10 02:12:20 +0000981 // Create a directory blocking our output location.
kenton@google.comfccb1462009-12-18 02:11:36 +0000982 CreateTempDir(output_file);
temporal40ee5512008-07-10 02:12:20 +0000983
984 Run("protocol_compiler --test_out=$tmpdir "
985 "--proto_path=$tmpdir foo.proto");
986
kenton@google.com5f121642009-12-23 07:03:06 +0000987 // MockCodeGenerator no longer detects an error because we actually write to
988 // an in-memory location first, then dump to disk at the end. This is no
989 // big deal.
990 // ExpectErrorSubstring("MockCodeGenerator detected write error.");
kenton@google.comfccb1462009-12-18 02:11:36 +0000991
temporal40ee5512008-07-10 02:12:20 +0000992#if defined(_WIN32) && !defined(__CYGWIN__)
993 // Windows with MSVCRT.dll produces EPERM instead of EISDIR.
kenton@google.comfccb1462009-12-18 02:11:36 +0000994 if (HasAlternateErrorSubstring(output_file + ": Permission denied")) {
temporal40ee5512008-07-10 02:12:20 +0000995 return;
996 }
997#endif
998
kenton@google.comfccb1462009-12-18 02:11:36 +0000999 ExpectErrorSubstring(output_file + ": Is a directory");
1000}
1001
1002TEST_F(CommandLineInterfaceTest, PluginOutputWriteError) {
1003 CreateTempFile("foo.proto",
1004 "syntax = \"proto2\";\n"
1005 "message Foo {}\n");
1006
1007 string output_file =
1008 MockCodeGenerator::GetOutputFileName("test_plugin", "foo.proto");
1009
1010 // Create a directory blocking our output location.
1011 CreateTempDir(output_file);
1012
1013 Run("protocol_compiler --plug_out=$tmpdir "
1014 "--proto_path=$tmpdir foo.proto");
1015
1016#if defined(_WIN32) && !defined(__CYGWIN__)
1017 // Windows with MSVCRT.dll produces EPERM instead of EISDIR.
1018 if (HasAlternateErrorSubstring(output_file + ": Permission denied")) {
1019 return;
1020 }
1021#endif
1022
1023 ExpectErrorSubstring(output_file + ": Is a directory");
temporal40ee5512008-07-10 02:12:20 +00001024}
1025
1026TEST_F(CommandLineInterfaceTest, OutputDirectoryNotFoundError) {
temporal40ee5512008-07-10 02:12:20 +00001027 CreateTempFile("foo.proto",
1028 "syntax = \"proto2\";\n"
1029 "message Foo {}\n");
1030
1031 Run("protocol_compiler --test_out=$tmpdir/nosuchdir "
1032 "--proto_path=$tmpdir foo.proto");
1033
kenton@google.comfccb1462009-12-18 02:11:36 +00001034 ExpectErrorSubstring("nosuchdir/: No such file or directory");
1035}
1036
1037TEST_F(CommandLineInterfaceTest, PluginOutputDirectoryNotFoundError) {
1038 CreateTempFile("foo.proto",
1039 "syntax = \"proto2\";\n"
1040 "message Foo {}\n");
1041
1042 Run("protocol_compiler --plug_out=$tmpdir/nosuchdir "
1043 "--proto_path=$tmpdir foo.proto");
1044
1045 ExpectErrorSubstring("nosuchdir/: No such file or directory");
temporal40ee5512008-07-10 02:12:20 +00001046}
1047
1048TEST_F(CommandLineInterfaceTest, OutputDirectoryIsFileError) {
temporal40ee5512008-07-10 02:12:20 +00001049 CreateTempFile("foo.proto",
1050 "syntax = \"proto2\";\n"
1051 "message Foo {}\n");
1052
1053 Run("protocol_compiler --test_out=$tmpdir/foo.proto "
1054 "--proto_path=$tmpdir foo.proto");
1055
1056#if defined(_WIN32) && !defined(__CYGWIN__)
1057 // Windows with MSVCRT.dll produces EINVAL instead of ENOTDIR.
1058 if (HasAlternateErrorSubstring("foo.proto/: Invalid argument")) {
1059 return;
1060 }
1061#endif
1062
1063 ExpectErrorSubstring("foo.proto/: Not a directory");
1064}
1065
1066TEST_F(CommandLineInterfaceTest, GeneratorError) {
kenton@google.comfccb1462009-12-18 02:11:36 +00001067 CreateTempFile("foo.proto",
1068 "syntax = \"proto2\";\n"
1069 "message MockCodeGenerator_Error {}\n");
1070
1071 Run("protocol_compiler --test_out=$tmpdir "
1072 "--proto_path=$tmpdir foo.proto");
1073
1074 ExpectErrorSubstring(
1075 "--test_out: foo.proto: Saw message type MockCodeGenerator_Error.");
1076}
1077
1078TEST_F(CommandLineInterfaceTest, GeneratorPluginError) {
1079 // Test a generator plugin that returns an error.
temporal40ee5512008-07-10 02:12:20 +00001080
1081 CreateTempFile("foo.proto",
1082 "syntax = \"proto2\";\n"
kenton@google.comfccb1462009-12-18 02:11:36 +00001083 "message MockCodeGenerator_Error {}\n");
temporal40ee5512008-07-10 02:12:20 +00001084
kenton@google.comfccb1462009-12-18 02:11:36 +00001085 Run("protocol_compiler --plug_out=TestParameter:$tmpdir "
temporal40ee5512008-07-10 02:12:20 +00001086 "--proto_path=$tmpdir foo.proto");
1087
kenton@google.comfccb1462009-12-18 02:11:36 +00001088 ExpectErrorSubstring(
1089 "--plug_out: foo.proto: Saw message type MockCodeGenerator_Error.");
1090}
1091
1092TEST_F(CommandLineInterfaceTest, GeneratorPluginFail) {
1093 // Test a generator plugin that exits with an error code.
1094
1095 CreateTempFile("foo.proto",
1096 "syntax = \"proto2\";\n"
1097 "message MockCodeGenerator_Exit {}\n");
1098
1099 Run("protocol_compiler --plug_out=TestParameter:$tmpdir "
1100 "--proto_path=$tmpdir foo.proto");
1101
1102 ExpectErrorSubstring("Saw message type MockCodeGenerator_Exit.");
1103 ExpectErrorSubstring(
1104 "--plug_out: prefix-gen-plug: Plugin failed with status code 123.");
1105}
1106
1107TEST_F(CommandLineInterfaceTest, GeneratorPluginCrash) {
1108 // Test a generator plugin that crashes.
1109
1110 CreateTempFile("foo.proto",
1111 "syntax = \"proto2\";\n"
1112 "message MockCodeGenerator_Abort {}\n");
1113
1114 Run("protocol_compiler --plug_out=TestParameter:$tmpdir "
1115 "--proto_path=$tmpdir foo.proto");
1116
1117 ExpectErrorSubstring("Saw message type MockCodeGenerator_Abort.");
1118
kenton@google.com684d45b2009-12-19 04:50:00 +00001119#ifdef _WIN32
1120 // Windows doesn't have signals. It looks like abort()ing causes the process
1121 // to exit with status code 3, but let's not depend on the exact number here.
1122 ExpectErrorSubstring(
1123 "--plug_out: prefix-gen-plug: Plugin failed with status code");
1124#else
kenton@google.comfccb1462009-12-18 02:11:36 +00001125 // Don't depend on the exact signal number.
1126 ExpectErrorSubstring(
1127 "--plug_out: prefix-gen-plug: Plugin killed by signal");
kenton@google.com684d45b2009-12-19 04:50:00 +00001128#endif
kenton@google.comfccb1462009-12-18 02:11:36 +00001129}
1130
1131TEST_F(CommandLineInterfaceTest, GeneratorPluginNotFound) {
1132 // Test what happens if the plugin isn't found.
1133
1134 CreateTempFile("error.proto",
1135 "syntax = \"proto2\";\n"
1136 "message Foo {}\n");
1137
1138 Run("protocol_compiler --badplug_out=TestParameter:$tmpdir "
1139 "--plugin=prefix-gen-badplug=no_such_file "
1140 "--proto_path=$tmpdir error.proto");
1141
kenton@google.com684d45b2009-12-19 04:50:00 +00001142#ifdef _WIN32
1143 ExpectErrorSubstring(
1144 "--badplug_out: prefix-gen-badplug: The system cannot find the file "
1145 "specified.");
1146#else
1147 // Error written to stdout by child process after exec() fails.
kenton@google.comfccb1462009-12-18 02:11:36 +00001148 ExpectErrorSubstring(
1149 "no_such_file: program not found or is not executable");
1150
kenton@google.com684d45b2009-12-19 04:50:00 +00001151 // Error written by parent process when child fails.
kenton@google.comfccb1462009-12-18 02:11:36 +00001152 ExpectErrorSubstring(
1153 "--badplug_out: prefix-gen-badplug: Plugin failed with status code 1.");
kenton@google.com684d45b2009-12-19 04:50:00 +00001154#endif
kenton@google.comfccb1462009-12-18 02:11:36 +00001155}
1156
1157TEST_F(CommandLineInterfaceTest, GeneratorPluginNotAllowed) {
1158 // Test what happens if plugins aren't allowed.
1159
1160 CreateTempFile("error.proto",
1161 "syntax = \"proto2\";\n"
1162 "message Foo {}\n");
1163
1164 DisallowPlugins();
1165 Run("protocol_compiler --plug_out=TestParameter:$tmpdir "
1166 "--proto_path=$tmpdir error.proto");
1167
1168 ExpectErrorSubstring("Unknown flag: --plug_out");
temporal40ee5512008-07-10 02:12:20 +00001169}
1170
1171TEST_F(CommandLineInterfaceTest, HelpText) {
temporal40ee5512008-07-10 02:12:20 +00001172 Run("test_exec_name --help");
1173
1174 ExpectErrorSubstring("Usage: test_exec_name ");
1175 ExpectErrorSubstring("--test_out=OUT_DIR");
1176 ExpectErrorSubstring("Test output.");
kenton@google.comfccb1462009-12-18 02:11:36 +00001177 ExpectErrorSubstring("--alt_out=OUT_DIR");
1178 ExpectErrorSubstring("Alt output.");
temporal40ee5512008-07-10 02:12:20 +00001179}
1180
kenton@google.comf663b162009-04-15 19:50:54 +00001181TEST_F(CommandLineInterfaceTest, GccFormatErrors) {
1182 // Test --error_format=gcc (which is the default, but we want to verify
1183 // that it can be set explicitly).
1184
kenton@google.comf663b162009-04-15 19:50:54 +00001185 CreateTempFile("foo.proto",
1186 "syntax = \"proto2\";\n"
1187 "badsyntax\n");
1188
1189 Run("protocol_compiler --test_out=$tmpdir "
1190 "--proto_path=$tmpdir --error_format=gcc foo.proto");
1191
1192 ExpectErrorText(
1193 "foo.proto:2:1: Expected top-level statement (e.g. \"message\").\n");
1194}
1195
1196TEST_F(CommandLineInterfaceTest, MsvsFormatErrors) {
1197 // Test --error_format=msvs
1198
kenton@google.comf663b162009-04-15 19:50:54 +00001199 CreateTempFile("foo.proto",
1200 "syntax = \"proto2\";\n"
1201 "badsyntax\n");
1202
1203 Run("protocol_compiler --test_out=$tmpdir "
1204 "--proto_path=$tmpdir --error_format=msvs foo.proto");
1205
1206 ExpectErrorText(
kenton@google.com6793c1a2010-04-05 21:45:45 +00001207 "$tmpdir/foo.proto(2) : error in column=1: Expected top-level statement "
kenton@google.comf663b162009-04-15 19:50:54 +00001208 "(e.g. \"message\").\n");
1209}
1210
1211TEST_F(CommandLineInterfaceTest, InvalidErrorFormat) {
1212 // Test --error_format=msvs
1213
kenton@google.comf663b162009-04-15 19:50:54 +00001214 CreateTempFile("foo.proto",
1215 "syntax = \"proto2\";\n"
1216 "badsyntax\n");
1217
1218 Run("protocol_compiler --test_out=$tmpdir "
1219 "--proto_path=$tmpdir --error_format=invalid foo.proto");
1220
1221 ExpectErrorText(
1222 "Unknown error format: invalid\n");
1223}
1224
temporal40ee5512008-07-10 02:12:20 +00001225// -------------------------------------------------------------------
1226// Flag parsing tests
1227
1228TEST_F(CommandLineInterfaceTest, ParseSingleCharacterFlag) {
1229 // Test that a single-character flag works.
1230
temporal40ee5512008-07-10 02:12:20 +00001231 CreateTempFile("foo.proto",
1232 "syntax = \"proto2\";\n"
1233 "message Foo {}\n");
1234
temporal779f61c2008-08-13 03:15:00 +00001235 Run("protocol_compiler -t$tmpdir "
temporal40ee5512008-07-10 02:12:20 +00001236 "--proto_path=$tmpdir foo.proto");
1237
1238 ExpectNoErrors();
kenton@google.comfccb1462009-12-18 02:11:36 +00001239 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
temporal40ee5512008-07-10 02:12:20 +00001240}
1241
1242TEST_F(CommandLineInterfaceTest, ParseSpaceDelimitedValue) {
1243 // Test that separating the flag value with a space works.
1244
temporal40ee5512008-07-10 02:12:20 +00001245 CreateTempFile("foo.proto",
1246 "syntax = \"proto2\";\n"
1247 "message Foo {}\n");
1248
1249 Run("protocol_compiler --test_out $tmpdir "
1250 "--proto_path=$tmpdir foo.proto");
1251
1252 ExpectNoErrors();
kenton@google.comfccb1462009-12-18 02:11:36 +00001253 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
temporal40ee5512008-07-10 02:12:20 +00001254}
1255
1256TEST_F(CommandLineInterfaceTest, ParseSingleCharacterSpaceDelimitedValue) {
1257 // Test that separating the flag value with a space works for
1258 // single-character flags.
1259
temporal40ee5512008-07-10 02:12:20 +00001260 CreateTempFile("foo.proto",
1261 "syntax = \"proto2\";\n"
1262 "message Foo {}\n");
1263
temporal779f61c2008-08-13 03:15:00 +00001264 Run("protocol_compiler -t $tmpdir "
temporal40ee5512008-07-10 02:12:20 +00001265 "--proto_path=$tmpdir foo.proto");
1266
1267 ExpectNoErrors();
kenton@google.comfccb1462009-12-18 02:11:36 +00001268 ExpectGenerated("test_generator", "", "foo.proto", "Foo");
temporal40ee5512008-07-10 02:12:20 +00001269}
1270
1271TEST_F(CommandLineInterfaceTest, MissingValueError) {
1272 // Test that we get an error if a flag is missing its value.
1273
temporal40ee5512008-07-10 02:12:20 +00001274 Run("protocol_compiler --test_out --proto_path=$tmpdir foo.proto");
1275
1276 ExpectErrorText("Missing value for flag: --test_out\n");
1277}
1278
1279TEST_F(CommandLineInterfaceTest, MissingValueAtEndError) {
1280 // Test that we get an error if the last argument is a flag requiring a
1281 // value.
1282
temporal40ee5512008-07-10 02:12:20 +00001283 Run("protocol_compiler --test_out");
1284
1285 ExpectErrorText("Missing value for flag: --test_out\n");
1286}
1287
temporal779f61c2008-08-13 03:15:00 +00001288// ===================================================================
1289
1290// Test for --encode and --decode. Note that it would be easier to do this
1291// test as a shell script, but we'd like to be able to run the test on
1292// platforms that don't have a Bourne-compatible shell available (especially
1293// Windows/MSVC).
1294class EncodeDecodeTest : public testing::Test {
1295 protected:
1296 virtual void SetUp() {
1297 duped_stdin_ = dup(STDIN_FILENO);
1298 }
1299
1300 virtual void TearDown() {
1301 dup2(duped_stdin_, STDIN_FILENO);
1302 close(duped_stdin_);
1303 }
1304
1305 void RedirectStdinFromText(const string& input) {
1306 string filename = TestTempDir() + "/test_stdin";
1307 File::WriteStringToFileOrDie(input, filename);
1308 GOOGLE_CHECK(RedirectStdinFromFile(filename));
1309 }
1310
1311 bool RedirectStdinFromFile(const string& filename) {
1312 int fd = open(filename.c_str(), O_RDONLY);
1313 if (fd < 0) return false;
1314 dup2(fd, STDIN_FILENO);
1315 close(fd);
1316 return true;
1317 }
1318
1319 // Remove '\r' characters from text.
1320 string StripCR(const string& text) {
1321 string result;
1322
1323 for (int i = 0; i < text.size(); i++) {
1324 if (text[i] != '\r') {
1325 result.push_back(text[i]);
1326 }
1327 }
1328
1329 return result;
1330 }
1331
1332 enum Type { TEXT, BINARY };
1333 enum ReturnCode { SUCCESS, ERROR };
1334
1335 bool Run(const string& command) {
1336 vector<string> args;
1337 args.push_back("protoc");
1338 SplitStringUsing(command, " ", &args);
1339 args.push_back("--proto_path=" + TestSourceDir());
1340
1341 scoped_array<const char*> argv(new const char*[args.size()]);
1342 for (int i = 0; i < args.size(); i++) {
1343 argv[i] = args[i].c_str();
1344 }
1345
1346 CommandLineInterface cli;
1347 cli.SetInputsAreProtoPathRelative(true);
1348
1349 CaptureTestStdout();
1350 CaptureTestStderr();
1351
1352 int result = cli.Run(args.size(), argv.get());
1353
1354 captured_stdout_ = GetCapturedTestStdout();
1355 captured_stderr_ = GetCapturedTestStderr();
1356
1357 return result == 0;
1358 }
1359
1360 void ExpectStdoutMatchesBinaryFile(const string& filename) {
1361 string expected_output;
1362 ASSERT_TRUE(File::ReadFileToString(filename, &expected_output));
1363
1364 // Don't use EXPECT_EQ because we don't want to print raw binary data to
1365 // stdout on failure.
1366 EXPECT_TRUE(captured_stdout_ == expected_output);
1367 }
1368
1369 void ExpectStdoutMatchesTextFile(const string& filename) {
1370 string expected_output;
1371 ASSERT_TRUE(File::ReadFileToString(filename, &expected_output));
1372
1373 ExpectStdoutMatchesText(expected_output);
1374 }
1375
1376 void ExpectStdoutMatchesText(const string& expected_text) {
1377 EXPECT_EQ(StripCR(expected_text), StripCR(captured_stdout_));
1378 }
1379
1380 void ExpectStderrMatchesText(const string& expected_text) {
1381 EXPECT_EQ(StripCR(expected_text), StripCR(captured_stderr_));
1382 }
1383
1384 private:
1385 int duped_stdin_;
1386 string captured_stdout_;
1387 string captured_stderr_;
1388};
1389
1390TEST_F(EncodeDecodeTest, Encode) {
1391 RedirectStdinFromFile(TestSourceDir() +
1392 "/google/protobuf/testdata/text_format_unittest_data.txt");
1393 EXPECT_TRUE(Run("google/protobuf/unittest.proto "
1394 "--encode=protobuf_unittest.TestAllTypes"));
1395 ExpectStdoutMatchesBinaryFile(TestSourceDir() +
1396 "/google/protobuf/testdata/golden_message");
1397 ExpectStderrMatchesText("");
1398}
1399
1400TEST_F(EncodeDecodeTest, Decode) {
1401 RedirectStdinFromFile(TestSourceDir() +
1402 "/google/protobuf/testdata/golden_message");
1403 EXPECT_TRUE(Run("google/protobuf/unittest.proto "
1404 "--decode=protobuf_unittest.TestAllTypes"));
1405 ExpectStdoutMatchesTextFile(TestSourceDir() +
1406 "/google/protobuf/testdata/text_format_unittest_data.txt");
1407 ExpectStderrMatchesText("");
1408}
1409
1410TEST_F(EncodeDecodeTest, Partial) {
1411 RedirectStdinFromText("");
1412 EXPECT_TRUE(Run("google/protobuf/unittest.proto "
1413 "--encode=protobuf_unittest.TestRequired"));
1414 ExpectStdoutMatchesText("");
1415 ExpectStderrMatchesText(
1416 "warning: Input message is missing required fields: a, b, c\n");
1417}
1418
1419TEST_F(EncodeDecodeTest, DecodeRaw) {
1420 protobuf_unittest::TestAllTypes message;
1421 message.set_optional_int32(123);
1422 message.set_optional_string("foo");
1423 string data;
1424 message.SerializeToString(&data);
1425
1426 RedirectStdinFromText(data);
1427 EXPECT_TRUE(Run("--decode_raw"));
1428 ExpectStdoutMatchesText("1: 123\n"
1429 "14: \"foo\"\n");
1430 ExpectStderrMatchesText("");
1431}
1432
1433TEST_F(EncodeDecodeTest, UnknownType) {
1434 EXPECT_FALSE(Run("google/protobuf/unittest.proto "
1435 "--encode=NoSuchType"));
1436 ExpectStdoutMatchesText("");
1437 ExpectStderrMatchesText("Type not defined: NoSuchType\n");
1438}
1439
1440TEST_F(EncodeDecodeTest, ProtoParseError) {
1441 EXPECT_FALSE(Run("google/protobuf/no_such_file.proto "
1442 "--encode=NoSuchType"));
1443 ExpectStdoutMatchesText("");
1444 ExpectStderrMatchesText(
1445 "google/protobuf/no_such_file.proto: File not found.\n");
1446}
1447
temporal40ee5512008-07-10 02:12:20 +00001448} // anonymous namespace
1449
1450} // namespace compiler
1451} // namespace protobuf
1452} // namespace google