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