shill: Cleans-up scan state machine to address field trial anomalies.

The progressive scan field trial shows a few scan-to-connect times that
extend much, much longer than expected (5% of one study group shows
times that extend to multiple hours).  I suspect that various timeouts
in the scan/connect process (including the user being too frustrated to
continue) would terminate this process long before we reached an hour
and, therefore, that these anamolies are a function of measurement
rather than operational problems.  This would suggest that some
operations that should terminate the scan state machine aren't being
caught and, consequently, that occasionally some wifi connections are
being linked to previous, unrelated, scans.

This CL explores this possibility by rearranging the SetScanState calls
to follow pending_service_ (an indicator of scan/connect state that has
gotten a lot of testing and time in the field).  This has the added
benefit of insuring that different code that expresses the state of
the scan/connect mechanism track each other more closely.

One ramification of this CL is that scan-to-connect and
scan-to-not-found state transitions occur earlier than before.  The
transition was moved to the actual cause of the transition (to the call
to SetPendingService, for instance) rather than a convenient spot to
view it (in ProgressiveScanTask).

In certain cases (in PendingTimeoutHandler, for instance), SetScanState
calls had to be moved relative to existing calls to SetPendingState
(which now calls SetScanState) in order to reflect the actual scan state
correctly.  Similar reasoning instigated the creation
kScanTransitionToConnecting to correctly document the scan state if,
during the scan/connect process, we try to connect to a service that is
not the pending_service_.

Another change resets the WiFi connect timer (just as we reset the WiFi
scan timer) when the scan state reaches 'kScanIdle'.

BUG=chromium:260759
TEST=unittest

Change-Id: Ib1c559b27d91df5b7408a42b187fb67cf03c8784
Reviewed-on: https://gerrit.chromium.org/gerrit/62131
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Commit-Queue: Wade Guthrie <wdg@chromium.org>
Reviewed-by: Wade Guthrie <wdg@chromium.org>
Tested-by: Wade Guthrie <wdg@chromium.org>
diff --git a/wifi.cc b/wifi.cc
index 7e0dce3..2684f39 100644
--- a/wifi.cc
+++ b/wifi.cc
@@ -315,6 +315,8 @@
         total_fraction += fraction_per_scan_;
         scan_fractions.push_back(fraction_per_scan_);
       } while (total_fraction < 1.0);
+      DCHECK(scan_state_ == kScanIdle);
+      DCHECK(scan_method_ == kScanMethodNone);
       scan_session_.reset(
           new ScanSession(netlink_manager_,
                           dispatcher(),
@@ -406,6 +408,13 @@
   }
 
   if (pending_service_ && pending_service_ != service) {
+    // This is a signal to SetPendingService(NULL) to not modify the scan
+    // state since the overall story arc isn't reflected by the disconnect.
+    // It is, instead, described by the transition to either kScanFoundNothing
+    // or kScanConnecting (made by |SetPendingService|, below).
+    if (scan_method_ != kScanMethodNone) {
+      SetScanState(kScanTransitionToConnecting, scan_method_, __func__);
+    }
     DisconnectFrom(pending_service_);
   }
 
@@ -422,6 +431,7 @@
       rpcid_by_service_[service] = network_path;
     } catch (const DBus::Error &e) {  // NOLINT
       LOG(ERROR) << "exception while adding network: " << e.what();
+      SetScanState(kScanIdle, scan_method_, __func__);
       return;
     }
   }
@@ -864,8 +874,8 @@
     // Boring case: we've connected to the service we asked
     // for. Simply update |current_service_| and |pending_service_|.
     current_service_ = service;
-    SetPendingService(NULL);
     SetScanState(kScanConnected, scan_method_, __func__);
+    SetPendingService(NULL);
     return;
   }
 
