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