// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/bind.h" #include "base/command_line.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/memory/scoped_ptr.h" #include "base/run_loop.h" #include "base/test/scoped_path_override.h" #include "chrome/browser/extensions/test_extension_system.h" #include "chrome/browser/media_galleries/gallery_watch_manager.h" #include "chrome/browser/media_galleries/gallery_watch_manager_observer.h" #include "chrome/browser/media_galleries/media_galleries_preferences.h" #include "chrome/browser/media_galleries/media_galleries_preferences_factory.h" #include "chrome/browser/media_galleries/media_galleries_test_util.h" #include "chrome/test/base/testing_profile.h" #include "components/storage_monitor/test_storage_monitor.h" #include "content/public/test/test_browser_thread_bundle.h" #include "extensions/browser/extension_system.h" #include "extensions/common/extension.h" #include "extensions/common/permissions/media_galleries_permission.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h" #include "chrome/browser/chromeos/settings/cros_settings.h" #include "chrome/browser/chromeos/settings/device_settings_service.h" #endif namespace component_updater { namespace { void ConfirmWatch(base::RunLoop* loop, const std::string& error) { EXPECT_TRUE(error.empty()); loop->Quit(); } void ExpectWatchError(base::RunLoop* loop, const std::string& expected_error, const std::string& error) { EXPECT_EQ(expected_error, error); loop->Quit(); } } // namespace class GalleryWatchManagerTest : public GalleryWatchManagerObserver, public testing::Test { public: GalleryWatchManagerTest() : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), profile_(new TestingProfile()), gallery_prefs_(NULL), expect_gallery_changed_(false), expect_gallery_watch_dropped_(false), pending_loop_(NULL) {} ~GalleryWatchManagerTest() override {} void SetUp() override { monitor_ = storage_monitor::TestStorageMonitor::CreateAndInstall(); ASSERT_TRUE(monitor_); extensions::TestExtensionSystem* extension_system( static_cast( extensions::ExtensionSystem::Get(profile_.get()))); extension_system->CreateExtensionService( base::CommandLine::ForCurrentProcess(), base::FilePath(), false); gallery_prefs_ = MediaGalleriesPreferencesFactory::GetForProfile(profile_.get()); base::RunLoop loop; gallery_prefs_->EnsureInitialized(loop.QuitClosure()); loop.Run(); std::vector read_permissions; read_permissions.push_back( extensions::MediaGalleriesPermission::kReadPermission); extension_ = AddMediaGalleriesApp("read", read_permissions, profile_.get()); manager_.reset(new GalleryWatchManager); manager_->AddObserver(profile_.get(), this); } void TearDown() override { if (profile_) { manager_->RemoveObserver(profile_.get()); } manager_.reset(); storage_monitor::TestStorageMonitor::Destroy(); } protected: // Create the specified path, and add it to preferences as a gallery. MediaGalleryPrefId AddGallery(const base::FilePath& path) { MediaGalleryPrefInfo gallery_info; EXPECT_FALSE(gallery_prefs_->LookUpGalleryByPath(path, &gallery_info)); MediaGalleryPrefId id = gallery_prefs_->AddGallery(gallery_info.device_id, gallery_info.path, MediaGalleryPrefInfo::kUserAdded, gallery_info.volume_label, gallery_info.vendor_name, gallery_info.model_name, gallery_info.total_size_in_bytes, gallery_info.last_attach_time, 0, 0, 0); EXPECT_NE(kInvalidMediaGalleryPrefId, id); EXPECT_TRUE(gallery_prefs_->SetGalleryPermissionForExtension( *extension_, id, true)); return id; } TestingProfile* profile() { return profile_.get(); } GalleryWatchManager* manager() { return manager_.get(); } extensions::Extension* extension() { return extension_.get(); } MediaGalleriesPreferences* gallery_prefs() { return gallery_prefs_; } storage_monitor::TestStorageMonitor* storage_monitor() { return monitor_; } bool GalleryWatchesSupported() { return base::FilePathWatcher::RecursiveWatchAvailable(); } void AddAndConfirmWatch(MediaGalleryPrefId gallery_id) { base::RunLoop loop; manager()->AddWatch(profile(), extension(), gallery_id, base::Bind(&ConfirmWatch, base::Unretained(&loop))); loop.Run(); } void ExpectGalleryChanged(base::RunLoop* loop) { expect_gallery_changed_ = true; pending_loop_ = loop; } void ExpectGalleryWatchDropped(base::RunLoop* loop) { expect_gallery_watch_dropped_ = true; pending_loop_ = loop; } void ShutdownProfile() { profile_.reset(nullptr); } private: // GalleryWatchManagerObserver implementation. void OnGalleryChanged(const std::string& extension_id, MediaGalleryPrefId gallery_id) override { EXPECT_TRUE(expect_gallery_changed_); pending_loop_->Quit(); } void OnGalleryWatchDropped(const std::string& extension_id, MediaGalleryPrefId gallery_id) override { EXPECT_TRUE(expect_gallery_watch_dropped_); pending_loop_->Quit(); } scoped_ptr manager_; // Needed for extension service & friends to work. content::TestBrowserThreadBundle thread_bundle_; scoped_refptr extension_; EnsureMediaDirectoriesExists mock_gallery_locations_; #if defined(OS_CHROMEOS) chromeos::ScopedTestDeviceSettingsService test_device_settings_service_; chromeos::ScopedTestCrosSettings test_cros_settings_; chromeos::ScopedTestUserManager test_user_manager_; #endif storage_monitor::TestStorageMonitor* monitor_; scoped_ptr profile_; MediaGalleriesPreferences* gallery_prefs_; bool expect_gallery_changed_; bool expect_gallery_watch_dropped_; base::RunLoop* pending_loop_; DISALLOW_COPY_AND_ASSIGN(GalleryWatchManagerTest); }; TEST_F(GalleryWatchManagerTest, Basic) { base::ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); MediaGalleryPrefId id = AddGallery(temp_dir.path()); base::RunLoop loop; if (GalleryWatchesSupported()) { manager()->AddWatch(profile(), extension(), id, base::Bind(&ConfirmWatch, base::Unretained(&loop))); } else { manager()->AddWatch( profile(), extension(), id, base::Bind(&ExpectWatchError, base::Unretained(&loop), GalleryWatchManager::kCouldNotWatchGalleryError)); } loop.Run(); } TEST_F(GalleryWatchManagerTest, AddAndRemoveTwoWatches) { if (!GalleryWatchesSupported()) return; EXPECT_TRUE(manager()->GetWatchSet(profile(), extension()->id()).empty()); base::ScopedTempDir temp1; ASSERT_TRUE(temp1.CreateUniqueTempDir()); MediaGalleryPrefId id1 = AddGallery(temp1.path()); base::ScopedTempDir temp2; ASSERT_TRUE(temp2.CreateUniqueTempDir()); MediaGalleryPrefId id2 = AddGallery(temp2.path()); // Add first watch and test it was added correctly. AddAndConfirmWatch(id1); MediaGalleryPrefIdSet set1 = manager()->GetWatchSet(profile(), extension()->id()); EXPECT_EQ(1u, set1.size()); EXPECT_TRUE(ContainsKey(set1, id1)); // Test that the second watch was added correctly too. AddAndConfirmWatch(id2); MediaGalleryPrefIdSet set2 = manager()->GetWatchSet(profile(), extension()->id()); EXPECT_EQ(2u, set2.size()); EXPECT_TRUE(ContainsKey(set2, id1)); EXPECT_TRUE(ContainsKey(set2, id2)); // Remove first watch and test that the second is still in there. manager()->RemoveWatch(profile(), extension()->id(), id1); MediaGalleryPrefIdSet set3 = manager()->GetWatchSet(profile(), extension()->id()); EXPECT_EQ(1u, set3.size()); EXPECT_TRUE(ContainsKey(set3, id2)); // Try removing the first watch again and test that it has no effect. manager()->RemoveWatch(profile(), extension()->id(), id1); EXPECT_EQ(1u, manager()->GetWatchSet(profile(), extension()->id()).size()); // Remove the second watch and test that the new watch set is empty. manager()->RemoveWatch(profile(), extension()->id(), id2); EXPECT_TRUE(manager()->GetWatchSet(profile(), extension()->id()).empty()); } TEST_F(GalleryWatchManagerTest, RemoveAllWatches) { if (!GalleryWatchesSupported()) return; base::ScopedTempDir temp1; ASSERT_TRUE(temp1.CreateUniqueTempDir()); MediaGalleryPrefId id1 = AddGallery(temp1.path()); base::ScopedTempDir temp2; ASSERT_TRUE(temp2.CreateUniqueTempDir()); MediaGalleryPrefId id2 = AddGallery(temp2.path()); // Add watches. AddAndConfirmWatch(id1); AddAndConfirmWatch(id2); EXPECT_EQ(2u, manager()->GetWatchSet(profile(), extension()->id()).size()); // RemoveAllWatches using a different extension ID and verify watches remain. manager()->RemoveAllWatches(profile(), "OtherExtensionId"); EXPECT_EQ(2u, manager()->GetWatchSet(profile(), extension()->id()).size()); // RemoveAllWatches using the correct extension ID and verify watches gone. manager()->RemoveAllWatches(profile(), extension()->id()); EXPECT_TRUE(manager()->GetWatchSet(profile(), extension()->id()).empty()); } TEST_F(GalleryWatchManagerTest, DropWatchOnGalleryRemoved) { if (!GalleryWatchesSupported()) return; base::ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); MediaGalleryPrefId id = AddGallery(temp_dir.path()); AddAndConfirmWatch(id); base::RunLoop success_loop; ExpectGalleryWatchDropped(&success_loop); gallery_prefs()->EraseGalleryById(id); success_loop.Run(); } TEST_F(GalleryWatchManagerTest, DropWatchOnGalleryPermissionRevoked) { if (!GalleryWatchesSupported()) return; base::ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); MediaGalleryPrefId id = AddGallery(temp_dir.path()); AddAndConfirmWatch(id); base::RunLoop success_loop; ExpectGalleryWatchDropped(&success_loop); gallery_prefs()->SetGalleryPermissionForExtension(*extension(), id, false); success_loop.Run(); } TEST_F(GalleryWatchManagerTest, DropWatchOnStorageRemoved) { if (!GalleryWatchesSupported()) return; // Create a temporary directory and treat is as a removable storage device. base::ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); storage_monitor()->AddRemovablePath(temp_dir.path()); storage_monitor::StorageInfo storage_info; ASSERT_TRUE( storage_monitor()->GetStorageInfoForPath(temp_dir.path(), &storage_info)); storage_monitor()->receiver()->ProcessAttach(storage_info); MediaGalleryPrefId id = AddGallery(temp_dir.path()); AddAndConfirmWatch(id); base::RunLoop success_loop; ExpectGalleryWatchDropped(&success_loop); storage_monitor()->receiver()->ProcessDetach(storage_info.device_id()); success_loop.Run(); } TEST_F(GalleryWatchManagerTest, TestWatchOperation) { if (!GalleryWatchesSupported()) return; base::ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); MediaGalleryPrefId id = AddGallery(temp_dir.path()); AddAndConfirmWatch(id); base::RunLoop success_loop; ExpectGalleryChanged(&success_loop); ASSERT_EQ( 4, base::WriteFile(temp_dir.path().AppendASCII("fake file"), "blah", 4)); success_loop.Run(); } TEST_F(GalleryWatchManagerTest, TestWatchOperationAfterProfileShutdown) { if (!GalleryWatchesSupported()) return; base::ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); MediaGalleryPrefId id = AddGallery(temp_dir.path()); AddAndConfirmWatch(id); ShutdownProfile(); // Trigger a watch that should have been removed when the profile was // destroyed to catch regressions. crbug.com/467627 base::RunLoop run_loop; ASSERT_EQ( 4, base::WriteFile(temp_dir.path().AppendASCII("fake file"), "blah", 4)); run_loop.RunUntilIdle(); } TEST_F(GalleryWatchManagerTest, TestStorageRemovedAfterProfileShutdown) { if (!GalleryWatchesSupported()) return; // Create a temporary directory and treat is as a removable storage device. base::ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); storage_monitor()->AddRemovablePath(temp_dir.path()); storage_monitor::StorageInfo storage_info; ASSERT_TRUE( storage_monitor()->GetStorageInfoForPath(temp_dir.path(), &storage_info)); storage_monitor()->receiver()->ProcessAttach(storage_info); MediaGalleryPrefId id = AddGallery(temp_dir.path()); AddAndConfirmWatch(id); ShutdownProfile(); // Trigger a removable storage event that should be ignored now that the // profile has been destroyed to catch regressions. crbug.com/467627 base::RunLoop run_loop; storage_monitor()->receiver()->ProcessDetach(storage_info.device_id()); run_loop.RunUntilIdle(); } } // namespace component_updater