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