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