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 (at) 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", ¬_touched)); 587 EXPECT_EQ("not touched", not_touched); 588 EXPECT_FALSE(source_tree_.VirtualFileToDiskFile("baz/foo", ¬_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