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/compiler/importer.h>
36 
37 #include <memory>
38 #include <unordered_map>
39 
40 #include <google/protobuf/stubs/logging.h>
41 #include <google/protobuf/stubs/common.h>
42 #include <google/protobuf/testing/file.h>
43 #include <google/protobuf/testing/file.h>
44 #include <google/protobuf/testing/file.h>
45 #include <google/protobuf/io/zero_copy_stream_impl.h>
46 #include <google/protobuf/descriptor.h>
47 #include <google/protobuf/stubs/substitute.h>
48 #include <google/protobuf/testing/googletest.h>
49 #include <gtest/gtest.h>
50 #include <google/protobuf/stubs/map_util.h>
51 #include <google/protobuf/stubs/strutil.h>
52 
53 namespace google {
54 namespace protobuf {
55 namespace compiler {
56 
57 namespace {
58 
FileExists(const std::string & path)59 bool FileExists(const std::string& path) {
60   return File::Exists(path);
61 }
62 
63 #define EXPECT_SUBSTRING(needle, haystack) \
64   EXPECT_PRED_FORMAT2(testing::IsSubstring, (needle), (haystack))
65 
66 class MockErrorCollector : public MultiFileErrorCollector {
67  public:
MockErrorCollector()68   MockErrorCollector() {}
~MockErrorCollector()69   ~MockErrorCollector() {}
70 
71   std::string text_;
72   std::string warning_text_;
73 
74   // implements ErrorCollector ---------------------------------------
AddError(const std::string & filename,int line,int column,const std::string & message)75   void AddError(const std::string& filename, int line, int column,
76                 const std::string& message) {
77     strings::SubstituteAndAppend(&text_, "$0:$1:$2: $3\n", filename, line,
78                                  column, message);
79   }
80 
AddWarning(const std::string & filename,int line,int column,const std::string & message)81   void AddWarning(const std::string& filename, int line, int column,
82                   const std::string& message) {
83     strings::SubstituteAndAppend(&warning_text_, "$0:$1:$2: $3\n", filename,
84                                  line, column, message);
85   }
86 };
87 
88 // -------------------------------------------------------------------
89 
90 // A dummy implementation of SourceTree backed by a simple map.
91 class MockSourceTree : public SourceTree {
92  public:
MockSourceTree()93   MockSourceTree() {}
~MockSourceTree()94   ~MockSourceTree() {}
95 
AddFile(const std::string & name,const char * contents)96   void AddFile(const std::string& name, const char* contents) {
97     files_[name] = contents;
98   }
99 
100   // implements SourceTree -------------------------------------------
Open(const std::string & filename)101   io::ZeroCopyInputStream* Open(const std::string& filename) {
102     const char* contents = FindPtrOrNull(files_, filename);
103     if (contents == NULL) {
104       return NULL;
105     } else {
106       return new io::ArrayInputStream(contents, strlen(contents));
107     }
108   }
109 
GetLastErrorMessage()110   std::string GetLastErrorMessage() { return "File not found."; }
111 
112  private:
113   std::unordered_map<std::string, const char*> files_;
114 };
115 
116 // ===================================================================
117 
118 class ImporterTest : public testing::Test {
119  protected:
ImporterTest()120   ImporterTest() : importer_(&source_tree_, &error_collector_) {}
121 
AddFile(const std::string & filename,const char * text)122   void AddFile(const std::string& filename, const char* text) {
123     source_tree_.AddFile(filename, text);
124   }
125 
126   // Return the collected error text
warning() const127   std::string warning() const { return error_collector_.warning_text_; }
128 
129   MockErrorCollector error_collector_;
130   MockSourceTree source_tree_;
131   Importer importer_;
132 };
133 
TEST_F(ImporterTest,Import)134 TEST_F(ImporterTest, Import) {
135   // Test normal importing.
136   AddFile("foo.proto",
137           "syntax = \"proto2\";\n"
138           "message Foo {}\n");
139 
140   const FileDescriptor* file = importer_.Import("foo.proto");
141   EXPECT_EQ("", error_collector_.text_);
142   ASSERT_TRUE(file != NULL);
143 
144   ASSERT_EQ(1, file->message_type_count());
145   EXPECT_EQ("Foo", file->message_type(0)->name());
146 
147   // Importing again should return same object.
148   EXPECT_EQ(file, importer_.Import("foo.proto"));
149 }
150 
TEST_F(ImporterTest,ImportNested)151 TEST_F(ImporterTest, ImportNested) {
152   // Test that importing a file which imports another file works.
153   AddFile("foo.proto",
154           "syntax = \"proto2\";\n"
155           "import \"bar.proto\";\n"
156           "message Foo {\n"
157           "  optional Bar bar = 1;\n"
158           "}\n");
159   AddFile("bar.proto",
160           "syntax = \"proto2\";\n"
161           "message Bar {}\n");
162 
163   // Note that both files are actually parsed by the first call to Import()
164   // here, since foo.proto imports bar.proto.  The second call just returns
165   // the same ProtoFile for bar.proto which was constructed while importing
166   // foo.proto.  We test that this is the case below by checking that bar
167   // is among foo's dependencies (by pointer).
168   const FileDescriptor* foo = importer_.Import("foo.proto");
169   const FileDescriptor* bar = importer_.Import("bar.proto");
170   EXPECT_EQ("", error_collector_.text_);
171   ASSERT_TRUE(foo != NULL);
172   ASSERT_TRUE(bar != NULL);
173 
174   // Check that foo's dependency is the same object as bar.
175   ASSERT_EQ(1, foo->dependency_count());
176   EXPECT_EQ(bar, foo->dependency(0));
177 
178   // Check that foo properly cross-links bar.
179   ASSERT_EQ(1, foo->message_type_count());
180   ASSERT_EQ(1, bar->message_type_count());
181   ASSERT_EQ(1, foo->message_type(0)->field_count());
182   ASSERT_EQ(FieldDescriptor::TYPE_MESSAGE,
183             foo->message_type(0)->field(0)->type());
184   EXPECT_EQ(bar->message_type(0),
185             foo->message_type(0)->field(0)->message_type());
186 }
187 
TEST_F(ImporterTest,FileNotFound)188 TEST_F(ImporterTest, FileNotFound) {
189   // Error:  Parsing a file that doesn't exist.
190   EXPECT_TRUE(importer_.Import("foo.proto") == NULL);
191   EXPECT_EQ("foo.proto:-1:0: File not found.\n", error_collector_.text_);
192 }
193 
TEST_F(ImporterTest,ImportNotFound)194 TEST_F(ImporterTest, ImportNotFound) {
195   // Error:  Importing a file that doesn't exist.
196   AddFile("foo.proto",
197           "syntax = \"proto2\";\n"
198           "import \"bar.proto\";\n");
199 
200   EXPECT_TRUE(importer_.Import("foo.proto") == NULL);
201   EXPECT_EQ(
202       "bar.proto:-1:0: File not found.\n"
203       "foo.proto:1:0: Import \"bar.proto\" was not found or had errors.\n",
204       error_collector_.text_);
205 }
206 
TEST_F(ImporterTest,RecursiveImport)207 TEST_F(ImporterTest, RecursiveImport) {
208   // Error:  Recursive import.
209   AddFile("recursive1.proto",
210           "syntax = \"proto2\";\n"
211           "\n"
212           "import \"recursive2.proto\";\n");
213   AddFile("recursive2.proto",
214           "syntax = \"proto2\";\n"
215           "import \"recursive1.proto\";\n");
216 
217   EXPECT_TRUE(importer_.Import("recursive1.proto") == NULL);
218   EXPECT_EQ(
219       "recursive1.proto:2:0: File recursively imports itself: "
220       "recursive1.proto "
221       "-> recursive2.proto -> recursive1.proto\n"
222       "recursive2.proto:1:0: Import \"recursive1.proto\" was not found "
223       "or had errors.\n"
224       "recursive1.proto:2:0: Import \"recursive2.proto\" was not found "
225       "or had errors.\n",
226       error_collector_.text_);
227 }
228 
TEST_F(ImporterTest,RecursiveImportSelf)229 TEST_F(ImporterTest, RecursiveImportSelf) {
230   // Error:  Recursive import.
231   AddFile("recursive.proto",
232           "syntax = \"proto2\";\n"
233           "\n"
234           "import \"recursive.proto\";\n");
235 
236   EXPECT_TRUE(importer_.Import("recursive.proto") == nullptr);
237   EXPECT_EQ(
238       "recursive.proto:2:0: File recursively imports itself: "
239       "recursive.proto -> recursive.proto\n",
240       error_collector_.text_);
241 }
242 
TEST_F(ImporterTest,LiteRuntimeImport)243 TEST_F(ImporterTest, LiteRuntimeImport) {
244   // Error:  Recursive import.
245   AddFile("bar.proto",
246           "syntax = \"proto2\";\n"
247           "option optimize_for = LITE_RUNTIME;\n");
248   AddFile("foo.proto",
249           "syntax = \"proto2\";\n"
250           "import \"bar.proto\";\n");
251 
252   EXPECT_TRUE(importer_.Import("foo.proto") == nullptr);
253   EXPECT_EQ(
254       "foo.proto:1:0: Files that do not use optimize_for = LITE_RUNTIME "
255       "cannot import files which do use this option.  This file is not "
256       "lite, but it imports \"bar.proto\" which is.\n",
257       error_collector_.text_);
258 }
259 
260 
261 // ===================================================================
262 
263 class DiskSourceTreeTest : public testing::Test {
264  protected:
SetUp()265   virtual void SetUp() {
266     dirnames_.push_back(TestTempDir() + "/test_proto2_import_path_1");
267     dirnames_.push_back(TestTempDir() + "/test_proto2_import_path_2");
268 
269     for (int i = 0; i < dirnames_.size(); i++) {
270       if (FileExists(dirnames_[i])) {
271         File::DeleteRecursively(dirnames_[i], NULL, NULL);
272       }
273       GOOGLE_CHECK_OK(File::CreateDir(dirnames_[i], 0777));
274     }
275   }
276 
TearDown()277   virtual void TearDown() {
278     for (int i = 0; i < dirnames_.size(); i++) {
279       if (FileExists(dirnames_[i])) {
280         File::DeleteRecursively(dirnames_[i], NULL, NULL);
281       }
282     }
283   }
284 
AddFile(const std::string & filename,const char * contents)285   void AddFile(const std::string& filename, const char* contents) {
286     GOOGLE_CHECK_OK(File::SetContents(filename, contents, true));
287   }
288 
AddSubdir(const std::string & dirname)289   void AddSubdir(const std::string& dirname) {
290     GOOGLE_CHECK_OK(File::CreateDir(dirname, 0777));
291   }
292 
ExpectFileContents(const std::string & filename,const char * expected_contents)293   void ExpectFileContents(const std::string& filename,
294                           const char* expected_contents) {
295     std::unique_ptr<io::ZeroCopyInputStream> input(source_tree_.Open(filename));
296 
297     ASSERT_FALSE(input == NULL);
298 
299     // Read all the data from the file.
300     std::string file_contents;
301     const void* data;
302     int size;
303     while (input->Next(&data, &size)) {
304       file_contents.append(reinterpret_cast<const char*>(data), size);
305     }
306 
307     EXPECT_EQ(expected_contents, file_contents);
308   }
309 
ExpectCannotOpenFile(const std::string & filename,const std::string & error_message)310   void ExpectCannotOpenFile(const std::string& filename,
311                             const std::string& error_message) {
312     std::unique_ptr<io::ZeroCopyInputStream> input(source_tree_.Open(filename));
313     EXPECT_TRUE(input == NULL);
314     EXPECT_EQ(error_message, source_tree_.GetLastErrorMessage());
315   }
316 
317   DiskSourceTree source_tree_;
318 
319   // Paths of two on-disk directories to use during the test.
320   std::vector<std::string> dirnames_;
321 };
322 
TEST_F(DiskSourceTreeTest,MapRoot)323 TEST_F(DiskSourceTreeTest, MapRoot) {
324   // Test opening a file in a directory that is mapped to the root of the
325   // source tree.
326   AddFile(dirnames_[0] + "/foo", "Hello World!");
327   source_tree_.MapPath("", dirnames_[0]);
328 
329   ExpectFileContents("foo", "Hello World!");
330   ExpectCannotOpenFile("bar", "File not found.");
331 }
332 
TEST_F(DiskSourceTreeTest,MapDirectory)333 TEST_F(DiskSourceTreeTest, MapDirectory) {
334   // Test opening a file in a directory that is mapped to somewhere other
335   // than the root of the source tree.
336 
337   AddFile(dirnames_[0] + "/foo", "Hello World!");
338   source_tree_.MapPath("baz", dirnames_[0]);
339 
340   ExpectFileContents("baz/foo", "Hello World!");
341   ExpectCannotOpenFile("baz/bar", "File not found.");
342   ExpectCannotOpenFile("foo", "File not found.");
343   ExpectCannotOpenFile("bar", "File not found.");
344 
345   // Non-canonical file names should not work.
346   ExpectCannotOpenFile("baz//foo",
347                        "Backslashes, consecutive slashes, \".\", or \"..\" are "
348                        "not allowed in the virtual path");
349   ExpectCannotOpenFile("baz/../baz/foo",
350                        "Backslashes, consecutive slashes, \".\", or \"..\" are "
351                        "not allowed in the virtual path");
352   ExpectCannotOpenFile("baz/./foo",
353                        "Backslashes, consecutive slashes, \".\", or \"..\" are "
354                        "not allowed in the virtual path");
355   ExpectCannotOpenFile("baz/foo/", "File not found.");
356 }
357 
TEST_F(DiskSourceTreeTest,NoParent)358 TEST_F(DiskSourceTreeTest, NoParent) {
359   // Test that we cannot open files in a parent of a mapped directory.
360 
361   AddFile(dirnames_[0] + "/foo", "Hello World!");
362   AddSubdir(dirnames_[0] + "/bar");
363   AddFile(dirnames_[0] + "/bar/baz", "Blah.");
364   source_tree_.MapPath("", dirnames_[0] + "/bar");
365 
366   ExpectFileContents("baz", "Blah.");
367   ExpectCannotOpenFile("../foo",
368                        "Backslashes, consecutive slashes, \".\", or \"..\" are "
369                        "not allowed in the virtual path");
370   ExpectCannotOpenFile("../bar/baz",
371                        "Backslashes, consecutive slashes, \".\", or \"..\" are "
372                        "not allowed in the virtual path");
373 }
374 
TEST_F(DiskSourceTreeTest,MapFile)375 TEST_F(DiskSourceTreeTest, MapFile) {
376   // Test opening a file that is mapped directly into the source tree.
377 
378   AddFile(dirnames_[0] + "/foo", "Hello World!");
379   source_tree_.MapPath("foo", dirnames_[0] + "/foo");
380 
381   ExpectFileContents("foo", "Hello World!");
382   ExpectCannotOpenFile("bar", "File not found.");
383 }
384 
TEST_F(DiskSourceTreeTest,SearchMultipleDirectories)385 TEST_F(DiskSourceTreeTest, SearchMultipleDirectories) {
386   // Test mapping and searching multiple directories.
387 
388   AddFile(dirnames_[0] + "/foo", "Hello World!");
389   AddFile(dirnames_[1] + "/foo", "This file should be hidden.");
390   AddFile(dirnames_[1] + "/bar", "Goodbye World!");
391   source_tree_.MapPath("", dirnames_[0]);
392   source_tree_.MapPath("", dirnames_[1]);
393 
394   ExpectFileContents("foo", "Hello World!");
395   ExpectFileContents("bar", "Goodbye World!");
396   ExpectCannotOpenFile("baz", "File not found.");
397 }
398 
TEST_F(DiskSourceTreeTest,OrderingTrumpsSpecificity)399 TEST_F(DiskSourceTreeTest, OrderingTrumpsSpecificity) {
400   // Test that directories are always searched in order, even when a latter
401   // directory is more-specific than a former one.
402 
403   // Create the "bar" directory so we can put a file in it.
404   GOOGLE_CHECK_OK(File::CreateDir(dirnames_[0] + "/bar", 0777));
405 
406   // Add files and map paths.
407   AddFile(dirnames_[0] + "/bar/foo", "Hello World!");
408   AddFile(dirnames_[1] + "/foo", "This file should be hidden.");
409   source_tree_.MapPath("", dirnames_[0]);
410   source_tree_.MapPath("bar", dirnames_[1]);
411 
412   // Check.
413   ExpectFileContents("bar/foo", "Hello World!");
414 }
415 
TEST_F(DiskSourceTreeTest,DiskFileToVirtualFile)416 TEST_F(DiskSourceTreeTest, DiskFileToVirtualFile) {
417   // Test DiskFileToVirtualFile.
418 
419   AddFile(dirnames_[0] + "/foo", "Hello World!");
420   AddFile(dirnames_[1] + "/foo", "This file should be hidden.");
421   source_tree_.MapPath("bar", dirnames_[0]);
422   source_tree_.MapPath("bar", dirnames_[1]);
423 
424   std::string virtual_file;
425   std::string shadowing_disk_file;
426 
427   EXPECT_EQ(DiskSourceTree::NO_MAPPING,
428             source_tree_.DiskFileToVirtualFile("/foo", &virtual_file,
429                                                &shadowing_disk_file));
430 
431   EXPECT_EQ(DiskSourceTree::SHADOWED,
432             source_tree_.DiskFileToVirtualFile(
433                 dirnames_[1] + "/foo", &virtual_file, &shadowing_disk_file));
434   EXPECT_EQ("bar/foo", virtual_file);
435   EXPECT_EQ(dirnames_[0] + "/foo", shadowing_disk_file);
436 
437   EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
438             source_tree_.DiskFileToVirtualFile(
439                 dirnames_[1] + "/baz", &virtual_file, &shadowing_disk_file));
440   EXPECT_EQ("bar/baz", virtual_file);
441 
442   EXPECT_EQ(DiskSourceTree::SUCCESS,
443             source_tree_.DiskFileToVirtualFile(
444                 dirnames_[0] + "/foo", &virtual_file, &shadowing_disk_file));
445   EXPECT_EQ("bar/foo", virtual_file);
446 }
447 
TEST_F(DiskSourceTreeTest,DiskFileToVirtualFileCanonicalization)448 TEST_F(DiskSourceTreeTest, DiskFileToVirtualFileCanonicalization) {
449   // Test handling of "..", ".", etc. in DiskFileToVirtualFile().
450 
451   source_tree_.MapPath("dir1", "..");
452   source_tree_.MapPath("dir2", "../../foo");
453   source_tree_.MapPath("dir3", "./foo/bar/.");
454   source_tree_.MapPath("dir4", ".");
455   source_tree_.MapPath("", "/qux");
456   source_tree_.MapPath("dir5", "/quux/");
457 
458   std::string virtual_file;
459   std::string shadowing_disk_file;
460 
461   // "../.." should not be considered to be under "..".
462   EXPECT_EQ(DiskSourceTree::NO_MAPPING,
463             source_tree_.DiskFileToVirtualFile("../../baz", &virtual_file,
464                                                &shadowing_disk_file));
465 
466   // "/foo" is not mapped (it should not be misintepreted as being under ".").
467   EXPECT_EQ(DiskSourceTree::NO_MAPPING,
468             source_tree_.DiskFileToVirtualFile("/foo", &virtual_file,
469                                                &shadowing_disk_file));
470 
471 #ifdef WIN32
472   // "C:\foo" is not mapped (it should not be misintepreted as being under ".").
473   EXPECT_EQ(DiskSourceTree::NO_MAPPING,
474             source_tree_.DiskFileToVirtualFile("C:\\foo", &virtual_file,
475                                                &shadowing_disk_file));
476 #endif  // WIN32
477 
478   // But "../baz" should be.
479   EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
480             source_tree_.DiskFileToVirtualFile("../baz", &virtual_file,
481                                                &shadowing_disk_file));
482   EXPECT_EQ("dir1/baz", virtual_file);
483 
484   // "../../foo/baz" is under "../../foo".
485   EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
486             source_tree_.DiskFileToVirtualFile("../../foo/baz", &virtual_file,
487                                                &shadowing_disk_file));
488   EXPECT_EQ("dir2/baz", virtual_file);
489 
490   // "foo/./bar/baz" is under "./foo/bar/.".
491   EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
492             source_tree_.DiskFileToVirtualFile("foo/bar/baz", &virtual_file,
493                                                &shadowing_disk_file));
494   EXPECT_EQ("dir3/baz", virtual_file);
495 
496   // "bar" is under ".".
497   EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
498             source_tree_.DiskFileToVirtualFile("bar", &virtual_file,
499                                                &shadowing_disk_file));
500   EXPECT_EQ("dir4/bar", virtual_file);
501 
502   // "/qux/baz" is under "/qux".
503   EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
504             source_tree_.DiskFileToVirtualFile("/qux/baz", &virtual_file,
505                                                &shadowing_disk_file));
506   EXPECT_EQ("baz", virtual_file);
507 
508   // "/quux/bar" is under "/quux".
509   EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
510             source_tree_.DiskFileToVirtualFile("/quux/bar", &virtual_file,
511                                                &shadowing_disk_file));
512   EXPECT_EQ("dir5/bar", virtual_file);
513 }
514 
TEST_F(DiskSourceTreeTest,VirtualFileToDiskFile)515 TEST_F(DiskSourceTreeTest, VirtualFileToDiskFile) {
516   // Test VirtualFileToDiskFile.
517 
518   AddFile(dirnames_[0] + "/foo", "Hello World!");
519   AddFile(dirnames_[1] + "/foo", "This file should be hidden.");
520   AddFile(dirnames_[1] + "/quux", "This file should not be hidden.");
521   source_tree_.MapPath("bar", dirnames_[0]);
522   source_tree_.MapPath("bar", dirnames_[1]);
523 
524   // Existent files, shadowed and non-shadowed case.
525   std::string disk_file;
526   EXPECT_TRUE(source_tree_.VirtualFileToDiskFile("bar/foo", &disk_file));
527   EXPECT_EQ(dirnames_[0] + "/foo", disk_file);
528   EXPECT_TRUE(source_tree_.VirtualFileToDiskFile("bar/quux", &disk_file));
529   EXPECT_EQ(dirnames_[1] + "/quux", disk_file);
530 
531   // Nonexistent file in existent directory and vice versa.
532   std::string not_touched = "not touched";
533   EXPECT_FALSE(source_tree_.VirtualFileToDiskFile("bar/baz", &not_touched));
534   EXPECT_EQ("not touched", not_touched);
535   EXPECT_FALSE(source_tree_.VirtualFileToDiskFile("baz/foo", &not_touched));
536   EXPECT_EQ("not touched", not_touched);
537 
538   // Accept NULL as output parameter.
539   EXPECT_TRUE(source_tree_.VirtualFileToDiskFile("bar/foo", NULL));
540   EXPECT_FALSE(source_tree_.VirtualFileToDiskFile("baz/foo", NULL));
541 }
542 
543 }  // namespace
544 
545 }  // namespace compiler
546 }  // namespace protobuf
547 }  // namespace google
548