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