@@ -1128,12 +1138,8 @@
 }
 
 void WiFi::UpdateScanStateAfterScanDone() {
-  if (scan_state_ != kScanIdle) {
-    if (IsIdle()) {
-      SetScanState(kScanFoundNothing, scan_method_, __func__);
-    } else {
-      SetScanState(kScanConnecting, scan_method_, __func__);
-    }
+  if (scan_state_ != kScanIdle && IsIdle()) {
+    SetScanState(kScanFoundNothing, scan_method_, __func__);
   }
 }
 
@@ -1152,7 +1158,6 @@
   if ((pending_service_.get() && pending_service_->IsConnecting()) ||
       (current_service_.get() && current_service_->IsConnecting())) {
     SLOG(WiFi, 2) << "Ignoring scan request while connecting to an AP.";
-    SetScanState(kScanConnecting, scan_method_, __func__);
     return;
   }
   map<string, DBus::Variant> scan_args;
@@ -1212,7 +1217,6 @@
   if (!IsIdle()) {
     SLOG(WiFi, 2) << "Ignoring scan request while connecting to an AP.";
     scan_session_.reset();
-    SetScanState(kScanConnecting, scan_method_, __func__);
     return;
   }
   if (scan_session_->HasMoreFrequencies()) {
@@ -1593,10 +1597,29 @@
   SLOG(WiFi, 2) << "WiFi " << link_name() << " setting pending service to "
                 << (service ? service->unique_name(): "NULL");
   if (service) {
+    SetScanState(kScanConnecting, scan_method_, __func__);
     service->SetState(Service::kStateAssociating);
     StartPendingTimer();
-  } else if (pending_service_) {
-    StopPendingTimer();
+  } else {
+    // SetPendingService(NULL) is called in the following cases:
+    //  a) |ConnectTo|->|DisconnectFrom|.  Connecting to a service, disconnect
+    //     the old service (scan_state_ == kScanTransitionToConnecting).  No
+    //     state transition is needed here.
+    //  b) |HandleRoam|.  Connected to a service, it's no longer pending
+    //     (scan_state_ == kScanIdle).  No state transition is needed here.
+    //  c) |DisconnectFrom| and |HandleDisconnect|. Disconnected/disconnecting
+    //     from a service not during a scan (scan_state_ == kScanIdle).  No
+    //     state transition is needed here.
+    //  d) |DisconnectFrom| and |HandleDisconnect|. Disconnected/disconnecting
+    //     from a service during a scan (scan_state_ == kScanScanning or
+    //     kScanConnecting).  This is an odd case -- let's discard any
+    //     statistics we're gathering by transitioning directly into kScanIdle.
+    if (scan_state_ == kScanScanning || scan_state_ == kScanConnecting) {
+      SetScanState(kScanIdle, kScanMethodNone, __func__);
+    }
+    if (pending_service_) {
+      StopPendingTimer();
+    }
   }
   pending_service_ = service;
 }
@@ -1605,6 +1628,7 @@
   Error unused_error;
   LOG(INFO) << "WiFi Device " << link_name() << ": " << __func__;
   CHECK(pending_service_);
+  SetScanState(kScanFoundNothing, scan_method_, __func__);
   pending_service_->DisconnectWithFailure(
       Service::kFailureOutOfRange, &unused_error);
 
@@ -1618,7 +1642,6 @@
     LOG(INFO) << "Hidden service was not found.";
     DisconnectFrom(pending_service_);
   }
-  SetScanState(kScanFoundNothing, scan_method_, __func__);
 }
 
 void WiFi::StartReconnectTimer() {
@@ -1948,6 +1971,10 @@
   switch (new_state) {
     case kScanIdle:
       metrics()->ResetScanTimer(interface_index());
+      metrics()->ResetConnectTimer(interface_index());
+      if (scan_session_) {
+        scan_session_.reset();
+      }
       break;
     case kScanScanning:
       metrics()->NotifyDeviceScanStarted(interface_index());
@@ -1967,6 +1994,7 @@
       metrics()->NotifyDeviceScanFinished(interface_index());
       metrics()->ResetConnectTimer(interface_index());
       break;
+    case kScanTransitionToConnecting:  // FALLTHROUGH
     default:
       break;
   }
@@ -1997,6 +2025,8 @@
         default:
           NOTREACHED();
       }
