Add new config store tests

Add new test scenarios for config store testing in
WifiManagerTest & WifiEnterpriseTest.

The test scenario is as follows:
1. Connect to network_A.
2. Tun wifi off.
3. Turn wifi on.
4. Wait for reconnection to network_A.

Refactor the existing IOT test methods in WifiManagerTest so that
it can be reused for this new test.

BUG: 29381649
Change-Id: Id7eca3c36c89063d553073d5077e46da1aa755ab
TEST: Manual tests
diff --git a/acts/framework/acts/test_utils/wifi/wifi_test_utils.py b/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
index b6c43de..67f4220 100755
--- a/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
+++ b/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
@@ -726,6 +726,96 @@
     droid.wifiStopTrackingStateChange()
 
 
+def toggle_wifi_and_wait_for_reconnection(ad,
+                                          network,
+                                          num_of_tries=1,
+                                          assert_on_fail=True):
+    """Toggle wifi state and then wait for Android device to reconnect to
+    the provided wifi network.
+
+    This expects the device to be already connected to the provided network.
+
+    Logic steps are
+     1. Ensure that we're connected to the network.
+     2. Turn wifi off.
+     3. Wait for 10 seconds.
+     4. Turn wifi on.
+     5. Wait for the "connected" event, then confirm the connected ssid is the
+        one requested.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        network: A dictionary representing the network to await connection. The
+                 dictionary must have the key "SSID".
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure. Default is 1.
+        assert_on_fail: If True, error checks in this function will raise test
+                        failure signals.
+
+    Returns:
+        If assert_on_fail is False, function returns True if the toggle was
+        successful, False otherwise. If assert_on_fail is True, no return value.
+    """
+    _assert_on_fail_handler(_toggle_wifi_and_wait_for_reconnection,
+                            assert_on_fail, ad, network, num_of_tries)
+
+
+def _toggle_wifi_and_wait_for_reconnection(ad, network, num_of_tries=1):
+    """Toggle wifi state and then wait for Android device to reconnect to
+    the provided wifi network.
+
+    This expects the device to be already connected to the provided network.
+
+    Logic steps are
+     1. Ensure that we're connected to the network.
+     2. Turn wifi off.
+     3. Wait for 10 seconds.
+     4. Turn wifi on.
+     5. Wait for the "connected" event, then confirm the connected ssid is the
+        one requested.
+
+    This will directly fail a test if anything goes wrong.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        network: A dictionary representing the network to await connection. The
+                 dictionary must have the key "SSID".
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure. Default is 1.
+    """
+    serial = ad.serial
+    expected_ssid = network[WifiEnums.SSID_KEY]
+    # First ensure that we're already connected to the provided network.
+    verify_con = {WifiEnums.SSID_KEY: expected_ssid}
+    verify_wifi_connection_info(ad, verify_con)
+    # Now toggle wifi state and wait for the connection event.
+    wifi_toggle_state(ad, False)
+    time.sleep(10)
+    wifi_toggle_state(ad, True)
+    ad.droid.wifiStartTrackingStateChange()
+    try:
+        connect_result = None
+        for i in range(num_of_tries):
+            try:
+                connect_result = ad.ed.pop_event(WifiEventNames.WIFI_CONNECTED,
+                                                 30)
+                break
+            except Empty:
+                pass
+        asserts.assert_true(connect_result,
+                            "Failed to connect to Wi-Fi network %s on %s" %
+                            (network, serial))
+        log.debug("Connection result on %s: %s.", serial, connect_result)
+        actual_ssid = connect_result['data'][WifiEnums.SSID_KEY]
+        asserts.assert_equal(actual_ssid, expected_ssid,
+                             "Connected to the wrong network on %s."
+                             "Expected %s, but got %s." %
+                             (serial, expected_ssid, actual_ssid))
+        log.info("Connected to Wi-Fi network %s on %s", actual_ssid, serial)
+    finally:
+        ad.droid.wifiStopTrackingStateChange()
+
+
 def wifi_connect(ad, network, num_of_tries=1, assert_on_fail=True):
     """Connect an Android device to a wifi network.
 
diff --git a/acts/tests/google/wifi/WifiEnterpriseTest.py b/acts/tests/google/wifi/WifiEnterpriseTest.py
index eba2dff..a73232b 100755
--- a/acts/tests/google/wifi/WifiEnterpriseTest.py
+++ b/acts/tests/google/wifi/WifiEnterpriseTest.py
@@ -35,7 +35,8 @@
 class WifiEnterpriseTest(base_test.BaseTestClass):
     def __init__(self, controllers):
         base_test.BaseTestClass.__init__(self, controllers)
-        self.tests = ("test_eap_connect", "test_eap_connect_negative", )
+        self.tests = ("test_eap_connect", "test_eap_connect_negative",
+                      "test_eap_connect_config_store", )
 
     def setup_class(self):
         self.dut = self.android_devices[0]
@@ -305,6 +306,21 @@
             name += "-{}".format(config[Ent.PHASE2].name)
         return name
 
+    def gen_eap_test_name_for_config_store(self, config, ad):
+        """Generates a test case name based on an EAP configuration for config
+        store tests.
+
+        Args:
+            config: A dict representing an EAP credential.
+            ad: Discarded. This is here because name function signature needs
+                to be consistent with logic function signature for generated
+                test cases.
+
+        Returns:
+            A string representing the name of a generated EAP test case.
+        """
+        return self.gen_eap_test_name(config, ad) + "-config_store"
+
     def gen_passpoint_test_name(self, config, ad):
         """Generates a test case name based on an EAP passpoint configuration.
 
