| // | |
| // 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 <algorithm> | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include <base/bind.h> | |
| #include <base/location.h> | |
| #include <base/logging.h> | |
| #if BASE_VER < 780000 // Android | |
| #include <base/message_loop/message_loop.h> | |
| #endif // BASE_VER < 780000 | |
| #include <base/stl_util.h> | |
| #include <base/strings/string_number_conversions.h> | |
| #include <base/strings/string_util.h> | |
| #include <base/strings/stringprintf.h> | |
| #if BASE_VER >= 780000 // CrOS | |
| #include <base/task/single_thread_task_executor.h> | |
| #endif // BASE_VER >= 780000 | |
| #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> | |
| #ifdef __CHROMEOS__ | |
| #include <brillo/process/process.h> | |
| #else | |
| #include <brillo/process.h> | |
| #endif // __CHROMEOS__ | |
| #include <brillo/streams/file_stream.h> | |
| #include <brillo/streams/stream.h> | |
| #include <gtest/gtest.h> | |
| #include "update_engine/common/fake_hardware.h" | |
| #include "update_engine/common/file_fetcher.h" | |
| #include "update_engine/common/http_common.h" | |
| #include "update_engine/common/mock_http_fetcher.h" | |
| #include "update_engine/common/mock_proxy_resolver.h" | |
| #include "update_engine/common/multi_range_http_fetcher.h" | |
| #include "update_engine/common/proxy_resolver.h" | |
| #include "update_engine/common/test_utils.h" | |
| #include "update_engine/common/utils.h" | |
| #include "update_engine/libcurl_http_fetcher.h" | |
| using brillo::MessageLoop; | |
| using std::make_pair; | |
| using std::pair; | |
| using std::string; | |
| using std::unique_ptr; | |
| using std::vector; | |
| using testing::_; | |
| using testing::DoAll; | |
| using testing::Return; | |
| using testing::SaveArg; | |
| 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. | |
| // | |
| namespace { | |
| 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()); | |
| http_server->AddArg(test_utils::GetBuildArtifactsPath("test_http_server")); | |
| 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 AnyHttpFetcherFactory { | |
| public: | |
| AnyHttpFetcherFactory() {} | |
| virtual ~AnyHttpFetcherFactory() {} | |
| virtual HttpFetcher* NewLargeFetcher(ProxyResolver* proxy_resolver) = 0; | |
| HttpFetcher* NewLargeFetcher(size_t num_proxies) { | |
| proxy_resolver_.set_num_proxies(num_proxies); | |
| return NewLargeFetcher(&proxy_resolver_); | |
| } | |
| HttpFetcher* NewLargeFetcher() { return NewLargeFetcher(1); } | |
| virtual HttpFetcher* NewSmallFetcher(ProxyResolver* proxy_resolver) = 0; | |
| HttpFetcher* NewSmallFetcher() { | |
| proxy_resolver_.set_num_proxies(1); | |
| return NewSmallFetcher(&proxy_resolver_); | |
| } | |
| 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 bool IsHttpSupported() const = 0; | |
| virtual bool IsFileFetcher() const = 0; | |
| virtual void IgnoreServerAborting(HttpServer* server) const {} | |
| virtual HttpServer* CreateServer() = 0; | |
| FakeHardware* fake_hardware() { return &fake_hardware_; } | |
| protected: | |
| DirectProxyResolver proxy_resolver_; | |
| FakeHardware fake_hardware_; | |
| }; | |
| class MockHttpFetcherFactory : public AnyHttpFetcherFactory { | |
| public: | |
| // Necessary to unhide the definition in the base class. | |
| using AnyHttpFetcherFactory::NewLargeFetcher; | |
| HttpFetcher* NewLargeFetcher(ProxyResolver* proxy_resolver) override { | |
| brillo::Blob big_data(1000000); | |
| return new MockHttpFetcher( | |
| big_data.data(), big_data.size(), proxy_resolver); | |
| } | |
| // Necessary to unhide the definition in the base class. | |
| using AnyHttpFetcherFactory::NewSmallFetcher; | |
| HttpFetcher* NewSmallFetcher(ProxyResolver* proxy_resolver) override { | |
| return new MockHttpFetcher("x", 1, proxy_resolver); | |
| } | |
| bool IsMock() const override { return true; } | |
| bool IsMulti() const override { return false; } | |
| bool IsHttpSupported() const override { return true; } | |
| bool IsFileFetcher() const override { return false; } | |
| HttpServer* CreateServer() override { return new NullHttpServer; } | |
| }; | |
| class LibcurlHttpFetcherFactory : public AnyHttpFetcherFactory { | |
| public: | |
| // Necessary to unhide the definition in the base class. | |
| using AnyHttpFetcherFactory::NewLargeFetcher; | |
| HttpFetcher* NewLargeFetcher(ProxyResolver* proxy_resolver) override { | |
| LibcurlHttpFetcher* ret = | |
| new LibcurlHttpFetcher(proxy_resolver, &fake_hardware_); | |
| // Speed up test execution. | |
| ret->set_idle_seconds(1); | |
| ret->set_retry_seconds(1); | |
| fake_hardware_.SetIsOfficialBuild(false); | |
| return ret; | |
| } | |
| // Necessary to unhide the definition in the base class. | |
| using AnyHttpFetcherFactory::NewSmallFetcher; | |
| HttpFetcher* NewSmallFetcher(ProxyResolver* proxy_resolver) override { | |
| return NewLargeFetcher(proxy_resolver); | |
| } | |
| 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; } | |
| bool IsHttpSupported() const override { return true; } | |
| bool IsFileFetcher() const override { return false; } | |
| void IgnoreServerAborting(HttpServer* server) const override { | |
| // Nothing to do. | |
| } | |
| HttpServer* CreateServer() override { return new PythonHttpServer; } | |
| }; | |
| class MultiRangeHttpFetcherFactory : public LibcurlHttpFetcherFactory { | |
| public: | |
| // Necessary to unhide the definition in the base class. | |
| using AnyHttpFetcherFactory::NewLargeFetcher; | |
| HttpFetcher* NewLargeFetcher(ProxyResolver* proxy_resolver) override { | |
| MultiRangeHttpFetcher* ret = new MultiRangeHttpFetcher( | |
| new LibcurlHttpFetcher(proxy_resolver, &fake_hardware_)); | |
| ret->ClearRanges(); | |
| ret->AddRange(0); | |
| // Speed up test execution. | |
| ret->set_idle_seconds(1); | |
| ret->set_retry_seconds(1); | |
| fake_hardware_.SetIsOfficialBuild(false); | |
| return ret; | |
| } | |
| // Necessary to unhide the definition in the base class. | |
| using AnyHttpFetcherFactory::NewSmallFetcher; | |
| HttpFetcher* NewSmallFetcher(ProxyResolver* proxy_resolver) override { | |
| return NewLargeFetcher(proxy_resolver); | |
| } | |
| bool IsMulti() const override { return true; } | |
| }; | |
| class FileFetcherFactory : public AnyHttpFetcherFactory { | |
| public: | |
| // Necessary to unhide the definition in the base class. | |
| using AnyHttpFetcherFactory::NewLargeFetcher; | |
| HttpFetcher* NewLargeFetcher(ProxyResolver* /* proxy_resolver */) override { | |
| return new FileFetcher(); | |
| } | |
| // Necessary to unhide the definition in the base class. | |
| using AnyHttpFetcherFactory::NewSmallFetcher; | |
| HttpFetcher* NewSmallFetcher(ProxyResolver* proxy_resolver) override { | |
| return NewLargeFetcher(proxy_resolver); | |
| } | |
| string BigUrl(in_port_t port) const override { | |
| static string big_contents = []() { | |
| string buf; | |
| buf.reserve(kBigLength); | |
| constexpr const char* kBigUrlContent = "abcdefghij"; | |
| for (size_t i = 0; i < kBigLength; i += strlen(kBigUrlContent)) { | |
| buf.append(kBigUrlContent, | |
| std::min(kBigLength - i, strlen(kBigUrlContent))); | |
| } | |
| return buf; | |
| }(); | |
| test_utils::WriteFileString(temp_file_.path(), big_contents); | |
| return "file://" + temp_file_.path(); | |
| } | |
| string SmallUrl(in_port_t port) const override { | |
| test_utils::WriteFileString(temp_file_.path(), "small contents"); | |
| return "file://" + temp_file_.path(); | |
| } | |
| string ErrorUrl(in_port_t port) const override { | |
| return "file:///path/to/non-existing-file"; | |
| } | |
| bool IsMock() const override { return false; } | |
| bool IsMulti() const override { return false; } | |
| bool IsHttpSupported() const override { return false; } | |
| bool IsFileFetcher() const override { return true; } | |
| void IgnoreServerAborting(HttpServer* server) const override {} | |
| HttpServer* CreateServer() override { return new NullHttpServer; } | |
| private: | |
| ScopedTempFile temp_file_{"ue_file_fetcher.XXXXXX"}; | |
| }; | |
| class MultiRangeHttpFetcherOverFileFetcherFactory : public FileFetcherFactory { | |
| public: | |
| // Necessary to unhide the definition in the base class. | |
| using AnyHttpFetcherFactory::NewLargeFetcher; | |
| HttpFetcher* NewLargeFetcher(ProxyResolver* /* proxy_resolver */) override { | |
| MultiRangeHttpFetcher* ret = new MultiRangeHttpFetcher(new FileFetcher()); | |
| ret->ClearRanges(); | |
| // FileFetcher doesn't support range with unspecified length. | |
| ret->AddRange(0, 1); | |
| // Speed up test execution. | |
| ret->set_idle_seconds(1); | |
| ret->set_retry_seconds(1); | |
| fake_hardware_.SetIsOfficialBuild(false); | |
| return ret; | |
| } | |
| // Necessary to unhide the definition in the base class. | |
| using AnyHttpFetcherFactory::NewSmallFetcher; | |
| HttpFetcher* NewSmallFetcher(ProxyResolver* proxy_resolver) override { | |
| return NewLargeFetcher(proxy_resolver); | |
| } | |
| 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: | |
| #if BASE_VER < 780000 // Android | |
| base::MessageLoopForIO base_loop_; | |
| brillo::BaseMessageLoop loop_{&base_loop_}; | |
| #else // Chrome OS | |
| base::SingleThreadTaskExecutor base_loop_{base::MessagePumpType::IO}; | |
| brillo::BaseMessageLoop loop_{base_loop_.task_runner()}; | |
| #endif // BASE_VER < 780000 | |
| T test_; | |
| protected: | |
| HttpFetcherTest() { loop_.SetAsCurrent(); } | |
| void TearDown() override { | |
| EXPECT_EQ(0, brillo::MessageLoopRunMaxIterations(&loop_, 1)); | |
| } | |
| private: | |
| static void TypeConstraint(T* a) { | |
| AnyHttpFetcherFactory* b = a; | |
| if (b == 0) // Silence compiler warning of unused variable. | |
| *b = a; | |
| } | |
| }; | |
| // Test case types list. | |
| typedef ::testing::Types<LibcurlHttpFetcherFactory, | |
| MockHttpFetcherFactory, | |
| MultiRangeHttpFetcherFactory, | |
| FileFetcherFactory, | |
| MultiRangeHttpFetcherOverFileFetcherFactory> | |
| HttpFetcherTestTypes; | |
| TYPED_TEST_CASE(HttpFetcherTest, HttpFetcherTestTypes); | |
| class HttpFetcherTestDelegate : public HttpFetcherDelegate { | |
| public: | |
| HttpFetcherTestDelegate() = default; | |
| bool ReceivedBytes(HttpFetcher* /* fetcher */, | |
| const void* bytes, | |
| size_t length) override { | |
| data.append(reinterpret_cast<const char*>(bytes), length); | |
| // Update counters | |
| times_received_bytes_called_++; | |
| return true; | |
| } | |
| 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 { | |
| times_transfer_terminated_called_++; | |
| MessageLoop::current()->BreakLoop(); | |
| } | |
| // Are we expecting an error response? (default: no) | |
| bool is_expect_error_{false}; | |
| // Counters for callback invocations. | |
| int times_transfer_complete_called_{0}; | |
| int times_transfer_terminated_called_{0}; | |
| int times_received_bytes_called_{0}; | |
| // The received data bytes. | |
| string data; | |
| }; | |
| void StartTransfer(HttpFetcher* http_fetcher, const string& url) { | |
| http_fetcher->BeginTransfer(url); | |
| } | |
| 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(); | |
| EXPECT_EQ(0, delegate.times_transfer_terminated_called_); | |
| } | |
| 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(); | |
| EXPECT_EQ(0, delegate.times_transfer_terminated_called_); | |
| } | |
| // 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. | |
| EXPECT_EQ(0, delegate.times_received_bytes_called_); | |
| EXPECT_EQ(0U, fetcher->GetBytesDownloaded()); | |
| // Make sure that transfer completion was signaled once, and no termination | |
| // was signaled. | |
| EXPECT_EQ(1, delegate.times_transfer_complete_called_); | |
| EXPECT_EQ(0, delegate.times_transfer_terminated_called_); | |
| } | |
| TYPED_TEST(HttpFetcherTest, ExtraHeadersInRequestTest) { | |
| if (this->test_.IsMock() || !this->test_.IsHttpSupported()) | |
| return; | |
| HttpFetcherTestDelegate delegate; | |
| unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher()); | |
| fetcher->set_delegate(&delegate); | |
| fetcher->SetHeader("User-Agent", "MyTest"); | |
| fetcher->SetHeader("user-agent", "Override that header"); | |
| fetcher->SetHeader("Authorization", "Basic user:passwd"); | |
| // Invalid headers. | |
| fetcher->SetHeader("X-Foo", "Invalid\nHeader\nIgnored"); | |
| fetcher->SetHeader("X-Bar: ", "I do not know how to parse"); | |
| // Hide Accept header normally added by default. | |
| fetcher->SetHeader("Accept", ""); | |
| PythonHttpServer server; | |
| int port = server.GetPort(); | |
| ASSERT_TRUE(server.started_); | |
| this->loop_.PostTask( | |
| FROM_HERE, | |
| base::Bind(StartTransfer, | |
| fetcher.get(), | |
| LocalServerUrlForPath(port, "/echo-headers"))); | |
| this->loop_.Run(); | |
| EXPECT_NE(string::npos, | |
| delegate.data.find("user-agent: Override that header\r\n")); | |
| EXPECT_NE(string::npos, | |
| delegate.data.find("Authorization: Basic user:passwd\r\n")); | |
| EXPECT_EQ(string::npos, delegate.data.find("\nAccept:")); | |
| EXPECT_EQ(string::npos, delegate.data.find("X-Foo: Invalid")); | |
| EXPECT_EQ(string::npos, delegate.data.find("X-Bar: I do not")); | |
| } | |
| class PausingHttpFetcherTestDelegate : public HttpFetcherDelegate { | |
| public: | |
| bool ReceivedBytes(HttpFetcher* fetcher, | |
| const void* /* bytes */, | |
| size_t /* length */) override { | |
| CHECK(!paused_); | |
| paused_ = true; | |
| fetcher->Pause(); | |
| return true; | |
| } | |
| 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)); | |
| } | |
| 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)); | |
| } | |
| // This test will pause the fetcher while the download is not yet started | |
| // because it is waiting for the proxy to be resolved. | |
| TYPED_TEST(HttpFetcherTest, PauseWhileResolvingProxyTest) { | |
| if (this->test_.IsMock() || !this->test_.IsHttpSupported()) | |
| return; | |
| MockProxyResolver mock_resolver; | |
| unique_ptr<HttpFetcher> fetcher(this->test_.NewLargeFetcher(&mock_resolver)); | |
| // Saved arguments from the proxy call. | |
| ProxiesResolvedFn proxy_callback; | |
| EXPECT_CALL(mock_resolver, GetProxiesForUrl("http://fake_url", _)) | |
| .WillOnce(DoAll(SaveArg<1>(&proxy_callback), Return(true))); | |
| fetcher->BeginTransfer("http://fake_url"); | |
| testing::Mock::VerifyAndClearExpectations(&mock_resolver); | |
| // Pausing and unpausing while resolving the proxy should not affect anything. | |
| fetcher->Pause(); | |
| fetcher->Unpause(); | |
| fetcher->Pause(); | |
| // Proxy resolver comes back after we paused the fetcher. | |
| ASSERT_FALSE(proxy_callback.is_null()); | |
| proxy_callback.Run({1, kNoProxy}); | |
| } | |
| class AbortingHttpFetcherTestDelegate : public HttpFetcherDelegate { | |
| public: | |
| bool ReceivedBytes(HttpFetcher* fetcher, | |
| const void* bytes, | |
| size_t length) override { | |
| return true; | |
| } | |
| 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; | |
| } | |
| } | |
| 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); | |
| } | |
| TYPED_TEST(HttpFetcherTest, TerminateTransferWhileResolvingProxyTest) { | |
| if (this->test_.IsMock() || !this->test_.IsHttpSupported()) | |
| return; | |
| MockProxyResolver mock_resolver; | |
| unique_ptr<HttpFetcher> fetcher(this->test_.NewLargeFetcher(&mock_resolver)); | |
| HttpFetcherTestDelegate delegate; | |
| fetcher->set_delegate(&delegate); | |
| EXPECT_CALL(mock_resolver, GetProxiesForUrl(_, _)).WillOnce(Return(123)); | |
| fetcher->BeginTransfer("http://fake_url"); | |
| // Run the message loop until idle. This must call the MockProxyResolver with | |
| // the request. | |
| while (this->loop_.RunOnce(false)) { | |
| } | |
| testing::Mock::VerifyAndClearExpectations(&mock_resolver); | |
| EXPECT_CALL(mock_resolver, CancelProxyRequest(123)).WillOnce(Return(true)); | |
| // Terminate the transfer right before the proxy resolution response. | |
| fetcher->TerminateTransfer(); | |
| EXPECT_EQ(0, delegate.times_received_bytes_called_); | |
| EXPECT_EQ(0, delegate.times_transfer_complete_called_); | |
| EXPECT_EQ(1, delegate.times_transfer_terminated_called_); | |
| } | |
| class FlakyHttpFetcherTestDelegate : public HttpFetcherDelegate { | |
| public: | |
| bool ReceivedBytes(HttpFetcher* fetcher, | |
| const void* bytes, | |
| size_t length) override { | |
| data.append(reinterpret_cast<const char*>(bytes), length); | |
| return true; | |
| } | |
| 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; | |
| }; | |
| TYPED_TEST(HttpFetcherTest, FlakyTest) { | |
| if (this->test_.IsMock() || !this->test_.IsHttpSupported()) | |
| 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, static_cast<int>(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"); | |
| } | |
| } | |
| } | |
| // 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"; | |
| server_.reset(); | |
| LOG(INFO) << "server stopped"; | |
| } | |
| } | |
| bool ReceivedBytes(HttpFetcher* fetcher, | |
| const void* bytes, | |
| size_t length) override { | |
| if (server_) { | |
| LOG(INFO) << "Stopping server in ReceivedBytes"; | |
| server_.reset(); | |
| LOG(INFO) << "server stopped"; | |
| } | |
| return true; | |
| } | |
| void TransferComplete(HttpFetcher* fetcher, bool successful) override { | |
| EXPECT_FALSE(successful); | |
| EXPECT_EQ(0, fetcher->http_response_code()); | |
| times_transfer_complete_called_++; | |
| MessageLoop::current()->BreakLoop(); | |
| } | |
| void TransferTerminated(HttpFetcher* fetcher) override { | |
| times_transfer_terminated_called_++; | |
| MessageLoop::current()->BreakLoop(); | |
| } | |
| unique_ptr<PythonHttpServer> server_; | |
| int times_transfer_terminated_called_{0}; | |
| int times_transfer_complete_called_{0}; | |
| }; | |
| 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(); | |
| EXPECT_EQ(1, delegate.times_transfer_complete_called_); | |
| EXPECT_EQ(0, delegate.times_transfer_terminated_called_); | |
| // 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(); | |
| EXPECT_EQ(1, delegate.times_transfer_complete_called_); | |
| EXPECT_EQ(0, delegate.times_transfer_terminated_called_); | |
| // Check that no other callback runs in the next two seconds. That would | |
| // indicate a leaked callback. | |
| bool timeout = false; | |
| auto callback = base::Bind([](bool* timeout) { *timeout = true; }, | |
| base::Unretained(&timeout)); | |
| 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(); | |
| EXPECT_EQ(1, delegate.times_transfer_complete_called_); | |
| EXPECT_EQ(0, delegate.times_transfer_terminated_called_); | |
| // Exiting and testing happens in the delegate | |
| } | |
| // Test that we can cancel a transfer while it is still trying to connect to the | |
| // server. This test kills the server after a few bytes are received. | |
| TYPED_TEST(HttpFetcherTest, TerminateTransferWhenServerDiedTest) { | |
| if (this->test_.IsMock() || !this->test_.IsHttpSupported()) | |
| 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)))); | |
| // Terminating the transfer after 3 seconds gives it a chance to contact the | |
| // server and enter the retry loop. | |
| this->loop_.PostDelayedTask(FROM_HERE, | |
| base::Bind(&HttpFetcher::TerminateTransfer, | |
| base::Unretained(fetcher.get())), | |
| base::TimeDelta::FromSeconds(3)); | |
| // Exiting and testing happens in the delegate. | |
| this->loop_.Run(); | |
| EXPECT_EQ(0, delegate.times_transfer_complete_called_); | |
| EXPECT_EQ(1, delegate.times_transfer_terminated_called_); | |
| // Check that no other callback runs in the next two seconds. That would | |
| // indicate a leaked callback. | |
| bool timeout = false; | |
| auto callback = base::Bind([](bool* timeout) { *timeout = true; }, | |
| base::Unretained(&timeout)); | |
| this->loop_.PostDelayedTask( | |
| FROM_HERE, callback, base::TimeDelta::FromSeconds(2)); | |
| EXPECT_TRUE(this->loop_.RunOnce(true)); | |
| EXPECT_TRUE(timeout); | |
| } | |
| const HttpResponseCode kRedirectCodes[] = {kHttpResponseMovedPermanently, | |
| kHttpResponseFound, | |
| kHttpResponseSeeOther, | |
| kHttpResponseTempRedirect}; | |
| class RedirectHttpFetcherTestDelegate : public HttpFetcherDelegate { | |
| public: | |
| explicit RedirectHttpFetcherTestDelegate(bool expected_successful) | |
| : expected_successful_(expected_successful) {} | |
| bool ReceivedBytes(HttpFetcher* fetcher, | |
| const void* bytes, | |
| size_t length) override { | |
| data.append(reinterpret_cast<const char*>(bytes), length); | |
| return true; | |
| } | |
| 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(static_cast<size_t>(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"); | |
| } | |
| } | |
| } | |
| TYPED_TEST(HttpFetcherTest, SimpleRedirectTest) { | |
| if (this->test_.IsMock() || !this->test_.IsHttpSupported()) | |
| return; | |
| unique_ptr<HttpServer> server(this->test_.CreateServer()); | |
| ASSERT_TRUE(server->started_); | |
| for (size_t c = 0; c < base::size(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() || !this->test_.IsHttpSupported()) | |
| 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 % base::size(kRedirectCodes)]); | |
| } | |
| url += base::StringPrintf("/download/%d", kMediumLength); | |
| RedirectTest(server.get(), true, url, this->test_.NewLargeFetcher()); | |
| } | |
| TYPED_TEST(HttpFetcherTest, BeyondMaxRedirectTest) { | |
| if (this->test_.IsMock() || !this->test_.IsHttpSupported()) | |
| 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 % base::size(kRedirectCodes)]); | |
| } | |
| url += base::StringPrintf("/download/%d", kMediumLength); | |
| RedirectTest(server.get(), false, url, this->test_.NewLargeFetcher()); | |
| } | |
| class MultiHttpFetcherTestDelegate : public HttpFetcherDelegate { | |
| public: | |
| explicit MultiHttpFetcherTestDelegate(int expected_response_code) | |
| : expected_response_code_(expected_response_code) {} | |
| bool ReceivedBytes(HttpFetcher* fetcher, | |
| const void* bytes, | |
| size_t length) override { | |
| EXPECT_EQ(fetcher, fetcher_.get()); | |
| data.append(reinterpret_cast<const char*>(bytes), length); | |
| return true; | |
| } | |
| 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, | |
| FakeHardware* fake_hardware, | |
| const string& url, | |
| const vector<pair<off_t, off_t>>& ranges, | |
| const string& expected_prefix, | |
| size_t expected_size, | |
| HttpResponseCode expected_response_code) { | |
| MultiHttpFetcherTestDelegate delegate(expected_response_code); | |
| delegate.fetcher_.reset(fetcher_in); | |
| MultiRangeHttpFetcher* multi_fetcher = | |
| static_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; | |
| } | |
| 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())); | |
| } | |
| 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, 17)); | |
| MultiTest(this->test_.NewLargeFetcher(), | |
| this->test_.fake_hardware(), | |
| this->test_.BigUrl(server->GetPort()), | |
| ranges, | |
| "abcdefghijabcdefghijabcdejabcdefghijabcdef", | |
| 25 + 17, | |
| this->test_.IsFileFetcher() ? kHttpResponseOk | |
| : kHttpResponsePartialContent); | |
| } | |
| TYPED_TEST(HttpFetcherTest, MultiHttpFetcherUnspecifiedEndTest) { | |
| if (!this->test_.IsMulti() || this->test_.IsFileFetcher()) | |
| 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_.fake_hardware(), | |
| 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_.fake_hardware(), | |
| this->test_.BigUrl(server->GetPort()), | |
| ranges, | |
| "abcdefghijabcdefghijabcd", | |
| 24, | |
| this->test_.IsFileFetcher() ? kHttpResponseOk | |
| : kHttpResponsePartialContent); | |
| } | |
| TYPED_TEST(HttpFetcherTest, MultiHttpFetcherMultiEndTest) { | |
| if (!this->test_.IsMulti() || this->test_.IsFileFetcher()) | |
| 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_.fake_hardware(), | |
| 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_.fake_hardware(), | |
| 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() || this->test_.IsFileFetcher()) | |
| 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), | |
| this->test_.fake_hardware(), | |
| 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() || this->test_.IsFileFetcher()) | |
| 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), | |
| this->test_.fake_hardware(), | |
| LocalServerUrlForPath( | |
| server->GetPort(), | |
| base::StringPrintf("/error-if-offset/%d/3", kBigLength)), | |
| ranges, | |
| "abcdefghijabcdefghijabcde", // only received the first chunk | |
| 25, | |
| kHttpResponseUndefined); | |
| } | |
| // This HttpFetcherDelegate calls TerminateTransfer at a configurable point. | |
| class MultiHttpFetcherTerminateTestDelegate : public HttpFetcherDelegate { | |
| public: | |
| explicit MultiHttpFetcherTerminateTestDelegate(size_t terminate_trigger_bytes) | |
| : terminate_trigger_bytes_(terminate_trigger_bytes) {} | |
| bool ReceivedBytes(HttpFetcher* fetcher, | |
| const void* bytes, | |
| size_t length) override { | |
| LOG(INFO) << "ReceivedBytes, " << length << " bytes."; | |
| EXPECT_EQ(fetcher, fetcher_.get()); | |
| bool should_terminate = false; | |
| if (bytes_downloaded_ < terminate_trigger_bytes_ && | |
| bytes_downloaded_ + length >= terminate_trigger_bytes_) { | |
| MessageLoop::current()->PostTask( | |
| FROM_HERE, | |
| base::Bind(&HttpFetcher::TerminateTransfer, | |
| base::Unretained(fetcher_.get()))); | |
| should_terminate = true; | |
| } | |
| bytes_downloaded_ += length; | |
| return !should_terminate; | |
| } | |
| void TransferComplete(HttpFetcher* fetcher, bool successful) override { | |
| ADD_FAILURE() << "TransferComplete called but expected a failure"; | |
| // Destroy the fetcher (because we're allowed to). | |
| fetcher_.reset(nullptr); | |
| MessageLoop::current()->BreakLoop(); | |
| } | |
| void TransferTerminated(HttpFetcher* fetcher) override { | |
| // Destroy the fetcher (because we're allowed to). | |
| fetcher_.reset(nullptr); | |
| MessageLoop::current()->BreakLoop(); | |
| } | |
| unique_ptr<HttpFetcher> fetcher_; | |
| size_t bytes_downloaded_{0}; | |
| size_t terminate_trigger_bytes_; | |
| }; | |
| TYPED_TEST(HttpFetcherTest, MultiHttpFetcherTerminateBetweenRangesTest) { | |
| if (!this->test_.IsMulti()) | |
| return; | |
| const size_t kRangeTrigger = 1000; | |
| MultiHttpFetcherTerminateTestDelegate delegate(kRangeTrigger); | |
| unique_ptr<HttpServer> server(this->test_.CreateServer()); | |
| ASSERT_TRUE(server->started_); | |
| MultiRangeHttpFetcher* multi_fetcher = | |
| static_cast<MultiRangeHttpFetcher*>(this->test_.NewLargeFetcher()); | |
| ASSERT_TRUE(multi_fetcher); | |
| // Transfer ownership of the fetcher to the delegate. | |
| delegate.fetcher_.reset(multi_fetcher); | |
| multi_fetcher->set_delegate(&delegate); | |
| multi_fetcher->ClearRanges(); | |
| multi_fetcher->AddRange(45, kRangeTrigger); | |
| multi_fetcher->AddRange(2000, 100); | |
| this->test_.fake_hardware()->SetIsOfficialBuild(false); | |
| StartTransfer(multi_fetcher, this->test_.BigUrl(server->GetPort())); | |
| MessageLoop::current()->Run(); | |
| // Check that the delegate made it to the trigger point. | |
| EXPECT_EQ(kRangeTrigger, delegate.bytes_downloaded_); | |
| } | |
| class BlockedTransferTestDelegate : public HttpFetcherDelegate { | |
| public: | |
| bool ReceivedBytes(HttpFetcher* fetcher, | |
| const void* bytes, | |
| size_t length) override { | |
| ADD_FAILURE(); | |
| return true; | |
| } | |
| void TransferComplete(HttpFetcher* fetcher, bool successful) override { | |
| EXPECT_FALSE(successful); | |
| MessageLoop::current()->BreakLoop(); | |
| } | |
| void TransferTerminated(HttpFetcher* fetcher) override { ADD_FAILURE(); } | |
| }; | |
| void BlockedTransferTestHelper(AnyHttpFetcherFactory* 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. | |
| fetcher_test->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(); | |
| } | |
| TYPED_TEST(HttpFetcherTest, BlockedTransferTest) { | |
| BlockedTransferTestHelper(&this->test_, false); | |
| } | |
| TYPED_TEST(HttpFetcherTest, BlockedTransferOfficialBuildTest) { | |
| BlockedTransferTestHelper(&this->test_, true); | |
| } | |
| } // namespace | |
| } // namespace chromeos_update_engine |