+    case kScanTransitionToConnecting:
+      return "TRANSITION_TO_CONNECTING";
     case kScanConnecting:
       switch (method) {
         case kScanMethodNone:
diff --git a/wifi.h b/wifi.h
index 7a165da..20c6b6a 100644
--- a/wifi.h
+++ b/wifi.h
@@ -208,6 +208,7 @@
   enum ScanState {
     kScanIdle,
     kScanScanning,
+    kScanTransitionToConnecting,
     kScanConnecting,
     kScanConnected,
     kScanFoundNothing
@@ -216,6 +217,9 @@
   friend class WiFiObjectTest;  // access to supplicant_*_proxy_, link_up_
   friend class WiFiTimerTest;  // kNumFastScanAttempts, kFastScanIntervalSeconds
   FRIEND_TEST(WiFiMainTest, AppendBgscan);
+  FRIEND_TEST(WiFiMainTest, ConnectToServiceNotPending);  // ScanState
+  FRIEND_TEST(WiFiMainTest, ConnectToWithError);  // ScanState
+  FRIEND_TEST(WiFiMainTest, ConnectWhileNotScanning);  // ScanState
   FRIEND_TEST(WiFiMainTest, CurrentBSSChangedUpdateServiceEndpoint);
   FRIEND_TEST(WiFiMainTest, DisconnectCurrentServiceWithErrors);
   FRIEND_TEST(WiFiMainTest, FlushBSSOnResume);  // kMaxBSSResumeAgeSeconds
@@ -225,11 +229,14 @@
   FRIEND_TEST(WiFiMainTest, InitialSupplicantState);  // kInterfaceStateUnknown
   FRIEND_TEST(WiFiMainTest, LinkMonitorFailure);  // set_link_monitor()
   FRIEND_TEST(WiFiMainTest, ProgressiveScanConnectingToConnected);
+  FRIEND_TEST(WiFiMainTest, ProgressiveScanConnectingToNotFound);
   FRIEND_TEST(WiFiMainTest, ProgressiveScanError);  // ScanMethod, ScanState
   FRIEND_TEST(WiFiMainTest, ProgressiveScanFound);  // ScanMethod, ScanState
   FRIEND_TEST(WiFiMainTest, ProgressiveScanNotFound);  // ScanMethod, ScanState
   FRIEND_TEST(WiFiMainTest, ScanResults);             // EndpointMap
   FRIEND_TEST(WiFiMainTest, ScanResultsWithUpdates);  // EndpointMap
+  FRIEND_TEST(WiFiMainTest, ScanStateHandleDisconnect);  // ScanState
+  FRIEND_TEST(WiFiMainTest, ScanStateNotScanningNoUma);  // ScanState
   FRIEND_TEST(WiFiMainTest, ScanStateUma);  // ScanState, ScanMethod
   FRIEND_TEST(WiFiMainTest, Stop);  // weak_ptr_factory_
   FRIEND_TEST(WiFiMainTest, TimeoutPendingServiceWithEndpoints);
diff --git a/wifi_unittest.cc b/wifi_unittest.cc
index 5092136..4c9e3bb 100644
--- a/wifi_unittest.cc
+++ b/wifi_unittest.cc
@@ -688,7 +688,7 @@
     wifi_->OnWiFiDebugScopeChanged(enabled);
   }
   void SetPendingService(const WiFiServiceRefPtr &service) {
-    wifi_->pending_service_ = service;
+    wifi_->SetPendingService(service);
   }
   bool SetScanInterval(uint16_t interval_seconds, Error *error) {
     return wifi_->SetScanInterval(interval_seconds, error);
@@ -775,6 +775,10 @@
     return wifi_->SetBgscanSignalThreshold(threshold, error);
   }
 
+  void TimeoutPendingConnection() {
+    wifi_->PendingTimeoutHandler();
+  }
+
   NiceMockControl *control_interface() {
     return &control_interface_;
   }
@@ -1179,7 +1183,7 @@
   SetCurrentService(service);
   EXPECT_CALL(*service, IsConnecting()).WillOnce(Return(true));
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
-  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
+  EXPECT_TRUE(IsScanSessionNull());
   TriggerFullScan();
   dispatcher_.DispatchPendingEvents();
   Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
@@ -1187,7 +1191,7 @@
   // But otherwise we'll honor the request.
   EXPECT_CALL(*service, IsConnecting()).WillOnce(Return(false));
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(1);
-  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
+  EXPECT_TRUE(IsScanSessionNull());
   TriggerFullScan();
   dispatcher_.DispatchPendingEvents();
   Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
@@ -1273,7 +1277,7 @@
   EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
   EXPECT_CALL(log, Log(_, _, EndsWith("already connecting or connected.")));
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
-  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
+  EXPECT_TRUE(IsScanSessionNull());
   OnAfterResume();
   dispatcher_.DispatchPendingEvents();
 }
