shill: portal: Recheck portal state

Introduce a retry interval for automatically retrying portal
checks.  Also provide a Manager API method for immediately
re-checking portal status.

BUG=chromium-os:27335
TEST=New unit tests, tested on real machine, including setting
PortaCheckInterval over DBus, and using Jason's addition to
test-flimflam for 'recheck-portal'.
Change-Id: Idc7def18c6f863859e94f4d4e9f266ab2670679c
Reviewed-on: https://gerrit.chromium.org/gerrit/17367
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Commit-Ready: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/device_unittest.cc b/device_unittest.cc
index f47895d..90dadf0 100644
--- a/device_unittest.cc
+++ b/device_unittest.cc
@@ -28,6 +28,7 @@
 #include "shill/mock_glib.h"
 #include "shill/mock_ipconfig.h"
 #include "shill/mock_manager.h"
+#include "shill/mock_portal_detector.h"
 #include "shill/mock_rtnl_handler.h"
 #include "shill/mock_service.h"
 #include "shill/mock_store.h"
@@ -59,7 +60,8 @@
                            kDeviceName,
                            kDeviceAddress,
                            0,
-                           Technology::kUnknown)) {
+                           Technology::kUnknown)),
+        device_info_(control_interface(), NULL, NULL, NULL) {
     DHCPProvider::GetInstance()->glib_ = glib();
     DHCPProvider::GetInstance()->control_interface_ = control_interface();
   }
@@ -81,23 +83,13 @@
     device_->SelectService(service);
   }
 
-  bool StartPortalDetection() { return device_->StartPortalDetection(); }
-  void StopPortalDetection() { device_->StopPortalDetection(); }
-
-  void PortalDetectorCallback(const PortalDetector::Result &result) {
-    device_->PortalDetectorCallback(result);
-  }
-
-  void SetManager(Manager *manager) {
-    device_->manager_ = manager;
-  }
-
   void SetConnection(ConnectionRefPtr connection) {
     device_->connection_ = connection;
   }
 
   MockControl control_interface_;
   DeviceRefPtr device_;
+  MockDeviceInfo device_info_;
   StrictMock<MockRTNLHandler> rtnl_handler_;
 };
 
@@ -328,111 +320,111 @@
   EXPECT_FALSE(device_->selected_service_.get());
 }
 