@@ -322,6 +338,42 @@
         name = name.replace("connect", "passpoint_connect")
         return name
 
+    def gen_passpoint_test_name_for_config_store(self, config, ad):
+        """Generates a test case name based on an EAP passpoint configuration
+        for config store tests.
+
+        Args:
+            config: A dict representing an EAP passpoint credential.
+            ad: Discarded. This is here because name function signature needs
+                to be consistent with logic function signature for generated
+                test cases.
+
+        Returns:
+            A string representing the name of a generated EAP passpoint connect
+            test case.
+        """
+        return self.gen_passpoint_test_name(config, ad) + "-config_store"
+
+    def eap_connect_toggle_wifi(self,
+                                config,
+                                *args):
+        """Connects to an enterprise network, toggles wifi state and ensures
+        that the device reconnects.
+
+        This logic expect the enterprise network to have Internet access.
+
+        Args:
+            config: A dict representing a wifi enterprise configuration.
+            args: args to be passed to |wutils.eap_connect|.
+
+        Returns:
+            True if the connection is successful and Internet access works.
+        """
+        wutils.eap_connect(config, *args)
+        ad = args[0]
+        wutils.toggle_wifi_and_wait_for_reconnection(ad, config,
+                                                     num_of_tries=5)
+
     """Tests"""
 
     @signals.generated_test
@@ -383,6 +435,39 @@
         asserts.assert_equal(len(failed), 0, msg)
 
     @signals.generated_test
+    def test_eap_connect_config_store(self):
+        """Test connecting to enterprise networks of different authentication
+        types after wifi toggle.
+
+        The authentication types tested are:
+            EAP-TLS
+            EAP-PEAP with different phase2 types.
+            EAP-TTLS with different phase2 types.
+
+        Procedures:
+            For each enterprise wifi network
+            1. Connect to the network.
+            2. Send a GET request to a website and check response.
+            3. Toggle wifi.
+            4. Ensure that the device reconnects to the same network.
+
+        Expect:
+            Successful connection and Internet access through the enterprise
+            networks.
+        """
+        eap_configs = self.gen_eap_configs()
+        self.log.info("Testing %d different configs.", len(eap_configs))
+        random.shuffle(eap_configs)
+        failed = self.run_generated_testcases(
+            self.eap_connect_toggle_wifi,
+            eap_configs,
+            args=(self.dut, ),
+            name_func=self.gen_eap_test_name_for_config_store)
+        asserts.assert_equal(
+            len(failed), 0, "The following configs failed EAP connect test: %s"
+            % pprint.pformat(failed))
+
+    @signals.generated_test
     def test_passpoint_connect(self):
         """Test connecting to enterprise networks of different authentication
         types with passpoint support.
@@ -448,3 +533,39 @@
             len(failed), 0,
             "The following configs failed negative passpoint connect test: %s"
             % pprint.pformat(failed))
+
+    @signals.generated_test
+    def test_passpoint_connect_config_store(self):
+        """Test connecting to enterprise networks of different authentication
+        types with passpoint support after wifi toggle.
+
+        The authentication types tested are:
+            EAP-TLS
+            EAP-TTLS with MSCHAPV2 as phase2.
+
+        Procedures:
+            For each enterprise wifi network
+            1. Connect to the network.
+            2. Send a GET request to a website and check response.
+            3. Toggle wifi.
+            4. Ensure that the device reconnects to the same network.
+
+        Expect:
+            Successful connection and Internet access through the enterprise
+            networks with passpoint support.
+        """
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on device %s" %
+                        self.dut.model)
+        passpoint_configs = self.gen_passpoint_configs()
+        self.log.info("Testing %d different configs.", len(passpoint_configs))
+        random.shuffle(passpoint_configs)
+        failed = self.run_generated_testcases(
+            self.eap_connect_toggle_wifi,
+            passpoint_configs,
+            args=(self.dut, ),
+            name_func=self.gen_passpoint_test_name_for_config_store)
+        asserts.assert_equal(
+            len(failed), 0,
+            "The following configs failed passpoint connect test: %s" %
+            pprint.pformat(failed))
diff --git a/acts/tests/google/wifi/WifiManagerTest.py b/acts/tests/google/wifi/WifiManagerTest.py
index 1ca0315..29b5c95 100755
--- a/acts/tests/google/wifi/WifiManagerTest.py
+++ b/acts/tests/google/wifi/WifiManagerTest.py
@@ -46,8 +46,9 @@
         if getattr(self, "attenuators", []):
             for a in self.attenuators:
                 a.set_atten(0)
-        req_params = ("iot_networks", "open_network", "iperf_server_address",
-                      "tdls_models", "energy_info_models")
+        req_params = ("iot_networks", "open_network", "config_store_networks",
+                      "iperf_server_address", "tdls_models",
+                      "energy_info_models")
         self.unpack_userparams(req_params)
         asserts.assert_true(
             len(self.iot_networks) > 0,
@@ -72,20 +73,12 @@
 
     """Helper Functions"""
 
-    def connect_to_wifi_network_with_password(self, params):
+    def connect_to_wifi_network(self, params):
         """Connection logic for open and psk wifi networks.
 