@@ -1498,12 +1502,12 @@
   // Initiate a connection.
   WiFiEndpointRefPtr endpoint;
   ::DBus::Path bss_path;
-  MockWiFiServiceRefPtr service =
-      SetupConnectingService(DBus::Path(), &endpoint, &bss_path);
-  ReportScanDoneKeepScanSession();
   EXPECT_CALL(*metrics(), NotifyDeviceScanFinished(_));
   EXPECT_CALL(*metrics(), NotifyDeviceConnectStarted(_, _));
   EXPECT_CALL(*adaptor_, EmitBoolChanged(flimflam::kScanningProperty, false));
+  MockWiFiServiceRefPtr service =
+      SetupConnectingService(DBus::Path(), &endpoint, &bss_path);
+  ReportScanDoneKeepScanSession();
   dispatcher_.DispatchPendingEvents();
   VerifyScanState(WiFi::kScanConnecting, WiFi::kScanMethodProgressive);
 
@@ -1749,6 +1753,10 @@
 
   // Connect after second scan.
   MockWiFiServiceRefPtr service = MakeMockService(flimflam::kSecurityNone);
+  EXPECT_CALL(*metrics(), NotifyDeviceScanFinished(_));
+  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
+  EXPECT_CALL(*adaptor_, EmitBoolChanged(flimflam::kScanningProperty, false));
   SetPendingService(service);
 
   // Verify that the third scan aborts and there is no further scan.
@@ -1756,10 +1764,6 @@
   EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
   EXPECT_CALL(log, Log(_, _, EndsWith(
       "Ignoring scan request while connecting to an AP."))).Times(1);
-  EXPECT_CALL(*metrics(), NotifyDeviceScanFinished(_));
-  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
-  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
-  EXPECT_CALL(*adaptor_, EmitBoolChanged(flimflam::kScanningProperty, false));
   dispatcher_.DispatchPendingEvents();
   VerifyScanState(WiFi::kScanConnecting, WiFi::kScanMethodProgressive);
 }
@@ -2171,6 +2175,7 @@
   CancelScanTimer();
   EXPECT_TRUE(GetScanTimer().IsCancelled());
   dispatcher_.DispatchPendingEvents();
+  InstallMockScanSession();
   EXPECT_CALL(*scan_session_, InitiateScan());
   FireScanTimer();
   dispatcher_.DispatchPendingEvents();
@@ -2254,6 +2259,7 @@
   dispatcher_.DispatchPendingEvents();
   ReportScanDone();
   SetupConnectedService(DBus::Path(), NULL, NULL);
+  InstallMockScanSession();
   vector<uint8_t>kSSID(1, 'a');
   ByteArrays ssids;
   ssids.push_back(kSSID);
@@ -2269,7 +2275,7 @@
   dispatcher_.DispatchPendingEvents();
   SetupConnectedService(DBus::Path(), NULL, NULL);
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
-  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
+  EXPECT_TRUE(IsScanSessionNull());
   EXPECT_CALL(*wifi_provider(), GetHiddenSSIDList())
       .WillRepeatedly(Return(ByteArrays()));
   ReportCurrentBSSChanged(WPASupplicant::kCurrentBSSNull);