-TEST_F(DeviceTest, PortalDetectionDisabled) {
-  scoped_refptr<MockService> service(
-      new StrictMock<MockService>(control_interface(),
-                                  dispatcher(),
-                                  metrics(),
-                                  manager()));
-  EXPECT_CALL(*service.get(), IsConnected())
+
+class DevicePortalDetectionTest : public DeviceTest {
+ public:
+  DevicePortalDetectionTest()
+      : connection_(new StrictMock<MockConnection>(&device_info_)),
+        manager_(control_interface(),
+                 dispatcher(),
+                 metrics(),
+                 glib()),
+        service_(new StrictMock<MockService>(control_interface(),
+                                             dispatcher(),
+                                             metrics(),
+                                             &manager_)),
+        portal_detector_(new StrictMock<MockPortalDetector>(connection_)) {}
+    virtual ~DevicePortalDetectionTest() {}
+  virtual void SetUp() {
+    DeviceTest::SetUp();
+    SelectService(service_);
+    SetConnection(connection_.get());
+    device_->portal_detector_.reset(portal_detector_);  // Passes ownership.
+    device_->manager_ = &manager_;
+  }
+
+ protected:
+  bool StartPortalDetection() { return device_->StartPortalDetection(); }
+  void StopPortalDetection() { device_->StopPortalDetection(); }
+
+  void PortalDetectorCallback(const PortalDetector::Result &result) {
+    device_->PortalDetectorCallback(result);
+  }
+  bool RequestPortalDetection() {
+    return device_->RequestPortalDetection();
+  }
+  void SetServiceConnectedState(Service::ConnectState state) {
+    device_->SetServiceConnectedState(state);
+  }
+  void ExpectPortalDetectorReset() {
+    EXPECT_FALSE(device_->portal_detector_.get());
+  }
+  void ExpectPortalDetectorSet() {
+    EXPECT_TRUE(device_->portal_detector_.get());
+  }
+  void ExpectPortalDetectorIsMock() {
+    EXPECT_EQ(portal_detector_, device_->portal_detector_.get());
+  }
+  scoped_refptr<MockConnection> connection_;
+  StrictMock<MockManager> manager_;
+  scoped_refptr<MockService> service_;
+
+  // Used only for EXPECT_CALL().  Object is owned by device.
+  MockPortalDetector *portal_detector_;
+};
+
+TEST_F(DevicePortalDetectionTest, PortalDetectionDisabled) {
+  EXPECT_CALL(*service_.get(), IsConnected())
       .WillRepeatedly(Return(true));
-  StrictMock<MockManager> manager(control_interface(),
-                                  dispatcher(),
-                                  metrics(),
-                                  glib());
-  SetManager(&manager);
-  EXPECT_CALL(manager, IsPortalDetectionEnabled(device_->technology()))
+  EXPECT_CALL(manager_, IsPortalDetectionEnabled(device_->technology()))
       .WillOnce(Return(false));
-  SelectService(service);
-  EXPECT_CALL(*service.get(), SetState(Service::kStateOnline));
+  EXPECT_CALL(*service_.get(), SetState(Service::kStateOnline));
   EXPECT_FALSE(StartPortalDetection());
 }
 
-TEST_F(DeviceTest, PortalDetectionProxyConfig) {
-  scoped_refptr<MockService> service(
-      new StrictMock<MockService>(control_interface(),
-                                  dispatcher(),
-                                  metrics(),
-                                  manager()));
-  EXPECT_CALL(*service.get(), IsConnected())
+TEST_F(DevicePortalDetectionTest, PortalDetectionProxyConfig) {
+  EXPECT_CALL(*service_.get(), IsConnected())
       .WillRepeatedly(Return(true));
-  EXPECT_CALL(*service.get(), HasProxyConfig())
+  EXPECT_CALL(*service_.get(), HasProxyConfig())
       .WillOnce(Return(true));
-  SelectService(service);
-  StrictMock<MockManager> manager(control_interface(),
-                                  dispatcher(),
-                                  metrics(),
-                                  glib());
-  EXPECT_CALL(manager, IsPortalDetectionEnabled(device_->technology()))
+  EXPECT_CALL(manager_, IsPortalDetectionEnabled(device_->technology()))
       .WillOnce(Return(true));
-  SetManager(&manager);
-  EXPECT_CALL(*service.get(), SetState(Service::kStateOnline));
+  EXPECT_CALL(*service_.get(), SetState(Service::kStateOnline));
   EXPECT_FALSE(StartPortalDetection());
 }
 
-TEST_F(DeviceTest, PortalDetectionBadUrl) {
-  scoped_refptr<MockService> service(
-      new StrictMock<MockService>(control_interface(),
-                                  dispatcher(),
-                                  metrics(),
-                                  manager()));
-  EXPECT_CALL(*service.get(), IsConnected())
+TEST_F(DevicePortalDetectionTest, PortalDetectionBadUrl) {
+  EXPECT_CALL(*service_.get(), IsConnected())
       .WillRepeatedly(Return(true));
-  EXPECT_CALL(*service.get(), HasProxyConfig())
+  EXPECT_CALL(*service_.get(), HasProxyConfig())
       .WillOnce(Return(false));
-  SelectService(service);
-  StrictMock<MockManager> manager(control_interface(),
-                                  dispatcher(),
-                                  metrics(),
-                                  glib());
-  EXPECT_CALL(manager, IsPortalDetectionEnabled(device_->technology()))
+  EXPECT_CALL(manager_, IsPortalDetectionEnabled(device_->technology()))
       .WillOnce(Return(true));
   const string portal_url;
-  EXPECT_CALL(manager, GetPortalCheckURL())
+  EXPECT_CALL(manager_, GetPortalCheckURL())
       .WillRepeatedly(ReturnRef(portal_url));
-  SetManager(&manager);
-  EXPECT_CALL(*service.get(), SetState(Service::kStateOnline));
+  EXPECT_CALL(*service_.get(), SetState(Service::kStateOnline));
   EXPECT_FALSE(StartPortalDetection());
 }
 
-TEST_F(DeviceTest, PortalDetectionStart) {
-  scoped_refptr<MockService> service(
-      new StrictMock<MockService>(control_interface(),
-                                  dispatcher(),
-                                  metrics(),
-                                  manager()));
-  EXPECT_CALL(*service.get(), IsConnected())
+TEST_F(DevicePortalDetectionTest, PortalDetectionStart) {
+  EXPECT_CALL(*service_.get(), IsConnected())
       .WillRepeatedly(Return(true));
-  EXPECT_CALL(*service.get(), HasProxyConfig())
+  EXPECT_CALL(*service_.get(), HasProxyConfig())
       .WillOnce(Return(false));
-  SelectService(service);
-  StrictMock<MockManager> manager(control_interface(),
-                                  dispatcher(),
-                                  metrics(),
-                                  glib());
-  EXPECT_CALL(manager, IsPortalDetectionEnabled(device_->technology()))
+  EXPECT_CALL(manager_, IsPortalDetectionEnabled(device_->technology()))
       .WillOnce(Return(true));
   const string portal_url(PortalDetector::kDefaultURL);
-  EXPECT_CALL(manager, GetPortalCheckURL())
+  EXPECT_CALL(manager_, GetPortalCheckURL())
       .WillRepeatedly(ReturnRef(portal_url));
-  SetManager(&manager);
-  EXPECT_CALL(*service.get(), SetState(Service::kStateOnline))
+  EXPECT_CALL(*service_.get(), SetState(Service::kStateOnline))
       .Times(0);
-  scoped_ptr<MockDeviceInfo> device_info(
-      new NiceMock<MockDeviceInfo>(
-          control_interface(),
-          reinterpret_cast<EventDispatcher *>(NULL),
-          reinterpret_cast<Metrics *>(NULL),
-          reinterpret_cast<Manager *>(NULL)));
-  scoped_refptr<MockConnection> connection(
-      new NiceMock<MockConnection>(device_info.get()));
   const string kInterfaceName("int0");
-  EXPECT_CALL(*connection.get(), interface_name())
-              .WillRepeatedly(ReturnRef(kInterfaceName));
+  EXPECT_CALL(*connection_.get(), interface_name())
+      .WillRepeatedly(ReturnRef(kInterfaceName));
   const vector<string> kDNSServers;
-  EXPECT_CALL(*connection.get(), dns_servers())
-              .WillRepeatedly(ReturnRef(kDNSServers));
-  SetConnection(connection.get());
+  EXPECT_CALL(*connection_.get(), dns_servers())
+      .WillRepeatedly(ReturnRef(kDNSServers));
   EXPECT_TRUE(StartPortalDetection());
 
   // Drop all references to device_info before it falls out of scope.
@@ -440,53 +432,133 @@
   StopPortalDetection();
 }
 
-TEST_F(DeviceTest, PortalDetectionNonFinal) {
-  scoped_refptr<MockService> service(
-      new StrictMock<MockService>(control_interface(),
-                                  dispatcher(),
-                                  metrics(),
-                                  manager()));
-  EXPECT_CALL(*service.get(), IsConnected())
+TEST_F(DevicePortalDetectionTest, PortalDetectionNonFinal) {
+  EXPECT_CALL(*service_.get(), IsConnected())
       .Times(0);
-  EXPECT_CALL(*service.get(), SetState(_))
+  EXPECT_CALL(*service_.get(), SetState(_))
       .Times(0);
-  SelectService(service);
   PortalDetectorCallback(PortalDetector::Result(
       PortalDetector::kPhaseUnknown,
       PortalDetector::kStatusFailure,
       false));
 }
 
-TEST_F(DeviceTest, PortalDetectionFailure) {
-  scoped_refptr<MockService> service(
-      new StrictMock<MockService>(control_interface(),
-                                  dispatcher(),
-                                  metrics(),
-                                  manager()));
-  EXPECT_CALL(*service.get(), IsConnected())
+TEST_F(DevicePortalDetectionTest, PortalDetectionFailure) {
+  EXPECT_CALL(*service_.get(), IsConnected())
       .WillOnce(Return(true));
-  SelectService(service);
-  EXPECT_CALL(*service.get(), SetState(Service::kStatePortal));
+  EXPECT_CALL(*service_.get(), SetState(Service::kStatePortal));
+  EXPECT_CALL(*connection_.get(), is_default())
+      .WillOnce(Return(false));
   PortalDetectorCallback(PortalDetector::Result(
       PortalDetector::kPhaseUnknown,
       PortalDetector::kStatusFailure,
       true));
 }
 
-TEST_F(DeviceTest, PortalDetectionSuccess) {
-  scoped_refptr<MockService> service(
-      new StrictMock<MockService>(control_interface(),
-                                  dispatcher(),
-                                  metrics(),
-                                  manager()));
-  EXPECT_CALL(*service.get(), IsConnected())
+TEST_F(DevicePortalDetectionTest, PortalDetectionSuccess) {
+  EXPECT_CALL(*service_.get(), IsConnected())
       .WillOnce(Return(true));
-  SelectService(service);
-  EXPECT_CALL(*service.get(), SetState(Service::kStateOnline));
+  EXPECT_CALL(*service_.get(), SetState(Service::kStateOnline));
   PortalDetectorCallback(PortalDetector::Result(
       PortalDetector::kPhaseUnknown,
       PortalDetector::kStatusSuccess,
       true));
 }
 
+TEST_F(DevicePortalDetectionTest, RequestPortalDetection) {
+  EXPECT_CALL(*service_.get(), state())
+      .WillOnce(Return(Service::kStateOnline))
+      .WillRepeatedly(Return(Service::kStatePortal));
+  EXPECT_FALSE(RequestPortalDetection());
+
+  EXPECT_CALL(*connection_.get(), is_default())
+      .WillOnce(Return(false))
+      .WillRepeatedly(Return(true));
+  EXPECT_FALSE(RequestPortalDetection());
+
+  EXPECT_CALL(*portal_detector_, IsInProgress())
+      .WillOnce(Return(true));
+  // Portal detection already running.
+  EXPECT_TRUE(RequestPortalDetection());
+
+  // Make sure our running mock portal detector was not replaced.
+  ExpectPortalDetectorIsMock();
+
+  // Throw away our pre-fabricated portal detector, and have the device create
+  // a new one.
+  StopPortalDetection();
+  EXPECT_CALL(manager_, IsPortalDetectionEnabled(device_->technology()))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*service_.get(), HasProxyConfig())
+      .WillRepeatedly(Return(false));
+  const string kPortalCheckURL("http://portal");
+  EXPECT_CALL(manager_, GetPortalCheckURL())
+      .WillOnce(ReturnRef(kPortalCheckURL));
+  const string kInterfaceName("int0");
+  EXPECT_CALL(*connection_.get(), interface_name())
+      .WillRepeatedly(ReturnRef(kInterfaceName));
+  const vector<string> kDNSServers;
+  EXPECT_CALL(*connection_.get(), dns_servers())
+      .WillRepeatedly(ReturnRef(kDNSServers));
+  EXPECT_TRUE(RequestPortalDetection());
+}
+
+TEST_F(DevicePortalDetectionTest, NotConnected) {
+  EXPECT_CALL(*service_.get(), IsConnected())
+      .WillOnce(Return(false));
+  SetServiceConnectedState(Service::kStatePortal);
+  // We don't check for the portal detector to be reset here, because
+  // it would have been reset as a part of disconnection.
+}
+
+TEST_F(DevicePortalDetectionTest, NotPortal) {
+  EXPECT_CALL(*service_.get(), IsConnected())
+      .WillOnce(Return(true));
+  EXPECT_CALL(*service_.get(), SetState(Service::kStateOnline));
+  SetServiceConnectedState(Service::kStateOnline);
+  ExpectPortalDetectorReset();
+}
+
+TEST_F(DevicePortalDetectionTest, NotDefault) {
+  EXPECT_CALL(*service_.get(), IsConnected())
+      .WillOnce(Return(true));
+  EXPECT_CALL(*connection_.get(), is_default())
+      .WillOnce(Return(false));
+  EXPECT_CALL(*service_.get(), SetState(Service::kStatePortal));
+  SetServiceConnectedState(Service::kStatePortal);
+  ExpectPortalDetectorReset();
+}
+
+TEST_F(DevicePortalDetectionTest, PortalIntervalIsZero) {
+  EXPECT_CALL(*service_.get(), IsConnected())
+      .WillOnce(Return(true));
+  EXPECT_CALL(*connection_.get(), is_default())
+      .WillOnce(Return(true));
+  EXPECT_CALL(manager_, GetPortalCheckInterval())
+      .WillOnce(Return(0));
+  EXPECT_CALL(*service_.get(), SetState(Service::kStatePortal));
+  SetServiceConnectedState(Service::kStatePortal);
+  ExpectPortalDetectorReset();
+}
+
+TEST_F(DevicePortalDetectionTest, RestartPortalDetection) {
+  EXPECT_CALL(*service_.get(), IsConnected())
+      .WillOnce(Return(true));
+  EXPECT_CALL(*connection_.get(), is_default())
+      .WillOnce(Return(true));
+  const int kPortalDetectionInterval = 10;
+  EXPECT_CALL(manager_, GetPortalCheckInterval())
+      .Times(AtLeast(1))
+      .WillRepeatedly(Return(kPortalDetectionInterval));
+  const string kPortalCheckURL("http://portal");
+  EXPECT_CALL(manager_, GetPortalCheckURL())
+      .WillOnce(ReturnRef(kPortalCheckURL));
+  EXPECT_CALL(*portal_detector_, StartAfterDelay(kPortalCheckURL,
+                                                 kPortalDetectionInterval))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*service_.get(), SetState(Service::kStatePortal));
+  SetServiceConnectedState(Service::kStatePortal);
+  ExpectPortalDetectorSet();
+}
+
 }  // namespace shill