-        Logic steps are
-        1. Connect to the network.
-        2. Run iperf traffic.
-
         Args:
             params: A tuple of network info and AndroidDevice object.
-
-        Returns:
-            True if successful, False otherwise.
         """
-        wait_time = 5
         network, ad = params
         droid = ad.droid
         ed = ad.ed
@@ -95,6 +88,16 @@
         scan_results = droid.wifiGetScanResults()
         wutils.assert_network_in_list({WifiEnums.SSID_KEY: SSID}, scan_results)
         wutils.wifi_connect(ad, network, num_of_tries=3)
+
+    def run_iperf_client(self, params):
+        """Run iperf traffic after connection.
+
+        Args:
+            params: A tuple of network info and AndroidDevice object.
+        """
+        wait_time = 5
+        network, ad = params
+        SSID = network[WifiEnums.SSID_KEY]
         self.log.info("Starting iperf traffic through {}".format(SSID))
         time.sleep(wait_time)
         port_arg = "-p {}".format(self.iperf_server.port)
@@ -103,6 +106,39 @@
         self.log.debug(pprint.pformat(data))
         asserts.assert_true(success, "Error occurred in iPerf traffic.")
 
+    def connect_to_wifi_network_and_run_iperf(self, params):
+        """Connection logic for open and psk wifi networks.
+
+        Logic steps are
+        1. Connect to the network.
+        2. Run iperf traffic.
+
+        Args:
+            params: A tuple of network info and AndroidDevice object.
+        """
+        self.connect_to_wifi_network(params)
+        self.run_iperf_client(params)
+
+    def connect_to_wifi_network_toggle_wifi_and_run_iperf(self, params):
+        """ Connect to the provided network and then toggle wifi mode and wait
+        for reconnection to the provided network.
+
+        Logic steps are
+        1. Connect to the network.
+        2. Turn wifi off.
+        3. Turn wifi on.
+        4. Wait for connection to the network.
+        5. Run iperf traffic.
+
+        Args:
+            params: A tuple of network info and AndroidDevice object.
+       """
+        network, ad = params
+        self.connect_to_wifi_network(params)
+        wutils.toggle_wifi_and_wait_for_reconnection(
+                ad, network, num_of_tries=5)
+        self.run_iperf_client(params)
+
     def run_iperf(self, iperf_args):
         if "iperf_server_address" not in self.user_params:
             self.log.error(("Missing iperf_server_address. "
@@ -186,7 +222,18 @@
                                         self.android_devices))
         name_gen = lambda p: "test_connection_to-%s" % p[0][WifiEnums.SSID_KEY]
         failed = self.run_generated_testcases(
-            self.connect_to_wifi_network_with_password,
+            self.connect_to_wifi_network_and_run_iperf,
+            params,
+            name_func=name_gen)
+        asserts.assert_true(not failed, "Failed ones: {}".format(failed))
+
+    @acts.signals.generated_test
+    def test_config_store(self):
+        params = list(itertools.product(self.config_store_networks,
+                                        self.android_devices))
+        name_gen = lambda p: "test_connection_to-%s" % p[0][WifiEnums.SSID_KEY]
+        failed = self.run_generated_testcases(
+            self.connect_to_wifi_network_toggle_wifi_and_run_iperf,
             params,
             name_func=name_gen)
         asserts.assert_false(failed, "Failed ones: {}".format(failed))