@@ -2800,11 +2806,11 @@
   // Setup a service and ask to connect to it.
   WiFiEndpointRefPtr endpoint;
   ::DBus::Path bss_path;
+  EXPECT_CALL(*adaptor_, EmitBoolChanged(flimflam::kScanningProperty, false));
   MockWiFiServiceRefPtr service =
       SetupConnectingService(DBus::Path(), &endpoint, &bss_path);
 
   ReportScanDoneKeepScanSession();
-  EXPECT_CALL(*adaptor_, EmitBoolChanged(flimflam::kScanningProperty, false));
   dispatcher_.DispatchPendingEvents();  // Launch UpdateScanStateAfterScanDone
   VerifyScanState(WiFi::kScanConnecting, WiFi::kScanMethodFull);
 
@@ -2835,13 +2841,13 @@
   // Setup a service and ask to connect to it.
   WiFiEndpointRefPtr endpoint;
   ::DBus::Path bss_path;
+  EXPECT_CALL(*adaptor_, EmitBoolChanged(flimflam::kScanningProperty, false));
+  EXPECT_CALL(*metrics(), NotifyDeviceScanFinished(_));
+  EXPECT_CALL(*metrics(), NotifyDeviceConnectStarted(_, _));
   MockWiFiServiceRefPtr service =
       SetupConnectingService(DBus::Path(), &endpoint, &bss_path);
 
   ReportScanDoneKeepScanSession();
-  EXPECT_CALL(*adaptor_, EmitBoolChanged(flimflam::kScanningProperty, false));
-  EXPECT_CALL(*metrics(), NotifyDeviceScanFinished(_));
-  EXPECT_CALL(*metrics(), NotifyDeviceConnectStarted(_, _));
   dispatcher_.DispatchPendingEvents();  // Launch ProgressiveScanTask
   VerifyScanState(WiFi::kScanConnecting, WiFi::kScanMethodProgressive);
 
@@ -2857,13 +2863,173 @@
   ScopeLogger::GetInstance()->EnableScopesByName("-wifi");
 }
 
+TEST_F(WiFiMainTest, ProgressiveScanConnectingToNotFound) {
+  NiceScopedMockLog log;
+
+  EXPECT_CALL(*adaptor_, EmitBoolChanged(_, _)).Times(AnyNumber());
+  EXPECT_CALL(*metrics(), NotifyDeviceScanStarted(_));
+  VerifyScanState(WiFi::kScanIdle, WiFi::kScanMethodNone);
+  StartWiFi();
+  EXPECT_CALL(*adaptor_, EmitBoolChanged(flimflam::kScanningProperty, true));
+  dispatcher_.DispatchPendingEvents();  // Runs ProgressiveScanTask
+  VerifyScanState(WiFi::kScanScanning, WiFi::kScanMethodProgressive);
+
+  // Setup a service and ask to connect to it.
+  WiFiEndpointRefPtr endpoint;
+  ::DBus::Path bss_path;
+  EXPECT_CALL(*adaptor_, EmitBoolChanged(flimflam::kScanningProperty, false));
+  EXPECT_CALL(*metrics(), NotifyDeviceScanFinished(_));
+  EXPECT_CALL(*metrics(), NotifyDeviceConnectStarted(_, _));
+  MockWiFiServiceRefPtr service =
+      SetupConnectingService(DBus::Path(), &endpoint, &bss_path);
+
+  ReportScanDoneKeepScanSession();
+  dispatcher_.DispatchPendingEvents();  // Launch ProgressiveScanTask
+  VerifyScanState(WiFi::kScanConnecting, WiFi::kScanMethodProgressive);
+
+  // Simulate connection timeout.
+  ScopeLogger::GetInstance()->EnableScopesByName("wifi");
+  ScopeLogger::GetInstance()->set_verbose_level(10);
+  EXPECT_CALL(*service,
+              NotifyCurrentEndpoint(EndpointMatch(endpoint))).Times(0);
+  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
+  EXPECT_CALL(log,
+              Log(_, _, HasSubstr("-> PROGRESSIVE_FINISHED_NOCONNECTION")));
+  EXPECT_CALL(*metrics(), NotifyDeviceConnectFinished(_)).Times(0);
+  EXPECT_CALL(*metrics(), NotifyDeviceScanFinished(_));
+  EXPECT_CALL(*metrics(), ResetConnectTimer(_)).Times(2);
+  TimeoutPendingConnection();
+  ScopeLogger::GetInstance()->set_verbose_level(0);
+  ScopeLogger::GetInstance()->EnableScopesByName("-wifi");
+  VerifyScanState(WiFi::kScanIdle, WiFi::kScanMethodNone);
+}
+
 TEST_F(WiFiMainTest, ScanStateUma) {
   EXPECT_CALL(*metrics(), SendEnumToUMA(Metrics::kMetricScanResult, _, _)).
       Times(0);
+  EXPECT_CALL(*metrics(), NotifyDeviceScanStarted(_));
   SetScanState(WiFi::kScanScanning, WiFi::kScanMethodProgressive, __func__);
 
+  EXPECT_CALL(*metrics(), NotifyDeviceScanFinished(_));
+  EXPECT_CALL(*metrics(), NotifyDeviceConnectStarted(_, _));
+  SetScanState(WiFi::kScanConnecting, WiFi::kScanMethodProgressive, __func__);
+
+  EXPECT_CALL(*metrics(), NotifyDeviceConnectFinished(_));
   EXPECT_CALL(*metrics(), SendEnumToUMA(Metrics::kMetricScanResult, _, _));
   SetScanState(WiFi::kScanConnected, WiFi::kScanMethodProgressive, __func__);
 }
 
