|  | // | 
|  | // Copyright (C) 2011 The Android Open Source Project | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  | // | 
|  |  | 
|  | #include "update_engine/payload_consumer/download_action.h" | 
|  |  | 
|  | #include <gmock/gmock.h> | 
|  | #include <gtest/gtest.h> | 
|  |  | 
|  | #include <memory> | 
|  | #include <string> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include <base/bind.h> | 
|  | #include <base/files/file_path.h> | 
|  | #include <base/files/file_util.h> | 
|  | #include <base/location.h> | 
|  | #include <base/strings/stringprintf.h> | 
|  | #include <brillo/message_loops/fake_message_loop.h> | 
|  | #include <brillo/message_loops/message_loop.h> | 
|  |  | 
|  | #include "update_engine/common/action_pipe.h" | 
|  | #include "update_engine/common/hash_calculator.h" | 
|  | #include "update_engine/common/mock_http_fetcher.h" | 
|  | #include "update_engine/common/mock_prefs.h" | 
|  | #include "update_engine/common/test_utils.h" | 
|  | #include "update_engine/common/utils.h" | 
|  | #include "update_engine/fake_p2p_manager_configuration.h" | 
|  | #include "update_engine/fake_system_state.h" | 
|  | #include "update_engine/mock_file_writer.h" | 
|  | #include "update_engine/payload_consumer/mock_download_action.h" | 
|  | #include "update_engine/update_manager/fake_update_manager.h" | 
|  |  | 
|  | namespace chromeos_update_engine { | 
|  |  | 
|  | using base::FilePath; | 
|  | using base::ReadFileToString; | 
|  | using base::WriteFile; | 
|  | using std::string; | 
|  | using std::unique_ptr; | 
|  | using test_utils::ScopedTempFile; | 
|  | using testing::_; | 
|  | using testing::AtLeast; | 
|  | using testing::InSequence; | 
|  | using testing::Return; | 
|  | using testing::SetArgPointee; | 
|  |  | 
|  | class DownloadActionTest : public ::testing::Test {}; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class DownloadActionTestProcessorDelegate : public ActionProcessorDelegate { | 
|  | public: | 
|  | DownloadActionTestProcessorDelegate() | 
|  | : processing_done_called_(false), expected_code_(ErrorCode::kSuccess) {} | 
|  | ~DownloadActionTestProcessorDelegate() override { | 
|  | EXPECT_TRUE(processing_done_called_); | 
|  | } | 
|  | void ProcessingDone(const ActionProcessor* processor, | 
|  | ErrorCode code) override { | 
|  | brillo::MessageLoop::current()->BreakLoop(); | 
|  | brillo::Blob found_data; | 
|  | ASSERT_TRUE(utils::ReadFile(path_, &found_data)); | 
|  | if (expected_code_ != ErrorCode::kDownloadWriteError) { | 
|  | ASSERT_EQ(expected_data_.size(), found_data.size()); | 
|  | for (unsigned i = 0; i < expected_data_.size(); i++) { | 
|  | EXPECT_EQ(expected_data_[i], found_data[i]); | 
|  | } | 
|  | } | 
|  | processing_done_called_ = true; | 
|  | } | 
|  |  | 
|  | void ActionCompleted(ActionProcessor* processor, | 
|  | AbstractAction* action, | 
|  | ErrorCode code) override { | 
|  | const string type = action->Type(); | 
|  | if (type == DownloadAction::StaticType()) { | 
|  | EXPECT_EQ(expected_code_, code); | 
|  | p2p_file_id_ = static_cast<DownloadAction*>(action)->p2p_file_id(); | 
|  | } else { | 
|  | EXPECT_EQ(ErrorCode::kSuccess, code); | 
|  | } | 
|  | } | 
|  |  | 
|  | string path_; | 
|  | brillo::Blob expected_data_; | 
|  | bool processing_done_called_; | 
|  | ErrorCode expected_code_; | 
|  | string p2p_file_id_; | 
|  | }; | 
|  |  | 
|  | class TestDirectFileWriter : public DirectFileWriter { | 
|  | public: | 
|  | TestDirectFileWriter() : fail_write_(0), current_write_(0) {} | 
|  | void set_fail_write(int fail_write) { fail_write_ = fail_write; } | 
|  |  | 
|  | virtual bool Write(const void* bytes, size_t count) { | 
|  | if (++current_write_ == fail_write_) { | 
|  | return false; | 
|  | } | 
|  | return DirectFileWriter::Write(bytes, count); | 
|  | } | 
|  |  | 
|  | private: | 
|  | // If positive, fail on the |fail_write_| call to Write. | 
|  | int fail_write_; | 
|  | int current_write_; | 
|  | }; | 
|  |  | 
|  | void StartProcessorInRunLoop(ActionProcessor* processor, | 
|  | MockHttpFetcher* http_fetcher) { | 
|  | processor->StartProcessing(); | 
|  | http_fetcher->SetOffset(1); | 
|  | } | 
|  |  | 
|  | void TestWithData(const brillo::Blob& data, | 
|  | int fail_write, | 
|  | bool use_download_delegate) { | 
|  | brillo::FakeMessageLoop loop(nullptr); | 
|  | loop.SetAsCurrent(); | 
|  | FakeSystemState fake_system_state; | 
|  |  | 
|  | // TODO(adlr): see if we need a different file for build bots | 
|  | ScopedTempFile output_temp_file; | 
|  | TestDirectFileWriter writer; | 
|  | EXPECT_EQ( | 
|  | 0, writer.Open(output_temp_file.path().c_str(), O_WRONLY | O_CREAT, 0)); | 
|  | writer.set_fail_write(fail_write); | 
|  |  | 
|  | uint64_t size = data.size() - 1; | 
|  | InstallPlan install_plan; | 
|  | install_plan.payloads.push_back( | 
|  | {.size = size, .type = InstallPayloadType::kDelta}); | 
|  | // We pull off the first byte from data and seek past it. | 
|  | EXPECT_TRUE(HashCalculator::RawHashOfBytes( | 
|  | &data[1], data.size() - 1, &install_plan.payloads[0].hash)); | 
|  | install_plan.source_slot = 0; | 
|  | install_plan.target_slot = 1; | 
|  | // We mark both slots as bootable. Only the target slot should be unbootable | 
|  | // after the download starts. | 
|  | fake_system_state.fake_boot_control()->SetSlotBootable( | 
|  | install_plan.source_slot, true); | 
|  | fake_system_state.fake_boot_control()->SetSlotBootable( | 
|  | install_plan.target_slot, true); | 
|  | auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>(); | 
|  | feeder_action->set_obj(install_plan); | 
|  | MockPrefs prefs; | 
|  | MockHttpFetcher* http_fetcher = | 
|  | new MockHttpFetcher(data.data(), data.size(), nullptr); | 
|  | // takes ownership of passed in HttpFetcher | 
|  | auto download_action = | 
|  | std::make_unique<DownloadAction>(&prefs, | 
|  | fake_system_state.boot_control(), | 
|  | fake_system_state.hardware(), | 
|  | &fake_system_state, | 
|  | http_fetcher, | 
|  | false /* interactive */); | 
|  | download_action->SetTestFileWriter(&writer); | 
|  | BondActions(feeder_action.get(), download_action.get()); | 
|  | MockDownloadActionDelegate download_delegate; | 
|  | if (use_download_delegate) { | 
|  | InSequence s; | 
|  | download_action->set_delegate(&download_delegate); | 
|  | if (data.size() > kMockHttpFetcherChunkSize) | 
|  | EXPECT_CALL(download_delegate, | 
|  | BytesReceived(_, kMockHttpFetcherChunkSize, _)); | 
|  | EXPECT_CALL(download_delegate, BytesReceived(_, _, _)).Times(AtLeast(1)); | 
|  | EXPECT_CALL(download_delegate, DownloadComplete()) | 
|  | .Times(fail_write == 0 ? 1 : 0); | 
|  | } | 
|  | DownloadActionTestProcessorDelegate delegate; | 
|  | delegate.expected_code_ = | 
|  | (fail_write > 0) ? ErrorCode::kDownloadWriteError : ErrorCode::kSuccess; | 
|  | delegate.expected_data_ = brillo::Blob(data.begin() + 1, data.end()); | 
|  | delegate.path_ = output_temp_file.path(); | 
|  | ActionProcessor processor; | 
|  | processor.set_delegate(&delegate); | 
|  | processor.EnqueueAction(std::move(feeder_action)); | 
|  | processor.EnqueueAction(std::move(download_action)); | 
|  |  | 
|  | loop.PostTask(FROM_HERE, | 
|  | base::Bind(&StartProcessorInRunLoop, &processor, http_fetcher)); | 
|  | loop.Run(); | 
|  | EXPECT_FALSE(loop.PendingTasks()); | 
|  |  | 
|  | EXPECT_TRUE(fake_system_state.fake_boot_control()->IsSlotBootable( | 
|  | install_plan.source_slot)); | 
|  | EXPECT_FALSE(fake_system_state.fake_boot_control()->IsSlotBootable( | 
|  | install_plan.target_slot)); | 
|  | } | 
|  | }  // namespace | 
|  |  | 
|  | TEST(DownloadActionTest, SimpleTest) { | 
|  | brillo::Blob small; | 
|  | const char* foo = "foo"; | 
|  | small.insert(small.end(), foo, foo + strlen(foo)); | 
|  | TestWithData(small, | 
|  | 0,      // fail_write | 
|  | true);  // use_download_delegate | 
|  | } | 
|  |  | 
|  | TEST(DownloadActionTest, LargeTest) { | 
|  | brillo::Blob big(5 * kMockHttpFetcherChunkSize); | 
|  | char c = '0'; | 
|  | for (unsigned int i = 0; i < big.size(); i++) { | 
|  | big[i] = c; | 
|  | c = ('9' == c) ? '0' : c + 1; | 
|  | } | 
|  | TestWithData(big, | 
|  | 0,      // fail_write | 
|  | true);  // use_download_delegate | 
|  | } | 
|  |  | 
|  | TEST(DownloadActionTest, FailWriteTest) { | 
|  | brillo::Blob big(5 * kMockHttpFetcherChunkSize); | 
|  | char c = '0'; | 
|  | for (unsigned int i = 0; i < big.size(); i++) { | 
|  | big[i] = c; | 
|  | c = ('9' == c) ? '0' : c + 1; | 
|  | } | 
|  | TestWithData(big, | 
|  | 2,      // fail_write | 
|  | true);  // use_download_delegate | 
|  | } | 
|  |  | 
|  | TEST(DownloadActionTest, NoDownloadDelegateTest) { | 
|  | brillo::Blob small; | 
|  | const char* foo = "foofoo"; | 
|  | small.insert(small.end(), foo, foo + strlen(foo)); | 
|  | TestWithData(small, | 
|  | 0,       // fail_write | 
|  | false);  // use_download_delegate | 
|  | } | 
|  |  | 
|  | TEST(DownloadActionTest, MultiPayloadProgressTest) { | 
|  | std::vector<brillo::Blob> payload_datas; | 
|  | // the first payload must be the largest, as it's the actual payload used by | 
|  | // the MockHttpFetcher for all downloaded data. | 
|  | payload_datas.emplace_back(4 * kMockHttpFetcherChunkSize + 256); | 
|  | payload_datas.emplace_back(2 * kMockHttpFetcherChunkSize); | 
|  | brillo::FakeMessageLoop loop(nullptr); | 
|  | loop.SetAsCurrent(); | 
|  | FakeSystemState fake_system_state; | 
|  | EXPECT_CALL(*fake_system_state.mock_payload_state(), NextPayload()) | 
|  | .WillOnce(Return(true)); | 
|  |  | 
|  | MockFileWriter mock_file_writer; | 
|  | EXPECT_CALL(mock_file_writer, Close()).WillRepeatedly(Return(0)); | 
|  | EXPECT_CALL(mock_file_writer, Write(_, _, _)) | 
|  | .WillRepeatedly( | 
|  | DoAll(SetArgPointee<2>(ErrorCode::kSuccess), Return(true))); | 
|  |  | 
|  | InstallPlan install_plan; | 
|  | uint64_t total_expected_download_size{0}; | 
|  | for (const auto& data : payload_datas) { | 
|  | uint64_t size = data.size(); | 
|  | install_plan.payloads.push_back( | 
|  | {.size = size, .type = InstallPayloadType::kFull}); | 
|  | total_expected_download_size += size; | 
|  | } | 
|  | auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>(); | 
|  | feeder_action->set_obj(install_plan); | 
|  | MockPrefs prefs; | 
|  | MockHttpFetcher* http_fetcher = new MockHttpFetcher( | 
|  | payload_datas[0].data(), payload_datas[0].size(), nullptr); | 
|  | // takes ownership of passed in HttpFetcher | 
|  | auto download_action = | 
|  | std::make_unique<DownloadAction>(&prefs, | 
|  | fake_system_state.boot_control(), | 
|  | fake_system_state.hardware(), | 
|  | &fake_system_state, | 
|  | http_fetcher, | 
|  | false /* interactive */); | 
|  | download_action->SetTestFileWriter(&mock_file_writer); | 
|  | BondActions(feeder_action.get(), download_action.get()); | 
|  | MockDownloadActionDelegate download_delegate; | 
|  | { | 
|  | InSequence s; | 
|  | download_action->set_delegate(&download_delegate); | 
|  | // these are hand-computed based on the payloads specified above | 
|  | EXPECT_CALL(download_delegate, | 
|  | BytesReceived(kMockHttpFetcherChunkSize, | 
|  | kMockHttpFetcherChunkSize, | 
|  | total_expected_download_size)); | 
|  | EXPECT_CALL(download_delegate, | 
|  | BytesReceived(kMockHttpFetcherChunkSize, | 
|  | kMockHttpFetcherChunkSize * 2, | 
|  | total_expected_download_size)); | 
|  | EXPECT_CALL(download_delegate, | 
|  | BytesReceived(kMockHttpFetcherChunkSize, | 
|  | kMockHttpFetcherChunkSize * 3, | 
|  | total_expected_download_size)); | 
|  | EXPECT_CALL(download_delegate, | 
|  | BytesReceived(kMockHttpFetcherChunkSize, | 
|  | kMockHttpFetcherChunkSize * 4, | 
|  | total_expected_download_size)); | 
|  | EXPECT_CALL(download_delegate, | 
|  | BytesReceived(256, | 
|  | kMockHttpFetcherChunkSize * 4 + 256, | 
|  | total_expected_download_size)); | 
|  | EXPECT_CALL(download_delegate, | 
|  | BytesReceived(kMockHttpFetcherChunkSize, | 
|  | kMockHttpFetcherChunkSize * 5 + 256, | 
|  | total_expected_download_size)); | 
|  | EXPECT_CALL(download_delegate, | 
|  | BytesReceived(kMockHttpFetcherChunkSize, | 
|  | total_expected_download_size, | 
|  | total_expected_download_size)); | 
|  | } | 
|  | ActionProcessor processor; | 
|  | processor.EnqueueAction(std::move(feeder_action)); | 
|  | processor.EnqueueAction(std::move(download_action)); | 
|  |  | 
|  | loop.PostTask( | 
|  | FROM_HERE, | 
|  | base::Bind( | 
|  | [](ActionProcessor* processor) { processor->StartProcessing(); }, | 
|  | base::Unretained(&processor))); | 
|  | loop.Run(); | 
|  | EXPECT_FALSE(loop.PendingTasks()); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  | class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate { | 
|  | public: | 
|  | void ProcessingStopped(const ActionProcessor* processor) { | 
|  | brillo::MessageLoop::current()->BreakLoop(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | void TerminateEarlyTestStarter(ActionProcessor* processor) { | 
|  | processor->StartProcessing(); | 
|  | CHECK(processor->IsRunning()); | 
|  | processor->StopProcessing(); | 
|  | } | 
|  |  | 
|  | void TestTerminateEarly(bool use_download_delegate) { | 
|  | brillo::FakeMessageLoop loop(nullptr); | 
|  | loop.SetAsCurrent(); | 
|  |  | 
|  | brillo::Blob data(kMockHttpFetcherChunkSize + kMockHttpFetcherChunkSize / 2); | 
|  | memset(data.data(), 0, data.size()); | 
|  |  | 
|  | ScopedTempFile temp_file; | 
|  | { | 
|  | DirectFileWriter writer; | 
|  | EXPECT_EQ(0, writer.Open(temp_file.path().c_str(), O_WRONLY | O_CREAT, 0)); | 
|  |  | 
|  | // takes ownership of passed in HttpFetcher | 
|  | auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>(); | 
|  | InstallPlan install_plan; | 
|  | install_plan.payloads.resize(1); | 
|  | feeder_action->set_obj(install_plan); | 
|  | FakeSystemState fake_system_state_; | 
|  | MockPrefs prefs; | 
|  | auto download_action = std::make_unique<DownloadAction>( | 
|  | &prefs, | 
|  | fake_system_state_.boot_control(), | 
|  | fake_system_state_.hardware(), | 
|  | &fake_system_state_, | 
|  | new MockHttpFetcher(data.data(), data.size(), nullptr), | 
|  | false /* interactive */); | 
|  | download_action->SetTestFileWriter(&writer); | 
|  | MockDownloadActionDelegate download_delegate; | 
|  | if (use_download_delegate) { | 
|  | download_action->set_delegate(&download_delegate); | 
|  | EXPECT_CALL(download_delegate, BytesReceived(_, _, _)).Times(0); | 
|  | } | 
|  | TerminateEarlyTestProcessorDelegate delegate; | 
|  | ActionProcessor processor; | 
|  | processor.set_delegate(&delegate); | 
|  | BondActions(feeder_action.get(), download_action.get()); | 
|  | processor.EnqueueAction(std::move(feeder_action)); | 
|  | processor.EnqueueAction(std::move(download_action)); | 
|  |  | 
|  | loop.PostTask(FROM_HERE, | 
|  | base::Bind(&TerminateEarlyTestStarter, &processor)); | 
|  | loop.Run(); | 
|  | EXPECT_FALSE(loop.PendingTasks()); | 
|  | } | 
|  |  | 
|  | // 1 or 0 chunks should have come through | 
|  | const off_t resulting_file_size(utils::FileSize(temp_file.path())); | 
|  | EXPECT_GE(resulting_file_size, 0); | 
|  | if (resulting_file_size != 0) | 
|  | EXPECT_EQ(kMockHttpFetcherChunkSize, | 
|  | static_cast<size_t>(resulting_file_size)); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | TEST(DownloadActionTest, TerminateEarlyTest) { | 
|  | TestTerminateEarly(true); | 
|  | } | 
|  |  | 
|  | TEST(DownloadActionTest, TerminateEarlyNoDownloadDelegateTest) { | 
|  | TestTerminateEarly(false); | 
|  | } | 
|  |  | 
|  | class DownloadActionTestAction; | 
|  |  | 
|  | template <> | 
|  | class ActionTraits<DownloadActionTestAction> { | 
|  | public: | 
|  | typedef InstallPlan OutputObjectType; | 
|  | typedef InstallPlan InputObjectType; | 
|  | }; | 
|  |  | 
|  | // This is a simple Action class for testing. | 
|  | class DownloadActionTestAction : public Action<DownloadActionTestAction> { | 
|  | public: | 
|  | DownloadActionTestAction() = default; | 
|  | typedef InstallPlan InputObjectType; | 
|  | typedef InstallPlan OutputObjectType; | 
|  | ActionPipe<InstallPlan>* in_pipe() { return in_pipe_.get(); } | 
|  | ActionPipe<InstallPlan>* out_pipe() { return out_pipe_.get(); } | 
|  | ActionProcessor* processor() { return processor_; } | 
|  | void PerformAction() { | 
|  | ASSERT_TRUE(HasInputObject()); | 
|  | EXPECT_TRUE(expected_input_object_ == GetInputObject()); | 
|  | ASSERT_TRUE(processor()); | 
|  | processor()->ActionComplete(this, ErrorCode::kSuccess); | 
|  | } | 
|  | static std::string StaticType() { return "DownloadActionTestAction"; } | 
|  | string Type() const { return StaticType(); } | 
|  | InstallPlan expected_input_object_; | 
|  | }; | 
|  |  | 
|  | namespace { | 
|  | // This class is an ActionProcessorDelegate that simply terminates the | 
|  | // run loop when the ActionProcessor has completed processing. It's used | 
|  | // only by the test PassObjectOutTest. | 
|  | class PassObjectOutTestProcessorDelegate : public ActionProcessorDelegate { | 
|  | public: | 
|  | void ProcessingDone(const ActionProcessor* processor, | 
|  | ErrorCode code) override { | 
|  | brillo::MessageLoop::current()->BreakLoop(); | 
|  | } | 
|  | void ActionCompleted(ActionProcessor* processor, | 
|  | AbstractAction* action, | 
|  | ErrorCode code) override { | 
|  | if (action->Type() == DownloadActionTestAction::StaticType()) { | 
|  | did_test_action_run_ = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool did_test_action_run_ = false; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | TEST(DownloadActionTest, PassObjectOutTest) { | 
|  | brillo::FakeMessageLoop loop(nullptr); | 
|  | loop.SetAsCurrent(); | 
|  |  | 
|  | DirectFileWriter writer; | 
|  | EXPECT_EQ(0, writer.Open("/dev/null", O_WRONLY | O_CREAT, 0)); | 
|  |  | 
|  | // takes ownership of passed in HttpFetcher | 
|  | InstallPlan install_plan; | 
|  | install_plan.payloads.push_back({.size = 1}); | 
|  | EXPECT_TRUE( | 
|  | HashCalculator::RawHashOfData({'x'}, &install_plan.payloads[0].hash)); | 
|  | auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>(); | 
|  | feeder_action->set_obj(install_plan); | 
|  | MockPrefs prefs; | 
|  | FakeSystemState fake_system_state_; | 
|  | auto download_action = | 
|  | std::make_unique<DownloadAction>(&prefs, | 
|  | fake_system_state_.boot_control(), | 
|  | fake_system_state_.hardware(), | 
|  | &fake_system_state_, | 
|  | new MockHttpFetcher("x", 1, nullptr), | 
|  | false /* interactive */); | 
|  | download_action->SetTestFileWriter(&writer); | 
|  |  | 
|  | auto test_action = std::make_unique<DownloadActionTestAction>(); | 
|  | test_action->expected_input_object_ = install_plan; | 
|  | BondActions(feeder_action.get(), download_action.get()); | 
|  | BondActions(download_action.get(), test_action.get()); | 
|  |  | 
|  | ActionProcessor processor; | 
|  | PassObjectOutTestProcessorDelegate delegate; | 
|  | processor.set_delegate(&delegate); | 
|  | processor.EnqueueAction(std::move(feeder_action)); | 
|  | processor.EnqueueAction(std::move(download_action)); | 
|  | processor.EnqueueAction(std::move(test_action)); | 
|  |  | 
|  | loop.PostTask( | 
|  | FROM_HERE, | 
|  | base::Bind( | 
|  | [](ActionProcessor* processor) { processor->StartProcessing(); }, | 
|  | base::Unretained(&processor))); | 
|  | loop.Run(); | 
|  | EXPECT_FALSE(loop.PendingTasks()); | 
|  |  | 
|  | EXPECT_EQ(true, delegate.did_test_action_run_); | 
|  | } | 
|  |  | 
|  | // Test fixture for P2P tests. | 
|  | class P2PDownloadActionTest : public testing::Test { | 
|  | protected: | 
|  | P2PDownloadActionTest() | 
|  | : start_at_offset_(0), fake_um_(fake_system_state_.fake_clock()) {} | 
|  |  | 
|  | ~P2PDownloadActionTest() override {} | 
|  |  | 
|  | // Derived from testing::Test. | 
|  | void SetUp() override { loop_.SetAsCurrent(); } | 
|  |  | 
|  | // Derived from testing::Test. | 
|  | void TearDown() override { EXPECT_FALSE(loop_.PendingTasks()); } | 
|  |  | 
|  | // To be called by tests to setup the download. The | 
|  | // |starting_offset| parameter is for where to resume. | 
|  | void SetupDownload(off_t starting_offset) { | 
|  | start_at_offset_ = starting_offset; | 
|  | // Prepare data 10 kB of data. | 
|  | data_.clear(); | 
|  | for (unsigned int i = 0; i < 10 * 1000; i++) | 
|  | data_ += 'a' + (i % 25); | 
|  |  | 
|  | // Setup p2p. | 
|  | FakeP2PManagerConfiguration* test_conf = new FakeP2PManagerConfiguration(); | 
|  | p2p_manager_.reset(P2PManager::Construct(test_conf, | 
|  | nullptr, | 
|  | &fake_um_, | 
|  | "cros_au", | 
|  | 3, | 
|  | base::TimeDelta::FromDays(5))); | 
|  | fake_system_state_.set_p2p_manager(p2p_manager_.get()); | 
|  | } | 
|  |  | 
|  | // To be called by tests to perform the download. The | 
|  | // |use_p2p_to_share| parameter is used to indicate whether the | 
|  | // payload should be shared via p2p. | 
|  | void StartDownload(bool use_p2p_to_share) { | 
|  | EXPECT_CALL(*fake_system_state_.mock_payload_state(), | 
|  | GetUsingP2PForSharing()) | 
|  | .WillRepeatedly(Return(use_p2p_to_share)); | 
|  |  | 
|  | ScopedTempFile output_temp_file; | 
|  | TestDirectFileWriter writer; | 
|  | EXPECT_EQ( | 
|  | 0, writer.Open(output_temp_file.path().c_str(), O_WRONLY | O_CREAT, 0)); | 
|  | InstallPlan install_plan; | 
|  | install_plan.payloads.push_back( | 
|  | {.size = data_.length(), | 
|  | .hash = {'1', '2', '3', '4', 'h', 'a', 's', 'h'}}); | 
|  | auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>(); | 
|  | feeder_action->set_obj(install_plan); | 
|  | MockPrefs prefs; | 
|  | // Note that DownloadAction takes ownership of the passed in HttpFetcher. | 
|  | auto download_action = std::make_unique<DownloadAction>( | 
|  | &prefs, | 
|  | fake_system_state_.boot_control(), | 
|  | fake_system_state_.hardware(), | 
|  | &fake_system_state_, | 
|  | new MockHttpFetcher(data_.c_str(), data_.length(), nullptr), | 
|  | false /* interactive */); | 
|  | auto http_fetcher = download_action->http_fetcher(); | 
|  | download_action->SetTestFileWriter(&writer); | 
|  | BondActions(feeder_action.get(), download_action.get()); | 
|  | delegate_.expected_data_ = | 
|  | brillo::Blob(data_.begin() + start_at_offset_, data_.end()); | 
|  | delegate_.path_ = output_temp_file.path(); | 
|  | processor_.set_delegate(&delegate_); | 
|  | processor_.EnqueueAction(std::move(feeder_action)); | 
|  | processor_.EnqueueAction(std::move(download_action)); | 
|  |  | 
|  | loop_.PostTask( | 
|  | FROM_HERE, | 
|  | base::Bind( | 
|  | [](P2PDownloadActionTest* action_test, HttpFetcher* http_fetcher) { | 
|  | action_test->processor_.StartProcessing(); | 
|  | http_fetcher->SetOffset(action_test->start_at_offset_); | 
|  | }, | 
|  | base::Unretained(this), | 
|  | base::Unretained(http_fetcher))); | 
|  | loop_.Run(); | 
|  | } | 
|  |  | 
|  | // Mainloop used to make StartDownload() synchronous. | 
|  | brillo::FakeMessageLoop loop_{nullptr}; | 
|  |  | 
|  | // Delegate that is passed to the ActionProcessor. | 
|  | DownloadActionTestProcessorDelegate delegate_; | 
|  |  | 
|  | // The P2PManager used in the test. | 
|  | unique_ptr<P2PManager> p2p_manager_; | 
|  |  | 
|  | // The ActionProcessor used for running the actions. | 
|  | ActionProcessor processor_; | 
|  |  | 
|  | // A fake system state. | 
|  | FakeSystemState fake_system_state_; | 
|  |  | 
|  | // The data being downloaded. | 
|  | string data_; | 
|  |  | 
|  | private: | 
|  | // The requested starting offset passed to SetupDownload(). | 
|  | off_t start_at_offset_; | 
|  |  | 
|  | chromeos_update_manager::FakeUpdateManager fake_um_; | 
|  | }; | 
|  |  | 
|  | TEST_F(P2PDownloadActionTest, IsWrittenTo) { | 
|  | SetupDownload(0);     // starting_offset | 
|  | StartDownload(true);  // use_p2p_to_share | 
|  |  | 
|  | // Check the p2p file and its content matches what was sent. | 
|  | string file_id = delegate_.p2p_file_id_; | 
|  | EXPECT_NE("", file_id); | 
|  | EXPECT_EQ(static_cast<int>(data_.length()), | 
|  | p2p_manager_->FileGetSize(file_id)); | 
|  | EXPECT_EQ(static_cast<int>(data_.length()), | 
|  | p2p_manager_->FileGetExpectedSize(file_id)); | 
|  | string p2p_file_contents; | 
|  | EXPECT_TRUE( | 
|  | ReadFileToString(p2p_manager_->FileGetPath(file_id), &p2p_file_contents)); | 
|  | EXPECT_EQ(data_, p2p_file_contents); | 
|  | } | 
|  |  | 
|  | TEST_F(P2PDownloadActionTest, DeleteIfHoleExists) { | 
|  | SetupDownload(1000);  // starting_offset | 
|  | StartDownload(true);  // use_p2p_to_share | 
|  |  | 
|  | // DownloadAction should convey that the file is not being shared. | 
|  | // and that we don't have any p2p files. | 
|  | EXPECT_EQ(delegate_.p2p_file_id_, ""); | 
|  | EXPECT_EQ(p2p_manager_->CountSharedFiles(), 0); | 
|  | } | 
|  |  | 
|  | TEST_F(P2PDownloadActionTest, CanAppend) { | 
|  | SetupDownload(1000);  // starting_offset | 
|  |  | 
|  | // Prepare the file with existing data before starting to write to | 
|  | // it via DownloadAction. | 
|  | string file_id = utils::CalculateP2PFileId( | 
|  | {'1', '2', '3', '4', 'h', 'a', 's', 'h'}, data_.length()); | 
|  | ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length())); | 
|  | string existing_data; | 
|  | for (unsigned int i = 0; i < 1000; i++) | 
|  | existing_data += '0' + (i % 10); | 
|  | ASSERT_EQ( | 
|  | WriteFile( | 
|  | p2p_manager_->FileGetPath(file_id), existing_data.c_str(), 1000), | 
|  | 1000); | 
|  |  | 
|  | StartDownload(true);  // use_p2p_to_share | 
|  |  | 
|  | // DownloadAction should convey the same file_id and the file should | 
|  | // have the expected size. | 
|  | EXPECT_EQ(delegate_.p2p_file_id_, file_id); | 
|  | EXPECT_EQ(static_cast<ssize_t>(data_.length()), | 
|  | p2p_manager_->FileGetSize(file_id)); | 
|  | EXPECT_EQ(static_cast<ssize_t>(data_.length()), | 
|  | p2p_manager_->FileGetExpectedSize(file_id)); | 
|  | string p2p_file_contents; | 
|  | // Check that the first 1000 bytes wasn't touched and that we | 
|  | // appended the remaining as appropriate. | 
|  | EXPECT_TRUE( | 
|  | ReadFileToString(p2p_manager_->FileGetPath(file_id), &p2p_file_contents)); | 
|  | EXPECT_EQ(existing_data, p2p_file_contents.substr(0, 1000)); | 
|  | EXPECT_EQ(data_.substr(1000), p2p_file_contents.substr(1000)); | 
|  | } | 
|  |  | 
|  | TEST_F(P2PDownloadActionTest, DeletePartialP2PFileIfResumingWithoutP2P) { | 
|  | SetupDownload(1000);  // starting_offset | 
|  |  | 
|  | // Prepare the file with all existing data before starting to write | 
|  | // to it via DownloadAction. | 
|  | string file_id = utils::CalculateP2PFileId( | 
|  | {'1', '2', '3', '4', 'h', 'a', 's', 'h'}, data_.length()); | 
|  | ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length())); | 
|  | string existing_data; | 
|  | for (unsigned int i = 0; i < 1000; i++) | 
|  | existing_data += '0' + (i % 10); | 
|  | ASSERT_EQ( | 
|  | WriteFile( | 
|  | p2p_manager_->FileGetPath(file_id), existing_data.c_str(), 1000), | 
|  | 1000); | 
|  |  | 
|  | // Check that the file is there. | 
|  | EXPECT_EQ(1000, p2p_manager_->FileGetSize(file_id)); | 
|  | EXPECT_EQ(1, p2p_manager_->CountSharedFiles()); | 
|  |  | 
|  | StartDownload(false);  // use_p2p_to_share | 
|  |  | 
|  | // DownloadAction should have deleted the p2p file. Check that it's gone. | 
|  | EXPECT_EQ(-1, p2p_manager_->FileGetSize(file_id)); | 
|  | EXPECT_EQ(0, p2p_manager_->CountSharedFiles()); | 
|  | } | 
|  |  | 
|  | }  // namespace chromeos_update_engine |