| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/file_manager/io_task_controller.h" |
| |
| #include <memory> |
| |
| #include "base/run_loop.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/task_environment.h" |
| #include "chrome/browser/ash/file_manager/io_task.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "storage/browser/file_system/file_system_url.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using base::test::RunClosure; |
| using testing::_; |
| using testing::AllOf; |
| using testing::Field; |
| using testing::Invoke; |
| using testing::Property; |
| |
| namespace file_manager { |
| namespace io_task { |
| namespace { |
| |
| MATCHER_P(EntryStatusUrls, matcher, "") { |
| std::vector<storage::FileSystemURL> urls; |
| for (const auto& status : arg) { |
| urls.push_back(status.url); |
| } |
| return testing::ExplainMatchResult(matcher, urls, result_listener); |
| } |
| |
| storage::FileSystemURL CreateFileSystemURL(std::string url) { |
| return storage::FileSystemURL::CreateForTest(GURL(url)); |
| } |
| |
| class IOTaskStatusObserver : public IOTaskController::Observer { |
| public: |
| MOCK_METHOD(void, OnIOTaskStatus, (const ProgressStatus&), (override)); |
| }; |
| |
| class IOTaskControllerTest : public testing::Test { |
| protected: |
| content::BrowserTaskEnvironment browser_task_environment_; |
| |
| IOTaskController io_task_controller_; |
| }; |
| |
| TEST_F(IOTaskControllerTest, SimpleQueueing) { |
| IOTaskStatusObserver observer; |
| io_task_controller_.AddObserver(&observer); |
| |
| std::vector<storage::FileSystemURL> source_urls{ |
| CreateFileSystemURL("filesystem:chrome-extension://abc/external/foo/src"), |
| }; |
| auto dest = CreateFileSystemURL( |
| "filesystem:chrome-extension://abc/external/foo/dest"); |
| |
| // All progress statuses should return the same |type|, |source_urls| and |
| // |destination_folder| as given, so set up a base matcher to check this. |
| auto base_matcher = |
| AllOf(Field(&ProgressStatus::type, OperationType::kCopy), |
| Field(&ProgressStatus::sources, EntryStatusUrls(source_urls)), |
| Property(&ProgressStatus::GetDestinationFolder, dest)); |
| |
| // The controller should synchronously send out a progress status when queued. |
| EXPECT_CALL(observer, OnIOTaskStatus( |
| AllOf(Field(&ProgressStatus::state, State::kQueued), |
| base_matcher))); |
| |
| // The controller should also synchronously execute the I/O task, which will |
| // send out another status. |
| EXPECT_CALL(observer, OnIOTaskStatus(AllOf( |
| Field(&ProgressStatus::state, State::kInProgress), |
| base_matcher))); |
| |
| // Queue the I/O task, which will also synchronously execute it. |
| EXPECT_EQ(0, io_task_controller_.wake_lock_counter_for_tests()); |
| auto task_id = io_task_controller_.Add( |
| std::make_unique<DummyIOTask>(source_urls, dest, OperationType::kCopy)); |
| EXPECT_EQ(1, io_task_controller_.wake_lock_counter_for_tests()); |
| EXPECT_FALSE(io_task_controller_.TaskStatuses().empty()); |
| EXPECT_EQ(io_task_controller_.TaskStatuses()[0].get().task_id, task_id); |
| |
| // Wait for the callbacks posted to the main sequence to finish. |
| { |
| base::RunLoop run_loop; |
| EXPECT_CALL(observer, OnIOTaskStatus(AllOf( |
| Field(&ProgressStatus::state, State::kSuccess), |
| base_matcher))) |
| .WillOnce(RunClosure(run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| EXPECT_TRUE(io_task_controller_.TaskStatuses().empty()); |
| |
| // Cancel() should have no effect once a task is completed. |
| io_task_controller_.Cancel(task_id); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(0, io_task_controller_.wake_lock_counter_for_tests()); |
| |
| io_task_controller_.RemoveObserver(&observer); |
| } |
| |
| TEST_F(IOTaskControllerTest, Cancel) { |
| IOTaskStatusObserver observer; |
| io_task_controller_.AddObserver(&observer); |
| |
| std::vector<storage::FileSystemURL> source_urls{ |
| CreateFileSystemURL("filesystem:chrome-extension://abc/external/foo/src"), |
| }; |
| auto dest = CreateFileSystemURL( |
| "filesystem:chrome-extension://abc/external/foo/dest"); |
| |
| // All progress statuses should return the same |type|, |source_urls| and |
| // |destination_folder| given, so set up a base matcher to check this. |
| auto base_matcher = |
| AllOf(Field(&ProgressStatus::type, OperationType::kMove), |
| Field(&ProgressStatus::sources, EntryStatusUrls(source_urls)), |
| Property(&ProgressStatus::GetDestinationFolder, dest)); |
| |
| // The controller should synchronously send out a progress status when queued. |
| EXPECT_CALL(observer, OnIOTaskStatus( |
| AllOf(Field(&ProgressStatus::state, State::kQueued), |
| base_matcher))); |
| |
| // The controller should also synchronously execute the I/O task, which will |
| // send out another status. |
| EXPECT_CALL(observer, OnIOTaskStatus(AllOf( |
| Field(&ProgressStatus::state, State::kInProgress), |
| base_matcher))); |
| |
| auto task_id = io_task_controller_.Add( |
| std::make_unique<DummyIOTask>(source_urls, dest, OperationType::kMove)); |
| EXPECT_FALSE(io_task_controller_.TaskStatuses().empty()); |
| EXPECT_EQ(io_task_controller_.TaskStatuses()[0].get().task_id, task_id); |
| |
| // Cancel should synchronously send a progress status. |
| EXPECT_CALL(observer, |
| OnIOTaskStatus(AllOf( |
| Field(&ProgressStatus::state, State::kCancelled), |
| Field(&ProgressStatus::task_id, task_id), base_matcher))); |
| |
| io_task_controller_.Cancel(task_id); |
| EXPECT_TRUE(io_task_controller_.TaskStatuses().empty()); |
| |
| // No more observer notifications should come after Cancel() as the task is |
| // deleted. |
| base::RunLoop().RunUntilIdle(); |
| |
| io_task_controller_.RemoveObserver(&observer); |
| } |
| |
| TEST_F(IOTaskControllerTest, PauseResume) { |
| IOTaskStatusObserver observer; |
| io_task_controller_.AddObserver(&observer); |
| |
| std::vector<storage::FileSystemURL> source_urls{ |
| CreateFileSystemURL("filesystem:chrome-extension://abc/external/foo/src"), |
| }; |
| auto dest = CreateFileSystemURL( |
| "filesystem:chrome-extension://abc/external/foo/dest"); |
| |
| // All progress statuses should return the same |type|, |source_urls| and |
| // |destination_folder| given, so set up a base matcher to check this. |
| auto base_matcher = |
| AllOf(Field(&ProgressStatus::type, OperationType::kMove), |
| Field(&ProgressStatus::sources, EntryStatusUrls(source_urls)), |
| Property(&ProgressStatus::GetDestinationFolder, dest)); |
| |
| // The controller should synchronously send out a progress status when queued. |
| EXPECT_CALL(observer, OnIOTaskStatus( |
| AllOf(Field(&ProgressStatus::state, State::kQueued), |
| base_matcher))); |
| |
| // The controller should also synchronously execute the I/O task, which will |
| // send out another status. |
| EXPECT_CALL(observer, OnIOTaskStatus(AllOf( |
| Field(&ProgressStatus::state, State::kInProgress), |
| base_matcher))); |
| |
| auto task_id = io_task_controller_.Add( |
| std::make_unique<DummyIOTask>(source_urls, dest, OperationType::kMove)); |
| EXPECT_FALSE(io_task_controller_.TaskStatuses().empty()); |
| EXPECT_EQ(io_task_controller_.TaskStatuses()[0].get().task_id, task_id); |
| |
| // Pause should synchronously send a progress status. |
| PauseParams pause_params; |
| pause_params.policy_params = PolicyPauseParams(policy::Policy::kDlp); |
| EXPECT_CALL(observer, OnIOTaskStatus(AllOf( |
| Field(&ProgressStatus::state, State::kPaused), |
| Field(&ProgressStatus::task_id, task_id), |
| Field(&ProgressStatus::pause_params, pause_params), |
| base_matcher))); |
| io_task_controller_.Pause(task_id, pause_params); |
| EXPECT_FALSE(io_task_controller_.TaskStatuses().empty()); |
| |
| // ProgressPausedTasks should synchronously send another paused status update. |
| EXPECT_CALL(observer, OnIOTaskStatus(AllOf( |
| Field(&ProgressStatus::state, State::kPaused), |
| Field(&ProgressStatus::task_id, task_id), |
| Field(&ProgressStatus::pause_params, pause_params), |
| base_matcher))); |
| io_task_controller_.ProgressPausedTasks(); |
| |
| // Resume should synchronously send a progress status. |
| EXPECT_CALL(observer, |
| OnIOTaskStatus(AllOf( |
| Field(&ProgressStatus::state, State::kInProgress), |
| Field(&ProgressStatus::task_id, task_id), base_matcher))); |
| io_task_controller_.Resume(task_id, ResumeParams()); |
| EXPECT_FALSE(io_task_controller_.TaskStatuses().empty()); |
| |
| // Wait for the task to finish successfully. |
| EXPECT_CALL(observer, |
| OnIOTaskStatus(AllOf( |
| Field(&ProgressStatus::state, State::kSuccess), |
| Field(&ProgressStatus::task_id, task_id), base_matcher))); |
| base::RunLoop().RunUntilIdle(); |
| |
| // ProgressPausedTasks shouldn't send any more updates after task completes. |
| EXPECT_CALL(observer, OnIOTaskStatus(AllOf( |
| Field(&ProgressStatus::state, State::kPaused), |
| Field(&ProgressStatus::task_id, task_id), |
| Field(&ProgressStatus::pause_params, pause_params), |
| base_matcher))) |
| .Times(0); |
| io_task_controller_.ProgressPausedTasks(); |
| |
| io_task_controller_.RemoveObserver(&observer); |
| } |
| |
| TEST_F(IOTaskControllerTest, CompleteWithError) { |
| IOTaskStatusObserver observer; |
| io_task_controller_.AddObserver(&observer); |
| |
| std::vector<storage::FileSystemURL> source_urls{ |
| CreateFileSystemURL("filesystem:chrome-extension://abc/external/foo/src"), |
| }; |
| auto dest = CreateFileSystemURL( |
| "filesystem:chrome-extension://abc/external/foo/dest"); |
| |
| // All progress statuses should return the same |type|, |source_urls| and |
| // |destination_folder| given, so set up a base matcher to check this. |
| auto base_matcher = |
| AllOf(Field(&ProgressStatus::type, OperationType::kMove), |
| Field(&ProgressStatus::sources, EntryStatusUrls(source_urls)), |
| Property(&ProgressStatus::GetDestinationFolder, dest)); |
| |
| // The controller should synchronously send out a progress status when queued. |
| EXPECT_CALL(observer, OnIOTaskStatus( |
| AllOf(Field(&ProgressStatus::state, State::kQueued), |
| base_matcher))); |
| |
| // The controller should also synchronously execute the I/O task, which will |
| // send out another status. |
| EXPECT_CALL(observer, OnIOTaskStatus(AllOf( |
| Field(&ProgressStatus::state, State::kInProgress), |
| base_matcher))); |
| |
| auto task_id = io_task_controller_.Add(std::make_unique<DummyIOTask>( |
| source_urls, dest, OperationType::kMove, |
| /*show_notification=*/true, /*progress_succeeds=*/false)); |
| EXPECT_FALSE(io_task_controller_.TaskStatuses().empty()); |
| EXPECT_EQ(io_task_controller_.TaskStatuses()[0].get().task_id, task_id); |
| |
| // CompleteWithError should synchronously send a progress status. |
| EXPECT_CALL(observer, |
| OnIOTaskStatus(AllOf(Field(&ProgressStatus::state, State::kError), |
| Field(&ProgressStatus::task_id, task_id), |
| Field(&ProgressStatus::policy_error, |
| PolicyError(PolicyErrorType::kDlp, |
| /*blocked_files=*/2)), |
| base_matcher))); |
| io_task_controller_.CompleteWithError( |
| task_id, PolicyError(PolicyErrorType::kDlp, /*blocked_files=*/2)); |
| |
| EXPECT_TRUE(io_task_controller_.TaskStatuses().empty()); |
| |
| // No more observer notifications should come after CompleteWithError as the |
| // task is deleted. |
| base::RunLoop().RunUntilIdle(); |
| |
| io_task_controller_.RemoveObserver(&observer); |
| } |
| |
| } // namespace |
| } // namespace io_task |
| } // namespace file_manager |