Split payload application code into a subdirectory.
This patch splits from the main libupdate_engine code the part that
is strictly used to download and apply a payload into a new static
library, moving the code to subdirectories. The new library is divided
in two subdirectories: common/ and payload_consumer/, and should not
depend on other update_engine files outside those two subdirectories.
The main difference between those two is that the common/ tools are more
generic and not tied to the payload consumer process, but otherwise they
are both compiled together.
There are still dependencies from the new libpayload_consumer library
into the main directory files and DBus generated files. Those will be
addressed in follow up CLs.
Bug: 25197634
Test: FEATURES=test emerge-link update_engine; `mm` on Brillo.
Change-Id: Id8d0204ea573627e6e26ca9ea17b9592ca95bc23
diff --git a/common/http_fetcher_unittest.cc b/common/http_fetcher_unittest.cc
new file mode 100644
index 0000000..a6ba9c8
--- /dev/null
+++ b/common/http_fetcher_unittest.cc
@@ -0,0 +1,1127 @@
+//
+// Copyright (C) 2012 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 <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/message_loop/message_loop.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <brillo/message_loops/base_message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <brillo/process.h>
+#include <brillo/streams/file_stream.h>
+#include <brillo/streams/stream.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/http_common.h"
+#include "update_engine/common/libcurl_http_fetcher.h"
+#include "update_engine/common/mock_http_fetcher.h"
+#include "update_engine/common/multi_range_http_fetcher.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/proxy_resolver.h"
+
+using brillo::MessageLoop;
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace {
+
+const int kBigLength = 100000;
+const int kMediumLength = 1000;
+const int kFlakyTruncateLength = 29000;
+const int kFlakySleepEvery = 3;
+const int kFlakySleepSecs = 10;
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+static const char *kUnusedUrl = "unused://unused";
+
+static inline string LocalServerUrlForPath(in_port_t port,
+ const string& path) {
+ string port_str = (port ? base::StringPrintf(":%hu", port) : "");
+ return base::StringPrintf("http://127.0.0.1%s%s", port_str.c_str(),
+ path.c_str());
+}
+
+//
+// Class hierarchy for HTTP server implementations.
+//
+
+class HttpServer {
+ public:
+ // This makes it an abstract class (dirty but works).
+ virtual ~HttpServer() = 0;
+
+ virtual in_port_t GetPort() const {
+ return 0;
+ }
+
+ bool started_;
+};
+
+HttpServer::~HttpServer() {}
+
+
+class NullHttpServer : public HttpServer {
+ public:
+ NullHttpServer() {
+ started_ = true;
+ }
+};
+
+
+class PythonHttpServer : public HttpServer {
+ public:
+ PythonHttpServer() : port_(0) {
+ started_ = false;
+
+ // Spawn the server process.
+ unique_ptr<brillo::Process> http_server(new brillo::ProcessImpl());
+ base::FilePath test_server_path =
+ test_utils::GetBuildArtifactsPath().Append("test_http_server");
+ http_server->AddArg(test_server_path.value());
+ http_server->RedirectUsingPipe(STDOUT_FILENO, false);
+
+ if (!http_server->Start()) {
+ ADD_FAILURE() << "failed to spawn http server process";
+ return;
+ }
+ LOG(INFO) << "started http server with pid " << http_server->pid();
+
+ // Wait for server to begin accepting connections, obtain its port.
+ brillo::StreamPtr stdout = brillo::FileStream::FromFileDescriptor(
+ http_server->GetPipe(STDOUT_FILENO), false /* own */, nullptr);
+ if (!stdout)
+ return;
+
+ vector<char> buf(128);
+ string line;
+ while (line.find('\n') == string::npos) {
+ size_t read;
+ if (!stdout->ReadBlocking(buf.data(), buf.size(), &read, nullptr)) {
+ ADD_FAILURE() << "error reading http server stdout";
+ return;
+ }
+ line.append(buf.data(), read);
+ if (read == 0)
+ break;
+ }
+ // Parse the port from the output line.
+ const size_t listening_msg_prefix_len = strlen(kServerListeningMsgPrefix);
+ if (line.size() < listening_msg_prefix_len) {
+ ADD_FAILURE() << "server output too short";
+ return;
+ }
+
+ EXPECT_EQ(kServerListeningMsgPrefix,
+ line.substr(0, listening_msg_prefix_len));
+ string port_str = line.substr(listening_msg_prefix_len);
+ port_str.resize(port_str.find('\n'));
+ EXPECT_TRUE(base::StringToUint(port_str, &port_));
+
+ started_ = true;
+ LOG(INFO) << "server running, listening on port " << port_;
+
+ // Any failure before this point will SIGKILL the test server if started
+ // when the |http_server| goes out of scope.
+ http_server_ = std::move(http_server);
+ }
+
+ ~PythonHttpServer() {
+ // If there's no process, do nothing.
+ if (!http_server_)
+ return;
+ // Wait up to 10 seconds for the process to finish. Destroying the process
+ // will kill it with a SIGKILL otherwise.
+ http_server_->Kill(SIGTERM, 10);
+ }
+
+ in_port_t GetPort() const override {
+ return port_;
+ }
+
+ private:
+ static const char* kServerListeningMsgPrefix;
+
+ unique_ptr<brillo::Process> http_server_;
+ unsigned int port_;
+};
+
+const char* PythonHttpServer::kServerListeningMsgPrefix = "listening on port ";
+
+//
+// Class hierarchy for HTTP fetcher test wrappers.
+//
+
+class AnyHttpFetcherTest {
+ public:
+ AnyHttpFetcherTest() {}
+ virtual ~AnyHttpFetcherTest() {}
+
+ virtual HttpFetcher* NewLargeFetcher(size_t num_proxies) = 0;
+ HttpFetcher* NewLargeFetcher() {
+ return NewLargeFetcher(1);
+ }
+
+ virtual HttpFetcher* NewSmallFetcher(size_t num_proxies) = 0;
+ HttpFetcher* NewSmallFetcher() {
+ return NewSmallFetcher(1);
+ }
+
+ virtual string BigUrl(in_port_t port) const { return kUnusedUrl; }
+ virtual string SmallUrl(in_port_t port) const { return kUnusedUrl; }
+ virtual string ErrorUrl(in_port_t port) const { return kUnusedUrl; }
+
+ virtual bool IsMock() const = 0;
+ virtual bool IsMulti() const = 0;
+
+ virtual void IgnoreServerAborting(HttpServer* server) const {}
+
+ virtual HttpServer* CreateServer() = 0;
+
+ protected:
+ DirectProxyResolver proxy_resolver_;
+ FakeSystemState fake_system_state_;
+};
+
+class MockHttpFetcherTest : public AnyHttpFetcherTest {
+ public:
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewLargeFetcher;
+ HttpFetcher* NewLargeFetcher(size_t num_proxies) override {
+ brillo::Blob big_data(1000000);
+ CHECK_GT(num_proxies, 0u);
+ proxy_resolver_.set_num_proxies(num_proxies);
+ return new MockHttpFetcher(
+ big_data.data(),
+ big_data.size(),
+ reinterpret_cast<ProxyResolver*>(&proxy_resolver_));
+ }
+
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewSmallFetcher;
+ HttpFetcher* NewSmallFetcher(size_t num_proxies) override {
+ CHECK_GT(num_proxies, 0u);
+ proxy_resolver_.set_num_proxies(num_proxies);
+ return new MockHttpFetcher(
+ "x",
+ 1,
+ reinterpret_cast<ProxyResolver*>(&proxy_resolver_));
+ }
+
+ bool IsMock() const override { return true; }
+ bool IsMulti() const override { return false; }
+
+ HttpServer* CreateServer() override {
+ return new NullHttpServer;
+ }
+};
+
+class LibcurlHttpFetcherTest : public AnyHttpFetcherTest {
+ public:
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewLargeFetcher;
+ HttpFetcher* NewLargeFetcher(size_t num_proxies) override {
+ CHECK_GT(num_proxies, 0u);
+ proxy_resolver_.set_num_proxies(num_proxies);
+ LibcurlHttpFetcher *ret = new
+ LibcurlHttpFetcher(reinterpret_cast<ProxyResolver*>(&proxy_resolver_),
+ &fake_system_state_);
+ // Speed up test execution.
+ ret->set_idle_seconds(1);
+ ret->set_retry_seconds(1);
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+ return ret;
+ }
+
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewSmallFetcher;
+ HttpFetcher* NewSmallFetcher(size_t num_proxies) override {
+ return NewLargeFetcher(num_proxies);
+ }
+
+ string BigUrl(in_port_t port) const override {
+ return LocalServerUrlForPath(port,
+ base::StringPrintf("/download/%d",
+ kBigLength));
+ }
+ string SmallUrl(in_port_t port) const override {
+ return LocalServerUrlForPath(port, "/foo");
+ }
+ string ErrorUrl(in_port_t port) const override {
+ return LocalServerUrlForPath(port, "/error");
+ }
+
+ bool IsMock() const override { return false; }
+ bool IsMulti() const override { return false; }
+
+ void IgnoreServerAborting(HttpServer* server) const override {
+ // Nothing to do.
+ }
+
+ HttpServer* CreateServer() override {
+ return new PythonHttpServer;
+ }
+};
+
+class MultiRangeHttpFetcherTest : public LibcurlHttpFetcherTest {
+ public:
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewLargeFetcher;
+ HttpFetcher* NewLargeFetcher(size_t num_proxies) override {
+ CHECK_GT(num_proxies, 0u);
+ proxy_resolver_.set_num_proxies(num_proxies);
+ ProxyResolver* resolver =
+ reinterpret_cast<ProxyResolver*>(&proxy_resolver_);
+ MultiRangeHttpFetcher *ret =
+ new MultiRangeHttpFetcher(
+ new LibcurlHttpFetcher(resolver, &fake_system_state_));
+ ret->ClearRanges();
+ ret->AddRange(0);
+ // Speed up test execution.
+ ret->set_idle_seconds(1);
+ ret->set_retry_seconds(1);
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+ return ret;
+ }
+
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewSmallFetcher;
+ HttpFetcher* NewSmallFetcher(size_t num_proxies) override {
+ return NewLargeFetcher(num_proxies);
+ }
+
+ bool IsMulti() const override { return true; }
+};
+
+
+//
+// Infrastructure for type tests of HTTP fetcher.
+// See: http://code.google.com/p/googletest/wiki/AdvancedGuide#Typed_Tests
+//
+
+// Fixture class template. We use an explicit constraint to guarantee that it
+// can only be instantiated with an AnyHttpFetcherTest type, see:
+// http://www2.research.att.com/~bs/bs_faq2.html#constraints
+template <typename T>
+class HttpFetcherTest : public ::testing::Test {
+ public:
+ base::MessageLoopForIO base_loop_;
+ brillo::BaseMessageLoop loop_{&base_loop_};
+
+ T test_;
+
+ protected:
+ HttpFetcherTest() {
+ loop_.SetAsCurrent();
+ }
+
+ void TearDown() override {
+ EXPECT_EQ(0, brillo::MessageLoopRunMaxIterations(&loop_, 1));
+ }
+
+ private:
+ static void TypeConstraint(T* a) {
+ AnyHttpFetcherTest *b = a;
+ if (b == 0) // Silence compiler warning of unused variable.
+ *b = a;
+ }
+};
+
+// Test case types list.
+typedef ::testing::Types<LibcurlHttpFetcherTest,
+ MockHttpFetcherTest,
+ MultiRangeHttpFetcherTest> HttpFetcherTestTypes;
+TYPED_TEST_CASE(HttpFetcherTest, HttpFetcherTestTypes);
+
+
+namespace {
+class HttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ HttpFetcherTestDelegate() :
+ is_expect_error_(false), times_transfer_complete_called_(0),
+ times_transfer_terminated_called_(0), times_received_bytes_called_(0) {}
+
+ void ReceivedBytes(HttpFetcher* /* fetcher */,
+ const void* /* bytes */, size_t /* length */) override {
+ // Update counters
+ times_received_bytes_called_++;
+ }
+
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ if (is_expect_error_)
+ EXPECT_EQ(kHttpResponseNotFound, fetcher->http_response_code());
+ else
+ EXPECT_EQ(kHttpResponseOk, fetcher->http_response_code());
+ MessageLoop::current()->BreakLoop();
+
+ // Update counter
+ times_transfer_complete_called_++;
+ }
+
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ times_transfer_terminated_called_++;
+ }
+
+ // Are we expecting an error response? (default: no)
+ bool is_expect_error_;
+
+ // Counters for callback invocations.
+ int times_transfer_complete_called_;
+ int times_transfer_terminated_called_;
+ int times_received_bytes_called_;
+};
+
+
+void StartTransfer(HttpFetcher* http_fetcher, const string& url) {
+ http_fetcher->BeginTransfer(url);
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, SimpleTest) {
+ HttpFetcherTestDelegate delegate;
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ this->test_.SmallUrl(server->GetPort())));
+ this->loop_.Run();
+}
+
+TYPED_TEST(HttpFetcherTest, SimpleBigTest) {
+ HttpFetcherTestDelegate delegate;
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewLargeFetcher());
+ fetcher->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ this->test_.BigUrl(server->GetPort())));
+ this->loop_.Run();
+}
+
+// Issue #9648: when server returns an error HTTP response, the fetcher needs to
+// terminate transfer prematurely, rather than try to process the error payload.
+TYPED_TEST(HttpFetcherTest, ErrorTest) {
+ if (this->test_.IsMock() || this->test_.IsMulti())
+ return;
+ HttpFetcherTestDelegate delegate;
+
+ // Delegate should expect an error response.
+ delegate.is_expect_error_ = true;
+
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ this->test_.ErrorUrl(server->GetPort())));
+ this->loop_.Run();
+
+ // Make sure that no bytes were received.
+ CHECK_EQ(delegate.times_received_bytes_called_, 0);
+ CHECK_EQ(fetcher->GetBytesDownloaded(), static_cast<size_t>(0));
+
+ // Make sure that transfer completion was signaled once, and no termination
+ // was signaled.
+ CHECK_EQ(delegate.times_transfer_complete_called_, 1);
+ CHECK_EQ(delegate.times_transfer_terminated_called_, 0);
+}
+
+namespace {
+class PausingHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* /* bytes */, size_t /* length */) override {
+ CHECK(!paused_);
+ paused_ = true;
+ fetcher->Pause();
+ }
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+ void Unpause() {
+ CHECK(paused_);
+ paused_ = false;
+ fetcher_->Unpause();
+ }
+ bool paused_;
+ HttpFetcher* fetcher_;
+};
+
+void UnpausingTimeoutCallback(PausingHttpFetcherTestDelegate* delegate,
+ MessageLoop::TaskId* my_id) {
+ if (delegate->paused_)
+ delegate->Unpause();
+ // Update the task id with the new scheduled callback.
+ *my_id = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UnpausingTimeoutCallback, delegate, my_id),
+ base::TimeDelta::FromMilliseconds(200));
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, PauseTest) {
+ {
+ PausingHttpFetcherTestDelegate delegate;
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewLargeFetcher());
+ delegate.paused_ = false;
+ delegate.fetcher_ = fetcher.get();
+ fetcher->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ MessageLoop::TaskId callback_id;
+ callback_id = this->loop_.PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UnpausingTimeoutCallback, &delegate, &callback_id),
+ base::TimeDelta::FromMilliseconds(200));
+ fetcher->BeginTransfer(this->test_.BigUrl(server->GetPort()));
+
+ this->loop_.Run();
+ EXPECT_TRUE(this->loop_.CancelTask(callback_id));
+ }
+}
+
+namespace {
+class AbortingHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {}
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ ADD_FAILURE(); // We should never get here
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ EXPECT_EQ(fetcher, fetcher_.get());
+ EXPECT_FALSE(once_);
+ EXPECT_TRUE(callback_once_);
+ callback_once_ = false;
+ // The fetcher could have a callback scheduled on the ProxyResolver that
+ // can fire after this callback. We wait until the end of the test to
+ // delete the fetcher.
+ }
+ void TerminateTransfer() {
+ CHECK(once_);
+ once_ = false;
+ fetcher_->TerminateTransfer();
+ }
+ void EndLoop() {
+ MessageLoop::current()->BreakLoop();
+ }
+ bool once_;
+ bool callback_once_;
+ unique_ptr<HttpFetcher> fetcher_;
+};
+
+void AbortingTimeoutCallback(AbortingHttpFetcherTestDelegate* delegate,
+ MessageLoop::TaskId* my_id) {
+ if (delegate->once_) {
+ delegate->TerminateTransfer();
+ *my_id = MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(AbortingTimeoutCallback, delegate, my_id));
+ } else {
+ delegate->EndLoop();
+ *my_id = MessageLoop::kTaskIdNull;
+ }
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, AbortTest) {
+ AbortingHttpFetcherTestDelegate delegate;
+ delegate.fetcher_.reset(this->test_.NewLargeFetcher());
+ delegate.once_ = true;
+ delegate.callback_once_ = true;
+ delegate.fetcher_->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ this->test_.IgnoreServerAborting(server.get());
+ ASSERT_TRUE(server->started_);
+
+ MessageLoop::TaskId task_id = MessageLoop::kTaskIdNull;
+
+ task_id = this->loop_.PostTask(
+ FROM_HERE,
+ base::Bind(AbortingTimeoutCallback, &delegate, &task_id));
+ delegate.fetcher_->BeginTransfer(this->test_.BigUrl(server->GetPort()));
+
+ this->loop_.Run();
+ CHECK(!delegate.once_);
+ CHECK(!delegate.callback_once_);
+ this->loop_.CancelTask(task_id);
+}
+
+namespace {
+class FlakyHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {
+ data.append(reinterpret_cast<const char*>(bytes), length);
+ }
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ EXPECT_TRUE(successful);
+ EXPECT_EQ(kHttpResponsePartialContent, fetcher->http_response_code());
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+ string data;
+};
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, FlakyTest) {
+ if (this->test_.IsMock())
+ return;
+ {
+ FlakyHttpFetcherTestDelegate delegate;
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ &StartTransfer,
+ fetcher.get(),
+ LocalServerUrlForPath(server->GetPort(),
+ base::StringPrintf("/flaky/%d/%d/%d/%d",
+ kBigLength,
+ kFlakyTruncateLength,
+ kFlakySleepEvery,
+ kFlakySleepSecs))));
+ this->loop_.Run();
+
+ // verify the data we get back
+ ASSERT_EQ(kBigLength, delegate.data.size());
+ for (int i = 0; i < kBigLength; i += 10) {
+ // Assert so that we don't flood the screen w/ EXPECT errors on failure.
+ ASSERT_EQ(delegate.data.substr(i, 10), "abcdefghij");
+ }
+ }
+}
+
+namespace {
+// This delegate kills the server attached to it after receiving any bytes.
+// This can be used for testing what happens when you try to fetch data and
+// the server dies.
+class FailureHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ explicit FailureHttpFetcherTestDelegate(PythonHttpServer* server)
+ : server_(server) {}
+
+ ~FailureHttpFetcherTestDelegate() override {
+ if (server_) {
+ LOG(INFO) << "Stopping server in destructor";
+ delete server_;
+ LOG(INFO) << "server stopped";
+ }
+ }
+
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {
+ if (server_) {
+ LOG(INFO) << "Stopping server in ReceivedBytes";
+ delete server_;
+ LOG(INFO) << "server stopped";
+ server_ = nullptr;
+ }
+ }
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ EXPECT_FALSE(successful);
+ EXPECT_EQ(0, fetcher->http_response_code());
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+ PythonHttpServer* server_;
+};
+} // namespace
+
+
+TYPED_TEST(HttpFetcherTest, FailureTest) {
+ // This test ensures that a fetcher responds correctly when a server isn't
+ // available at all.
+ if (this->test_.IsMock())
+ return;
+ {
+ FailureHttpFetcherTestDelegate delegate(nullptr);
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+
+ this->loop_.PostTask(FROM_HERE,
+ base::Bind(StartTransfer,
+ fetcher.get(),
+ "http://host_doesnt_exist99999999"));
+ this->loop_.Run();
+
+ // Exiting and testing happens in the delegate
+ }
+}
+
+TYPED_TEST(HttpFetcherTest, NoResponseTest) {
+ // This test starts a new http server but the server doesn't respond and just
+ // closes the connection.
+ if (this->test_.IsMock())
+ return;
+
+ PythonHttpServer* server = new PythonHttpServer();
+ int port = server->GetPort();
+ ASSERT_TRUE(server->started_);
+
+ // Handles destruction and claims ownership.
+ FailureHttpFetcherTestDelegate delegate(server);
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+ // The server will not reply at all, so we can limit the execution time of the
+ // test by reducing the low-speed timeout to something small. The test will
+ // finish once the TimeoutCallback() triggers (every second) and the timeout
+ // expired.
+ fetcher->set_low_speed_limit(kDownloadLowSpeedLimitBps, 1);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ LocalServerUrlForPath(port, "/hang")));
+ this->loop_.Run();
+
+ // Check that no other callback runs in the next two seconds. That would
+ // indicate a leaked callback.
+ bool timeout = false;
+ auto callback = base::Bind([&timeout]{ timeout = true;});
+ this->loop_.PostDelayedTask(FROM_HERE, callback,
+ base::TimeDelta::FromSeconds(2));
+ EXPECT_TRUE(this->loop_.RunOnce(true));
+ EXPECT_TRUE(timeout);
+}
+
+TYPED_TEST(HttpFetcherTest, ServerDiesTest) {
+ // This test starts a new http server and kills it after receiving its first
+ // set of bytes. It test whether or not our fetcher eventually gives up on
+ // retries and aborts correctly.
+ if (this->test_.IsMock())
+ return;
+ {
+ PythonHttpServer* server = new PythonHttpServer();
+ int port = server->GetPort();
+ ASSERT_TRUE(server->started_);
+
+ // Handles destruction and claims ownership.
+ FailureHttpFetcherTestDelegate delegate(server);
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ LocalServerUrlForPath(port,
+ base::StringPrintf("/flaky/%d/%d/%d/%d",
+ kBigLength,
+ kFlakyTruncateLength,
+ kFlakySleepEvery,
+ kFlakySleepSecs))));
+ this->loop_.Run();
+
+ // Exiting and testing happens in the delegate
+ }
+}
+
+namespace {
+const HttpResponseCode kRedirectCodes[] = {
+ kHttpResponseMovedPermanently, kHttpResponseFound, kHttpResponseSeeOther,
+ kHttpResponseTempRedirect
+};
+
+class RedirectHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ explicit RedirectHttpFetcherTestDelegate(bool expected_successful)
+ : expected_successful_(expected_successful) {}
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {
+ data.append(reinterpret_cast<const char*>(bytes), length);
+ }
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ EXPECT_EQ(expected_successful_, successful);
+ if (expected_successful_) {
+ EXPECT_EQ(kHttpResponseOk, fetcher->http_response_code());
+ } else {
+ EXPECT_GE(fetcher->http_response_code(), kHttpResponseMovedPermanently);
+ EXPECT_LE(fetcher->http_response_code(), kHttpResponseTempRedirect);
+ }
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+ bool expected_successful_;
+ string data;
+};
+
+// RedirectTest takes ownership of |http_fetcher|.
+void RedirectTest(const HttpServer* server,
+ bool expected_successful,
+ const string& url,
+ HttpFetcher* http_fetcher) {
+ RedirectHttpFetcherTestDelegate delegate(expected_successful);
+ unique_ptr<HttpFetcher> fetcher(http_fetcher);
+ fetcher->set_delegate(&delegate);
+
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ LocalServerUrlForPath(server->GetPort(), url)));
+ MessageLoop::current()->Run();
+ if (expected_successful) {
+ // verify the data we get back
+ ASSERT_EQ(kMediumLength, delegate.data.size());
+ for (int i = 0; i < kMediumLength; i += 10) {
+ // Assert so that we don't flood the screen w/ EXPECT errors on failure.
+ ASSERT_EQ(delegate.data.substr(i, 10), "abcdefghij");
+ }
+ }
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, SimpleRedirectTest) {
+ if (this->test_.IsMock())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ for (size_t c = 0; c < arraysize(kRedirectCodes); ++c) {
+ const string url = base::StringPrintf("/redirect/%d/download/%d",
+ kRedirectCodes[c],
+ kMediumLength);
+ RedirectTest(server.get(), true, url, this->test_.NewLargeFetcher());
+ }
+}
+
+TYPED_TEST(HttpFetcherTest, MaxRedirectTest) {
+ if (this->test_.IsMock())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ string url;
+ for (int r = 0; r < kDownloadMaxRedirects; r++) {
+ url += base::StringPrintf("/redirect/%d",
+ kRedirectCodes[r % arraysize(kRedirectCodes)]);
+ }
+ url += base::StringPrintf("/download/%d", kMediumLength);
+ RedirectTest(server.get(), true, url, this->test_.NewLargeFetcher());
+}
+
+TYPED_TEST(HttpFetcherTest, BeyondMaxRedirectTest) {
+ if (this->test_.IsMock())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ string url;
+ for (int r = 0; r < kDownloadMaxRedirects + 1; r++) {
+ url += base::StringPrintf("/redirect/%d",
+ kRedirectCodes[r % arraysize(kRedirectCodes)]);
+ }
+ url += base::StringPrintf("/download/%d", kMediumLength);
+ RedirectTest(server.get(), false, url, this->test_.NewLargeFetcher());
+}
+
+namespace {
+class MultiHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ explicit MultiHttpFetcherTestDelegate(int expected_response_code)
+ : expected_response_code_(expected_response_code) {}
+
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {
+ EXPECT_EQ(fetcher, fetcher_.get());
+ data.append(reinterpret_cast<const char*>(bytes), length);
+ }
+
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ EXPECT_EQ(fetcher, fetcher_.get());
+ EXPECT_EQ(expected_response_code_ != kHttpResponseUndefined, successful);
+ if (expected_response_code_ != 0)
+ EXPECT_EQ(expected_response_code_, fetcher->http_response_code());
+ // Destroy the fetcher (because we're allowed to).
+ fetcher_.reset(nullptr);
+ MessageLoop::current()->BreakLoop();
+ }
+
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+
+ unique_ptr<HttpFetcher> fetcher_;
+ int expected_response_code_;
+ string data;
+};
+
+void MultiTest(HttpFetcher* fetcher_in,
+ const string& url,
+ const vector<pair<off_t, off_t>>& ranges,
+ const string& expected_prefix,
+ off_t expected_size,
+ HttpResponseCode expected_response_code) {
+ MultiHttpFetcherTestDelegate delegate(expected_response_code);
+ delegate.fetcher_.reset(fetcher_in);
+
+ MultiRangeHttpFetcher* multi_fetcher =
+ dynamic_cast<MultiRangeHttpFetcher*>(fetcher_in);
+ ASSERT_TRUE(multi_fetcher);
+ multi_fetcher->ClearRanges();
+ for (vector<pair<off_t, off_t>>::const_iterator it = ranges.begin(),
+ e = ranges.end(); it != e; ++it) {
+ string tmp_str = base::StringPrintf("%jd+", it->first);
+ if (it->second > 0) {
+ base::StringAppendF(&tmp_str, "%jd", it->second);
+ multi_fetcher->AddRange(it->first, it->second);
+ } else {
+ base::StringAppendF(&tmp_str, "?");
+ multi_fetcher->AddRange(it->first);
+ }
+ LOG(INFO) << "added range: " << tmp_str;
+ }
+ dynamic_cast<FakeSystemState*>(fetcher_in->GetSystemState())
+ ->fake_hardware()->SetIsOfficialBuild(false);
+ multi_fetcher->set_delegate(&delegate);
+
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(StartTransfer, multi_fetcher, url));
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(expected_size, delegate.data.size());
+ EXPECT_EQ(expected_prefix,
+ string(delegate.data.data(), expected_prefix.size()));
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherSimpleTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(0, 25));
+ ranges.push_back(make_pair(99, 0));
+ MultiTest(this->test_.NewLargeFetcher(),
+ this->test_.BigUrl(server->GetPort()),
+ ranges,
+ "abcdefghijabcdefghijabcdejabcdefghijabcdef",
+ kBigLength - (99 - 25),
+ kHttpResponsePartialContent);
+}
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherLengthLimitTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(0, 24));
+ MultiTest(this->test_.NewLargeFetcher(),
+ this->test_.BigUrl(server->GetPort()),
+ ranges,
+ "abcdefghijabcdefghijabcd",
+ 24,
+ kHttpResponsePartialContent);
+}
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherMultiEndTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(kBigLength - 2, 0));
+ ranges.push_back(make_pair(kBigLength - 3, 0));
+ MultiTest(this->test_.NewLargeFetcher(),
+ this->test_.BigUrl(server->GetPort()),
+ ranges,
+ "ijhij",
+ 5,
+ kHttpResponsePartialContent);
+}
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherInsufficientTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(kBigLength - 2, 4));
+ for (int i = 0; i < 2; ++i) {
+ LOG(INFO) << "i = " << i;
+ MultiTest(this->test_.NewLargeFetcher(),
+ this->test_.BigUrl(server->GetPort()),
+ ranges,
+ "ij",
+ 2,
+ kHttpResponseUndefined);
+ ranges.push_back(make_pair(0, 5));
+ }
+}
+
+// Issue #18143: when a fetch of a secondary chunk out of a chain, then it
+// should retry with other proxies listed before giving up.
+//
+// (1) successful recovery: The offset fetch will fail twice but succeed with
+// the third proxy.
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherErrorIfOffsetRecoverableTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(0, 25));
+ ranges.push_back(make_pair(99, 0));
+ MultiTest(this->test_.NewLargeFetcher(3),
+ LocalServerUrlForPath(server->GetPort(),
+ base::StringPrintf("/error-if-offset/%d/2",
+ kBigLength)),
+ ranges,
+ "abcdefghijabcdefghijabcdejabcdefghijabcdef",
+ kBigLength - (99 - 25),
+ kHttpResponsePartialContent);
+}
+
+// (2) unsuccessful recovery: The offset fetch will fail repeatedly. The
+// fetcher will signal a (failed) completed transfer to the delegate.
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherErrorIfOffsetUnrecoverableTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(0, 25));
+ ranges.push_back(make_pair(99, 0));
+ MultiTest(this->test_.NewLargeFetcher(2),
+ LocalServerUrlForPath(server->GetPort(),
+ base::StringPrintf("/error-if-offset/%d/3",
+ kBigLength)),
+ ranges,
+ "abcdefghijabcdefghijabcde", // only received the first chunk
+ 25,
+ kHttpResponseUndefined);
+}
+
+
+
+namespace {
+class BlockedTransferTestDelegate : public HttpFetcherDelegate {
+ public:
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {
+ ADD_FAILURE();
+ }
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ EXPECT_FALSE(successful);
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+};
+
+void BlockedTransferTestHelper(AnyHttpFetcherTest* fetcher_test,
+ bool is_official_build) {
+ if (fetcher_test->IsMock() || fetcher_test->IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(fetcher_test->CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ BlockedTransferTestDelegate delegate;
+ unique_ptr<HttpFetcher> fetcher(fetcher_test->NewLargeFetcher());
+ LOG(INFO) << "is_official_build: " << is_official_build;
+ // NewLargeFetcher creates the HttpFetcher* with a FakeSystemState.
+ dynamic_cast<FakeSystemState*>(fetcher->GetSystemState())
+ ->fake_hardware()->SetIsOfficialBuild(is_official_build);
+ fetcher->set_delegate(&delegate);
+
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ LocalServerUrlForPath(server->GetPort(),
+ fetcher_test->SmallUrl(server->GetPort()))));
+ MessageLoop::current()->Run();
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, BlockedTransferTest) {
+ BlockedTransferTestHelper(&this->test_, false);
+}
+
+TYPED_TEST(HttpFetcherTest, BlockedTransferOfficialBuildTest) {
+ BlockedTransferTestHelper(&this->test_, true);
+}
+
+} // namespace chromeos_update_engine