1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 // Browser test for basic Chrome OS file manager functionality: 6 // - The file list is updated when a file is added externally to the Downloads 7 // folder. 8 // - Selecting a file and copy-pasting it with the keyboard copies the file. 9 // - Selecting a file and pressing delete deletes it. 10 11 #include <deque> 12 #include <string> 13 14 #include "base/bind.h" 15 #include "base/callback.h" 16 #include "base/file_util.h" 17 #include "base/files/file_path.h" 18 #include "base/time/time.h" 19 #include "chrome/browser/chrome_notification_types.h" 20 #include "chrome/browser/chromeos/drive/drive_integration_service.h" 21 #include "chrome/browser/chromeos/drive/file_system_interface.h" 22 #include "chrome/browser/chromeos/extensions/file_manager/drive_test_util.h" 23 #include "chrome/browser/drive/fake_drive_service.h" 24 #include "chrome/browser/extensions/api/test/test_api.h" 25 #include "chrome/browser/extensions/component_loader.h" 26 #include "chrome/browser/extensions/extension_apitest.h" 27 #include "chrome/browser/extensions/extension_test_message_listener.h" 28 #include "chrome/browser/google_apis/gdata_wapi_parser.h" 29 #include "chrome/browser/google_apis/test_util.h" 30 #include "chrome/browser/profiles/profile.h" 31 #include "chrome/common/chrome_switches.h" 32 #include "chrome/common/extensions/extension.h" 33 #include "chromeos/chromeos_switches.h" 34 #include "content/public/browser/browser_context.h" 35 #include "content/public/browser/notification_service.h" 36 #include "content/public/test/test_utils.h" 37 #include "net/test/embedded_test_server/embedded_test_server.h" 38 #include "webkit/browser/fileapi/external_mount_points.h" 39 40 namespace file_manager { 41 namespace { 42 43 enum EntryType { 44 FILE, 45 DIRECTORY, 46 }; 47 48 enum SharedOption { 49 NONE, 50 SHARED, 51 }; 52 53 enum GuestMode { 54 NOT_IN_GUEST_MODE, 55 IN_GUEST_MODE 56 }; 57 58 // This global operator is used from Google Test to format error messages. 59 std::ostream& operator<<(std::ostream& os, const GuestMode& guest_mode) { 60 return os << (guest_mode == IN_GUEST_MODE ? 61 "IN_GUEST_MODE" : "NOT_IN_GUEST_MODE"); 62 } 63 64 struct TestEntryInfo { 65 EntryType type; 66 const char* source_file_name; // Source file name to be used as a prototype. 67 const char* target_name; // Target file or directory name. 68 const char* mime_type; 69 SharedOption shared_option; 70 const char* last_modified_time_as_string; 71 }; 72 73 TestEntryInfo kTestEntrySetCommon[] = { 74 { FILE, "text.txt", "hello.txt", "text/plain", NONE, "4 Sep 1998 12:34:56" }, 75 { FILE, "image.png", "My Desktop Background.png", "text/plain", NONE, 76 "18 Jan 2038 01:02:03" }, 77 { FILE, "music.ogg", "Beautiful Song.ogg", "text/plain", NONE, 78 "12 Nov 2086 12:00:00" }, 79 { FILE, "video.ogv", "world.ogv", "text/plain", NONE, 80 "4 July 2012 10:35:00" }, 81 { DIRECTORY, "", "photos", NULL, NONE, "1 Jan 1980 23:59:59" }, 82 { DIRECTORY, "", ".warez", NULL, NONE, "26 Oct 1985 13:39" } 83 }; 84 85 TestEntryInfo kTestEntrySetDriveOnly[] = { 86 { FILE, "", "Test Document", "application/vnd.google-apps.document", NONE, 87 "10 Apr 2013 16:20:00" }, 88 { FILE, "", "Test Shared Document", "application/vnd.google-apps.document", 89 SHARED, "20 Mar 2013 22:40:00" } 90 }; 91 92 // The local volume class for test. 93 // This class provides the operations for a test volume that simulates local 94 // drive. 95 class LocalTestVolume { 96 public: 97 // Adds this volume to the file system as a local volume. Returns true on 98 // success. 99 bool Mount(Profile* profile) { 100 const std::string kDownloads = "Downloads"; 101 102 if (local_path_.empty()) { 103 if (!tmp_dir_.CreateUniqueTempDir()) 104 return false; 105 local_path_ = tmp_dir_.path().Append(kDownloads); 106 } 107 fileapi::ExternalMountPoints* const mount_points = 108 content::BrowserContext::GetMountPoints(profile); 109 mount_points->RevokeFileSystem(kDownloads); 110 111 return mount_points->RegisterFileSystem( 112 kDownloads, fileapi::kFileSystemTypeNativeLocal, local_path_) && 113 file_util::CreateDirectory(local_path_); 114 } 115 116 void CreateEntry(const TestEntryInfo& entry) { 117 base::FilePath target_path = local_path_.AppendASCII(entry.target_name); 118 switch (entry.type) { 119 case FILE: { 120 base::FilePath source_path = 121 google_apis::test_util::GetTestFilePath("chromeos/file_manager"). 122 AppendASCII(entry.source_file_name); 123 ASSERT_TRUE(base::CopyFile(source_path, target_path)) 124 << "Copy from " << source_path.value() 125 << " to " << target_path.value() << " failed."; 126 break; 127 } 128 case DIRECTORY: 129 ASSERT_TRUE(file_util::CreateDirectory(target_path)) << 130 "Failed to create a directory: " << target_path.value(); 131 break; 132 } 133 base::Time time; 134 ASSERT_TRUE(base::Time::FromString(entry.last_modified_time_as_string, 135 &time)); 136 ASSERT_TRUE(file_util::SetLastModifiedTime(target_path, time)); 137 } 138 139 private: 140 base::FilePath local_path_; 141 base::ScopedTempDir tmp_dir_; 142 }; 143 144 // The drive volume class for test. 145 // This class provides the operations for a test volume that simulates Google 146 // drive. 147 class DriveTestVolume { 148 public: 149 DriveTestVolume() : fake_drive_service_(NULL), 150 integration_service_(NULL) { 151 } 152 153 // Send request to add this volume to the file system as Google drive. 154 // This method must be calld at SetUp method of FileManagerBrowserTestBase. 155 // Returns true on success. 156 bool SetUp() { 157 if (!test_cache_root_.CreateUniqueTempDir()) 158 return false; 159 drive::DriveIntegrationServiceFactory::SetFactoryForTest( 160 base::Bind(&DriveTestVolume::CreateDriveIntegrationService, 161 base::Unretained(this))); 162 return true; 163 } 164 165 void CreateEntry(const TestEntryInfo& entry) { 166 switch (entry.type) { 167 case FILE: 168 CreateFile(entry.source_file_name, 169 entry.target_name, 170 entry.mime_type, 171 entry.shared_option == SHARED, 172 entry.last_modified_time_as_string); 173 break; 174 case DIRECTORY: 175 CreateDirectory(entry.target_name, entry.last_modified_time_as_string); 176 break; 177 } 178 } 179 180 // Creates an empty directory with the given |name| and |modification_time|. 181 void CreateDirectory(const std::string& name, 182 const std::string& modification_time) { 183 google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR; 184 scoped_ptr<google_apis::ResourceEntry> resource_entry; 185 fake_drive_service_->AddNewDirectory( 186 fake_drive_service_->GetRootResourceId(), 187 name, 188 google_apis::test_util::CreateCopyResultCallback(&error, 189 &resource_entry)); 190 base::MessageLoop::current()->RunUntilIdle(); 191 ASSERT_TRUE(error == google_apis::HTTP_CREATED); 192 ASSERT_TRUE(resource_entry); 193 194 base::Time time; 195 ASSERT_TRUE(base::Time::FromString(modification_time.c_str(), &time)); 196 fake_drive_service_->SetLastModifiedTime( 197 resource_entry->resource_id(), 198 time, 199 google_apis::test_util::CreateCopyResultCallback(&error, 200 &resource_entry)); 201 base::MessageLoop::current()->RunUntilIdle(); 202 ASSERT_TRUE(error == google_apis::HTTP_SUCCESS); 203 ASSERT_TRUE(resource_entry); 204 CheckForUpdates(); 205 } 206 207 // Creates a test file with the given spec. 208 // Serves |test_file_name| file. Pass an empty string for an empty file. 209 void CreateFile(const std::string& source_file_name, 210 const std::string& target_file_name, 211 const std::string& mime_type, 212 bool shared_with_me, 213 const std::string& modification_time) { 214 google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR; 215 216 std::string content_data; 217 if (!source_file_name.empty()) { 218 base::FilePath source_file_path = 219 google_apis::test_util::GetTestFilePath("chromeos/file_manager"). 220 AppendASCII(source_file_name); 221 ASSERT_TRUE(file_util::ReadFileToString(source_file_path, &content_data)); 222 } 223 224 scoped_ptr<google_apis::ResourceEntry> resource_entry; 225 fake_drive_service_->AddNewFile( 226 mime_type, 227 content_data, 228 fake_drive_service_->GetRootResourceId(), 229 target_file_name, 230 shared_with_me, 231 google_apis::test_util::CreateCopyResultCallback(&error, 232 &resource_entry)); 233 base::MessageLoop::current()->RunUntilIdle(); 234 ASSERT_EQ(google_apis::HTTP_CREATED, error); 235 ASSERT_TRUE(resource_entry); 236 237 base::Time time; 238 ASSERT_TRUE(base::Time::FromString(modification_time.c_str(), &time)); 239 fake_drive_service_->SetLastModifiedTime( 240 resource_entry->resource_id(), 241 time, 242 google_apis::test_util::CreateCopyResultCallback(&error, 243 &resource_entry)); 244 base::MessageLoop::current()->RunUntilIdle(); 245 ASSERT_EQ(google_apis::HTTP_SUCCESS, error); 246 ASSERT_TRUE(resource_entry); 247 248 CheckForUpdates(); 249 } 250 251 // Notifies FileSystem that the contents in FakeDriveService are 252 // changed, hence the new contents should be fetched. 253 void CheckForUpdates() { 254 if (integration_service_ && integration_service_->file_system()) { 255 integration_service_->file_system()->CheckForUpdates(); 256 } 257 } 258 259 // Sets the url base for the test server to be used to generate share urls 260 // on the files and directories. 261 void ConfigureShareUrlBase(const GURL& share_url_base) { 262 fake_drive_service_->set_share_url_base(share_url_base); 263 } 264 265 drive::DriveIntegrationService* CreateDriveIntegrationService( 266 Profile* profile) { 267 fake_drive_service_ = new drive::FakeDriveService; 268 fake_drive_service_->LoadResourceListForWapi( 269 "gdata/empty_feed.json"); 270 fake_drive_service_->LoadAccountMetadataForWapi( 271 "gdata/account_metadata.json"); 272 fake_drive_service_->LoadAppListForDriveApi("drive/applist.json"); 273 integration_service_ = new drive::DriveIntegrationService( 274 profile, 275 fake_drive_service_, 276 test_cache_root_.path(), 277 NULL); 278 return integration_service_; 279 } 280 281 private: 282 base::ScopedTempDir test_cache_root_; 283 drive::FakeDriveService* fake_drive_service_; 284 drive::DriveIntegrationService* integration_service_; 285 }; 286 287 // Listener to obtain the test relative messages synchronously. 288 class FileManagerTestListener : public content::NotificationObserver { 289 public: 290 struct Message { 291 int type; 292 std::string message; 293 extensions::TestSendMessageFunction* function; 294 }; 295 296 FileManagerTestListener() { 297 registrar_.Add(this, 298 chrome::NOTIFICATION_EXTENSION_TEST_PASSED, 299 content::NotificationService::AllSources()); 300 registrar_.Add(this, 301 chrome::NOTIFICATION_EXTENSION_TEST_FAILED, 302 content::NotificationService::AllSources()); 303 registrar_.Add(this, 304 chrome::NOTIFICATION_EXTENSION_TEST_MESSAGE, 305 content::NotificationService::AllSources()); 306 } 307 308 Message GetNextMessage() { 309 if (messages_.empty()) 310 content::RunMessageLoop(); 311 const Message entry = messages_.front(); 312 messages_.pop_front(); 313 return entry; 314 } 315 316 virtual void Observe(int type, 317 const content::NotificationSource& source, 318 const content::NotificationDetails& details) OVERRIDE { 319 Message entry; 320 entry.type = type; 321 entry.message = type != chrome::NOTIFICATION_EXTENSION_TEST_PASSED ? 322 *content::Details<std::string>(details).ptr() : 323 std::string(); 324 entry.function = type == chrome::NOTIFICATION_EXTENSION_TEST_MESSAGE ? 325 content::Source<extensions::TestSendMessageFunction>(source).ptr() : 326 NULL; 327 messages_.push_back(entry); 328 base::MessageLoopForUI::current()->Quit(); 329 } 330 331 private: 332 std::deque<Message> messages_; 333 content::NotificationRegistrar registrar_; 334 }; 335 336 // Parameter of FileManagerBrowserTest. 337 // The second value is the case name of JavaScript. 338 typedef std::tr1::tuple<GuestMode, const char*> TestParameter; 339 340 // The base test class. 341 class FileManagerBrowserTest : 342 public ExtensionApiTest, 343 public ::testing::WithParamInterface<TestParameter> { 344 protected: 345 FileManagerBrowserTest() : 346 local_volume_(new LocalTestVolume), 347 drive_volume_(std::tr1::get<0>(GetParam()) != IN_GUEST_MODE ? 348 new DriveTestVolume() : NULL) {} 349 350 virtual void SetUp() OVERRIDE { 351 // TODO(danakj): The GPU Video Decoder needs real GL bindings. 352 // crbug.com/269087 353 UseRealGLBindings(); 354 355 ExtensionApiTest::SetUp(); 356 } 357 358 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE; 359 360 virtual void SetUpOnMainThread() OVERRIDE; 361 362 // Adds an incognito and guest-mode flags for tests in the guest mode. 363 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE; 364 365 // Loads our testing extension and sends it a string identifying the current 366 // test. 367 void StartTest(); 368 369 const scoped_ptr<LocalTestVolume> local_volume_; 370 const scoped_ptr<DriveTestVolume> drive_volume_; 371 }; 372 373 void FileManagerBrowserTest::SetUpInProcessBrowserTestFixture() { 374 ExtensionApiTest::SetUpInProcessBrowserTestFixture(); 375 extensions::ComponentLoader::EnableBackgroundExtensionsForTesting(); 376 if (drive_volume_) 377 ASSERT_TRUE(drive_volume_->SetUp()); 378 } 379 380 void FileManagerBrowserTest::SetUpOnMainThread() { 381 ExtensionApiTest::SetUpOnMainThread(); 382 ASSERT_TRUE(local_volume_->Mount(browser()->profile())); 383 384 for (size_t i = 0; i < arraysize(kTestEntrySetCommon); ++i) 385 local_volume_->CreateEntry(kTestEntrySetCommon[i]); 386 387 if (drive_volume_) { 388 // Install the web server to serve the mocked share dialog. 389 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 390 const GURL share_url_base(embedded_test_server()->GetURL( 391 "/chromeos/file_manager/share_dialog_mock/index.html")); 392 drive_volume_->ConfigureShareUrlBase(share_url_base); 393 394 for (size_t i = 0; i < arraysize(kTestEntrySetCommon); ++i) 395 drive_volume_->CreateEntry(kTestEntrySetCommon[i]); 396 397 // For testing Drive, create more entries with Drive specific attributes. 398 // TODO(haruki): Add a case for an entry cached by DriveCache. 399 for (size_t i = 0; i < arraysize(kTestEntrySetDriveOnly); ++i) 400 drive_volume_->CreateEntry(kTestEntrySetDriveOnly[i]); 401 402 test_util::WaitUntilDriveMountPointIsAdded(browser()->profile()); 403 } 404 } 405 406 void FileManagerBrowserTest::SetUpCommandLine(CommandLine* command_line) { 407 if (std::tr1::get<0>(GetParam()) == IN_GUEST_MODE) { 408 command_line->AppendSwitch(chromeos::switches::kGuestSession); 409 command_line->AppendSwitchNative(chromeos::switches::kLoginUser, ""); 410 command_line->AppendSwitch(switches::kIncognito); 411 } 412 ExtensionApiTest::SetUpCommandLine(command_line); 413 } 414 415 IN_PROC_BROWSER_TEST_P(FileManagerBrowserTest, Test) { 416 // Launch the extension. 417 base::FilePath path = test_data_dir_.AppendASCII("file_manager_browsertest"); 418 const extensions::Extension* extension = LoadExtensionAsComponent(path); 419 ASSERT_TRUE(extension); 420 421 // Handle the messages from JavaScript. 422 // The while loop is break when the test is passed or failed. 423 FileManagerTestListener listener; 424 while (true) { 425 FileManagerTestListener::Message entry = listener.GetNextMessage(); 426 if (entry.type == chrome::NOTIFICATION_EXTENSION_TEST_PASSED) { 427 // Test succeed. 428 break; 429 } else if (entry.type == chrome::NOTIFICATION_EXTENSION_TEST_FAILED) { 430 // Test failed. 431 ADD_FAILURE() << entry.message; 432 break; 433 } else if (entry.message == "getTestName") { 434 // Pass the test case name. 435 entry.function->Reply(std::tr1::get<1>(GetParam())); 436 } else if (entry.message == "isInGuestMode") { 437 // Obtains whther the test is in guest mode or not. 438 entry.function->Reply(std::tr1::get<0>(GetParam()) ? "true" : "false"); 439 } else if (entry.message == "addEntry") { 440 // Add the extra entry. 441 const TestEntryInfo file = { 442 FILE, 443 "music.ogg", // Prototype file name. 444 "newly added file.ogg", // Target file name. 445 "audio/ogg", 446 NONE, 447 "4 Sep 1998 00:00:00" 448 }; 449 if (drive_volume_) 450 drive_volume_->CreateEntry(file); 451 local_volume_->CreateEntry(file); 452 entry.function->Reply("onEntryAdded"); 453 } 454 } 455 } 456 457 INSTANTIATE_TEST_CASE_P( 458 FileDisplay, 459 FileManagerBrowserTest, 460 ::testing::Values(TestParameter(NOT_IN_GUEST_MODE, "fileDisplayDownloads"), 461 TestParameter(IN_GUEST_MODE, "fileDisplayDownloads"), 462 TestParameter(NOT_IN_GUEST_MODE, "fileDisplayDrive"))); 463 464 // TODO(mtomasz): Fix this test. crbug.com/252561 465 INSTANTIATE_TEST_CASE_P( 466 OpenSpecialTypes, 467 FileManagerBrowserTest, 468 ::testing::Values(TestParameter(IN_GUEST_MODE, "videoOpenDownloads"), 469 TestParameter(NOT_IN_GUEST_MODE, "videoOpenDownloads"), 470 TestParameter(NOT_IN_GUEST_MODE, "videoOpenDrive"), 471 TestParameter(IN_GUEST_MODE, "audioOpenDownloads"), 472 TestParameter(NOT_IN_GUEST_MODE, "audioOpenDownloads"), 473 TestParameter(NOT_IN_GUEST_MODE, "audioOpenDrive"), 474 TestParameter(IN_GUEST_MODE, "galleryOpenDownloads"), 475 TestParameter(NOT_IN_GUEST_MODE, 476 "galleryOpenDownloads"), 477 TestParameter(NOT_IN_GUEST_MODE, "galleryOpenDrive"))); 478 479 INSTANTIATE_TEST_CASE_P( 480 KeyboardOperations, 481 FileManagerBrowserTest, 482 ::testing::Values(TestParameter(IN_GUEST_MODE, "keyboardDeleteDownloads"), 483 TestParameter(NOT_IN_GUEST_MODE, 484 "keyboardDeleteDownloads"), 485 TestParameter(NOT_IN_GUEST_MODE, "keyboardDeleteDrive"), 486 TestParameter(IN_GUEST_MODE, "keyboardCopyDownloads"), 487 TestParameter(NOT_IN_GUEST_MODE, "keyboardCopyDownloads"), 488 TestParameter(NOT_IN_GUEST_MODE, "keyboardCopyDrive"))); 489 490 INSTANTIATE_TEST_CASE_P( 491 DriveSpecific, 492 FileManagerBrowserTest, 493 ::testing::Values(TestParameter(NOT_IN_GUEST_MODE, "openSidebarRecent"), 494 TestParameter(NOT_IN_GUEST_MODE, "openSidebarOffline"), 495 TestParameter(NOT_IN_GUEST_MODE, 496 "openSidebarSharedWithMe"), 497 TestParameter(NOT_IN_GUEST_MODE, "autocomplete"))); 498 499 INSTANTIATE_TEST_CASE_P( 500 Transfer, 501 FileManagerBrowserTest, 502 ::testing::Values(TestParameter(NOT_IN_GUEST_MODE, 503 "transferFromDriveToDownloads"), 504 TestParameter(NOT_IN_GUEST_MODE, 505 "transferFromDownloadsToDrive"), 506 TestParameter(NOT_IN_GUEST_MODE, 507 "transferFromSharedToDownloads"), 508 TestParameter(NOT_IN_GUEST_MODE, 509 "transferFromSharedToDrive"), 510 TestParameter(NOT_IN_GUEST_MODE, 511 "transferFromRecentToDownloads"), 512 TestParameter(NOT_IN_GUEST_MODE, 513 "transferFromRecentToDrive"), 514 TestParameter(NOT_IN_GUEST_MODE, 515 "transferFromOfflineToDownloads"), 516 TestParameter(NOT_IN_GUEST_MODE, 517 "transferFromOfflineToDrive"))); 518 519 INSTANTIATE_TEST_CASE_P( 520 HideSearchBox, 521 FileManagerBrowserTest, 522 ::testing::Values(TestParameter(IN_GUEST_MODE, "hideSearchBox"), 523 TestParameter(NOT_IN_GUEST_MODE, "hideSearchBox"))); 524 525 INSTANTIATE_TEST_CASE_P( 526 RestorePrefs, 527 FileManagerBrowserTest, 528 ::testing::Values(TestParameter(IN_GUEST_MODE, "restoreSortColumn"), 529 TestParameter(NOT_IN_GUEST_MODE, "restoreSortColumn"), 530 TestParameter(IN_GUEST_MODE, "restoreCurrentView"), 531 TestParameter(NOT_IN_GUEST_MODE, "restoreCurrentView"))); 532 533 INSTANTIATE_TEST_CASE_P( 534 ShareDialog, 535 FileManagerBrowserTest, 536 ::testing::Values(TestParameter(NOT_IN_GUEST_MODE, "shareFile"), 537 TestParameter(NOT_IN_GUEST_MODE, "shareDirectory"))); 538 539 } // namespace 540 } // namespace file_manager 541