| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "remoting/host/file_transfer/ipc_file_operations.h" |
| |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/callback_list.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/path_service.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_path_override.h" |
| #include "base/test/task_environment.h" |
| #include "remoting/host/file_transfer/directory_helpers.h" |
| #include "remoting/host/file_transfer/ensure_user.h" |
| #include "remoting/host/file_transfer/fake_file_chooser.h" |
| #include "remoting/host/file_transfer/local_file_operations.h" |
| #include "remoting/host/file_transfer/session_file_operations_handler.h" |
| #include "remoting/host/file_transfer/test_byte_vector_utils.h" |
| #include "remoting/host/mojom/desktop_session.mojom.h" |
| #include "remoting/proto/file_transfer.pb.h" |
| #include "remoting/protocol/file_transfer_helpers.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace remoting { |
| |
| // Forward declare to allow for friending by the fake test classes. |
| class IpcFileOperationsTest; |
| |
| namespace { |
| |
| // A simplified DesktopSessionProxy implementation for file transfer testing. |
| class FakeDesktopSessionProxy : public IpcFileOperations::RequestHandler { |
| public: |
| FakeDesktopSessionProxy() = default; |
| |
| FakeDesktopSessionProxy(const FakeDesktopSessionProxy&) = delete; |
| FakeDesktopSessionProxy& operator=(const FakeDesktopSessionProxy&) = delete; |
| |
| ~FakeDesktopSessionProxy() override = default; |
| |
| // IpcFileOperations::RequestHandler implementation. |
| void BeginFileRead(IpcFileOperations::BeginFileReadCallback callback, |
| base::OnceClosure on_disconnect) override; |
| void BeginFileWrite(const base::FilePath& file_path, |
| IpcFileOperations::BeginFileWriteCallback callback, |
| base::OnceClosure on_disconnect) override; |
| |
| // Binds the pending DesktopSessionControl remote to |remote_|. |
| void Bind(mojo::PendingAssociatedRemote<mojom::DesktopSessionControl> remote); |
| |
| // When set, this instance will return |error| on the next IPC request. |
| void SetErrorForNextRequest(protocol::FileTransfer_Error error); |
| |
| // Runs any registered disconnect handlers. |
| void TriggerDisconnectHandlers(); |
| |
| private: |
| // This member mirrors the handler in the real DesktopSessionProxy class. |
| base::OnceClosureList disconnect_handlers_; |
| |
| // Holds disconnect handler subscriptions until they are either triggered or |
| // destroyed along with this test instance. |
| std::vector<base::CallbackListSubscription> disconnect_subscriptions_; |
| |
| // If set, this will be returned on the next file transfer operation request. |
| absl::optional<protocol::FileTransfer_Error> request_error_; |
| |
| // Remote end of the DesktopSessionControl channel, the receiver is owned by |
| // a FakeDesktopSessionAgent instance. |
| mojo::AssociatedRemote<mojom::DesktopSessionControl> remote_; |
| }; |
| |
| void FakeDesktopSessionProxy::BeginFileRead( |
| IpcFileOperations::BeginFileReadCallback callback, |
| base::OnceClosure on_disconnect) { |
| if (request_error_) { |
| std::move(callback).Run( |
| mojom::BeginFileReadResult::NewError(std::move(*request_error_))); |
| return; |
| } |
| |
| disconnect_subscriptions_.emplace_back( |
| disconnect_handlers_.Add(std::move(on_disconnect))); |
| remote_->BeginFileRead(std::move(callback)); |
| } |
| |
| void FakeDesktopSessionProxy::BeginFileWrite( |
| const base::FilePath& file_path, |
| IpcFileOperations::BeginFileWriteCallback callback, |
| base::OnceClosure on_disconnect) { |
| if (request_error_) { |
| std::move(callback).Run( |
| mojom::BeginFileWriteResult::NewError(std::move(*request_error_))); |
| return; |
| } |
| disconnect_subscriptions_.emplace_back( |
| disconnect_handlers_.Add(std::move(on_disconnect))); |
| remote_->BeginFileWrite(file_path, std::move(callback)); |
| } |
| |
| void FakeDesktopSessionProxy::Bind( |
| mojo::PendingAssociatedRemote<mojom::DesktopSessionControl> remote) { |
| remote_.Bind(std::move(remote)); |
| remote_.set_disconnect_handler( |
| base::BindOnce(&FakeDesktopSessionProxy::TriggerDisconnectHandlers, |
| base::Unretained(this))); |
| } |
| |
| void FakeDesktopSessionProxy::SetErrorForNextRequest( |
| protocol::FileTransfer_Error error) { |
| request_error_ = std::move(error); |
| } |
| |
| void FakeDesktopSessionProxy::TriggerDisconnectHandlers() { |
| disconnect_handlers_.Notify(); |
| } |
| |
| // A simplified DesktopSessionAgent implementation for file transfer testing. |
| class FakeDesktopSessionAgent : public mojom::DesktopSessionControl { |
| public: |
| explicit FakeDesktopSessionAgent( |
| scoped_refptr<base::SequencedTaskRunner> ui_task_runner); |
| |
| FakeDesktopSessionAgent(const FakeDesktopSessionAgent&) = delete; |
| FakeDesktopSessionAgent& operator=(const FakeDesktopSessionAgent&) = delete; |
| |
| ~FakeDesktopSessionAgent() override = default; |
| |
| // mojom::DesktopSessionControl implementation. |
| void CaptureFrame() override; |
| void SelectSource(int id) override; |
| void SetScreenResolution(const ScreenResolution& resolution) override; |
| void LockWorkstation() override; |
| void InjectSendAttentionSequence() override; |
| void InjectClipboardEvent(const protocol::ClipboardEvent& event) override; |
| void InjectKeyEvent(const protocol::KeyEvent& event) override; |
| void InjectMouseEvent(const protocol::MouseEvent& event) override; |
| void InjectTextEvent(const protocol::TextEvent& event) override; |
| void InjectTouchEvent(const protocol::TouchEvent& event) override; |
| void SetUpUrlForwarder() override; |
| void SignalWebAuthnExtension() override; |
| void BeginFileRead(BeginFileReadCallback callback) override; |
| void BeginFileWrite(const base::FilePath& file_path, |
| BeginFileWriteCallback callback) override; |
| |
| // Binds the pending DesktopSessionControl receiver to |receiver_|. |
| void Bind( |
| mojo::PendingAssociatedReceiver<mojom::DesktopSessionControl> receiver); |
| |
| // When set, this instance will return |error| for its next IPC response. |
| void SetErrorForNextResponse(protocol::FileTransfer_Error error); |
| |
| // Disconnect |receiver_| after the next request is received. This is used to |
| // simulate an error while the FakeDesktopSessionProxy is waiting for a reply. |
| void DisconnectReceiverOnNextRequest(); |
| |
| private: |
| friend class ::remoting::IpcFileOperationsTest; |
| |
| // If true, the agent will disconnect |receiver_| on the next IPC request. |
| bool disconnect_on_next_request_ = false; |
| |
| // If set, |response_error_| will be returned in the next IPC response. |
| absl::optional<protocol::FileTransfer_Error> response_error_; |
| |
| // Handles file transfer requests over Mojo and manages receiver lifetimes. |
| SessionFileOperationsHandler session_file_operations_handler_; |
| |
| // Receiver end of the DesktopSessionControl channel, the remote is owned by a |
| // FakeDesktopSessionProxy instance. |
| mojo::AssociatedReceiver<mojom::DesktopSessionControl> receiver_{this}; |
| }; |
| |
| FakeDesktopSessionAgent::FakeDesktopSessionAgent( |
| scoped_refptr<base::SequencedTaskRunner> ui_task_runner) |
| : session_file_operations_handler_( |
| std::make_unique<LocalFileOperations>(std::move(ui_task_runner))) {} |
| |
| void FakeDesktopSessionAgent::CaptureFrame() {} |
| |
| void FakeDesktopSessionAgent::SelectSource(int id) {} |
| |
| void FakeDesktopSessionAgent::SetScreenResolution( |
| const ScreenResolution& resolution) {} |
| |
| void FakeDesktopSessionAgent::LockWorkstation() {} |
| |
| void FakeDesktopSessionAgent::InjectSendAttentionSequence() {} |
| |
| void FakeDesktopSessionAgent::InjectClipboardEvent( |
| const protocol::ClipboardEvent& event) {} |
| |
| void FakeDesktopSessionAgent::InjectKeyEvent(const protocol::KeyEvent& event) {} |
| |
| void FakeDesktopSessionAgent::InjectMouseEvent( |
| const protocol::MouseEvent& event) {} |
| |
| void FakeDesktopSessionAgent::InjectTextEvent( |
| const protocol::TextEvent& event) {} |
| |
| void FakeDesktopSessionAgent::InjectTouchEvent( |
| const protocol::TouchEvent& event) {} |
| |
| void FakeDesktopSessionAgent::SetUpUrlForwarder() {} |
| |
| void FakeDesktopSessionAgent::SignalWebAuthnExtension() {} |
| |
| void FakeDesktopSessionAgent::BeginFileRead(BeginFileReadCallback callback) { |
| if (disconnect_on_next_request_) { |
| disconnect_on_next_request_ = false; |
| receiver_.reset(); |
| return; |
| } |
| if (response_error_) { |
| std::move(callback).Run( |
| mojom::BeginFileReadResult::NewError(std::move(*response_error_))); |
| return; |
| } |
| session_file_operations_handler_.BeginFileRead(std::move(callback)); |
| } |
| |
| void FakeDesktopSessionAgent::BeginFileWrite(const base::FilePath& file_path, |
| BeginFileWriteCallback callback) { |
| if (disconnect_on_next_request_) { |
| disconnect_on_next_request_ = false; |
| receiver_.reset(); |
| return; |
| } |
| if (response_error_) { |
| std::move(callback).Run( |
| mojom::BeginFileWriteResult::NewError(std::move(*response_error_))); |
| return; |
| } |
| session_file_operations_handler_.BeginFileWrite(file_path, |
| std::move(callback)); |
| } |
| |
| void FakeDesktopSessionAgent::Bind( |
| mojo::PendingAssociatedReceiver<mojom::DesktopSessionControl> receiver) { |
| receiver_.Bind(std::move(receiver)); |
| } |
| |
| void FakeDesktopSessionAgent::SetErrorForNextResponse( |
| protocol::FileTransfer_Error error) { |
| response_error_ = std::move(error); |
| } |
| |
| void FakeDesktopSessionAgent::DisconnectReceiverOnNextRequest() { |
| disconnect_on_next_request_ = true; |
| } |
| |
| } // namespace |
| |
| class IpcFileOperationsTest : public testing::Test { |
| public: |
| IpcFileOperationsTest(); |
| |
| IpcFileOperationsTest(const IpcFileOperationsTest&) = delete; |
| IpcFileOperationsTest& operator=(const IpcFileOperationsTest&) = delete; |
| |
| ~IpcFileOperationsTest() override; |
| |
| void SetUp() override; |
| |
| protected: |
| const base::FilePath kTestFilename = |
| base::FilePath::FromUTF8Unsafe("test-file.txt"); |
| const std::vector<std::uint8_t> kTestDataOne = |
| ByteArrayFrom("this is the first test string"); |
| const std::vector<std::uint8_t> kTestDataTwo = |
| ByteArrayFrom("this is the second test string"); |
| const std::vector<std::uint8_t> kTestDataThree = |
| ByteArrayFrom("this is the third test string"); |
| |
| base::FilePath TestDir(); |
| |
| size_t session_file_reader_count() const { |
| return fake_desktop_session_agent_.session_file_operations_handler_ |
| .file_readers_.size(); |
| } |
| |
| size_t session_file_writer_count() const { |
| return fake_desktop_session_agent_.session_file_operations_handler_ |
| .file_writers_.size(); |
| } |
| |
| // Destroys existing MojoFileReader and MojoFileWriter instances. This will |
| // also trigger any disconnect handlers set on the Mojo remote owned by the |
| // corresponding IpcFileReader / IpcFileWriter instances. |
| void clear_session_file_receivers() { |
| fake_desktop_session_agent_.session_file_operations_handler_.file_readers_ |
| .Clear(); |
| fake_desktop_session_agent_.session_file_operations_handler_.file_writers_ |
| .Clear(); |
| } |
| |
| // Points DIR_USER_DESKTOP at a scoped temporary directory. |
| base::ScopedPathOverride scoped_path_override_{base::DIR_USER_DESKTOP}; |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::MainThreadType::DEFAULT, |
| base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED}; |
| |
| FakeDesktopSessionProxy fake_desktop_session_proxy_; |
| FakeDesktopSessionAgent fake_desktop_session_agent_{ |
| task_environment_.GetMainThreadTaskRunner()}; |
| |
| IpcFileOperationsFactory ipc_file_operations_factory_{ |
| &fake_desktop_session_proxy_}; |
| |
| std::unique_ptr<FileOperations> file_operations_{ |
| ipc_file_operations_factory_.CreateFileOperations()}; |
| }; |
| |
| IpcFileOperationsTest::IpcFileOperationsTest() = default; |
| |
| IpcFileOperationsTest::~IpcFileOperationsTest() = default; |
| |
| void IpcFileOperationsTest::SetUp() { |
| // Connect the fake proxy and agent using a pair of associated endpoints. The |
| // real classes would use a pre-existing IPC channel but we don't need this |
| // for our testing. |
| mojo::AssociatedRemote<mojom::DesktopSessionControl> remote; |
| fake_desktop_session_agent_.Bind( |
| remote.BindNewEndpointAndPassDedicatedReceiver()); |
| fake_desktop_session_proxy_.Bind(remote.Unbind()); |
| |
| DisableUserContextCheckForTesting(); |
| SetFileUploadDirectoryForTesting(TestDir()); |
| } |
| |
| base::FilePath IpcFileOperationsTest::TestDir() { |
| base::FilePath result; |
| EXPECT_TRUE(base::PathService::Get(base::DIR_USER_DESKTOP, &result)); |
| return result; |
| } |
| |
| // Verifies that a file consisting of three chunks can be written successfully. |
| TEST_F(IpcFileOperationsTest, WritesThreeChunks) { |
| std::unique_ptr<FileOperations::Writer> writer = |
| file_operations_->CreateWriter(); |
| ASSERT_EQ(FileOperations::kCreated, writer->state()); |
| |
| absl::optional<FileOperations::Writer::Result> open_result; |
| writer->Open(kTestFilename, base::BindLambdaForTesting( |
| [&](FileOperations::Writer::Result result) { |
| open_result = std::move(result); |
| })); |
| ASSERT_EQ(FileOperations::kBusy, writer->state()); |
| task_environment_.RunUntilIdle(); |
| ASSERT_EQ(FileOperations::kReady, writer->state()); |
| ASSERT_TRUE(open_result); |
| ASSERT_TRUE(*open_result); |
| ASSERT_EQ(session_file_writer_count(), size_t{1}); |
| |
| for (const auto& chunk : {kTestDataOne, kTestDataTwo, kTestDataThree}) { |
| absl::optional<FileOperations::Writer::Result> write_result; |
| writer->WriteChunk(chunk, base::BindLambdaForTesting( |
| [&](FileOperations::Writer::Result result) { |
| write_result = std::move(result); |
| })); |
| ASSERT_EQ(FileOperations::kBusy, writer->state()); |
| task_environment_.RunUntilIdle(); |
| ASSERT_EQ(FileOperations::kReady, writer->state()); |
| ASSERT_TRUE(write_result); |
| ASSERT_TRUE(*write_result); |
| } |
| |
| absl::optional<FileOperations::Writer::Result> close_result; |
| writer->Close( |
| base::BindLambdaForTesting([&](FileOperations::Writer::Result result) { |
| close_result = std::move(result); |
| })); |
| ASSERT_EQ(FileOperations::kBusy, writer->state()); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(FileOperations::kComplete, writer->state()); |
| // Verify the MojoIpcWriter instance was released. |
| ASSERT_EQ(session_file_writer_count(), size_t{0}); |
| |
| std::string actual_file_data; |
| ASSERT_TRUE(base::ReadFileToString(TestDir().Append(kTestFilename), |
| &actual_file_data)); |
| EXPECT_EQ(ByteArrayFrom(kTestDataOne, kTestDataTwo, kTestDataThree), |
| ByteArrayFrom(actual_file_data)); |
| } |
| |
| // Verifies that dropping early cancels the remote writer. |
| TEST_F(IpcFileOperationsTest, DroppingCancelsRemoteWriter) { |
| std::unique_ptr<FileOperations::Writer> writer = |
| file_operations_->CreateWriter(); |
| |
| absl::optional<FileOperations::Writer::Result> open_result; |
| writer->Open(kTestFilename, base::BindLambdaForTesting( |
| [&](FileOperations::Writer::Result result) { |
| open_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| ASSERT_TRUE(open_result); |
| ASSERT_TRUE(*open_result); |
| ASSERT_EQ(session_file_writer_count(), size_t{1}); |
| |
| for (const auto& chunk : {kTestDataOne, kTestDataTwo, kTestDataThree}) { |
| absl::optional<FileOperations::Writer::Result> write_result; |
| writer->WriteChunk(chunk, base::BindLambdaForTesting( |
| [&](FileOperations::Writer::Result result) { |
| write_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| ASSERT_TRUE(write_result); |
| ASSERT_TRUE(*write_result); |
| } |
| |
| writer.reset(); |
| task_environment_.RunUntilIdle(); |
| |
| EXPECT_TRUE(base::IsDirectoryEmpty(TestDir())); |
| |
| // Verify the MojoIpcWriter instance was released. |
| ASSERT_EQ(session_file_writer_count(), size_t{0}); |
| } |
| |
| // Verifies that dropping works while an operation is pending. |
| TEST_F(IpcFileOperationsTest, CancelsWhileOperationPending) { |
| std::unique_ptr<FileOperations::Writer> writer = |
| file_operations_->CreateWriter(); |
| |
| absl::optional<FileOperations::Writer::Result> open_result; |
| writer->Open(kTestFilename, base::BindLambdaForTesting( |
| [&](FileOperations::Writer::Result result) { |
| open_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| ASSERT_TRUE(open_result); |
| ASSERT_TRUE(*open_result); |
| ASSERT_EQ(session_file_writer_count(), size_t{1}); |
| |
| absl::optional<FileOperations::Writer::Result> write_result; |
| writer->WriteChunk( |
| kTestDataOne, |
| base::BindLambdaForTesting([&](FileOperations::Writer::Result result) { |
| write_result = std::move(result); |
| })); |
| |
| EXPECT_EQ(FileOperations::kBusy, writer->state()); |
| writer.reset(); |
| task_environment_.RunUntilIdle(); |
| |
| EXPECT_FALSE(write_result); |
| EXPECT_TRUE(base::IsDirectoryEmpty(TestDir())); |
| |
| // Verify the MojoIpcWriter instance was released. |
| ASSERT_EQ(session_file_writer_count(), size_t{0}); |
| } |
| |
| // Verifies that a file can be successfully read in three chunks. |
| TEST_F(IpcFileOperationsTest, ReadsThreeChunks) { |
| base::FilePath path = TestDir().Append(kTestFilename); |
| std::vector<std::uint8_t> contents = |
| ByteArrayFrom(kTestDataOne, kTestDataTwo, kTestDataThree); |
| ASSERT_TRUE(base::WriteFile(path, contents)); |
| |
| std::unique_ptr<FileOperations::Reader> reader = |
| file_operations_->CreateReader(); |
| ASSERT_EQ(FileOperations::kCreated, reader->state()); |
| |
| FakeFileChooser::SetResult(path); |
| absl::optional<FileOperations::Reader::OpenResult> open_result; |
| reader->Open(base::BindLambdaForTesting( |
| [&](FileOperations::Reader::OpenResult result) { |
| open_result = std::move(result); |
| })); |
| ASSERT_EQ(FileOperations::kBusy, reader->state()); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(FileOperations::kReady, reader->state()); |
| ASSERT_TRUE(open_result); |
| ASSERT_TRUE(*open_result); |
| ASSERT_EQ(session_file_reader_count(), size_t{1}); |
| |
| for (const auto& chunk : {kTestDataOne, kTestDataTwo, kTestDataThree}) { |
| absl::optional<FileOperations::Reader::ReadResult> read_result; |
| reader->ReadChunk(chunk.size(), |
| base::BindLambdaForTesting( |
| [&](FileOperations::Reader::ReadResult result) { |
| read_result = std::move(result); |
| })); |
| ASSERT_EQ(FileOperations::kBusy, reader->state()); |
| task_environment_.RunUntilIdle(); |
| ASSERT_EQ(FileOperations::kReady, reader->state()); |
| ASSERT_TRUE(read_result); |
| ASSERT_TRUE(*read_result); |
| EXPECT_EQ(chunk, **read_result); |
| } |
| ASSERT_EQ(session_file_reader_count(), size_t{1}); |
| |
| reader.reset(); |
| task_environment_.RunUntilIdle(); |
| |
| // Verify the MojoIpcReader instance was released. |
| ASSERT_EQ(session_file_reader_count(), size_t{0}); |
| } |
| |
| // Verifies proper EOF handling. |
| TEST_F(IpcFileOperationsTest, ReaderHandlesEof) { |
| constexpr std::size_t kOverreadAmount = 5; |
| base::FilePath path = TestDir().Append(kTestFilename); |
| std::vector<std::uint8_t> contents = |
| ByteArrayFrom(kTestDataOne, kTestDataTwo, kTestDataThree); |
| ASSERT_TRUE(base::WriteFile(path, contents)); |
| |
| std::unique_ptr<FileOperations::Reader> reader = |
| file_operations_->CreateReader(); |
| |
| FakeFileChooser::SetResult(path); |
| absl::optional<FileOperations::Reader::OpenResult> open_result; |
| reader->Open(base::BindLambdaForTesting( |
| [&](FileOperations::Reader::OpenResult result) { |
| open_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| ASSERT_TRUE(open_result); |
| ASSERT_TRUE(*open_result); |
| ASSERT_EQ(session_file_reader_count(), size_t{1}); |
| |
| absl::optional<FileOperations::Reader::ReadResult> read_result; |
| reader->ReadChunk( |
| contents.size() + |
| kOverreadAmount, // Attempt to read more than is in file. |
| base::BindLambdaForTesting( |
| [&](FileOperations::Reader::ReadResult result) { |
| read_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| ASSERT_EQ(FileOperations::kReady, reader->state()); |
| ASSERT_TRUE(read_result); |
| ASSERT_TRUE(*read_result); |
| EXPECT_EQ(contents, **read_result); |
| ASSERT_EQ(session_file_reader_count(), size_t{1}); |
| |
| read_result.reset(); |
| reader->ReadChunk(kOverreadAmount, |
| base::BindLambdaForTesting( |
| [&](FileOperations::Reader::ReadResult result) { |
| read_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(FileOperations::kComplete, reader->state()); |
| ASSERT_TRUE(read_result); |
| ASSERT_TRUE(*read_result); |
| EXPECT_EQ(std::size_t{0}, (*read_result)->size()); |
| |
| // Verify the MojoIpcReader instance was released. |
| ASSERT_EQ(session_file_reader_count(), size_t{0}); |
| } |
| |
| // Verifies proper handling of zero-size file |
| TEST_F(IpcFileOperationsTest, ReaderHandlesZeroSize) { |
| constexpr std::size_t kChunkSize = 5; |
| base::FilePath path = TestDir().Append(kTestFilename); |
| ASSERT_TRUE(base::WriteFile(path, "")); |
| |
| std::unique_ptr<FileOperations::Reader> reader = |
| file_operations_->CreateReader(); |
| |
| FakeFileChooser::SetResult(path); |
| absl::optional<FileOperations::Reader::OpenResult> open_result; |
| reader->Open(base::BindLambdaForTesting( |
| [&](FileOperations::Reader::OpenResult result) { |
| open_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| ASSERT_TRUE(open_result); |
| ASSERT_TRUE(*open_result); |
| ASSERT_EQ(session_file_reader_count(), size_t{1}); |
| |
| absl::optional<FileOperations::Reader::ReadResult> read_result; |
| reader->ReadChunk(kChunkSize, |
| base::BindLambdaForTesting( |
| [&](FileOperations::Reader::ReadResult result) { |
| read_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(FileOperations::kComplete, reader->state()); |
| ASSERT_TRUE(read_result); |
| ASSERT_TRUE(*read_result); |
| EXPECT_EQ(std::size_t{0}, (*read_result)->size()); |
| |
| // Verify the MojoIpcReader instance was released. |
| ASSERT_EQ(session_file_reader_count(), size_t{0}); |
| } |
| |
| // Concurrent Read operations are handled and valid data is retuned. |
| TEST_F(IpcFileOperationsTest, ConcurrentReadOperationsSupported) { |
| base::FilePath base_path = TestDir().Append(kTestFilename); |
| std::vector<base::FilePath> paths{ |
| base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(0)")), |
| base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(1)")), |
| base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(2)")), |
| base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(3)")), |
| base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(4)"))}; |
| |
| std::vector<std::uint8_t> contents = |
| ByteArrayFrom(kTestDataOne, kTestDataTwo, kTestDataThree); |
| for (const auto& path : paths) { |
| ASSERT_TRUE(base::WriteFile(path, contents)); |
| } |
| |
| std::vector<std::unique_ptr<FileOperations::Reader>> readers; |
| for (size_t i = 0; i < paths.size(); i++) { |
| readers.emplace_back(file_operations_->CreateReader()); |
| } |
| |
| int reader_count = static_cast<int>(readers.size()); |
| for (int i = 0; i < reader_count; i++) { |
| FakeFileChooser::SetResult(paths[i]); |
| absl::optional<FileOperations::Reader::OpenResult> open_result; |
| readers[i]->Open(base::BindLambdaForTesting( |
| [&](FileOperations::Reader::OpenResult result) { |
| open_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| ASSERT_TRUE(open_result); |
| ASSERT_TRUE(*open_result); |
| ASSERT_EQ(session_file_reader_count(), static_cast<size_t>(i + 1)); |
| } |
| ASSERT_EQ(session_file_reader_count(), readers.size()); |
| |
| for (int i = 0; i < reader_count; i++) { |
| for (const auto& chunk : {kTestDataOne, kTestDataTwo, kTestDataThree}) { |
| absl::optional<FileOperations::Reader::ReadResult> read_result; |
| readers[i]->ReadChunk(chunk.size(), |
| base::BindLambdaForTesting( |
| [&](FileOperations::Reader::ReadResult result) { |
| read_result = std::move(result); |
| })); |
| ASSERT_EQ(FileOperations::kBusy, readers[i]->state()); |
| task_environment_.RunUntilIdle(); |
| ASSERT_EQ(FileOperations::kReady, readers[i]->state()); |
| ASSERT_TRUE(read_result); |
| ASSERT_TRUE(*read_result); |
| EXPECT_EQ(chunk, **read_result); |
| } |
| } |
| ASSERT_EQ(session_file_reader_count(), readers.size()); |
| |
| for (int i = reader_count - 1; i >= 0; i--) { |
| absl::optional<FileOperations::Reader::ReadResult> read_result; |
| // Simulate EOF by reading 1 additional byte. |
| readers[i]->ReadChunk(1, |
| base::BindLambdaForTesting( |
| [&](FileOperations::Reader::ReadResult result) { |
| read_result = std::move(result); |
| })); |
| ASSERT_EQ(FileOperations::kBusy, readers[i]->state()); |
| task_environment_.RunUntilIdle(); |
| ASSERT_EQ(FileOperations::kComplete, readers[i]->state()); |
| ASSERT_TRUE(read_result); |
| // Verify each MojoIpcReader instance is released as it completes and that |
| // other instances are retained. |
| ASSERT_EQ(session_file_reader_count(), static_cast<size_t>(i)); |
| } |
| ASSERT_EQ(session_file_reader_count(), size_t{0}); |
| } |
| |
| // Concurrent Write operations handled. |
| TEST_F(IpcFileOperationsTest, ConcurrentWriteOperationsSupported) { |
| base::FilePath base_path = TestDir().Append(kTestFilename); |
| std::vector<base::FilePath> paths{ |
| base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(0)")), |
| base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(1)")), |
| base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(2)")), |
| base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(3)")), |
| base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(4)"))}; |
| |
| std::vector<std::uint8_t> contents = |
| ByteArrayFrom(kTestDataOne, kTestDataTwo, kTestDataThree); |
| |
| std::vector<std::unique_ptr<FileOperations::Writer>> writers; |
| for (size_t i = 0; i < paths.size(); i++) { |
| writers.emplace_back(file_operations_->CreateWriter()); |
| } |
| |
| int writer_count = static_cast<int>(writers.size()); |
| for (int i = 0; i < writer_count; i++) { |
| absl::optional<FileOperations::Writer::Result> open_result; |
| writers[i]->Open(paths[i], base::BindLambdaForTesting( |
| [&](FileOperations::Writer::Result result) { |
| open_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| ASSERT_TRUE(open_result); |
| ASSERT_TRUE(*open_result); |
| ASSERT_EQ(session_file_writer_count(), static_cast<size_t>(i + 1)); |
| } |
| ASSERT_EQ(session_file_writer_count(), writers.size()); |
| |
| for (const auto& chunk : {kTestDataOne, kTestDataTwo, kTestDataThree}) { |
| for (int i = 0; i < writer_count; i++) { |
| absl::optional<FileOperations::Writer::Result> write_result; |
| writers[i]->WriteChunk(chunk, |
| base::BindLambdaForTesting( |
| [&](FileOperations::Writer::Result result) { |
| write_result = std::move(result); |
| })); |
| EXPECT_EQ(writers[i]->state(), FileOperations::kBusy); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(writers[i]->state(), FileOperations::kReady); |
| ASSERT_TRUE(write_result); |
| ASSERT_FALSE(write_result->is_error()); |
| ASSERT_TRUE(*write_result); |
| } |
| } |
| ASSERT_EQ(session_file_writer_count(), writers.size()); |
| |
| for (int i = writer_count - 1; i >= 0; i--) { |
| absl::optional<FileOperations::Writer::Result> close_result; |
| writers[i]->Close( |
| base::BindLambdaForTesting([&](FileOperations::Writer::Result result) { |
| close_result = std::move(result); |
| })); |
| ASSERT_EQ(writers[i]->state(), FileOperations::kBusy); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(writers[i]->state(), FileOperations::kComplete); |
| ASSERT_TRUE(close_result); |
| ASSERT_FALSE(close_result->is_error()); |
| ASSERT_TRUE(*close_result); |
| // Verify each MojoIpcWriter instance is released as it completes and that |
| // any other instances are still retained. |
| ASSERT_EQ(session_file_writer_count(), static_cast<size_t>(i)); |
| } |
| ASSERT_EQ(session_file_writer_count(), size_t{0}); |
| ASSERT_FALSE(base::IsDirectoryEmpty(TestDir())); |
| } |
| |
| // Concurrent Read and Write operations handled. |
| TEST_F(IpcFileOperationsTest, ConcurrentReadAndWriteOperationsSupported) { |
| std::vector<std::uint8_t> contents = |
| ByteArrayFrom(kTestDataOne, kTestDataTwo, kTestDataThree); |
| base::FilePath base_path = TestDir().Append(kTestFilename); |
| |
| std::unique_ptr<FileOperations::Reader> reader = |
| file_operations_->CreateReader(); |
| std::unique_ptr<FileOperations::Writer> writer = |
| file_operations_->CreateWriter(); |
| |
| // Write to the read path first so that the writer can select a 'unique' file |
| // which doesn't conflict with this |read_path|. |
| base::FilePath read_path( |
| base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(read)"))); |
| ASSERT_TRUE(base::WriteFile(read_path, contents)); |
| |
| // Pending open file operations. |
| absl::optional<FileOperations::Writer::Result> open_for_write_result; |
| writer->Open( |
| base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(write)")), |
| base::BindLambdaForTesting([&](FileOperations::Writer::Result result) { |
| open_for_write_result = std::move(result); |
| })); |
| FakeFileChooser::SetResult(read_path); |
| absl::optional<FileOperations::Reader::OpenResult> open_for_read_result; |
| reader->Open(base::BindLambdaForTesting( |
| [&](FileOperations::Reader::OpenResult result) { |
| open_for_read_result = std::move(result); |
| })); |
| EXPECT_EQ(writer->state(), FileOperations::kBusy); |
| EXPECT_EQ(reader->state(), FileOperations::kBusy); |
| // Complete the operations. |
| task_environment_.RunUntilIdle(); |
| // Validate results. |
| EXPECT_EQ(writer->state(), FileOperations::kReady); |
| EXPECT_EQ(reader->state(), FileOperations::kReady); |
| ASSERT_TRUE(open_for_write_result); |
| ASSERT_TRUE(open_for_read_result); |
| ASSERT_TRUE(*open_for_write_result); |
| ASSERT_TRUE(*open_for_read_result); |
| ASSERT_EQ(session_file_writer_count(), size_t{1}); |
| ASSERT_EQ(session_file_reader_count(), size_t{1}); |
| |
| // Pending write operation. |
| absl::optional<FileOperations::Writer::Result> write_result; |
| writer->WriteChunk(contents, base::BindLambdaForTesting( |
| [&](FileOperations::Writer::Result result) { |
| write_result = std::move(result); |
| })); |
| // Pending read operation. |
| absl::optional<FileOperations::Reader::ReadResult> read_result; |
| reader->ReadChunk(contents.size(), |
| base::BindLambdaForTesting( |
| [&](FileOperations::Reader::ReadResult result) { |
| read_result = std::move(result); |
| })); |
| EXPECT_EQ(writer->state(), FileOperations::kBusy); |
| EXPECT_EQ(reader->state(), FileOperations::kBusy); |
| // Complete the pending operations. |
| task_environment_.RunUntilIdle(); |
| // Validate the results. |
| EXPECT_EQ(writer->state(), FileOperations::kReady); |
| EXPECT_EQ(reader->state(), FileOperations::kReady); |
| ASSERT_TRUE(write_result); |
| ASSERT_TRUE(read_result); |
| ASSERT_TRUE(*write_result); |
| ASSERT_TRUE(*read_result); |
| ASSERT_EQ(session_file_writer_count(), size_t{1}); |
| ASSERT_EQ(session_file_reader_count(), size_t{1}); |
| |
| // Close the writer. |
| absl::optional<FileOperations::Writer::Result> close_result; |
| writer->Close( |
| base::BindLambdaForTesting([&](FileOperations::Writer::Result result) { |
| close_result = std::move(result); |
| })); |
| ASSERT_EQ(writer->state(), FileOperations::kBusy); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(writer->state(), FileOperations::kComplete); |
| ASSERT_TRUE(close_result); |
| ASSERT_TRUE(*close_result); |
| ASSERT_EQ(session_file_writer_count(), size_t{0}); |
| ASSERT_EQ(session_file_reader_count(), size_t{1}); |
| |
| // Close the reader by reading 1 additional byte to simulate EOF. |
| reader->ReadChunk(1, base::BindLambdaForTesting( |
| [&](FileOperations::Reader::ReadResult result) { |
| read_result = std::move(result); |
| })); |
| ASSERT_EQ(reader->state(), FileOperations::kBusy); |
| task_environment_.RunUntilIdle(); |
| ASSERT_EQ(reader->state(), FileOperations::kComplete); |
| ASSERT_TRUE(read_result); |
| ASSERT_EQ(session_file_writer_count(), size_t{0}); |
| ASSERT_EQ(session_file_reader_count(), size_t{0}); |
| |
| ASSERT_FALSE(base::IsDirectoryEmpty(TestDir())); |
| |
| // Reset the handlers, then trigger a 'disconnect' to verify it is a no-op. |
| reader.reset(); |
| writer.reset(); |
| fake_desktop_session_proxy_.TriggerDisconnectHandlers(); |
| } |
| |
| // Verify a file chooser error is propagated. |
| TEST_F(IpcFileOperationsTest, ReaderPropagatesErrorFromFileChooser) { |
| std::unique_ptr<FileOperations::Reader> reader = |
| file_operations_->CreateReader(); |
| |
| // Currently non-existent file. |
| FakeFileChooser::SetResult(TestDir().Append(kTestFilename)); |
| absl::optional<FileOperations::Reader::OpenResult> open_result; |
| reader->Open(base::BindLambdaForTesting( |
| [&](FileOperations::Reader::OpenResult result) { |
| open_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(FileOperations::kFailed, reader->state()); |
| ASSERT_TRUE(open_result); |
| ASSERT_FALSE(*open_result); |
| // Verify the MojoIpcReader instance was not retained. |
| ASSERT_EQ(session_file_reader_count(), size_t{0}); |
| } |
| |
| // Verify IpcFileReader handles an error returned from the request handler. |
| TEST_F(IpcFileOperationsTest, ErrorReturnedByFileReadRequestHandler) { |
| std::unique_ptr<FileOperations::Reader> reader = |
| file_operations_->CreateReader(); |
| fake_desktop_session_proxy_.SetErrorForNextRequest( |
| protocol::MakeFileTransferError( |
| FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR)); |
| absl::optional<FileOperations::Reader::OpenResult> open_result; |
| reader->Open(base::BindLambdaForTesting( |
| [&](FileOperations::Reader::OpenResult result) { |
| open_result = std::move(result); |
| })); |
| ASSERT_TRUE(open_result->is_error()); |
| ASSERT_EQ(reader->state(), FileOperations::kFailed); |
| // Verify the MojoIpcReader instance was not created. |
| ASSERT_EQ(session_file_reader_count(), size_t{0}); |
| } |
| |
| // Verify IpcFileWriter handles an error returned from the request handler. |
| TEST_F(IpcFileOperationsTest, ErrorReturnedByFileWriteRequestHandler) { |
| std::unique_ptr<FileOperations::Writer> writer = |
| file_operations_->CreateWriter(); |
| fake_desktop_session_proxy_.SetErrorForNextRequest( |
| protocol::MakeFileTransferError( |
| FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR)); |
| absl::optional<FileOperations::Writer::Result> open_result; |
| writer->Open( |
| TestDir().Append(kTestFilename), |
| base::BindLambdaForTesting([&](FileOperations::Writer::Result result) { |
| open_result = std::move(result); |
| })); |
| ASSERT_TRUE(open_result->is_error()); |
| ASSERT_EQ(writer->state(), FileOperations::kFailed); |
| // Verify the MojoIpcWriter instance was not created. |
| ASSERT_EQ(session_file_writer_count(), size_t{0}); |
| ASSERT_TRUE(base::IsDirectoryEmpty(TestDir())); |
| } |
| |
| // Verify IpcFileReader handles an error returned from the mojo receiver. |
| TEST_F(IpcFileOperationsTest, ErrorReturnedByMojoReceiverForFileRead) { |
| std::unique_ptr<FileOperations::Reader> reader = |
| file_operations_->CreateReader(); |
| fake_desktop_session_agent_.SetErrorForNextResponse( |
| protocol::MakeFileTransferError( |
| FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR)); |
| absl::optional<FileOperations::Reader::OpenResult> open_result; |
| reader->Open(base::BindLambdaForTesting( |
| [&](FileOperations::Reader::OpenResult result) { |
| open_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| ASSERT_TRUE(open_result->is_error()); |
| ASSERT_EQ(reader->state(), FileOperations::kFailed); |
| // Verify a MojoIpcReader instance was not created. |
| ASSERT_EQ(session_file_reader_count(), size_t{0}); |
| } |
| |
| // Verify IpcFileWriter handles an error returned from the mojo receiver. |
| TEST_F(IpcFileOperationsTest, ErrorReturnedByMojoReceiverForFileWrite) { |
| std::unique_ptr<FileOperations::Writer> writer = |
| file_operations_->CreateWriter(); |
| fake_desktop_session_agent_.SetErrorForNextResponse( |
| protocol::MakeFileTransferError( |
| FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR)); |
| absl::optional<FileOperations::Writer::Result> open_result; |
| writer->Open( |
| TestDir().Append(kTestFilename), |
| base::BindLambdaForTesting([&](FileOperations::Writer::Result result) { |
| open_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| ASSERT_TRUE(open_result->is_error()); |
| ASSERT_EQ(writer->state(), FileOperations::kFailed); |
| // Verify the MojoIpcWriter instance was not retained. |
| ASSERT_EQ(session_file_writer_count(), size_t{0}); |
| ASSERT_TRUE(base::IsDirectoryEmpty(TestDir())); |
| } |
| |
| TEST_F(IpcFileOperationsTest, ReaderNotifiedOfIpcChannelDisconnect) { |
| std::unique_ptr<FileOperations::Reader> reader = |
| file_operations_->CreateReader(); |
| // This will trigger the DesktopSessionControl remote disconnect handler. |
| fake_desktop_session_agent_.DisconnectReceiverOnNextRequest(); |
| absl::optional<FileOperations::Reader::OpenResult> open_result; |
| reader->Open(base::BindLambdaForTesting( |
| [&](FileOperations::Reader::OpenResult result) { |
| open_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| // Verify a MojoIpcReader instance was not created. |
| ASSERT_EQ(session_file_reader_count(), size_t{0}); |
| ASSERT_EQ(reader->state(), FileOperations::kFailed); |
| ASSERT_TRUE(open_result); |
| ASSERT_TRUE(open_result->is_error()); |
| } |
| |
| TEST_F(IpcFileOperationsTest, WriterNotifiedOfIpcChannelDisconnect) { |
| std::unique_ptr<FileOperations::Writer> writer = |
| file_operations_->CreateWriter(); |
| // This will trigger the DesktopSessionControl remote disconnect handler. |
| fake_desktop_session_agent_.DisconnectReceiverOnNextRequest(); |
| absl::optional<FileOperations::Writer::Result> open_result; |
| writer->Open( |
| TestDir().Append(kTestFilename), |
| base::BindLambdaForTesting([&](FileOperations::Writer::Result result) { |
| open_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| // Verify a MojoIpcWriter instance was not created. |
| ASSERT_EQ(session_file_writer_count(), size_t{0}); |
| ASSERT_EQ(writer->state(), FileOperations::kFailed); |
| ASSERT_TRUE(open_result); |
| ASSERT_TRUE(open_result->is_error()); |
| ASSERT_TRUE(base::IsDirectoryEmpty(TestDir())); |
| } |
| |
| TEST_F(IpcFileOperationsTest, ErrorWhenReadChunkCalledBeforeOpen) { |
| constexpr std::size_t kChunkSize = 5; |
| std::unique_ptr<FileOperations::Reader> reader = |
| file_operations_->CreateReader(); |
| absl::optional<FileOperations::Reader::ReadResult> read_result; |
| reader->ReadChunk(kChunkSize, |
| base::BindLambdaForTesting( |
| [&](FileOperations::Reader::ReadResult result) { |
| read_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(FileOperations::kFailed, reader->state()); |
| ASSERT_TRUE(read_result); |
| ASSERT_TRUE(read_result->is_error()); |
| ASSERT_FALSE(*read_result); |
| // Verify a MojoIpcReader instance was not created. |
| ASSERT_EQ(session_file_reader_count(), size_t{0}); |
| } |
| |
| TEST_F(IpcFileOperationsTest, ErrorWhenWriteChunkCalledBeforeOpen) { |
| std::unique_ptr<FileOperations::Writer> writer = |
| file_operations_->CreateWriter(); |
| absl::optional<FileOperations::Writer::Result> write_result; |
| writer->WriteChunk( |
| std::vector<uint8_t>{16, 16, 16, 16, 16}, |
| base::BindLambdaForTesting([&](FileOperations::Writer::Result result) { |
| write_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(FileOperations::kFailed, writer->state()); |
| ASSERT_TRUE(write_result); |
| ASSERT_TRUE(write_result->is_error()); |
| ASSERT_FALSE(*write_result); |
| // Verify a MojoIpcWriter instance was not created. |
| ASSERT_EQ(session_file_writer_count(), size_t{0}); |
| } |
| |
| TEST_F(IpcFileOperationsTest, ErrorWhenCloseCalledBeforeOpen) { |
| std::unique_ptr<FileOperations::Writer> writer = |
| file_operations_->CreateWriter(); |
| absl::optional<FileOperations::Writer::Result> close_result; |
| writer->Close( |
| base::BindLambdaForTesting([&](FileOperations::Writer::Result result) { |
| close_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(FileOperations::kFailed, writer->state()); |
| ASSERT_TRUE(close_result); |
| ASSERT_TRUE(close_result->is_error()); |
| ASSERT_FALSE(*close_result); |
| // Verify a MojoIpcWriter instance was not created. |
| ASSERT_EQ(session_file_writer_count(), size_t{0}); |
| } |
| |
| TEST_F(IpcFileOperationsTest, ErrorWhenReadChunkCalledAfterReceiverDisconnect) { |
| constexpr std::size_t kChunkSize = 5; |
| base::FilePath path = TestDir().Append(kTestFilename); |
| ASSERT_TRUE(base::WriteFile(path, "")); |
| |
| std::unique_ptr<FileOperations::Reader> reader = |
| file_operations_->CreateReader(); |
| |
| FakeFileChooser::SetResult(path); |
| absl::optional<FileOperations::Reader::OpenResult> open_result; |
| reader->Open(base::BindLambdaForTesting( |
| [&](FileOperations::Reader::OpenResult result) { |
| open_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| ASSERT_TRUE(open_result); |
| ASSERT_TRUE(*open_result); |
| ASSERT_EQ(session_file_reader_count(), size_t{1}); |
| |
| clear_session_file_receivers(); |
| // Verify the MojoIpcReader instance was released. |
| ASSERT_EQ(session_file_reader_count(), size_t{0}); |
| task_environment_.RunUntilIdle(); |
| |
| absl::optional<FileOperations::Reader::ReadResult> read_result; |
| reader->ReadChunk(kChunkSize, |
| base::BindLambdaForTesting( |
| [&](FileOperations::Reader::ReadResult result) { |
| read_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(FileOperations::kFailed, reader->state()); |
| ASSERT_TRUE(read_result); |
| ASSERT_TRUE(read_result->is_error()); |
| ASSERT_FALSE(*read_result); |
| } |
| |
| TEST_F(IpcFileOperationsTest, |
| ErrorWhenWriteChunkCalledAfterReceiverDisconnect) { |
| std::unique_ptr<FileOperations::Writer> writer = |
| file_operations_->CreateWriter(); |
| |
| absl::optional<FileOperations::Writer::Result> open_result; |
| writer->Open(kTestFilename, base::BindLambdaForTesting( |
| [&](FileOperations::Writer::Result result) { |
| open_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| ASSERT_EQ(FileOperations::kReady, writer->state()); |
| ASSERT_EQ(session_file_writer_count(), size_t{1}); |
| |
| clear_session_file_receivers(); |
| // Verify the MojoIpcWriter instance was released. |
| ASSERT_EQ(session_file_writer_count(), size_t{0}); |
| task_environment_.RunUntilIdle(); |
| |
| absl::optional<FileOperations::Writer::Result> write_result; |
| writer->WriteChunk( |
| std::vector<uint8_t>{16, 16, 16, 16, 16}, |
| base::BindLambdaForTesting([&](FileOperations::Writer::Result result) { |
| write_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(FileOperations::kFailed, writer->state()); |
| ASSERT_TRUE(write_result); |
| ASSERT_TRUE(write_result->is_error()); |
| ASSERT_FALSE(*write_result); |
| ASSERT_TRUE(base::IsDirectoryEmpty(TestDir())); |
| } |
| |
| TEST_F(IpcFileOperationsTest, ErrorWhenCloseCalledAfterReceiverDisconnect) { |
| std::unique_ptr<FileOperations::Writer> writer = |
| file_operations_->CreateWriter(); |
| |
| absl::optional<FileOperations::Writer::Result> open_result; |
| writer->Open(kTestFilename, base::BindLambdaForTesting( |
| [&](FileOperations::Writer::Result result) { |
| open_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| ASSERT_EQ(FileOperations::kReady, writer->state()); |
| ASSERT_EQ(session_file_writer_count(), size_t{1}); |
| |
| clear_session_file_receivers(); |
| // Verify the MojoIpcWriter instance was released. |
| ASSERT_EQ(session_file_writer_count(), size_t{0}); |
| task_environment_.RunUntilIdle(); |
| |
| absl::optional<FileOperations::Writer::Result> close_result; |
| writer->Close( |
| base::BindLambdaForTesting([&](FileOperations::Writer::Result result) { |
| close_result = std::move(result); |
| })); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(FileOperations::kFailed, writer->state()); |
| ASSERT_TRUE(close_result); |
| ASSERT_TRUE(close_result->is_error()); |
| ASSERT_FALSE(*close_result); |
| ASSERT_TRUE(base::IsDirectoryEmpty(TestDir())); |
| } |
| |
| TEST_F(IpcFileOperationsTest, |
| ErrorWhenUsingExistingFileOperationsAfterFactoryDestroyed) { |
| std::unique_ptr<IpcFileOperationsFactory> ipc_file_operations_factory = |
| std::make_unique<IpcFileOperationsFactory>(&fake_desktop_session_proxy_); |
| std::unique_ptr<FileOperations> file_operations = |
| ipc_file_operations_factory->CreateFileOperations(); |
| |
| std::unique_ptr<FileOperations::Writer> writer = |
| file_operations->CreateWriter(); |
| std::unique_ptr<FileOperations::Reader> reader = |
| file_operations->CreateReader(); |
| |
| ipc_file_operations_factory.reset(); |
| |
| absl::optional<FileOperations::Writer::Result> writer_open_result; |
| writer->Open(kTestFilename, base::BindLambdaForTesting( |
| [&](FileOperations::Writer::Result result) { |
| writer_open_result = std::move(result); |
| })); |
| // We expect this to immediately fail. |
| ASSERT_EQ(writer->state(), FileOperations::kFailed); |
| ASSERT_TRUE(writer_open_result->is_error()); |
| |
| absl::optional<FileOperations::Reader::OpenResult> reader_open_result; |
| reader->Open(base::BindLambdaForTesting( |
| [&](FileOperations::Reader::OpenResult result) { |
| reader_open_result = std::move(result); |
| })); |
| // We expect this to immediately fail. |
| ASSERT_EQ(reader->state(), FileOperations::kFailed); |
| ASSERT_TRUE(reader_open_result->is_error()); |
| } |
| |
| TEST_F(IpcFileOperationsTest, |
| ErrorWhenUsingNewFileOperationsAfterFactoryDestroyed) { |
| std::unique_ptr<IpcFileOperationsFactory> ipc_file_operations_factory = |
| std::make_unique<IpcFileOperationsFactory>(&fake_desktop_session_proxy_); |
| std::unique_ptr<FileOperations> file_operations = |
| ipc_file_operations_factory->CreateFileOperations(); |
| |
| ipc_file_operations_factory.reset(); |
| |
| std::unique_ptr<FileOperations::Writer> writer = |
| file_operations->CreateWriter(); |
| std::unique_ptr<FileOperations::Reader> reader = |
| file_operations->CreateReader(); |
| |
| absl::optional<FileOperations::Writer::Result> writer_open_result; |
| writer->Open(kTestFilename, base::BindLambdaForTesting( |
| [&](FileOperations::Writer::Result result) { |
| writer_open_result = std::move(result); |
| })); |
| // We expect this to immediately fail. |
| ASSERT_EQ(writer->state(), FileOperations::kFailed); |
| ASSERT_TRUE(writer_open_result->is_error()); |
| |
| absl::optional<FileOperations::Reader::OpenResult> reader_open_result; |
| reader->Open(base::BindLambdaForTesting( |
| [&](FileOperations::Reader::OpenResult result) { |
| reader_open_result = std::move(result); |
| })); |
| // We expect this to immediately fail. |
| ASSERT_EQ(reader->state(), FileOperations::kFailed); |
| ASSERT_TRUE(reader_open_result->is_error()); |
| } |
| |
| } // namespace remoting |