importer_unittest.cc revision fbaaef999ba563838ebd00874ed8a1c01fbf286d
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
49namespace google {
50namespace protobuf {
51namespace compiler {
52
53namespace {
54
55#define EXPECT_SUBSTRING(needle, haystack) \
56  EXPECT_PRED_FORMAT2(testing::IsSubstring, (needle), (haystack))
57
58class 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.
76class 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
101class 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
118TEST_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
135TEST_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
172TEST_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
180TEST_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
193TEST_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.
215TEST_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
242TEST_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
257TEST_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
269TEST_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
281TEST_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
296TEST_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
311TEST_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
329class 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
385TEST_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
395TEST_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
414TEST_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
427TEST_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
437TEST_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
451TEST_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
469TEST_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
501TEST_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
568TEST_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
601