+TEST_F(WiFiMainTest, ScanStateNotScanningNoUma) {
+  EXPECT_CALL(*metrics(), NotifyDeviceScanStarted(_)).Times(0);
+  EXPECT_CALL(*metrics(), NotifyDeviceConnectStarted(_, _));
+  SetScanState(WiFi::kScanConnecting, WiFi::kScanMethodNone, __func__);
+
+  EXPECT_CALL(*metrics(), NotifyDeviceConnectFinished(_));
+  EXPECT_CALL(*metrics(), SendEnumToUMA(Metrics::kMetricScanResult, _, _)).
+      Times(0);
+  SetScanState(WiFi::kScanConnected, WiFi::kScanMethodNone, __func__);
+}
+
+TEST_F(WiFiMainTest, ConnectToServiceNotPending) {
+  // Test for SetPendingService(NULL), condition a)
+  // |ConnectTo|->|DisconnectFrom|.
+  NiceScopedMockLog log;
+
+  // Start scanning.
+  StartWiFi();
+  dispatcher_.DispatchPendingEvents();  // Runs ProgressiveScanTask
+  VerifyScanState(WiFi::kScanScanning, WiFi::kScanMethodProgressive);
+
+  // Setup pending service.
+  MockWiFiServiceRefPtr service_pending(
+      SetupConnectingService(DBus::Path(), NULL, NULL));
+  EXPECT_EQ(service_pending.get(), GetPendingService().get());
+
+  // ConnectTo a different service than the pending one.
+  ScopeLogger::GetInstance()->EnableScopesByName("wifi");
+  ScopeLogger::GetInstance()->set_verbose_level(10);
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Disconnect());
+  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
+  EXPECT_CALL(log, Log(_, _, HasSubstr("-> TRANSITION_TO_CONNECTING")));
+  EXPECT_CALL(log, Log(_, _, HasSubstr("-> PROGRESSIVE_CONNECTING")));
+  MockWiFiServiceRefPtr service_connecting(
+      SetupConnectingService(DBus::Path(), NULL, NULL));
+  ScopeLogger::GetInstance()->set_verbose_level(0);
+  ScopeLogger::GetInstance()->EnableScopesByName("-wifi");
+  EXPECT_EQ(service_connecting.get(), GetPendingService().get());
+  EXPECT_EQ(NULL, GetCurrentService().get());
+  VerifyScanState(WiFi::kScanConnecting, WiFi::kScanMethodProgressive);
+}
+
+TEST_F(WiFiMainTest, ConnectToWithError) {
+  StartWiFi();
+  dispatcher_.DispatchPendingEvents();  // Runs ProgressiveScanTask
+  VerifyScanState(WiFi::kScanScanning, WiFi::kScanMethodProgressive);
+
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), AddNetwork(_)).
+      WillOnce(Throw(
+          DBus::Error(
+              "fi.w1.wpa_supplicant1.InterfaceUnknown",
+              "test threw fi.w1.wpa_supplicant1.InterfaceUnknown")));
+  EXPECT_CALL(*metrics(), NotifyDeviceScanFinished(_)).Times(0);
+  EXPECT_CALL(*metrics(), SendEnumToUMA(Metrics::kMetricScanResult, _, _)).
+      Times(0);
+  MockWiFiServiceRefPtr service = MakeMockService(flimflam::kSecurityNone);
+  InitiateConnect(service);
+  VerifyScanState(WiFi::kScanIdle, WiFi::kScanMethodNone);
+  EXPECT_TRUE(IsScanSessionNull());
+}
+
+TEST_F(WiFiMainTest, ScanStateHandleDisconnect) {
+  // Test for SetPendingService(NULL), condition d) Disconnect while scanning.
+  // Start scanning.
+  StartWiFi();
+  dispatcher_.DispatchPendingEvents();  // Runs ProgressiveScanTask
+  VerifyScanState(WiFi::kScanScanning, WiFi::kScanMethodProgressive);
+
+  // Set the pending service.
+  ReportScanDoneKeepScanSession();
+  MockWiFiServiceRefPtr service = MakeMockService(flimflam::kSecurityNone);
+  SetPendingService(service);
+  VerifyScanState(WiFi::kScanConnecting, WiFi::kScanMethodProgressive);
+
+  // Disconnect from the pending service.
+  EXPECT_CALL(*metrics(), NotifyDeviceScanFinished(_)).Times(0);
+  EXPECT_CALL(*metrics(), SendEnumToUMA(Metrics::kMetricScanResult, _, _)).
+      Times(0);
+  ReportCurrentBSSChanged(WPASupplicant::kCurrentBSSNull);
+  VerifyScanState(WiFi::kScanIdle, WiFi::kScanMethodNone);
+}
+
+TEST_F(WiFiMainTest, ConnectWhileNotScanning) {
+  NiceScopedMockLog log;
+
+  // Setup WiFi but terminate scan.
+  StartWiFi();
+  ReportScanDone();
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_CALL(*metrics(), ResetConnectTimer(_));
+  SetScanState(WiFi::kScanIdle, WiFi::kScanMethodNone, __func__);
+
+  // Connecting.
+  EXPECT_CALL(*metrics(), NotifyDeviceScanStarted(_)).Times(0);
+  WiFiEndpointRefPtr endpoint;
+  ::DBus::Path bss_path;
+  ScopeLogger::GetInstance()->EnableScopesByName("wifi");
+  ScopeLogger::GetInstance()->set_verbose_level(10);
+  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
+  EXPECT_CALL(log, Log(_, _, HasSubstr("-> TRANSITION_TO_CONNECTING"))).
+      Times(0);
+  EXPECT_CALL(log, Log(_, _, HasSubstr("-> CONNECTING (not scan related)")));
+  MockWiFiServiceRefPtr service =
+      SetupConnectingService(DBus::Path(), &endpoint, &bss_path);
+
+  // Connected.
+  EXPECT_CALL(log, Log(_, _, HasSubstr("-> CONNECTED (not scan related")));
+  ReportCurrentBSSChanged(bss_path);
+  ScopeLogger::GetInstance()->set_verbose_level(0);
+  ScopeLogger::GetInstance()->EnableScopesByName("-wifi");
+  VerifyScanState(WiFi::kScanIdle, WiFi::kScanMethodNone);
+}
+
 }  // namespace shill