def test_get_protocolinfo(self, get_protocolinfo_mock): """ Exercises the get_protocolinfo() method. """ # use the handy mocked protocolinfo response protocolinfo_msg = ControlMessage.from_str( '250-PROTOCOLINFO 1\r\n250 OK\r\n', 'PROTOCOLINFO') get_protocolinfo_mock.side_effect = coro_func_returning_value( protocolinfo_msg) # compare the str representation of these object, because the class # does not have, nor need, a direct comparison operator self.assertEqual(str(protocolinfo_msg), str(self.controller.get_protocolinfo())) # raise an exception in the stem.connection.get_protocolinfo() call get_protocolinfo_mock.side_effect = coro_func_raising_exc( ProtocolError) # get a default value when the call fails self.assertEqual( 'default returned', self.controller.get_protocolinfo(default='default returned')) # no default value, accept the error self.assertRaises(ProtocolError, self.controller.get_protocolinfo)
def test_get_effective_rate(self, get_conf_mock): """ Exercise the get_effective_rate() method. """ # check default if nothing was set async def get_conf_mock_side_effect(self, param, *args, **kwargs): return { 'BandwidthRate': '1073741824', 'BandwidthBurst': '1073741824', 'RelayBandwidthRate': '0', 'RelayBandwidthBurst': '0', 'MaxAdvertisedBandwidth': '1073741824', }[param] get_conf_mock.side_effect = get_conf_mock_side_effect self.assertEqual(1073741824, self.controller.get_effective_rate()) self.assertEqual(1073741824, self.controller.get_effective_rate(burst=True)) get_conf_mock.side_effect = coro_func_raising_exc( ControllerError('nope, too bad')) self.assertRaises(ControllerError, self.controller.get_effective_rate) self.assertEqual('my_default', self.controller.get_effective_rate('my_default'))
def test_get_network_status_for_ourselves(self, get_info_mock): """ Exercises the get_network_status() method for getting our own relay. """ # when there's an issue getting our fingerprint get_info_mock.side_effect = coro_func_raising_exc( ControllerError('nope, too bad')) exc_msg = 'Unable to determine our own fingerprint: nope, too bad' self.assertRaisesWith(ControllerError, exc_msg, self.controller.get_network_status) self.assertEqual('boom', self.controller.get_network_status(default='boom')) # successful request desc = NS_DESC % ('moria1', '/96bKo4soysolMgKn5Hex2nyFSY') async def get_info_mock_side_effect(self, param, **kwargs): return { 'fingerprint': '9695DFC35FFEB861329B9F1AB04C46397020CE31', 'ns/id/9695DFC35FFEB861329B9F1AB04C46397020CE31': desc, }[param] get_info_mock.side_effect = get_info_mock_side_effect self.assertEqual( stem.descriptor.router_status_entry.RouterStatusEntryV3(desc), self.controller.get_network_status())
def test_get_accounting_stats(self, get_info_mock): """ Exercises the get_accounting_stats() method. """ async def get_info_mock_side_effect(self, param, **kwargs): return { 'accounting/enabled': '1', 'accounting/hibernating': 'awake', 'accounting/interval-end': '2014-09-14 19:41:00', 'accounting/bytes': '4837 2050', 'accounting/bytes-left': '102944 7440', }[param] get_info_mock.side_effect = get_info_mock_side_effect expected = stem.control.AccountingStats( 1410723598.276578, 'awake', datetime.datetime(2014, 9, 14, 19, 41), 62, 4837, 102944, 107781, 2050, 7440, 9490, ) self.assertEqual(expected, self.controller.get_accounting_stats()) get_info_mock.side_effect = coro_func_raising_exc(ControllerError('nope, too bad')) self.assertRaises(ControllerError, self.controller.get_accounting_stats) self.assertEqual('my default', self.controller.get_accounting_stats('my default'))
def test_get_network_status_when_unavailable(self, get_info_mock): """ Exercises the get_network_status() method. """ exc = InvalidArguments(None, 'GETINFO request contained unrecognized keywords: ns/id/5AC9C5AA75BA1F18D8459B326B4B8111A856D290') get_info_mock.side_effect = coro_func_raising_exc(exc) exc_msg = "Tor was unable to provide the descriptor for '5AC9C5AA75BA1F18D8459B326B4B8111A856D290'" self.assertRaisesWith(DescriptorUnavailable, exc_msg, self.controller.get_network_status, '5AC9C5AA75BA1F18D8459B326B4B8111A856D290')
def test_auth_failure(self, authenticate_mock, stdout_mock): control_socket = stem.socket.ControlPort() authenticate_mock.side_effect = coro_func_raising_exc( stem.connection.IncorrectSocketType('unable to connect to socket')) self._assert_authenticate_fails_with( control_socket, stdout_mock, 'Please check in your torrc that 9051 is the ControlPort.') control_socket = stem.socket.ControlSocketFile() self._assert_authenticate_fails_with( control_socket, stdout_mock, 'Are you sure the interface you specified belongs to') authenticate_mock.side_effect = coro_func_raising_exc( stem.connection.UnrecognizedAuthMethods('unable to connect', ['telepathy'])) self._assert_authenticate_fails_with( control_socket, stdout_mock, 'Tor is using a type of authentication we do not recognize...\n\n telepathy' ) authenticate_mock.side_effect = coro_func_raising_exc( stem.connection.IncorrectPassword('password rejected')) self._assert_authenticate_fails_with(control_socket, stdout_mock, 'Incorrect password') authenticate_mock.side_effect = coro_func_raising_exc( stem.connection.UnreadableCookieFile('permission denied', '/tmp/my_cookie', False)) self._assert_authenticate_fails_with( control_socket, stdout_mock, "We were unable to read tor's authentication cookie...\n\n Path: /tmp/my_cookie\n Issue: permission denied" ) authenticate_mock.side_effect = coro_func_raising_exc( stem.connection.OpenAuthRejected('crazy failure')) self._assert_authenticate_fails_with( control_socket, stdout_mock, 'Unable to authenticate: crazy failure')
def test_get_version(self, get_info_mock): """ Exercises the get_version() method. """ try: # Use one version for first check. version_2_1 = '0.2.1.32' version_2_1_object = stem.version.Version(version_2_1) get_info_mock.side_effect = coro_func_returning_value(version_2_1) # Return a version with a cold cache. self.assertEqual(version_2_1_object, self.controller.get_version()) # Use a different version for second check. version_2_2 = '0.2.2.39' version_2_2_object = stem.version.Version(version_2_2) get_info_mock.side_effect = coro_func_returning_value(version_2_2) # Return a version with a hot cache, so it will be the old version. self.assertEqual(version_2_1_object, self.controller.get_version()) # Turn off caching. self.controller._is_caching_enabled = False # Return a version without caching, so it will be the new version. self.assertEqual(version_2_2_object, self.controller.get_version()) # Spec says the getinfo response may optionally be prefixed by 'Tor '. In # practice it doesn't but we should accept that. get_info_mock.side_effect = coro_func_returning_value( 'Tor 0.2.1.32') self.assertEqual(version_2_1_object, self.controller.get_version()) # Raise an exception in the get_info() call. get_info_mock.side_effect = coro_func_raising_exc(InvalidArguments) # Get a default value when the call fails. self.assertEqual( 'default returned', self.controller.get_version(default='default returned')) # No default value, accept the error. self.assertRaises(InvalidArguments, self.controller.get_version) # Give a bad version. The stem.version.Version ValueError should bubble up. version_A_42 = '0.A.42.spam' get_info_mock.side_effect = coro_func_returning_value(version_A_42) self.assertRaises(ValueError, self.controller.get_version) finally: # Turn caching back on before we leave. self.controller._is_caching_enabled = True
def test_event_listing_with_error(self): """ Raise an exception in an event listener to confirm it doesn't break our event thread. """ self.circ_listener.side_effect = coro_func_raising_exc(ValueError('boom')) self._emit_event(CIRC_EVENT) self.circ_listener.assert_called_once_with(CIRC_EVENT) self.bw_listener.assert_not_called() self.malformed_listener.assert_not_called() self._emit_event(BW_EVENT) self.bw_listener.assert_called_once_with(BW_EVENT)
def test_get_network_status(self, get_info_mock): """ Exercises the get_network_status() method. """ # build a single router status entry nickname = 'Beaver' fingerprint = '/96bKo4soysolMgKn5Hex2nyFSY' desc = NS_DESC % (nickname, fingerprint) router = stem.descriptor.router_status_entry.RouterStatusEntryV3(desc) # always return the same router status entry get_info_mock.side_effect = coro_func_returning_value(desc) # pretend to get the router status entry with its name self.assertEqual(router, self.controller.get_network_status(nickname)) # pretend to get the router status entry with its fingerprint hex_fingerprint = stem.descriptor.router_status_entry._base64_to_hex( fingerprint, False) self.assertEqual(router, self.controller.get_network_status(hex_fingerprint)) # mangle hex fingerprint and try again hex_fingerprint = hex_fingerprint[2:] self.assertRaises(ValueError, self.controller.get_network_status, hex_fingerprint) # raise an exception in the get_info() call get_info_mock.side_effect = coro_func_raising_exc(InvalidArguments) # get a default value when the call fails self.assertEqual( 'default returned', self.controller.get_network_status(nickname, default='default returned')) # no default value, accept the error self.assertRaises(InvalidArguments, self.controller.get_network_status, nickname)
def test_get_ports(self, get_conf_mock, get_info_mock): """ Exercises the get_ports() and get_listeners() methods. """ # Exercise as an old version of tor that doesn't support the 'GETINFO # net/listeners/*' options. get_info_mock.side_effect = coro_func_raising_exc(InvalidArguments) async def get_conf_mock_side_effect(self, param, *args, **kwargs): return { 'ControlPort': '9050', 'ControlListenAddress': ['127.0.0.1'], }[param] get_conf_mock.side_effect = get_conf_mock_side_effect self.assertEqual([('127.0.0.1', 9050)], self.controller.get_listeners(Listener.CONTROL)) self.assertEqual(set([9050]), self.controller.get_ports(Listener.CONTROL)) self.controller.clear_cache() # non-local addresss async def get_conf_mock_side_effect(self, param, *args, **kwargs): return { 'ControlPort': '9050', 'ControlListenAddress': ['27.4.4.1'], }[param] get_conf_mock.side_effect = get_conf_mock_side_effect self.assertEqual([('27.4.4.1', 9050)], self.controller.get_listeners(Listener.CONTROL)) self.assertEqual(set(), self.controller.get_ports(Listener.CONTROL)) self.controller.clear_cache() # exercise via the GETINFO option listeners = '"127.0.0.1:1112" "127.0.0.1:1114"' get_info_mock.side_effect = coro_func_returning_value(listeners) self.assertEqual([('127.0.0.1', 1112), ('127.0.0.1', 1114)], self.controller.get_listeners(Listener.CONTROL)) self.assertEqual(set([1112, 1114]), self.controller.get_ports(Listener.CONTROL)) self.controller.clear_cache() # with all localhost addresses, including a couple that aren't listeners = '"27.4.4.1:1113" "127.0.0.5:1114" "0.0.0.0:1115" "[::]:1116" "[::1]:1117" "[10::]:1118"' get_info_mock.side_effect = coro_func_returning_value(listeners) self.assertEqual(set([1114, 1115, 1116, 1117]), self.controller.get_ports(Listener.OR)) self.controller.clear_cache() # IPv6 address listeners = '"0.0.0.0:9001" "[fe80:0000:0000:0000:0202:b3ff:fe1e:8329]:9001"' get_info_mock.side_effect = coro_func_returning_value(listeners) self.assertEqual([('0.0.0.0', 9001), ('fe80:0000:0000:0000:0202:b3ff:fe1e:8329', 9001)], self.controller.get_listeners(Listener.CONTROL)) # unix socket file self.controller.clear_cache() get_info_mock.side_effect = coro_func_returning_value( '"unix:/tmp/tor/socket"') self.assertEqual([], self.controller.get_listeners(Listener.CONTROL)) self.assertEqual(set(), self.controller.get_ports(Listener.CONTROL))
async def test_all_use_cases(self, authenticate_safecookie_mock, authenticate_cookie_mock, authenticate_password_mock, authenticate_none_mock): """ Does basic validation that all valid use cases for the PROTOCOLINFO input and dependent functions result in either success or a AuthenticationFailed subclass being raised. """ # mute the logger for this test since otherwise the output is overwhelming stem_logger = log.get_logger() stem_logger.setLevel(log.logging_level(None)) # exceptions that the authentication functions are documented to raise all_auth_none_exc = (None, stem.connection.OpenAuthRejected(None), stem.ControllerError(None)) all_auth_password_exc = (None, stem.connection.PasswordAuthRejected(None), stem.connection.IncorrectPassword(None), stem.ControllerError(None)) all_auth_cookie_exc = (None, stem.connection.CookieAuthRejected( None, False, None), stem.connection.IncorrectCookieValue( None, False, None), stem.connection.IncorrectCookieSize( None, False, None), stem.connection.UnreadableCookieFile( None, False, None), stem.connection.AuthChallengeFailed(None, None), stem.ControllerError(None)) auth_method_combinations = test.get_all_combinations( [ stem.connection.AuthMethod.NONE, stem.connection.AuthMethod.PASSWORD, stem.connection.AuthMethod.COOKIE, stem.connection.AuthMethod.SAFECOOKIE, stem.connection.AuthMethod.UNKNOWN, ], include_empty=True) protocolinfo = ControlMessage.from_str( '250-PROTOCOLINFO 1\r\n250 OK\r\n', 'PROTOCOLINFO') protocolinfo.cookie_path = '/tmp/blah' for auth_methods in auth_method_combinations: for auth_none_exc in all_auth_none_exc: for auth_password_exc in all_auth_password_exc: for auth_cookie_exc in all_auth_cookie_exc: # Skip iteration if it's to test exceptions for authentication # we're not using. if auth_none_exc and stem.connection.AuthMethod.NONE not in auth_methods: continue elif auth_password_exc and stem.connection.AuthMethod.PASSWORD not in auth_methods: continue elif auth_cookie_exc and stem.connection.AuthMethod.COOKIE not in auth_methods and stem.connection.AuthMethod.SAFECOOKIE not in auth_methods: continue # Determine if the authenticate() call will succeed and mock each # of the authenticate_* function to raise its given exception. # # This implementation is slightly inaccurate in a couple regards... # a. it raises safecookie exceptions from authenticate_cookie() # b. exceptions raised by authenticate_cookie() and # authenticate_safecookie() are always the same # # However, adding another loop for safe_cookie exceptions means # multiplying our runtime many fold. This exercises everything that # matters so the above inaccuracies seem fine. expect_success = False protocolinfo.auth_methods = auth_methods for auth_method in auth_methods: if auth_method == stem.connection.AuthMethod.NONE: auth_mock, raised_exc = authenticate_none_mock, auth_none_exc elif auth_method == stem.connection.AuthMethod.PASSWORD: auth_mock, raised_exc = authenticate_password_mock, auth_password_exc elif auth_method == stem.connection.AuthMethod.COOKIE: auth_mock, raised_exc = authenticate_cookie_mock, auth_cookie_exc elif auth_method == stem.connection.AuthMethod.SAFECOOKIE: auth_mock, raised_exc = authenticate_safecookie_mock, auth_cookie_exc if raised_exc: auth_mock.side_effect = coro_func_raising_exc( raised_exc) else: auth_mock.side_effect = coro_func_returning_value( None) expect_success = True if expect_success: await stem.connection.authenticate( None, 'blah', None, protocolinfo) else: with self.assertRaises( stem.connection.AuthenticationFailure): await stem.connection.authenticate( None, 'blah', None, protocolinfo) # revert logging back to normal stem_logger.setLevel(log.logging_level(log.TRACE))