1 //
2 // Copyright (C) 2019 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16
17 #include "update_engine/libcurl_http_fetcher.h"
18
19 #include <string>
20
21 #include <brillo/message_loops/fake_message_loop.h>
22 #include <gmock/gmock.h>
23 #include <gtest/gtest.h>
24
25 #include "update_engine/common/fake_hardware.h"
26 #include "update_engine/common/mock_proxy_resolver.h"
27 #include "update_engine/mock_libcurl_http_fetcher.h"
28
29 using std::string;
30
31 namespace chromeos_update_engine {
32
33 namespace {
34 constexpr char kHeaderName[] = "X-Goog-Test-Header";
35 }
36
37 class LibcurlHttpFetcherTest : public ::testing::Test {
38 protected:
SetUp()39 void SetUp() override {
40 loop_.SetAsCurrent();
41 fake_hardware_.SetIsOfficialBuild(true);
42 fake_hardware_.SetIsOOBEEnabled(false);
43 }
44
45 brillo::FakeMessageLoop loop_{nullptr};
46 FakeHardware fake_hardware_;
47 MockLibcurlHttpFetcher libcurl_fetcher_{nullptr, &fake_hardware_};
48 UnresolvedHostStateMachine state_machine_;
49 };
50
TEST_F(LibcurlHttpFetcherTest,GetEmptyHeaderValueTest)51 TEST_F(LibcurlHttpFetcherTest, GetEmptyHeaderValueTest) {
52 const string header_value = "";
53 string actual_header_value;
54 libcurl_fetcher_.SetHeader(kHeaderName, header_value);
55 EXPECT_TRUE(libcurl_fetcher_.GetHeader(kHeaderName, &actual_header_value));
56 EXPECT_EQ("", actual_header_value);
57 }
58
TEST_F(LibcurlHttpFetcherTest,GetHeaderTest)59 TEST_F(LibcurlHttpFetcherTest, GetHeaderTest) {
60 const string header_value = "This-is-value 123";
61 string actual_header_value;
62 libcurl_fetcher_.SetHeader(kHeaderName, header_value);
63 EXPECT_TRUE(libcurl_fetcher_.GetHeader(kHeaderName, &actual_header_value));
64 EXPECT_EQ(header_value, actual_header_value);
65 }
66
TEST_F(LibcurlHttpFetcherTest,GetNonExistentHeaderValueTest)67 TEST_F(LibcurlHttpFetcherTest, GetNonExistentHeaderValueTest) {
68 string actual_header_value;
69 // Skip |SetHeaader()| call.
70 EXPECT_FALSE(libcurl_fetcher_.GetHeader(kHeaderName, &actual_header_value));
71 // Even after a failed |GetHeaderValue()|, enforce that the passed pointer to
72 // modifiable string was cleared to be empty.
73 EXPECT_EQ("", actual_header_value);
74 }
75
TEST_F(LibcurlHttpFetcherTest,GetHeaderEdgeCaseTest)76 TEST_F(LibcurlHttpFetcherTest, GetHeaderEdgeCaseTest) {
77 const string header_value = "\a\b\t\v\f\r\\ edge:-case: \a\b\t\v\f\r\\";
78 string actual_header_value;
79 libcurl_fetcher_.SetHeader(kHeaderName, header_value);
80 EXPECT_TRUE(libcurl_fetcher_.GetHeader(kHeaderName, &actual_header_value));
81 EXPECT_EQ(header_value, actual_header_value);
82 }
83
TEST_F(LibcurlHttpFetcherTest,InvalidURLTest)84 TEST_F(LibcurlHttpFetcherTest, InvalidURLTest) {
85 int no_network_max_retries = 1;
86 libcurl_fetcher_.set_no_network_max_retries(no_network_max_retries);
87
88 libcurl_fetcher_.BeginTransfer("not-a-URL");
89 while (loop_.PendingTasks()) {
90 loop_.RunOnce(true);
91 }
92
93 EXPECT_EQ(libcurl_fetcher_.get_no_network_max_retries(),
94 no_network_max_retries);
95 }
96
TEST_F(LibcurlHttpFetcherTest,CouldNotResolveHostTest)97 TEST_F(LibcurlHttpFetcherTest, CouldNotResolveHostTest) {
98 int no_network_max_retries = 1;
99 libcurl_fetcher_.set_no_network_max_retries(no_network_max_retries);
100
101 libcurl_fetcher_.BeginTransfer("https://An-uNres0lvable-uRl.invalid");
102
103 // It's slower on Android that libcurl handle may not finish within 1 cycle.
104 // Will need to wait for more cycles until it finishes. Original test didn't
105 // correctly handle when we need to re-watch libcurl fds.
106 while (loop_.PendingTasks() &&
107 libcurl_fetcher_.GetAuxiliaryErrorCode() == ErrorCode::kSuccess) {
108 loop_.RunOnce(true);
109 }
110
111 EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
112 ErrorCode::kUnresolvedHostError);
113
114 while (loop_.PendingTasks()) {
115 loop_.RunOnce(true);
116 }
117 // The auxilary error code should've have been changed.
118 EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
119 ErrorCode::kUnresolvedHostError);
120
121 // If libcurl fails to resolve the name, we call res_init() to reload
122 // resolv.conf and retry exactly once more. See crbug.com/982813 for details.
123 EXPECT_EQ(libcurl_fetcher_.get_no_network_max_retries(),
124 no_network_max_retries + 1);
125 }
126
TEST_F(LibcurlHttpFetcherTest,HostResolvedTest)127 TEST_F(LibcurlHttpFetcherTest, HostResolvedTest) {
128 int no_network_max_retries = 2;
129 libcurl_fetcher_.set_no_network_max_retries(no_network_max_retries);
130
131 // This test actually sends request to internet but according to
132 // https://tools.ietf.org/html/rfc2606#section-2, .invalid domain names are
133 // reserved and sure to be invalid. Ideally we should mock libcurl or
134 // reorganize LibcurlHttpFetcher so the part that sends request can be mocked
135 // easily.
136 // TODO(xiaochu) Refactor LibcurlHttpFetcher (and its relates) so it's
137 // easier to mock the part that depends on internet connectivity.
138 libcurl_fetcher_.BeginTransfer("https://An-uNres0lvable-uRl.invalid");
139
140 // It's slower on Android that libcurl handle may not finish within 1 cycle.
141 // Will need to wait for more cycles until it finishes. Original test didn't
142 // correctly handle when we need to re-watch libcurl fds.
143 while (loop_.PendingTasks() &&
144 libcurl_fetcher_.GetAuxiliaryErrorCode() == ErrorCode::kSuccess) {
145 loop_.RunOnce(true);
146 }
147
148 EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
149 ErrorCode::kUnresolvedHostError);
150
151 // The second time, it will resolve, with error code 200 but we set the
152 // download size be smaller than the transfer size so it will retry again.
153 EXPECT_CALL(libcurl_fetcher_, GetHttpResponseCode())
154 .WillOnce(testing::Invoke(
155 [this]() { libcurl_fetcher_.http_response_code_ = 200; }))
156 .WillRepeatedly(testing::Invoke(
157 [this]() { libcurl_fetcher_.http_response_code_ = 0; }));
158 libcurl_fetcher_.transfer_size_ = 10;
159
160 // It's slower on Android that libcurl handle may not finish within 1 cycle.
161 // Will need to wait for more cycles until it finishes. Original test didn't
162 // correctly handle when we need to re-watch libcurl fds.
163 while (loop_.PendingTasks() && libcurl_fetcher_.GetAuxiliaryErrorCode() ==
164 ErrorCode::kUnresolvedHostError) {
165 loop_.RunOnce(true);
166 }
167
168 EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
169 ErrorCode::kUnresolvedHostRecovered);
170
171 while (loop_.PendingTasks()) {
172 loop_.RunOnce(true);
173 }
174 // The auxilary error code should not have been changed.
175 EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
176 ErrorCode::kUnresolvedHostRecovered);
177
178 // If libcurl fails to resolve the name, we call res_init() to reload
179 // resolv.conf and retry exactly once more. See crbug.com/982813 for details.
180 EXPECT_EQ(libcurl_fetcher_.get_no_network_max_retries(),
181 no_network_max_retries + 1);
182 }
183
TEST_F(LibcurlHttpFetcherTest,HttpFetcherStateMachineRetryFailedTest)184 TEST_F(LibcurlHttpFetcherTest, HttpFetcherStateMachineRetryFailedTest) {
185 state_machine_.UpdateState(true);
186 state_machine_.UpdateState(true);
187 EXPECT_EQ(state_machine_.GetState(),
188 UnresolvedHostStateMachine::State::kNotRetry);
189 }
190
TEST_F(LibcurlHttpFetcherTest,HttpFetcherStateMachineRetrySucceedTest)191 TEST_F(LibcurlHttpFetcherTest, HttpFetcherStateMachineRetrySucceedTest) {
192 state_machine_.UpdateState(true);
193 state_machine_.UpdateState(false);
194 EXPECT_EQ(state_machine_.GetState(),
195 UnresolvedHostStateMachine::State::kRetriedSuccess);
196 }
197
TEST_F(LibcurlHttpFetcherTest,HttpFetcherStateMachineNoRetryTest)198 TEST_F(LibcurlHttpFetcherTest, HttpFetcherStateMachineNoRetryTest) {
199 state_machine_.UpdateState(false);
200 state_machine_.UpdateState(false);
201 EXPECT_EQ(state_machine_.GetState(),
202 UnresolvedHostStateMachine::State::kInit);
203 }
204
205 } // namespace chromeos_update_engine
206