def do_test(self, ipv6_mode, ip6_privacy=None, auto_connect=True):
        '''Actual test code, parameterized for the particular test case'''

        self.setup_eth(ipv6_mode)
        self.start_nm(self.dev_e_client, auto_connect=auto_connect)

        ip4_method = NM.SETTING_IP4_CONFIG_METHOD_DISABLED
        ip6_method = NM.SETTING_IP6_CONFIG_METHOD_IGNORE
        if ipv6_mode is None:
            ip4_method = NM.SETTING_IP4_CONFIG_METHOD_AUTO
        else:
            ip6_method = NM.SETTING_IP6_CONFIG_METHOD_AUTO

        if auto_connect:
            # ethernet should auto-connect quickly without an existing defined connection
            self.assertEventually(
                lambda: len(self.nmclient.get_active_connections()) > 0,
                'timed out waiting for active connections',
                timeout=100)
            active_conn = self.nmclient.get_active_connections()[0]
        else:
            # auto-connection was disabled, set up manual connection
            partial_conn = NM.SimpleConnection.new()
            partial_conn.add_setting(NM.SettingIP4Config(method=ip4_method))
            if ip6_privacy is not None:
                partial_conn.add_setting(
                    NM.SettingIP6Config(ip6_privacy=ip6_privacy,
                                        method=ip6_method))

            ml = GLib.MainLoop()
            self.cb_conn = None
            self.cancel = Gio.Cancellable()
            self.timeout_tag = 0

            def add_activate_cb(client, res, data):
                if (self.timeout_tag > 0):
                    GLib.source_remove(self.timeout_tag)
                    self.timeout_tag = 0
                try:
                    self.cb_conn = \
                        self.nmclient.add_and_activate_connection_finish(res)
                except gi.repository.GLib.Error as e:
                    # Check if the error is "Operation was cancelled"
                    if (e.domain != "g-io-error-quark" or e.code != 19):
                        self.fail(
                            "add_and_activate_connection failed: %s (%s, %d)" %
                            (e.message, e.domain, e.code))
                ml.quit()

            def timeout_cb():
                self.timeout_tag = -1
                self.cancel.cancel()
                ml.quit()
                return GLib.SOURCE_REMOVE

            self.nmclient.add_and_activate_connection_async(
                partial_conn, self.nmdev_e, None, self.cancel, add_activate_cb,
                None)
            self.timeout_tag = GLib.timeout_add_seconds(300, timeout_cb)
            ml.run()
            if (self.timeout_tag < 0):
                self.timeout_tag = 0
                self.fail('Main loop for adding connection timed out!')
            self.assertNotEqual(self.cb_conn, None)
            active_conn = self.cb_conn
            self.cb_conn = None

        # we are usually ACTIVATING at this point; wait for completion
        # TODO: 5s is not enough, argh slow DHCP client
        self.assertEventually(lambda: active_conn.get_state(
        ) == NM.ActiveConnectionState.ACTIVATED,
                              'timed out waiting for %s to get activated' %
                              active_conn.get_connection(),
                              timeout=150)
        self.assertEqual(self.nmdev_e.get_state(), NM.DeviceState.ACTIVATED)

        conn = self.conn_from_active_conn(active_conn)
        self.assertTrue(conn.verify())

        # check NMActiveConnection object
        self.assertIn(
            active_conn.get_uuid(),
            [c.get_uuid() for c in self.nmclient.get_active_connections()])
        self.assertEqual([d.get_udi() for d in active_conn.get_devices()],
                         [self.nmdev_e.get_udi()])

        # for IPv6, check privacy setting
        if ipv6_mode is not None:
            assert ip6_privacy is not None, 'for IPv6 tests you need to specify ip6_privacy flag'
            if ip6_privacy not in (NM.SettingIP6ConfigPrivacy.UNKNOWN,
                                   NM.SettingIP6ConfigPrivacy.DISABLED):
                ip6_setting = conn.get_setting_ip6_config()
                self.assertEqual(ip6_setting.props.ip6_privacy, ip6_privacy)

        self.check_low_level_config(self.dev_e_client, ipv6_mode, ip6_privacy)
    def connect_to_ap(self, ap, secret, ipv6_mode, ip6_privacy):
        '''Connect to an NMAccessPoint.

        secret should be None for open networks, and a string with the password
        for WEP/WPA.

        ip6_privacy is a NM.SettingIP6ConfigPrivacy flag.

        Return (NMConnection, NMActiveConnection) objects.
        '''

        ip4_method = NM.SETTING_IP4_CONFIG_METHOD_DISABLED
        ip6_method = NM.SETTING_IP6_CONFIG_METHOD_IGNORE
        if ipv6_mode is None:
            ip4_method = NM.SETTING_IP4_CONFIG_METHOD_AUTO
        else:
            ip6_method = NM.SETTING_IP6_CONFIG_METHOD_AUTO

        # If we have a secret, supply it to the new connection right away;
        # adding it afterwards with update_secrets() does not work, and we
        # can't implement a SecretAgent as get_secrets() would need to build a
        # map of a map of gpointers to gpointers which is too much for PyGI
        partial_conn = NM.SimpleConnection.new()
        partial_conn.add_setting(NM.SettingIP4Config(method=ip4_method))
        if secret:
            partial_conn.add_setting(NM.SettingWirelessSecurity.new())
            # FIXME: needs update for other auth types
            partial_conn.update_secrets(
                NM.SETTING_WIRELESS_SECURITY_SETTING_NAME,
                GLib.Variant('a{sv}', {'psk': GLib.Variant('s', secret)}))
        if ip6_privacy is not None:
            partial_conn.add_setting(
                NM.SettingIP6Config(ip6_privacy=ip6_privacy,
                                    method=ip6_method))

        ml = GLib.MainLoop()
        self.cb_conn = None
        self.cancel = Gio.Cancellable()
        self.timeout_tag = 0

        def add_activate_cb(client, res, data):
            if (self.timeout_tag > 0):
                GLib.source_remove(self.timeout_tag)
                self.timeout_tag = 0
            try:
                self.cb_conn = \
                    self.nmclient.add_and_activate_connection_finish(res)
            except gi.repository.GLib.Error as e:
                # Check if the error is "Operation was cancelled"
                if (e.domain != "g-io-error-quark" or e.code != 19):
                    self.fail(
                        "add_and_activate_connection failed: %s (%s, %d)" %
                        (e.message, e.domain, e.code))
            ml.quit()

        def timeout_cb():
            self.timeout_tag = -1
            self.cancel.cancel()
            ml.quit()
            return GLib.SOURCE_REMOVE

        self.nmclient.add_and_activate_connection_async(
            partial_conn, self.nmdev_w, ap.get_path(), self.cancel,
            add_activate_cb, None)
        self.timeout_tag = GLib.timeout_add_seconds(300, timeout_cb)
        ml.run()
        if (self.timeout_tag < 0):
            self.timeout_tag = 0
            self.fail('Main loop for adding connection timed out!')
        self.assertNotEqual(self.cb_conn, None)
        active_conn = self.cb_conn
        self.cb_conn = None

        conn = self.conn_from_active_conn(active_conn)
        self.assertTrue(conn.verify())

        # verify need_secrets()
        needed_secrets = conn.need_secrets()
        if secret is None:
            self.assertEqual(needed_secrets, (None, []))
        else:
            self.assertEqual(needed_secrets[0],
                             NM.SETTING_WIRELESS_SECURITY_SETTING_NAME)
            self.assertEqual(type(needed_secrets[1]), list)
            self.assertGreaterEqual(len(needed_secrets[1]), 1)
            # FIXME: needs update for other auth types
            self.assertIn(needed_secrets[1][0],
                          [NM.SETTING_WIRELESS_SECURITY_PSK])

        # we are usually ACTIVATING at this point; wait for completion
        # TODO: 5s is not enough, argh slow DHCP client
        self.assertEventually(lambda: active_conn.get_state(
        ) == NM.ActiveConnectionState.ACTIVATED,
                              'timed out waiting for %s to get activated' %
                              active_conn.get_connection(),
                              timeout=600)
        self.assertEqual(self.nmdev_w.get_state(), NM.DeviceState.ACTIVATED)
        return (conn, active_conn)