importer_unittest.cc revision afb4b72037e3f13db208590fc782c4bc8e27f862
1// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc.  All rights reserved.
3// https://developers.google.com/protocol-buffers/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
8//
9//     * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11//     * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15//     * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31// Author: kenton@google.com (Kenton Varda)
32//  Based on original Protocol Buffers design by
33//  Sanjay Ghemawat, Jeff Dean, and others.
34
35#include <google/protobuf/stubs/hash.h>
36#include <memory>
37
38#include <google/protobuf/compiler/importer.h>
39#include <google/protobuf/descriptor.h>
40#include <google/protobuf/io/zero_copy_stream_impl.h>
41
42#include <google/protobuf/stubs/map_util.h>
43#include <google/protobuf/stubs/common.h>
44#include <google/protobuf/testing/file.h>
45#include <google/protobuf/stubs/strutil.h>
46#include <google/protobuf/stubs/substitute.h>
47#include <google/protobuf/testing/googletest.h>
48#include <gtest/gtest.h>
49
50namespace google {
51namespace protobuf {
52namespace compiler {
53
54namespace {
55
56#define EXPECT_SUBSTRING(needle, haystack) \
57  EXPECT_PRED_FORMAT2(testing::IsSubstring, (needle), (haystack))
58
59class MockErrorCollector : public MultiFileErrorCollector {
60 public:
61  MockErrorCollector() {}
62  ~MockErrorCollector() {}
63
64  string text_;
65
66  // implements ErrorCollector ---------------------------------------
67  void AddError(const string& filename, int line, int column,
68                const string& message) {
69    strings::SubstituteAndAppend(&text_, "$0:$1:$2: $3\n",
70                                 filename, line, column, message);
71  }
72};
73
74// -------------------------------------------------------------------
75
76// A dummy implementation of SourceTree backed by a simple map.
77class MockSourceTree : public SourceTree {
78 public:
79  MockSourceTree() {}
80  ~MockSourceTree() {}
81
82  void AddFile(const string& name, const char* contents) {
83    files_[name] = contents;
84  }
85
86  // implements SourceTree -------------------------------------------
87  io::ZeroCopyInputStream* Open(const string& filename) {
88    const char* contents = FindPtrOrNull(files_, filename);
89    if (contents == NULL) {
90      return NULL;
91    } else {
92      return new io::ArrayInputStream(contents, strlen(contents));
93    }
94  }
95
96  string GetLastErrorMessage() {
97    return "File not found.";
98  }
99
100 private:
101  hash_map<string, const char*> files_;
102};
103
104// ===================================================================
105
106class ImporterTest : public testing::Test {
107 protected:
108  ImporterTest()
109    : importer_(&source_tree_, &error_collector_) {}
110
111  void AddFile(const string& filename, const char* text) {
112    source_tree_.AddFile(filename, text);
113  }
114
115  // Return the collected error text
116  string error() const { return error_collector_.text_; }
117
118  MockErrorCollector error_collector_;
119  MockSourceTree source_tree_;
120  Importer importer_;
121};
122
123TEST_F(ImporterTest, Import) {
124  // Test normal importing.
125  AddFile("foo.proto",
126    "syntax = \"proto2\";\n"
127    "message Foo {}\n");
128
129  const FileDescriptor* file = importer_.Import("foo.proto");
130  EXPECT_EQ("", error_collector_.text_);
131  ASSERT_TRUE(file != NULL);
132
133  ASSERT_EQ(1, file->message_type_count());
134  EXPECT_EQ("Foo", file->message_type(0)->name());
135
136  // Importing again should return same object.
137  EXPECT_EQ(file, importer_.Import("foo.proto"));
138}
139
140TEST_F(ImporterTest, ImportNested) {
141  // Test that importing a file which imports another file works.
142  AddFile("foo.proto",
143    "syntax = \"proto2\";\n"
144    "import \"bar.proto\";\n"
145    "message Foo {\n"
146    "  optional Bar bar = 1;\n"
147    "}\n");
148  AddFile("bar.proto",
149    "syntax = \"proto2\";\n"
150    "message Bar {}\n");
151
152  // Note that both files are actually parsed by the first call to Import()
153  // here, since foo.proto imports bar.proto.  The second call just returns
154  // the same ProtoFile for bar.proto which was constructed while importing
155  // foo.proto.  We test that this is the case below by checking that bar
156  // is among foo's dependencies (by pointer).
157  const FileDescriptor* foo = importer_.Import("foo.proto");
158  const FileDescriptor* bar = importer_.Import("bar.proto");
159  EXPECT_EQ("", error_collector_.text_);
160  ASSERT_TRUE(foo != NULL);
161  ASSERT_TRUE(bar != NULL);
162
163  // Check that foo's dependency is the same object as bar.
164  ASSERT_EQ(1, foo->dependency_count());
165  EXPECT_EQ(bar, foo->dependency(0));
166
167  // Check that foo properly cross-links bar.
168  ASSERT_EQ(1, foo->message_type_count());
169  ASSERT_EQ(1, bar->message_type_count());
170  ASSERT_EQ(1, foo->message_type(0)->field_count());
171  ASSERT_EQ(FieldDescriptor::TYPE_MESSAGE,
172            foo->message_type(0)->field(0)->type());
173  EXPECT_EQ(bar->message_type(0),
174            foo->message_type(0)->field(0)->message_type());
175}
176
177TEST_F(ImporterTest, FileNotFound) {
178  // Error:  Parsing a file that doesn't exist.
179  EXPECT_TRUE(importer_.Import("foo.proto") == NULL);
180  EXPECT_EQ(
181    "foo.proto:-1:0: File not found.\n",
182    error_collector_.text_);
183}
184
185TEST_F(ImporterTest, ImportNotFound) {
186  // Error:  Importing a file that doesn't exist.
187  AddFile("foo.proto",
188    "syntax = \"proto2\";\n"
189    "import \"bar.proto\";\n");
190
191  EXPECT_TRUE(importer_.Import("foo.proto") == NULL);
192  EXPECT_EQ(
193    "bar.proto:-1:0: File not found.\n"
194    "foo.proto:-1:0: Import \"bar.proto\" was not found or had errors.\n",
195    error_collector_.text_);
196}
197
198TEST_F(ImporterTest, RecursiveImport) {
199  // Error:  Recursive import.
200  AddFile("recursive1.proto",
201    "syntax = \"proto2\";\n"
202    "import \"recursive2.proto\";\n");
203  AddFile("recursive2.proto",
204    "syntax = \"proto2\";\n"
205    "import \"recursive1.proto\";\n");
206
207  EXPECT_TRUE(importer_.Import("recursive1.proto") == NULL);
208  EXPECT_EQ(
209    "recursive1.proto:-1:0: File recursively imports itself: recursive1.proto "
210      "-> recursive2.proto -> recursive1.proto\n"
211    "recursive2.proto:-1:0: Import \"recursive1.proto\" was not found "
212      "or had errors.\n"
213    "recursive1.proto:-1:0: Import \"recursive2.proto\" was not found "
214      "or had errors.\n",
215    error_collector_.text_);
216}
217
218// TODO(sanjay): The MapField tests below more properly belong in
219// descriptor_unittest, but are more convenient to test here.
220TEST_F(ImporterTest, MapFieldValid) {
221  AddFile(
222      "map.proto",
223      "syntax = \"proto2\";\n"
224      "message Item {\n"
225      "  required string key = 1;\n"
226      "}\n"
227      "message Map {\n"
228      "  repeated Item items = 1 [experimental_map_key = \"key\"];\n"
229      "}\n"
230      );
231  const FileDescriptor* file = importer_.Import("map.proto");
232  ASSERT_TRUE(file != NULL) << error_collector_.text_;
233  EXPECT_EQ("", error_collector_.text_);
234
235  // Check that Map::items points to Item::key
236  const Descriptor* item_type = file->FindMessageTypeByName("Item");
237  ASSERT_TRUE(item_type != NULL);
238  const Descriptor* map_type = file->FindMessageTypeByName("Map");
239  ASSERT_TRUE(map_type != NULL);
240  const FieldDescriptor* key_field = item_type->FindFieldByName("key");
241  ASSERT_TRUE(key_field != NULL);
242  const FieldDescriptor* items_field = map_type->FindFieldByName("items");
243  ASSERT_TRUE(items_field != NULL);
244  EXPECT_EQ(items_field->experimental_map_key(), key_field);
245}
246
247TEST_F(ImporterTest, MapFieldNotRepeated) {
248  AddFile(
249      "map.proto",
250      "syntax = \"proto2\";\n"
251      "message Item {\n"
252      "  required string key = 1;\n"
253      "}\n"
254      "message Map {\n"
255      "  required Item items = 1 [experimental_map_key = \"key\"];\n"
256      "}\n"
257      );
258  EXPECT_TRUE(importer_.Import("map.proto") == NULL);
259  EXPECT_SUBSTRING("only allowed for repeated fields", error());
260}
261
262TEST_F(ImporterTest, MapFieldNotMessageType) {
263  AddFile(
264      "map.proto",
265      "syntax = \"proto2\";\n"
266      "message Map {\n"
267      "  repeated int32 items = 1 [experimental_map_key = \"key\"];\n"
268      "}\n"
269      );
270  EXPECT_TRUE(importer_.Import("map.proto") == NULL);
271  EXPECT_SUBSTRING("only allowed for fields with a message type", error());
272}
273
274TEST_F(ImporterTest, MapFieldTypeNotFound) {
275  AddFile(
276      "map.proto",
277      "syntax = \"proto2\";\n"
278      "message Map {\n"
279      "  repeated Unknown items = 1 [experimental_map_key = \"key\"];\n"
280      "}\n"
281      );
282  EXPECT_TRUE(importer_.Import("map.proto") == NULL);
283  EXPECT_SUBSTRING("not defined", error());
284}
285
286TEST_F(ImporterTest, MapFieldKeyNotFound) {
287  AddFile(
288      "map.proto",
289      "syntax = \"proto2\";\n"
290      "message Item {\n"
291      "  required string key = 1;\n"
292      "}\n"
293      "message Map {\n"
294      "  repeated Item items = 1 [experimental_map_key = \"badkey\"];\n"
295      "}\n"
296      );
297  EXPECT_TRUE(importer_.Import("map.proto") == NULL);
298  EXPECT_SUBSTRING("Could not find field", error());
299}
300
301TEST_F(ImporterTest, MapFieldKeyRepeated) {
302  AddFile(
303      "map.proto",
304      "syntax = \"proto2\";\n"
305      "message Item {\n"
306      "  repeated string key = 1;\n"
307      "}\n"
308      "message Map {\n"
309      "  repeated Item items = 1 [experimental_map_key = \"key\"];\n"
310      "}\n"
311      );
312  EXPECT_TRUE(importer_.Import("map.proto") == NULL);
313  EXPECT_SUBSTRING("must not name a repeated field", error());
314}
315
316TEST_F(ImporterTest, MapFieldKeyNotScalar) {
317  AddFile(
318      "map.proto",
319      "syntax = \"proto2\";\n"
320      "message ItemKey { }\n"
321      "message Item {\n"
322      "  required ItemKey key = 1;\n"
323      "}\n"
324      "message Map {\n"
325      "  repeated Item items = 1 [experimental_map_key = \"key\"];\n"
326      "}\n"
327      );
328  EXPECT_TRUE(importer_.Import("map.proto") == NULL);
329  EXPECT_SUBSTRING("must name a scalar or string", error());
330}
331
332
333// ===================================================================
334
335class DiskSourceTreeTest : public testing::Test {
336 protected:
337  virtual void SetUp() {
338    dirnames_.push_back(TestTempDir() + "/test_proto2_import_path_1");
339    dirnames_.push_back(TestTempDir() + "/test_proto2_import_path_2");
340
341    for (int i = 0; i < dirnames_.size(); i++) {
342      if (File::Exists(dirnames_[i])) {
343        File::DeleteRecursively(dirnames_[i], NULL, NULL);
344      }
345      GOOGLE_CHECK_OK(File::CreateDir(dirnames_[i], 0777));
346    }
347  }
348
349  virtual void TearDown() {
350    for (int i = 0; i < dirnames_.size(); i++) {
351      File::DeleteRecursively(dirnames_[i], NULL, NULL);
352    }
353  }
354
355  void AddFile(const string& filename, const char* contents) {
356    GOOGLE_CHECK_OK(File::SetContents(filename, contents, true));
357  }
358
359  void AddSubdir(const string& dirname) {
360    GOOGLE_CHECK_OK(File::CreateDir(dirname, 0777));
361  }
362
363  void ExpectFileContents(const string& filename,
364                          const char* expected_contents) {
365    scoped_ptr<io::ZeroCopyInputStream> input(source_tree_.Open(filename));
366
367    ASSERT_FALSE(input == NULL);
368
369    // Read all the data from the file.
370    string file_contents;
371    const void* data;
372    int size;
373    while (input->Next(&data, &size)) {
374      file_contents.append(reinterpret_cast<const char*>(data), size);
375    }
376
377    EXPECT_EQ(expected_contents, file_contents);
378  }
379
380  void ExpectCannotOpenFile(const string& filename,
381                            const string& error_message) {
382    scoped_ptr<io::ZeroCopyInputStream> input(source_tree_.Open(filename));
383    EXPECT_TRUE(input == NULL);
384    EXPECT_EQ(error_message, source_tree_.GetLastErrorMessage());
385  }
386
387  DiskSourceTree source_tree_;
388
389  // Paths of two on-disk directories to use during the test.
390  vector<string> dirnames_;
391};
392
393TEST_F(DiskSourceTreeTest, MapRoot) {
394  // Test opening a file in a directory that is mapped to the root of the
395  // source tree.
396  AddFile(dirnames_[0] + "/foo", "Hello World!");
397  source_tree_.MapPath("", dirnames_[0]);
398
399  ExpectFileContents("foo", "Hello World!");
400  ExpectCannotOpenFile("bar", "File not found.");
401}
402
403TEST_F(DiskSourceTreeTest, MapDirectory) {
404  // Test opening a file in a directory that is mapped to somewhere other
405  // than the root of the source tree.
406
407  AddFile(dirnames_[0] + "/foo", "Hello World!");
408  source_tree_.MapPath("baz", dirnames_[0]);
409
410  ExpectFileContents("baz/foo", "Hello World!");
411  ExpectCannotOpenFile("baz/bar", "File not found.");
412  ExpectCannotOpenFile("foo", "File not found.");
413  ExpectCannotOpenFile("bar", "File not found.");
414
415  // Non-canonical file names should not work.
416  ExpectCannotOpenFile("baz//foo",
417                       "Backslashes, consecutive slashes, \".\", or \"..\" are "
418                       "not allowed in the virtual path");
419  ExpectCannotOpenFile("baz/../baz/foo",
420                       "Backslashes, consecutive slashes, \".\", or \"..\" are "
421                       "not allowed in the virtual path");
422  ExpectCannotOpenFile("baz/./foo",
423                       "Backslashes, consecutive slashes, \".\", or \"..\" are "
424                       "not allowed in the virtual path");
425  ExpectCannotOpenFile("baz/foo/", "File not found.");
426}
427
428TEST_F(DiskSourceTreeTest, NoParent) {
429  // Test that we cannot open files in a parent of a mapped directory.
430
431  AddFile(dirnames_[0] + "/foo", "Hello World!");
432  AddSubdir(dirnames_[0] + "/bar");
433  AddFile(dirnames_[0] + "/bar/baz", "Blah.");
434  source_tree_.MapPath("", dirnames_[0] + "/bar");
435
436  ExpectFileContents("baz", "Blah.");
437  ExpectCannotOpenFile("../foo",
438                       "Backslashes, consecutive slashes, \".\", or \"..\" are "
439                       "not allowed in the virtual path");
440  ExpectCannotOpenFile("../bar/baz",
441                       "Backslashes, consecutive slashes, \".\", or \"..\" are "
442                       "not allowed in the virtual path");
443}
444
445TEST_F(DiskSourceTreeTest, MapFile) {
446  // Test opening a file that is mapped directly into the source tree.
447
448  AddFile(dirnames_[0] + "/foo", "Hello World!");
449  source_tree_.MapPath("foo", dirnames_[0] + "/foo");
450
451  ExpectFileContents("foo", "Hello World!");
452  ExpectCannotOpenFile("bar", "File not found.");
453}
454
455TEST_F(DiskSourceTreeTest, SearchMultipleDirectories) {
456  // Test mapping and searching multiple directories.
457
458  AddFile(dirnames_[0] + "/foo", "Hello World!");
459  AddFile(dirnames_[1] + "/foo", "This file should be hidden.");
460  AddFile(dirnames_[1] + "/bar", "Goodbye World!");
461  source_tree_.MapPath("", dirnames_[0]);
462  source_tree_.MapPath("", dirnames_[1]);
463
464  ExpectFileContents("foo", "Hello World!");
465  ExpectFileContents("bar", "Goodbye World!");
466  ExpectCannotOpenFile("baz", "File not found.");
467}
468
469TEST_F(DiskSourceTreeTest, OrderingTrumpsSpecificity) {
470  // Test that directories are always searched in order, even when a latter
471  // directory is more-specific than a former one.
472
473  // Create the "bar" directory so we can put a file in it.
474  GOOGLE_CHECK_OK(File::CreateDir(dirnames_[0] + "/bar", 0777));
475
476  // Add files and map paths.
477  AddFile(dirnames_[0] + "/bar/foo", "Hello World!");
478  AddFile(dirnames_[1] + "/foo", "This file should be hidden.");
479  source_tree_.MapPath("", dirnames_[0]);
480  source_tree_.MapPath("bar", dirnames_[1]);
481
482  // Check.
483  ExpectFileContents("bar/foo", "Hello World!");
484}
485
486TEST_F(DiskSourceTreeTest, DiskFileToVirtualFile) {
487  // Test DiskFileToVirtualFile.
488
489  AddFile(dirnames_[0] + "/foo", "Hello World!");
490  AddFile(dirnames_[1] + "/foo", "This file should be hidden.");
491  source_tree_.MapPath("bar", dirnames_[0]);
492  source_tree_.MapPath("bar", dirnames_[1]);
493
494  string virtual_file;
495  string shadowing_disk_file;
496
497  EXPECT_EQ(DiskSourceTree::NO_MAPPING,
498    source_tree_.DiskFileToVirtualFile(
499      "/foo", &virtual_file, &shadowing_disk_file));
500
501  EXPECT_EQ(DiskSourceTree::SHADOWED,
502    source_tree_.DiskFileToVirtualFile(
503      dirnames_[1] + "/foo", &virtual_file, &shadowing_disk_file));
504  EXPECT_EQ("bar/foo", virtual_file);
505  EXPECT_EQ(dirnames_[0] + "/foo", shadowing_disk_file);
506
507  EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
508    source_tree_.DiskFileToVirtualFile(
509      dirnames_[1] + "/baz", &virtual_file, &shadowing_disk_file));
510  EXPECT_EQ("bar/baz", virtual_file);
511
512  EXPECT_EQ(DiskSourceTree::SUCCESS,
513    source_tree_.DiskFileToVirtualFile(
514      dirnames_[0] + "/foo", &virtual_file, &shadowing_disk_file));
515  EXPECT_EQ("bar/foo", virtual_file);
516}
517
518TEST_F(DiskSourceTreeTest, DiskFileToVirtualFileCanonicalization) {
519  // Test handling of "..", ".", etc. in DiskFileToVirtualFile().
520
521  source_tree_.MapPath("dir1", "..");
522  source_tree_.MapPath("dir2", "../../foo");
523  source_tree_.MapPath("dir3", "./foo/bar/.");
524  source_tree_.MapPath("dir4", ".");
525  source_tree_.MapPath("", "/qux");
526  source_tree_.MapPath("dir5", "/quux/");
527
528  string virtual_file;
529  string shadowing_disk_file;
530
531  // "../.." should not be considered to be under "..".
532  EXPECT_EQ(DiskSourceTree::NO_MAPPING,
533    source_tree_.DiskFileToVirtualFile(
534      "../../baz", &virtual_file, &shadowing_disk_file));
535
536  // "/foo" is not mapped (it should not be misintepreted as being under ".").
537  EXPECT_EQ(DiskSourceTree::NO_MAPPING,
538    source_tree_.DiskFileToVirtualFile(
539      "/foo", &virtual_file, &shadowing_disk_file));
540
541#ifdef WIN32
542  // "C:\foo" is not mapped (it should not be misintepreted as being under ".").
543  EXPECT_EQ(DiskSourceTree::NO_MAPPING,
544    source_tree_.DiskFileToVirtualFile(
545      "C:\\foo", &virtual_file, &shadowing_disk_file));
546#endif  // WIN32
547
548  // But "../baz" should be.
549  EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
550    source_tree_.DiskFileToVirtualFile(
551      "../baz", &virtual_file, &shadowing_disk_file));
552  EXPECT_EQ("dir1/baz", virtual_file);
553
554  // "../../foo/baz" is under "../../foo".
555  EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
556    source_tree_.DiskFileToVirtualFile(
557      "../../foo/baz", &virtual_file, &shadowing_disk_file));
558  EXPECT_EQ("dir2/baz", virtual_file);
559
560  // "foo/./bar/baz" is under "./foo/bar/.".
561  EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
562    source_tree_.DiskFileToVirtualFile(
563      "foo/bar/baz", &virtual_file, &shadowing_disk_file));
564  EXPECT_EQ("dir3/baz", virtual_file);
565
566  // "bar" is under ".".
567  EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
568    source_tree_.DiskFileToVirtualFile(
569      "bar", &virtual_file, &shadowing_disk_file));
570  EXPECT_EQ("dir4/bar", virtual_file);
571
572  // "/qux/baz" is under "/qux".
573  EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
574    source_tree_.DiskFileToVirtualFile(
575      "/qux/baz", &virtual_file, &shadowing_disk_file));
576  EXPECT_EQ("baz", virtual_file);
577
578  // "/quux/bar" is under "/quux".
579  EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
580    source_tree_.DiskFileToVirtualFile(
581      "/quux/bar", &virtual_file, &shadowing_disk_file));
582  EXPECT_EQ("dir5/bar", virtual_file);
583}
584
585TEST_F(DiskSourceTreeTest, VirtualFileToDiskFile) {
586  // Test VirtualFileToDiskFile.
587
588  AddFile(dirnames_[0] + "/foo", "Hello World!");
589  AddFile(dirnames_[1] + "/foo", "This file should be hidden.");
590  AddFile(dirnames_[1] + "/quux", "This file should not be hidden.");
591  source_tree_.MapPath("bar", dirnames_[0]);
592  source_tree_.MapPath("bar", dirnames_[1]);
593
594  // Existent files, shadowed and non-shadowed case.
595  string disk_file;
596  EXPECT_TRUE(source_tree_.VirtualFileToDiskFile("bar/foo", &disk_file));
597  EXPECT_EQ(dirnames_[0] + "/foo", disk_file);
598  EXPECT_TRUE(source_tree_.VirtualFileToDiskFile("bar/quux", &disk_file));
599  EXPECT_EQ(dirnames_[1] + "/quux", disk_file);
600
601  // Nonexistent file in existent directory and vice versa.
602  string not_touched = "not touched";
603  EXPECT_FALSE(source_tree_.VirtualFileToDiskFile("bar/baz", &not_touched));
604  EXPECT_EQ("not touched", not_touched);
605  EXPECT_FALSE(source_tree_.VirtualFileToDiskFile("baz/foo", &not_touched));
606  EXPECT_EQ("not touched", not_touched);
607
608  // Accept NULL as output parameter.
609  EXPECT_TRUE(source_tree_.VirtualFileToDiskFile("bar/foo", NULL));
610  EXPECT_FALSE(source_tree_.VirtualFileToDiskFile("baz/foo", NULL));
611}
612
613}  // namespace
614
615}  // namespace compiler
616}  // namespace protobuf
617}  // namespace google
618