1 //
2 // Copyright (C) 2012 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 "shill/connectivity_trial.h"
18 
19 #include <memory>
20 #include <string>
21 
22 #include <base/bind.h>
23 #include <gmock/gmock.h>
24 #include <gtest/gtest.h>
25 
26 #include "shill/mock_connection.h"
27 #include "shill/mock_control.h"
28 #include "shill/mock_device_info.h"
29 #include "shill/mock_event_dispatcher.h"
30 #include "shill/mock_http_request.h"
31 #include "shill/net/mock_time.h"
32 
33 using base::Bind;
34 using base::Callback;
35 using base::Unretained;
36 using std::string;
37 using std::unique_ptr;
38 using std::vector;
39 using testing::_;
40 using testing::AtLeast;
41 using testing::DoAll;
42 using testing::InSequence;
43 using testing::Mock;
44 using testing::NiceMock;
45 using testing::Return;
46 using testing::ReturnRef;
47 using testing::SetArgumentPointee;
48 using testing::StrictMock;
49 using testing::Test;
50 
51 namespace shill {
52 
53 namespace {
54 const char kBadURL[] = "badurl";
55 const char kInterfaceName[] = "int0";
56 const char kURL[] = "http://www.chromium.org";
57 const char kDNSServer0[] = "8.8.8.8";
58 const char kDNSServer1[] = "8.8.4.4";
59 const char* kDNSServers[] = { kDNSServer0, kDNSServer1 };
60 }  // namespace
61 
62 MATCHER_P(IsResult, result, "") {
63   return (result.phase == arg.phase &&
64           result.status == arg.status);
65 }
66 
67 class ConnectivityTrialTest : public Test {
68  public:
ConnectivityTrialTest()69   ConnectivityTrialTest()
70       : device_info_(
71             new NiceMock<MockDeviceInfo>(&control_, nullptr, nullptr, nullptr)),
72         connection_(new StrictMock<MockConnection>(device_info_.get())),
73         connectivity_trial_(new ConnectivityTrial(
74             connection_.get(), &dispatcher_, kTrialTimeout,
75             callback_target_.result_callback())),
76         interface_name_(kInterfaceName),
77         dns_servers_(kDNSServers, kDNSServers + 2),
78         http_request_(nullptr) {
79     current_time_.tv_sec = current_time_.tv_usec = 0;
80   }
81 
SetUp()82   virtual void SetUp() {
83     EXPECT_CALL(*connection_.get(), IsIPv6())
84                 .WillRepeatedly(Return(false));
85     EXPECT_CALL(*connection_.get(), interface_name())
86         .WillRepeatedly(ReturnRef(interface_name_));
87     EXPECT_CALL(time_, GetTimeMonotonic(_))
88         .WillRepeatedly(Invoke(this, &ConnectivityTrialTest::GetTimeMonotonic));
89     EXPECT_CALL(*connection_.get(), dns_servers())
90         .WillRepeatedly(ReturnRef(dns_servers_));
91     EXPECT_FALSE(connectivity_trial_->request_.get());
92   }
93 
TearDown()94   virtual void TearDown() {
95     Mock::VerifyAndClearExpectations(&http_request_);
96     if (connectivity_trial_->request_.get()) {
97       EXPECT_CALL(*http_request(), Stop());
98 
99       // Delete the ConnectivityTrial while expectations still exist.
100       connectivity_trial_.reset();
101     }
102   }
103 
104  protected:
105   static const int kNumAttempts;
106   static const int kTrialTimeout;
107 
108   class CallbackTarget {
109    public:
CallbackTarget()110     CallbackTarget()
111         : result_callback_(Bind(&CallbackTarget::ResultCallback,
112                                 Unretained(this))) {
113     }
114 
115     MOCK_METHOD1(ResultCallback, void(ConnectivityTrial::Result result));
result_callback()116     Callback<void(ConnectivityTrial::Result)>& result_callback() {
117       return result_callback_;
118     }
119 
120    private:
121     Callback<void(ConnectivityTrial::Result)> result_callback_;
122   };
123 
AssignHTTPRequest()124   void AssignHTTPRequest() {
125     http_request_ = new StrictMock<MockHTTPRequest>(connection_);
126     connectivity_trial_->request_.reset(http_request_);  // Passes ownership.
127   }
128 
StartTrialWithDelay(const string & url_string,int delay)129   bool StartTrialWithDelay(const string& url_string, int delay) {
130     bool ret = connectivity_trial_->Start(url_string, delay);
131     if (ret) {
132       AssignHTTPRequest();
133     }
134     return ret;
135   }
136 
StartTrial(const string & url_string)137   bool StartTrial(const string& url_string) {
138     return StartTrialWithDelay(url_string, 0);
139   }
140 
StartTrialTask()141   void StartTrialTask() {
142     AssignHTTPRequest();
143     EXPECT_CALL(*http_request(), Start(_, _, _))
144         .WillOnce(Return(HTTPRequest::kResultInProgress));
145     EXPECT_CALL(dispatcher(), PostDelayedTask(_, kTrialTimeout * 1000));
146     connectivity_trial()->StartTrialTask();
147   }
148 
ExpectTrialReturn(const ConnectivityTrial::Result & result)149   void ExpectTrialReturn(const ConnectivityTrial::Result& result) {
150     EXPECT_CALL(callback_target(), ResultCallback(IsResult(result)));
151 
152     // Expect the PortalDetector to stop the current request.
153     EXPECT_CALL(*http_request(), Stop());
154   }
155 
TimeoutTrial()156   void TimeoutTrial() {
157     connectivity_trial_->TimeoutTrialTask();
158   }
159 
http_request()160   MockHTTPRequest* http_request() { return http_request_; }
connectivity_trial()161   ConnectivityTrial* connectivity_trial() { return connectivity_trial_.get(); }
dispatcher()162   MockEventDispatcher& dispatcher() { return dispatcher_; }
callback_target()163   CallbackTarget& callback_target() { return callback_target_; }
response_data()164   ByteString& response_data() { return response_data_; }
165 
ExpectReset()166   void ExpectReset() {
167     EXPECT_TRUE(callback_target_.result_callback().
168                 Equals(connectivity_trial_->trial_callback_));
169     EXPECT_FALSE(connectivity_trial_->request_.get());
170   }
171 
ExpectTrialRetry(const ConnectivityTrial::Result & result,int delay)172   void ExpectTrialRetry(const ConnectivityTrial::Result& result, int delay) {
173     EXPECT_CALL(callback_target(), ResultCallback(IsResult(result)));
174 
175     // Expect the ConnectivityTrial to stop the current request.
176     EXPECT_CALL(*http_request(), Stop());
177 
178     // Expect the ConnectivityTrial to schedule the next attempt.
179     EXPECT_CALL(dispatcher(), PostDelayedTask(_, delay));
180   }
181 
AdvanceTime(int milliseconds)182   void AdvanceTime(int milliseconds) {
183     struct timeval tv = { milliseconds / 1000, (milliseconds % 1000) * 1000 };
184     timeradd(&current_time_, &tv, &current_time_);
185   }
186 
StartTrial()187   void StartTrial() {
188     EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
189     EXPECT_TRUE(StartTrial(kURL));
190 
191     // Expect that the request will be started -- return failure.
192     EXPECT_CALL(*http_request(), Start(_, _, _))
193         .WillOnce(Return(HTTPRequest::kResultInProgress));
194     EXPECT_CALL(dispatcher(), PostDelayedTask(
195         _, kTrialTimeout * 1000));
196 
197     connectivity_trial()->StartTrialTask();
198   }
199 
AppendReadData(const string & read_data)200   void AppendReadData(const string& read_data) {
201     response_data_.Append(ByteString(read_data, false));
202     connectivity_trial_->RequestReadCallback(response_data_);
203   }
204 
205  private:
GetTimeMonotonic(struct timeval * tv)206   int GetTimeMonotonic(struct timeval* tv) {
207     *tv = current_time_;
208     return 0;
209   }
210 
211   StrictMock<MockEventDispatcher> dispatcher_;
212   MockControl control_;
213   unique_ptr<MockDeviceInfo> device_info_;
214   scoped_refptr<MockConnection> connection_;
215   CallbackTarget callback_target_;
216   unique_ptr<ConnectivityTrial> connectivity_trial_;
217   StrictMock<MockTime> time_;
218   struct timeval current_time_;
219   const string interface_name_;
220   vector<string> dns_servers_;
221   ByteString response_data_;
222   MockHTTPRequest* http_request_;
223 };
224 
225 // static
226 const int ConnectivityTrialTest::kNumAttempts = 0;
227 const int ConnectivityTrialTest::kTrialTimeout = 4;
228 
TEST_F(ConnectivityTrialTest,Constructor)229 TEST_F(ConnectivityTrialTest, Constructor) {
230   ExpectReset();
231 }
232 
TEST_F(ConnectivityTrialTest,InvalidURL)233 TEST_F(ConnectivityTrialTest, InvalidURL) {
234   EXPECT_FALSE(connectivity_trial()->IsActive());
235   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0)).Times(0);
236   EXPECT_FALSE(StartTrial(kBadURL));
237   ExpectReset();
238 
239   EXPECT_FALSE(connectivity_trial()->Retry(0));
240   EXPECT_FALSE(connectivity_trial()->IsActive());
241 }
242 
TEST_F(ConnectivityTrialTest,IsActive)243 TEST_F(ConnectivityTrialTest, IsActive) {
244   // Before the trial is started, should not be active.
245   EXPECT_FALSE(connectivity_trial()->IsActive());
246 
247   // Once the trial is started, IsActive should return true.
248   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
249   EXPECT_TRUE(StartTrial(kURL));
250   StartTrialTask();
251   EXPECT_TRUE(connectivity_trial()->IsActive());
252 
253   // Finish the trial, IsActive should return false.
254   EXPECT_CALL(*http_request(), Stop());
255   connectivity_trial()->CompleteTrial(
256       ConnectivityTrial::Result(ConnectivityTrial::kPhaseContent,
257                                 ConnectivityTrial::kStatusFailure));
258   EXPECT_FALSE(connectivity_trial()->IsActive());
259 }
260 
TEST_F(ConnectivityTrialTest,StartAttemptFailed)261 TEST_F(ConnectivityTrialTest, StartAttemptFailed) {
262   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
263   EXPECT_TRUE(StartTrial(kURL));
264 
265   // Expect that the request will be started -- return failure.
266   EXPECT_CALL(*http_request(), Start(_, _, _))
267       .WillOnce(Return(HTTPRequest::kResultConnectionFailure));
268   // Expect a failure to be relayed to the caller.
269   EXPECT_CALL(callback_target(),
270               ResultCallback(IsResult(
271                   ConnectivityTrial::Result(
272                       ConnectivityTrial::kPhaseConnection,
273                       ConnectivityTrial::kStatusFailure))))
274       .Times(1);
275 
276   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0)).Times(0);
277   EXPECT_CALL(*http_request(), Stop());
278 
279   connectivity_trial()->StartTrialTask();
280 }
281 
TEST_F(ConnectivityTrialTest,StartRepeated)282 TEST_F(ConnectivityTrialTest, StartRepeated) {
283   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0)).Times(1);
284   EXPECT_TRUE(StartTrial(kURL));
285 
286   // A second call should cancel the existing trial and set up the new one.
287   EXPECT_CALL(*http_request(), Stop());
288   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 10)).Times(1);
289   EXPECT_TRUE(StartTrialWithDelay(kURL, 10));
290 }
291 
TEST_F(ConnectivityTrialTest,StartTrialAfterDelay)292 TEST_F(ConnectivityTrialTest, StartTrialAfterDelay) {
293   const int kDelaySeconds = 123;
294   // The trial should be delayed by kDelaySeconds.
295   EXPECT_CALL(dispatcher(), PostDelayedTask(_, kDelaySeconds));
296   EXPECT_TRUE(StartTrialWithDelay(kURL, kDelaySeconds));
297 }
298 
TEST_F(ConnectivityTrialTest,TrialRetry)299 TEST_F(ConnectivityTrialTest, TrialRetry) {
300   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
301   EXPECT_TRUE(StartTrial(kURL));
302 
303   // Expect that the request will be started -- return failure.
304   EXPECT_CALL(*http_request(), Start(_, _, _))
305       .WillOnce(Return(HTTPRequest::kResultConnectionFailure));
306   EXPECT_CALL(*http_request(), Stop());
307   connectivity_trial()->StartTrialTask();
308 
309   const int kRetryDelay = 7;
310   EXPECT_CALL(*http_request(), Stop());
311   EXPECT_CALL(dispatcher(), PostDelayedTask(_, kRetryDelay)).Times(1);
312   EXPECT_TRUE(connectivity_trial()->Retry(kRetryDelay));
313 }
314 
TEST_F(ConnectivityTrialTest,TrialRetryFail)315 TEST_F(ConnectivityTrialTest, TrialRetryFail) {
316   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
317   EXPECT_TRUE(StartTrial(kURL));
318 
319   EXPECT_CALL(*http_request(), Stop());
320   connectivity_trial()->Stop();
321 
322   EXPECT_FALSE(connectivity_trial()->Retry(0));
323 }
324 
325 // Exactly like AttemptCount, except that the termination conditions are
326 // different because we're triggering a different sort of error.
TEST_F(ConnectivityTrialTest,ReadBadHeadersRetry)327 TEST_F(ConnectivityTrialTest, ReadBadHeadersRetry) {
328   int num_failures = 3;
329   int sec_between_attempts = 3;
330 
331   // Expect ConnectivityTrial to immediately post a task for the each attempt.
332   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
333   EXPECT_TRUE(StartTrial(kURL));
334 
335   // Expect that the request will be started and return the in progress status.
336   EXPECT_CALL(*http_request(), Start(_, _, _))
337       .Times(num_failures).WillRepeatedly(
338           Return(HTTPRequest::kResultInProgress));
339 
340   // Each HTTP request that gets started will have a request timeout.
341   EXPECT_CALL(dispatcher(), PostDelayedTask(_, kTrialTimeout * 1000))
342       .Times(num_failures);
343 
344   // Expect failures for all attempts but the last.
345   EXPECT_CALL(callback_target(),
346               ResultCallback(IsResult(
347                   ConnectivityTrial::Result(
348                       ConnectivityTrial::kPhaseContent,
349                       ConnectivityTrial::kStatusFailure))))
350       .Times(num_failures);
351 
352   // Expect the ConnectivityTrial to stop the current request each time, plus
353   // an extra time in ConnectivityTrial::Stop().
354   ByteString response_data("X", 1);
355 
356   for (int i = 0; i < num_failures; ++i) {
357     connectivity_trial()->StartTrialTask();
358     AdvanceTime(sec_between_attempts * 1000);
359     EXPECT_CALL(*http_request(), Stop()).Times(2);
360     EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0)).Times(1);
361     connectivity_trial()->RequestReadCallback(response_data);
362     EXPECT_TRUE(connectivity_trial()->Retry(0));
363   }
364 }
365 
366 
TEST_F(ConnectivityTrialTest,ReadBadHeader)367 TEST_F(ConnectivityTrialTest, ReadBadHeader) {
368   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
369   EXPECT_TRUE(StartTrial(kURL));
370 
371   StartTrialTask();
372 
373   ExpectTrialReturn(ConnectivityTrial::Result(
374       ConnectivityTrial::kPhaseContent,
375       ConnectivityTrial::kStatusFailure));
376   AppendReadData("X");
377 }
378 
TEST_F(ConnectivityTrialTest,RequestTimeout)379 TEST_F(ConnectivityTrialTest, RequestTimeout) {
380   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
381   EXPECT_TRUE(StartTrial(kURL));
382 
383   StartTrialTask();
384 
385   ExpectTrialReturn(ConnectivityTrial::Result(
386       ConnectivityTrial::kPhaseUnknown,
387       ConnectivityTrial::kStatusTimeout));
388 
389   EXPECT_CALL(*http_request(), response_data())
390       .WillOnce(ReturnRef(response_data()));
391 
392   TimeoutTrial();
393 }
394 
TEST_F(ConnectivityTrialTest,ReadPartialHeaderTimeout)395 TEST_F(ConnectivityTrialTest, ReadPartialHeaderTimeout) {
396   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
397   EXPECT_TRUE(StartTrial(kURL));
398 
399   StartTrialTask();
400 
401 
402   const string response_expected(ConnectivityTrial::kResponseExpected);
403   const size_t partial_size = response_expected.length() / 2;
404   AppendReadData(response_expected.substr(0, partial_size));
405 
406   ExpectTrialReturn(ConnectivityTrial::Result(
407       ConnectivityTrial::kPhaseContent,
408       ConnectivityTrial::kStatusTimeout));
409 
410   EXPECT_CALL(*http_request(), response_data())
411       .WillOnce(ReturnRef(response_data()));
412 
413   TimeoutTrial();
414 }
415 
TEST_F(ConnectivityTrialTest,ReadCompleteHeader)416 TEST_F(ConnectivityTrialTest, ReadCompleteHeader) {
417   const string response_expected(ConnectivityTrial::kResponseExpected);
418   const size_t partial_size = response_expected.length() / 2;
419 
420   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
421   EXPECT_TRUE(StartTrial(kURL));
422 
423   StartTrialTask();
424 
425   AppendReadData(response_expected.substr(0, partial_size));
426 
427   ExpectTrialReturn(ConnectivityTrial::Result(
428       ConnectivityTrial::kPhaseContent,
429       ConnectivityTrial::kStatusSuccess));
430 
431   AppendReadData(response_expected.substr(partial_size));
432 }
433 
TEST_F(ConnectivityTrialTest,ReadMatchingHeader)434 TEST_F(ConnectivityTrialTest, ReadMatchingHeader) {
435   const string kResponse("HTTP/9.8 204");
436 
437   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
438   EXPECT_TRUE(StartTrial(kURL));
439 
440   StartTrialTask();
441 
442   ExpectTrialReturn(ConnectivityTrial::Result(
443       ConnectivityTrial::kPhaseContent,
444       ConnectivityTrial::kStatusSuccess));
445 
446   AppendReadData(kResponse);
447 }
448 
449 struct ResultMapping {
ResultMappingshill::ResultMapping450   ResultMapping() : http_result(HTTPRequest::kResultUnknown), trial_result() {}
ResultMappingshill::ResultMapping451   ResultMapping(HTTPRequest::Result in_http_result,
452                 const ConnectivityTrial::Result& in_trial_result)
453       : http_result(in_http_result),
454         trial_result(in_trial_result) {}
455   HTTPRequest::Result http_result;
456   ConnectivityTrial::Result trial_result;
457 };
458 
459 class ConnectivityTrialResultMappingTest
460     : public testing::TestWithParam<ResultMapping> {};
461 
TEST_P(ConnectivityTrialResultMappingTest,MapResult)462 TEST_P(ConnectivityTrialResultMappingTest, MapResult) {
463   ConnectivityTrial::Result trial_result =
464       ConnectivityTrial::GetPortalResultForRequestResult(
465           GetParam().http_result);
466   EXPECT_EQ(trial_result.phase, GetParam().trial_result.phase);
467   EXPECT_EQ(trial_result.status, GetParam().trial_result.status);
468 }
469 
470 INSTANTIATE_TEST_CASE_P(
471     TrialResultMappingTest,
472     ConnectivityTrialResultMappingTest,
473     ::testing::Values(
474         ResultMapping(
475             HTTPRequest::kResultUnknown,
476             ConnectivityTrial::Result(ConnectivityTrial::kPhaseUnknown,
477                                       ConnectivityTrial::kStatusFailure)),
478         ResultMapping(
479             HTTPRequest::kResultInProgress,
480             ConnectivityTrial::Result(ConnectivityTrial::kPhaseUnknown,
481                                       ConnectivityTrial::kStatusFailure)),
482         ResultMapping(
483             HTTPRequest::kResultDNSFailure,
484             ConnectivityTrial::Result(ConnectivityTrial::kPhaseDNS,
485                                       ConnectivityTrial::kStatusFailure)),
486         ResultMapping(
487             HTTPRequest::kResultDNSTimeout,
488             ConnectivityTrial::Result(ConnectivityTrial::kPhaseDNS,
489                                       ConnectivityTrial::kStatusTimeout)),
490         ResultMapping(
491             HTTPRequest::kResultConnectionFailure,
492             ConnectivityTrial::Result(ConnectivityTrial::kPhaseConnection,
493                                       ConnectivityTrial::kStatusFailure)),
494         ResultMapping(
495             HTTPRequest::kResultConnectionTimeout,
496             ConnectivityTrial::Result(ConnectivityTrial::kPhaseConnection,
497                                       ConnectivityTrial::kStatusTimeout)),
498         ResultMapping(
499             HTTPRequest::kResultRequestFailure,
500             ConnectivityTrial::Result(ConnectivityTrial::kPhaseHTTP,
501                                       ConnectivityTrial::kStatusFailure)),
502         ResultMapping(
503             HTTPRequest::kResultRequestTimeout,
504             ConnectivityTrial::Result(ConnectivityTrial::kPhaseHTTP,
505                                       ConnectivityTrial::kStatusTimeout)),
506         ResultMapping(
507             HTTPRequest::kResultResponseFailure,
508             ConnectivityTrial::Result(ConnectivityTrial::kPhaseHTTP,
509                                       ConnectivityTrial::kStatusFailure)),
510         ResultMapping(
511             HTTPRequest::kResultResponseTimeout,
512             ConnectivityTrial::Result(ConnectivityTrial::kPhaseHTTP,
513                                       ConnectivityTrial::kStatusTimeout)),
514         ResultMapping(
515             HTTPRequest::kResultSuccess,
516             ConnectivityTrial::Result(ConnectivityTrial::kPhaseContent,
517                                       ConnectivityTrial::kStatusFailure))));
518 
519 }  // namespace shill
520