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/testing/googletest.h>
48 #include <gtest/gtest.h>
49 #include <google/protobuf/stubs/substitute.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, column,
78 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, line,
84 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", ¬_touched));
534 EXPECT_EQ("not touched", not_touched);
535 EXPECT_FALSE(source_tree_.VirtualFileToDiskFile("baz/foo", ¬_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