blob: cf2e70ffd12e53a135ae30c3286e4e7c800f6c58 [file] [log] [blame]
Peter Qiuc0beca52015-09-03 11:25:46 -07001//
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//
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070016
17#include "shill/connectivity_trial.h"
18
Ben Chan22f1fbc2014-10-17 14:18:07 -070019#include <memory>
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070020#include <string>
21
22#include <base/bind.h>
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070023#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"
Peter Qiu8d6b5972014-10-28 15:33:34 -070031#include "shill/net/mock_time.h"
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070032
33using base::Bind;
34using base::Callback;
35using base::Unretained;
36using std::string;
Ben Chan22f1fbc2014-10-17 14:18:07 -070037using std::unique_ptr;
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070038using std::vector;
39using testing::_;
40using testing::AtLeast;
41using testing::DoAll;
42using testing::InSequence;
43using testing::Mock;
44using testing::NiceMock;
45using testing::Return;
46using testing::ReturnRef;
47using testing::SetArgumentPointee;
48using testing::StrictMock;
49using testing::Test;
50
51namespace shill {
52
53namespace {
54const char kBadURL[] = "badurl";
55const char kInterfaceName[] = "int0";
56const char kURL[] = "http://www.chromium.org";
57const char kDNSServer0[] = "8.8.8.8";
58const char kDNSServer1[] = "8.8.4.4";
Paul Stewart3b30ca52015-06-16 13:13:10 -070059const char* kDNSServers[] = { kDNSServer0, kDNSServer1 };
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070060} // namespace
61
62MATCHER_P(IsResult, result, "") {
63 return (result.phase == arg.phase &&
64 result.status == arg.status);
65}
66
67class ConnectivityTrialTest : public Test {
68 public:
69 ConnectivityTrialTest()
Ben Chancc225ef2014-09-30 13:26:51 -070070 : device_info_(
71 new NiceMock<MockDeviceInfo>(&control_, nullptr, nullptr, nullptr)),
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070072 connection_(new StrictMock<MockConnection>(device_info_.get())),
73 connectivity_trial_(new ConnectivityTrial(
Ben Chancc225ef2014-09-30 13:26:51 -070074 connection_.get(), &dispatcher_, kTrialTimeout,
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070075 callback_target_.result_callback())),
76 interface_name_(kInterfaceName),
77 dns_servers_(kDNSServers, kDNSServers + 2),
Ben Chancc225ef2014-09-30 13:26:51 -070078 http_request_(nullptr) {
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -070079 current_time_.tv_sec = current_time_.tv_usec = 0;
80 }
81
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
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:
110 CallbackTarget()
111 : result_callback_(Bind(&CallbackTarget::ResultCallback,
112 Unretained(this))) {
113 }
114
115 MOCK_METHOD1(ResultCallback, void(ConnectivityTrial::Result result));
Paul Stewart3b30ca52015-06-16 13:13:10 -0700116 Callback<void(ConnectivityTrial::Result)>& result_callback() {
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700117 return result_callback_;
118 }
119
120 private:
121 Callback<void(ConnectivityTrial::Result)> result_callback_;
122 };
123
124 void AssignHTTPRequest() {
125 http_request_ = new StrictMock<MockHTTPRequest>(connection_);
126 connectivity_trial_->request_.reset(http_request_); // Passes ownership.
127 }
128
Paul Stewart3b30ca52015-06-16 13:13:10 -0700129 bool StartTrialWithDelay(const string& url_string, int delay) {
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700130 bool ret = connectivity_trial_->Start(url_string, delay);
131 if (ret) {
132 AssignHTTPRequest();
133 }
134 return ret;
135 }
136
Paul Stewart3b30ca52015-06-16 13:13:10 -0700137 bool StartTrial(const string& url_string) {
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700138 return StartTrialWithDelay(url_string, 0);
139 }
140
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
Paul Stewart3b30ca52015-06-16 13:13:10 -0700149 void ExpectTrialReturn(const ConnectivityTrial::Result& result) {
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700150 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
156 void TimeoutTrial() {
157 connectivity_trial_->TimeoutTrialTask();
158 }
159
Paul Stewart3b30ca52015-06-16 13:13:10 -0700160 MockHTTPRequest* http_request() { return http_request_; }
161 ConnectivityTrial* connectivity_trial() { return connectivity_trial_.get(); }
162 MockEventDispatcher& dispatcher() { return dispatcher_; }
163 CallbackTarget& callback_target() { return callback_target_; }
164 ByteString& response_data() { return response_data_; }
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700165
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
Paul Stewart3b30ca52015-06-16 13:13:10 -0700172 void ExpectTrialRetry(const ConnectivityTrial::Result& result, int delay) {
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700173 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
182 void AdvanceTime(int milliseconds) {
183 struct timeval tv = { milliseconds / 1000, (milliseconds % 1000) * 1000 };
184 timeradd(&current_time_, &tv, &current_time_);
185 }
186
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
Paul Stewart3b30ca52015-06-16 13:13:10 -0700200 void AppendReadData(const string& read_data) {
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700201 response_data_.Append(ByteString(read_data, false));
202 connectivity_trial_->RequestReadCallback(response_data_);
203 }
204
205 private:
Paul Stewart3b30ca52015-06-16 13:13:10 -0700206 int GetTimeMonotonic(struct timeval* tv) {
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700207 *tv = current_time_;
208 return 0;
209 }
210
211 StrictMock<MockEventDispatcher> dispatcher_;
212 MockControl control_;
Ben Chan22f1fbc2014-10-17 14:18:07 -0700213 unique_ptr<MockDeviceInfo> device_info_;
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700214 scoped_refptr<MockConnection> connection_;
215 CallbackTarget callback_target_;
Ben Chan22f1fbc2014-10-17 14:18:07 -0700216 unique_ptr<ConnectivityTrial> connectivity_trial_;
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700217 StrictMock<MockTime> time_;
218 struct timeval current_time_;
219 const string interface_name_;
220 vector<string> dns_servers_;
221 ByteString response_data_;
Paul Stewart3b30ca52015-06-16 13:13:10 -0700222 MockHTTPRequest* http_request_;
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700223};
224
225// static
226const int ConnectivityTrialTest::kNumAttempts = 0;
227const int ConnectivityTrialTest::kTrialTimeout = 4;
228
229TEST_F(ConnectivityTrialTest, Constructor) {
230 ExpectReset();
231}
232
233TEST_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
243TEST_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
261TEST_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
282TEST_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
292TEST_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
299TEST_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
315TEST_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.
327TEST_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
367TEST_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
379TEST_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
395TEST_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
416TEST_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
434TEST_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
449struct ResultMapping {
450 ResultMapping() : http_result(HTTPRequest::kResultUnknown), trial_result() {}
451 ResultMapping(HTTPRequest::Result in_http_result,
Paul Stewart3b30ca52015-06-16 13:13:10 -0700452 const ConnectivityTrial::Result& in_trial_result)
Rebecca Silberstein3d49ea42014-08-21 11:20:50 -0700453 : http_result(in_http_result),
454 trial_result(in_trial_result) {}
455 HTTPRequest::Result http_result;
456 ConnectivityTrial::Result trial_result;
457};
458
459class ConnectivityTrialResultMappingTest
460 : public testing::TestWithParam<ResultMapping> {};
461
462TEST_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
470INSTANTIATE_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