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