[tor-commits] [stem/master] Move test requirements to its own module

atagar at torproject.org atagar at torproject.org
Mon May 22 18:30:29 UTC 2017


commit a29627184ea7d50dc4072dfc4115e68372c52d5f
Author: Damian Johnson <atagar at torproject.org>
Date:   Thu May 18 15:35:32 2017 -0700

    Move test requirements to its own module
    
    Moving the @require_* annotations to its own dedicated module. Besides the
    obvious breakup of test.util this provides namespacing so we can truncate the
    function names and use a standard import.
---
 test/integ/connection/authentication.py           |  25 ++--
 test/integ/connection/connect.py                  |   9 +-
 test/integ/control/base_controller.py             |  20 ++-
 test/integ/control/controller.py                  | 153 +++++++++++-----------
 test/integ/descriptor/extrainfo_descriptor.py     |   8 +-
 test/integ/descriptor/microdescriptor.py          |   8 +-
 test/integ/descriptor/networkstatus.py            |  18 +--
 test/integ/descriptor/remote.py                   |  46 +++----
 test/integ/descriptor/server_descriptor.py        |   9 +-
 test/integ/installation.py                        |   8 +-
 test/integ/interpreter.py                         |   8 +-
 test/integ/process.py                             |  37 +++---
 test/integ/response/protocolinfo.py               |  11 +-
 test/integ/socket/control_message.py              |  21 ++-
 test/integ/socket/control_socket.py               |  17 +--
 test/integ/util/connection.py                     |   4 +-
 test/integ/util/proc.py                           |  20 ++-
 test/integ/util/system.py                         |  64 ++++-----
 test/integ/version.py                             |  10 +-
 test/require.py                                   | 106 +++++++++++++++
 test/unit/descriptor/certificate.py               |   8 +-
 test/unit/descriptor/hidden_service_descriptor.py |   7 +-
 test/unit/manual.py                               |   9 +-
 test/util.py                                      | 101 +-------------
 24 files changed, 341 insertions(+), 386 deletions(-)

diff --git a/test/integ/connection/authentication.py b/test/integ/connection/authentication.py
index 007042f..f43a61d 100644
--- a/test/integ/connection/authentication.py
+++ b/test/integ/connection/authentication.py
@@ -9,9 +9,10 @@ import unittest
 import stem.connection
 import stem.socket
 import stem.version
+import test.require
 import test.runner
 
-from test.util import require_controller, tor_version
+from test.util import tor_version
 
 # Responses given by tor for various authentication failures. These may change
 # in the future and if they do then this test should be updated.
@@ -106,7 +107,7 @@ class TestAuthenticate(unittest.TestCase):
     if tor_version() >= stem.version.Requirement.AUTH_SAFECOOKIE:
       self.cookie_auth_methods.append(stem.connection.AuthMethod.SAFECOOKIE)
 
-  @require_controller
+  @test.require.controller
   def test_authenticate_general_socket(self):
     """
     Tests that the authenticate function can authenticate to our socket.
@@ -118,7 +119,7 @@ class TestAuthenticate(unittest.TestCase):
       stem.connection.authenticate(control_socket, test.runner.CONTROL_PASSWORD, runner.get_chroot())
       test.runner.exercise_controller(self, control_socket)
 
-  @require_controller
+  @test.require.controller
   def test_authenticate_general_controller(self):
     """
     Tests that the authenticate function can authenticate via a Controller.
@@ -130,7 +131,7 @@ class TestAuthenticate(unittest.TestCase):
       stem.connection.authenticate(controller, test.runner.CONTROL_PASSWORD, runner.get_chroot())
       test.runner.exercise_controller(self, controller)
 
-  @require_controller
+  @test.require.controller
   def test_authenticate_general_example(self):
     """
     Tests the authenticate function with something like its pydoc example.
@@ -166,7 +167,7 @@ class TestAuthenticate(unittest.TestCase):
     finally:
       control_socket.close()
 
-  @require_controller
+  @test.require.controller
   def test_authenticate_general_password(self):
     """
     Tests the authenticate function's password argument.
@@ -201,7 +202,7 @@ class TestAuthenticate(unittest.TestCase):
       stem.connection.authenticate(control_socket, test.runner.CONTROL_PASSWORD, runner.get_chroot())
       test.runner.exercise_controller(self, control_socket)
 
-  @require_controller
+  @test.require.controller
   def test_authenticate_general_cookie(self):
     """
     Tests the authenticate function with only cookie authentication methods.
@@ -226,7 +227,7 @@ class TestAuthenticate(unittest.TestCase):
             protocolinfo_response.auth_methods = (method, )
             stem.connection.authenticate(control_socket, chroot_path = runner.get_chroot(), protocolinfo_response = protocolinfo_response)
 
-  @require_controller
+  @test.require.controller
   def test_authenticate_none(self):
     """
     Tests the authenticate_none function.
@@ -239,7 +240,7 @@ class TestAuthenticate(unittest.TestCase):
     else:
       self.assertRaises(stem.connection.OpenAuthRejected, self._check_auth, auth_type)
 
-  @require_controller
+  @test.require.controller
   def test_authenticate_password(self):
     """
     Tests the authenticate_password function.
@@ -267,7 +268,7 @@ class TestAuthenticate(unittest.TestCase):
 
         self.assertRaises(exc_type, self._check_auth, auth_type, auth_value)
 
-  @require_controller
+  @test.require.controller
   def test_authenticate_cookie(self):
     """
     Tests the authenticate_cookie function.
@@ -289,7 +290,7 @@ class TestAuthenticate(unittest.TestCase):
       else:
         self.assertRaises(stem.connection.CookieAuthRejected, self._check_auth, auth_type, auth_value, False)
 
-  @require_controller
+  @test.require.controller
   def test_authenticate_cookie_invalid(self):
     """
     Tests the authenticate_cookie function with a properly sized but incorrect
@@ -326,7 +327,7 @@ class TestAuthenticate(unittest.TestCase):
 
     os.remove(auth_value)
 
-  @require_controller
+  @test.require.controller
   def test_authenticate_cookie_missing(self):
     """
     Tests the authenticate_cookie function with a path that really, really
@@ -337,7 +338,7 @@ class TestAuthenticate(unittest.TestCase):
       auth_value = "/if/this/exists/then/they're/asking/for/a/failure"
       self.assertRaises(stem.connection.UnreadableCookieFile, self._check_auth, auth_type, auth_value, False)
 
-  @require_controller
+  @test.require.controller
   def test_authenticate_cookie_wrong_size(self):
     """
     Tests the authenticate_cookie function with our torrc as an auth cookie.
diff --git a/test/integ/connection/connect.py b/test/integ/connection/connect.py
index d50b3af..fd08742 100644
--- a/test/integ/connection/connect.py
+++ b/test/integ/connection/connect.py
@@ -11,10 +11,9 @@ except ImportError:
   from io import StringIO
 
 import stem.connection
+import test.require
 import test.runner
 
-from test.util import require_controller
-
 
 class TestConnect(unittest.TestCase):
   def setUp(self):
@@ -25,7 +24,7 @@ class TestConnect(unittest.TestCase):
   def tearDown(self):
     sys.stdout = self.original_stdout
 
-  @require_controller
+  @test.require.controller
   def test_connect(self):
     """
     Basic sanity checks for the connect function.
@@ -42,7 +41,7 @@ class TestConnect(unittest.TestCase):
 
     test.runner.exercise_controller(self, control_socket)
 
-  @require_controller
+  @test.require.controller
   def test_connect_port(self):
     """
     Basic sanity checks for the connect_port function.
@@ -62,7 +61,7 @@ class TestConnect(unittest.TestCase):
     else:
       self.assertEqual(control_socket, None)
 
-  @require_controller
+  @test.require.controller
   def test_connect_socket_file(self):
     """
     Basic sanity checks for the connect_socket_file function.
diff --git a/test/integ/control/base_controller.py b/test/integ/control/base_controller.py
index 1e64445..4c618e3 100644
--- a/test/integ/control/base_controller.py
+++ b/test/integ/control/base_controller.py
@@ -10,12 +10,10 @@ import unittest
 import stem.control
 import stem.socket
 import stem.util.system
-
+import test.require
 import test.runner
 import test.util
 
-from test.util import require_controller
-
 
 class StateObserver(object):
   """
@@ -39,7 +37,7 @@ class StateObserver(object):
 
 
 class TestBaseController(unittest.TestCase):
-  @require_controller
+  @test.require.controller
   def test_connect_repeatedly(self):
     """
     Connects and closes the socket repeatedly. This is a simple attempt to
@@ -57,7 +55,7 @@ class TestBaseController(unittest.TestCase):
         controller.connect()
         controller.close()
 
-  @require_controller
+  @test.require.controller
   def test_msg(self):
     """
     Tests a basic query with the msg() method.
@@ -67,7 +65,7 @@ class TestBaseController(unittest.TestCase):
       controller = stem.control.BaseController(control_socket)
       test.runner.exercise_controller(self, controller)
 
-  @require_controller
+  @test.require.controller
   def test_msg_invalid(self):
     """
     Tests the msg() method against an invalid controller command.
@@ -78,7 +76,7 @@ class TestBaseController(unittest.TestCase):
       response = controller.msg('invalid')
       self.assertEqual('Unrecognized command "invalid"', str(response))
 
-  @require_controller
+  @test.require.controller
   def test_msg_invalid_getinfo(self):
     """
     Tests the msg() method against a non-existant GETINFO option.
@@ -89,7 +87,7 @@ class TestBaseController(unittest.TestCase):
       response = controller.msg('GETINFO blarg')
       self.assertEqual('Unrecognized key "blarg"', str(response))
 
-  @require_controller
+  @test.require.controller
   def test_msg_repeatedly(self):
     """
     Connects, sends a burst of messages, and closes the socket repeatedly. This
@@ -127,7 +125,7 @@ class TestBaseController(unittest.TestCase):
       for msg_thread in message_threads:
         msg_thread.join()
 
-  @require_controller
+  @test.require.controller
   def test_asynchronous_event_handling(self):
     """
     Check that we can both receive asynchronous events while hammering our
@@ -181,7 +179,7 @@ class TestBaseController(unittest.TestCase):
         self.assertTrue(conf_changed_event.raw_content().startswith('650-CONF_CHANGED\r\n650-NodeFamily='))
         self.assertEqual(('650', '-'), conf_changed_event.content()[0][:2])
 
-  @require_controller
+  @test.require.controller
   def test_get_latest_heartbeat(self):
     """
     Basic check for get_latest_heartbeat().
@@ -193,7 +191,7 @@ class TestBaseController(unittest.TestCase):
       controller.msg('GETINFO version')
       self.assertTrue((time.time() - controller.get_latest_heartbeat()) < 5)
 
-  @require_controller
+  @test.require.controller
   def test_status_notifications(self):
     """
     Checks basic functionality of the add_status_listener() and
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 2445221..32ce047 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -19,6 +19,7 @@ import stem.socket
 import stem.util.str_tools
 import stem.version
 import test.network
+import test.require
 import test.runner
 
 from stem import Flag, Signal
@@ -26,15 +27,7 @@ from stem.control import EventType, Listener, State
 from stem.exit_policy import ExitPolicy
 from stem.version import Requirement
 
-from test.util import (
-  register_new_capability,
-  random_fingerprint,
-  tor_version,
-  only_run_once,
-  require_controller,
-  require_version,
-  require_online,
-)
+from test.util import register_new_capability, random_fingerprint, tor_version
 
 # Router status entry for a relay with a nickname other than 'Unnamed'. This is
 # used for a few tests that need to look up a relay.
@@ -43,8 +36,8 @@ TEST_ROUTER_STATUS_ENTRY = None
 
 
 class TestController(unittest.TestCase):
-  @only_run_once
-  @require_controller
+  @test.require.only_run_once
+  @test.require.controller
   def test_missing_capabilities(self):
     """
     Check to see if tor supports any events, signals, or features that we
@@ -88,8 +81,8 @@ class TestController(unittest.TestCase):
     else:
       self.assertRaises(stem.SocketError, stem.control.Controller.from_socket_file, test.runner.CONTROL_SOCKET_PATH)
 
-  @require_controller
-  @require_version(Requirement.EVENT_SIGNAL)
+  @test.require.controller
+  @test.require.version(Requirement.EVENT_SIGNAL)
   def test_reset_notification(self):
     """
     Checks that a notificiation listener is... well, notified of SIGHUPs.
@@ -125,7 +118,7 @@ class TestController(unittest.TestCase):
 
       controller.reset_conf('__OwningControllerProcess')
 
-  @require_controller
+  @test.require.controller
   def test_event_handling(self):
     """
     Add a couple listeners for various events and make sure that they receive
@@ -183,7 +176,7 @@ class TestController(unittest.TestCase):
 
       controller.reset_conf('NodeFamily')
 
-  @require_controller
+  @test.require.controller
   def test_reattaching_listeners(self):
     """
     Checks that event listeners are re-attached when a controller disconnects
@@ -224,7 +217,7 @@ class TestController(unittest.TestCase):
 
       controller.reset_conf('NodeFamily')
 
-  @require_controller
+  @test.require.controller
   def test_getinfo(self):
     """
     Exercises GETINFO with valid and invalid queries.
@@ -262,7 +255,7 @@ class TestController(unittest.TestCase):
       self.assertEqual({}, controller.get_info([]))
       self.assertEqual({}, controller.get_info([], {}))
 
-  @require_controller
+  @test.require.controller
   def test_get_version(self):
     """
     Test that the convenient method get_version() works.
@@ -275,7 +268,7 @@ class TestController(unittest.TestCase):
       self.assertTrue(isinstance(version, stem.version.Version))
       self.assertEqual(version, tor_version())
 
-  @require_controller
+  @test.require.controller
   def test_get_exit_policy(self):
     """
     Sanity test for get_exit_policy(). We have the default policy (no
@@ -318,7 +311,7 @@ class TestController(unittest.TestCase):
       policy_str = policy_str[:public_addr_start] + policy_str[public_addr_end:]
       self.assertEqual(str(expected), policy_str)
 
-  @require_controller
+  @test.require.controller
   def test_authenticate(self):
     """
     Test that the convenient method authenticate() works.
@@ -330,7 +323,7 @@ class TestController(unittest.TestCase):
       controller.authenticate(test.runner.CONTROL_PASSWORD)
       test.runner.exercise_controller(self, controller)
 
-  @require_controller
+  @test.require.controller
   def test_protocolinfo(self):
     """
     Test that the convenient method protocolinfo() works.
@@ -360,7 +353,7 @@ class TestController(unittest.TestCase):
 
       self.assertEqual(tuple(auth_methods), protocolinfo.auth_methods)
 
-  @require_controller
+  @test.require.controller
   def test_getconf(self):
     """
     Exercises GETCONF with valid and invalid queries.
@@ -423,7 +416,7 @@ class TestController(unittest.TestCase):
       self.assertEqual({}, controller.get_conf_map('', 'la-di-dah'))
       self.assertEqual({}, controller.get_conf_map([], 'la-di-dah'))
 
-  @require_controller
+  @test.require.controller
   def test_is_set(self):
     """
     Exercises our is_set() method.
@@ -450,7 +443,7 @@ class TestController(unittest.TestCase):
       controller.reset_conf('ConnLimit')
       self.assertFalse(controller.is_set('ConnLimit'))
 
-  @require_controller
+  @test.require.controller
   def test_hidden_services_conf(self):
     """
     Exercises the hidden service family of methods (get_hidden_service_conf,
@@ -557,8 +550,8 @@ class TestController(unittest.TestCase):
           except:
             pass
 
-  @require_controller
-  @require_version(Requirement.ADD_ONION)
+  @test.require.controller
+  @test.require.version(Requirement.ADD_ONION)
   def test_without_ephemeral_hidden_services(self):
     """
     Exercises ephemeral hidden service methods when none are present.
@@ -569,8 +562,8 @@ class TestController(unittest.TestCase):
       self.assertEqual([], controller.list_ephemeral_hidden_services(detached = True))
       self.assertEqual(False, controller.remove_ephemeral_hidden_service('gfzprpioee3hoppz'))
 
-  @require_controller
-  @require_version(Requirement.ADD_ONION)
+  @test.require.controller
+  @test.require.version(Requirement.ADD_ONION)
   def test_with_ephemeral_hidden_services(self):
     """
     Exercises creating ephemeral hidden services and methods when they're
@@ -620,8 +613,8 @@ class TestController(unittest.TestCase):
         self.assertEqual(2, len(controller.list_ephemeral_hidden_services()))
         self.assertEqual(0, len(second_controller.list_ephemeral_hidden_services()))
 
-  @require_controller
-  @require_version(Requirement.ADD_ONION_BASIC_AUTH)
+  @test.require.controller
+  @test.require.version(Requirement.ADD_ONION_BASIC_AUTH)
   def test_with_ephemeral_hidden_services_basic_auth(self):
     """
     Exercises creating ephemeral hidden services that uses basic authentication.
@@ -640,8 +633,8 @@ class TestController(unittest.TestCase):
       self.assertEqual(True, controller.remove_ephemeral_hidden_service(response.service_id))
       self.assertEqual([], controller.list_ephemeral_hidden_services())
 
-  @require_controller
-  @require_version(Requirement.ADD_ONION_BASIC_AUTH)
+  @test.require.controller
+  @test.require.version(Requirement.ADD_ONION_BASIC_AUTH)
   def test_with_ephemeral_hidden_services_basic_auth_no_credentials(self):
     """
     Exercises creating ephemeral hidden services when attempting to use basic
@@ -654,8 +647,8 @@ class TestController(unittest.TestCase):
       exc_msg = "ADD_ONION response didn't have an OK status: No auth clients specified"
       self.assertRaisesRegexp(stem.ProtocolError, exc_msg, controller.create_ephemeral_hidden_service, 4567, basic_auth = {})
 
-  @require_controller
-  @require_version(Requirement.ADD_ONION)
+  @test.require.controller
+  @test.require.version(Requirement.ADD_ONION)
   def test_with_detached_ephemeral_hidden_services(self):
     """
     Exercises creating detached ephemeral hidden services and methods when
@@ -690,7 +683,7 @@ class TestController(unittest.TestCase):
       self.assertEqual([response.service_id], controller.list_ephemeral_hidden_services(detached = True))
       controller.remove_ephemeral_hidden_service(response.service_id)
 
-  @require_controller
+  @test.require.controller
   def test_set_conf(self):
     """
     Exercises set_conf(), reset_conf(), and set_options() methods with valid
@@ -763,8 +756,8 @@ class TestController(unittest.TestCase):
 
         shutil.rmtree(tmpdir)
 
-  @require_controller
-  @require_version(Requirement.LOADCONF)
+  @test.require.controller
+  @test.require.version(Requirement.LOADCONF)
   def test_loadconf(self):
     """
     Exercises Controller.load_conf with valid and invalid requests.
@@ -801,7 +794,7 @@ class TestController(unittest.TestCase):
         controller.load_conf(oldconf)
         controller.reset_conf('__OwningControllerProcess')
 
-  @require_controller
+  @test.require.controller
   def test_saveconf(self):
     runner = test.runner.get_runner()
 
@@ -821,7 +814,7 @@ class TestController(unittest.TestCase):
         controller.save_conf()
         controller.reset_conf('__OwningControllerProcess')
 
-  @require_controller
+  @test.require.controller
   def test_get_ports(self):
     """
     Test Controller.get_ports against a running tor instance.
@@ -842,7 +835,7 @@ class TestController(unittest.TestCase):
       else:
         self.assertEqual([], controller.get_ports(Listener.CONTROL))
 
-  @require_controller
+  @test.require.controller
   def test_get_listeners(self):
     """
     Test Controller.get_listeners against a running tor instance.
@@ -863,7 +856,7 @@ class TestController(unittest.TestCase):
       else:
         self.assertEqual([], controller.get_listeners(Listener.CONTROL))
 
-  @require_controller
+  @test.require.controller
   def test_get_socks_listeners(self):
     """
     Test Controller.get_socks_listeners against a running tor instance.
@@ -872,9 +865,9 @@ class TestController(unittest.TestCase):
     with test.runner.get_runner().get_tor_controller() as controller:
       self.assertEqual([('127.0.0.1', 1112)], controller.get_socks_listeners())
 
-  @require_controller
-  @require_online
-  @require_version(stem.version.Version('0.1.2.2-alpha'))
+  @test.require.controller
+  @test.require.online
+  @test.require.version(stem.version.Version('0.1.2.2-alpha'))
   def test_enable_feature(self):
     """
     Test Controller.enable_feature with valid and invalid inputs.
@@ -895,7 +888,7 @@ class TestController(unittest.TestCase):
       else:
         self.fail()
 
-  @require_controller
+  @test.require.controller
   def test_signal(self):
     """
     Test controller.signal with valid and invalid signals.
@@ -908,7 +901,7 @@ class TestController(unittest.TestCase):
       # invalid signals
       self.assertRaises(stem.InvalidArguments, controller.signal, 'FOOBAR')
 
-  @require_controller
+  @test.require.controller
   def test_newnym_availability(self):
     """
     Test the is_newnym_available and get_newnym_wait methods.
@@ -923,9 +916,9 @@ class TestController(unittest.TestCase):
       self.assertEqual(False, controller.is_newnym_available())
       self.assertTrue(controller.get_newnym_wait() > 9.0)
 
-  @require_controller
-  @require_online
-  @require_version(Requirement.EXTENDCIRCUIT_PATH_OPTIONAL)
+  @test.require.controller
+  @test.require.online
+  @test.require.version(Requirement.EXTENDCIRCUIT_PATH_OPTIONAL)
   def test_extendcircuit(self):
     with test.runner.get_runner().get_tor_controller() as controller:
       circuit_id = controller.extend_circuit('0')
@@ -939,9 +932,9 @@ class TestController(unittest.TestCase):
       self.assertRaises(stem.InvalidRequest, controller.extend_circuit, '0', 'thisroutershouldntexistbecausestemexists!@##$%#')
       self.assertRaises(stem.InvalidRequest, controller.extend_circuit, '0', 'thisroutershouldntexistbecausestemexists!@##$%#', 'foo')
 
-  @require_controller
-  @require_online
-  @require_version(Requirement.EXTENDCIRCUIT_PATH_OPTIONAL)
+  @test.require.controller
+  @test.require.online
+  @test.require.version(Requirement.EXTENDCIRCUIT_PATH_OPTIONAL)
   def test_repurpose_circuit(self):
     """
     Tests Controller.repurpose_circuit with valid and invalid input.
@@ -962,9 +955,9 @@ class TestController(unittest.TestCase):
       self.assertRaises(stem.InvalidRequest, controller.repurpose_circuit, 'f934h9f3h4', 'fooo')
       self.assertRaises(stem.InvalidRequest, controller.repurpose_circuit, '4', 'fooo')
 
-  @require_controller
-  @require_online
-  @require_version(Requirement.EXTENDCIRCUIT_PATH_OPTIONAL)
+  @test.require.controller
+  @test.require.online
+  @test.require.version(Requirement.EXTENDCIRCUIT_PATH_OPTIONAL)
   def test_close_circuit(self):
     """
     Tests Controller.close_circuit with valid and invalid input.
@@ -989,8 +982,8 @@ class TestController(unittest.TestCase):
       self.assertRaises(stem.InvalidArguments, controller.close_circuit, circuit_id + '1024')
       self.assertRaises(stem.InvalidRequest, controller.close_circuit, '')
 
-  @require_controller
-  @require_online
+  @test.require.controller
+  @test.require.online
   def test_get_streams(self):
     """
     Tests Controller.get_streams().
@@ -1014,8 +1007,8 @@ class TestController(unittest.TestCase):
 
     self.assertTrue('%s:%s' % (host, port) in [stream.target for stream in streams])
 
-  @require_controller
-  @require_online
+  @test.require.controller
+  @test.require.online
   def test_close_stream(self):
     """
     Tests Controller.close_stream with valid and invalid input.
@@ -1053,8 +1046,8 @@ class TestController(unittest.TestCase):
 
       self.assertRaises(stem.InvalidArguments, controller.close_stream, 'blarg')
 
-  @require_controller
-  @require_online
+  @test.require.controller
+  @test.require.online
   def test_mapaddress(self):
     runner = test.runner.get_runner()
 
@@ -1091,9 +1084,9 @@ class TestController(unittest.TestCase):
       ip_addr = response[response.find(b'\r\n\r\n'):].strip()
       self.assertTrue(stem.util.connection.is_valid_ipv4_address(stem.util.str_tools._to_unicode(ip_addr)), "'%s' isn't an address" % ip_addr)
 
-  @require_controller
-  @require_online
-  @require_version(Requirement.MICRODESCRIPTOR_IS_DEFAULT)
+  @test.require.controller
+  @test.require.online
+  @test.require.version(Requirement.MICRODESCRIPTOR_IS_DEFAULT)
   def test_get_microdescriptor(self):
     """
     Basic checks for get_microdescriptor().
@@ -1116,7 +1109,7 @@ class TestController(unittest.TestCase):
 
       self.assertEqual(md_by_fingerprint, md_by_nickname)
 
-  @require_controller
+  @test.require.controller
   def test_get_microdescriptors(self):
     """
     Fetches a few descriptors via the get_microdescriptors() method.
@@ -1138,7 +1131,7 @@ class TestController(unittest.TestCase):
         if count > 10:
           break
 
-  @require_controller
+  @test.require.controller
   def test_get_server_descriptor(self):
     """
     Basic checks for get_server_descriptor().
@@ -1168,7 +1161,7 @@ class TestController(unittest.TestCase):
 
       self.assertEqual(desc_by_fingerprint, desc_by_nickname)
 
-  @require_controller
+  @test.require.controller
   def test_get_server_descriptors(self):
     """
     Fetches a few descriptors via the get_server_descriptors() method.
@@ -1195,8 +1188,8 @@ class TestController(unittest.TestCase):
         if count > 10:
           break
 
-  @require_controller
-  @require_online
+  @test.require.controller
+  @test.require.online
   def test_get_network_status(self):
     """
     Basic checks for get_network_status().
@@ -1219,8 +1212,8 @@ class TestController(unittest.TestCase):
 
       self.assertEqual(desc_by_fingerprint, desc_by_nickname)
 
-  @require_controller
-  @require_online
+  @test.require.controller
+  @test.require.online
   def test_get_network_statuses(self):
     """
     Fetches a few descriptors via the get_network_statuses() method.
@@ -1242,9 +1235,9 @@ class TestController(unittest.TestCase):
         if count > 10:
           break
 
-  @require_controller
-  @require_online
-  @require_version(Requirement.HSFETCH)
+  @test.require.controller
+  @test.require.online
+  @test.require.version(Requirement.HSFETCH)
   def test_get_hidden_service_descriptor(self):
     """
     Fetches a few descriptors via the get_hidden_service_descriptor() method.
@@ -1268,9 +1261,9 @@ class TestController(unittest.TestCase):
       self.assertEqual('pop goes the weasel', controller.get_hidden_service_descriptor('m4cfuk6qp4lpu2g5', 'pop goes the weasel'))
       self.assertEqual(None, controller.get_hidden_service_descriptor('m4cfuk6qp4lpu2g5', await_result = False))
 
-  @require_controller
-  @require_online
-  @require_version(Requirement.EXTENDCIRCUIT_PATH_OPTIONAL)
+  @test.require.controller
+  @test.require.online
+  @test.require.version(Requirement.EXTENDCIRCUIT_PATH_OPTIONAL)
   def test_attachstream(self):
     host = socket.gethostbyname('www.torproject.org')
     port = 80
@@ -1309,9 +1302,9 @@ class TestController(unittest.TestCase):
 
     self.assertEqual(our_stream.circ_id, circuit_id)
 
-  @require_controller
-  @require_online
-  @require_version(Requirement.EXTENDCIRCUIT_PATH_OPTIONAL)
+  @test.require.controller
+  @test.require.online
+  @test.require.version(Requirement.EXTENDCIRCUIT_PATH_OPTIONAL)
   def test_get_circuits(self):
     """
     Fetches circuits via the get_circuits() method.
@@ -1322,7 +1315,7 @@ class TestController(unittest.TestCase):
       circuits = controller.get_circuits()
       self.assertTrue(new_circ in [circ.id for circ in circuits])
 
-  @require_controller
+  @test.require.controller
   def test_transition_to_relay(self):
     """
     Transitions Tor to turn into a relay, then back to a client. This helps to
diff --git a/test/integ/descriptor/extrainfo_descriptor.py b/test/integ/descriptor/extrainfo_descriptor.py
index 6ea2b16..205b917 100644
--- a/test/integ/descriptor/extrainfo_descriptor.py
+++ b/test/integ/descriptor/extrainfo_descriptor.py
@@ -6,16 +6,14 @@ import os
 import unittest
 
 import stem.descriptor
+import test.require
 import test.runner
 
-from test.util import (
-  register_new_capability,
-  only_run_once,
-)
+from test.util import register_new_capability
 
 
 class TestExtraInfoDescriptor(unittest.TestCase):
-  @only_run_once
+  @test.require.only_run_once
   def test_cached_descriptor(self):
     """
     Parses the cached descriptor file in our data directory, checking that it
diff --git a/test/integ/descriptor/microdescriptor.py b/test/integ/descriptor/microdescriptor.py
index 97127d6..c6fa80e 100644
--- a/test/integ/descriptor/microdescriptor.py
+++ b/test/integ/descriptor/microdescriptor.py
@@ -6,16 +6,14 @@ import os
 import unittest
 
 import stem.descriptor
+import test.require
 import test.runner
 
-from test.util import (
-  register_new_capability,
-  only_run_once,
-)
+from test.util import register_new_capability
 
 
 class TestMicrodescriptor(unittest.TestCase):
-  @only_run_once
+  @test.require.only_run_once
   def test_cached_microdescriptors(self):
     """
     Parses the cached microdescriptor file in our data directory, checking that
diff --git a/test/integ/descriptor/networkstatus.py b/test/integ/descriptor/networkstatus.py
index 59dca00..96fec77 100644
--- a/test/integ/descriptor/networkstatus.py
+++ b/test/integ/descriptor/networkstatus.py
@@ -9,20 +9,16 @@ import stem
 import stem.descriptor
 import stem.descriptor.remote
 import stem.version
+import test.require
 import test.runner
 
-from test.util import (
-  register_new_capability,
-  only_run_once,
-  require_cryptography,
-  require_online,
-)
+from test.util import register_new_capability
 
 
 class TestNetworkStatus(unittest.TestCase):
-  @require_online
-  @require_cryptography
-  @only_run_once
+  @test.require.only_run_once
+  @test.require.online
+  @test.require.cryptography
   def test_signature_validation(self):
     """
     The full consensus is pretty sizable so rather than storing a copy of it
@@ -31,7 +27,7 @@ class TestNetworkStatus(unittest.TestCase):
 
     stem.descriptor.remote.get_consensus(document_handler = stem.descriptor.DocumentHandler.DOCUMENT, validate = True).run()
 
-  @only_run_once
+  @test.require.only_run_once
   def test_cached_consensus(self):
     """
     Parses the cached-consensus file in our data directory.
@@ -68,7 +64,7 @@ class TestNetworkStatus(unittest.TestCase):
 
     self.assertTrue(count > 100)
 
-  @only_run_once
+  @test.require.only_run_once
   def test_cached_microdesc_consensus(self):
     """
     Parses the cached-microdesc-consensus file in our data directory.
diff --git a/test/integ/descriptor/remote.py b/test/integ/descriptor/remote.py
index b873b96..3123ef0 100644
--- a/test/integ/descriptor/remote.py
+++ b/test/integ/descriptor/remote.py
@@ -10,16 +10,12 @@ import stem.descriptor.networkstatus
 import stem.descriptor.remote
 import stem.descriptor.router_status_entry
 import stem.descriptor.server_descriptor
-
-from test.util import (
-  only_run_once,
-  require_online,
-)
+import test.require
 
 
 class TestDescriptorDownloader(unittest.TestCase):
-  @require_online
-  @only_run_once
+  @test.require.only_run_once
+  @test.require.online
   def test_shorthand_aliases(self):
     """
     Quick sanity test that we can call our shorthand aliases for getting
@@ -35,8 +31,8 @@ class TestDescriptorDownloader(unittest.TestCase):
     consensus = list(stem.descriptor.remote.get_consensus())
     self.assertTrue(len(consensus) > 50)
 
-  @require_online
-  @only_run_once
+  @test.require.only_run_once
+  @test.require.online
   def test_authorities_are_up_to_date(self):
     """
     Check that our hardcoded directory authority data matches the present
@@ -62,8 +58,8 @@ class TestDescriptorDownloader(unittest.TestCase):
         if getattr(auth, attr) != getattr(stem_auth, attr):
           self.fail('%s has %s %s, but we expected %s' % (auth.nickname, attr, getattr(auth, attr), getattr(stem_auth, attr)))
 
-  @require_online
-  @only_run_once
+  @test.require.only_run_once
+  @test.require.online
   def test_using_authorities(self):
     """
     Fetches a descriptor from each of the directory authorities. This is
@@ -94,8 +90,8 @@ class TestDescriptorDownloader(unittest.TestCase):
       self.assertEqual(1, len(descriptors))
       self.assertEqual('moria1', descriptors[0].nickname)
 
-  @require_online
-  @only_run_once
+  @test.require.only_run_once
+  @test.require.online
   def test_use_directory_mirrors(self):
     """
     Checks that we can fetch and use a list of directory mirrors.
@@ -105,8 +101,8 @@ class TestDescriptorDownloader(unittest.TestCase):
     downloader.use_directory_mirrors()
     self.assertTrue(len(downloader._endpoints) > 50)
 
-  @require_online
-  @only_run_once
+  @test.require.only_run_once
+  @test.require.online
   def test_get_server_descriptors(self):
     """
     Exercises the downloader's get_server_descriptors() method.
@@ -138,8 +134,8 @@ class TestDescriptorDownloader(unittest.TestCase):
 
     self.assertEqual(2, len(list(multiple_query)))
 
-  @require_online
-  @only_run_once
+  @test.require.only_run_once
+  @test.require.online
   def test_get_extrainfo_descriptors(self):
     """
     Exercises the downloader's get_extrainfo_descriptors() method.
@@ -164,8 +160,8 @@ class TestDescriptorDownloader(unittest.TestCase):
 
     self.assertEqual(2, len(list(multiple_query)))
 
-  @require_online
-  @only_run_once
+  @test.require.only_run_once
+  @test.require.online
   def test_get_consensus(self):
     """
     Exercises the downloader's get_consensus() method.
@@ -180,8 +176,8 @@ class TestDescriptorDownloader(unittest.TestCase):
     self.assertTrue(len(consensus) > 50)
     self.assertTrue(isinstance(consensus[0], stem.descriptor.router_status_entry.RouterStatusEntryV3))
 
-  @require_online
-  @only_run_once
+  @test.require.only_run_once
+  @test.require.online
   def test_get_microdescriptor_consensus(self):
     """
     Exercises the downloader's get_consensus() method for fetching a
@@ -197,8 +193,8 @@ class TestDescriptorDownloader(unittest.TestCase):
     self.assertTrue(len(consensus) > 50)
     self.assertTrue(isinstance(consensus[0], stem.descriptor.router_status_entry.RouterStatusEntryMicroV3))
 
-  @require_online
-  @only_run_once
+  @test.require.only_run_once
+  @test.require.online
   def test_get_key_certificates(self):
     """
     Exercises the downloader's get_key_certificates() method.
@@ -223,7 +219,7 @@ class TestDescriptorDownloader(unittest.TestCase):
 
     self.assertEqual(2, len(list(multiple_query)))
 
-  @require_online
+  @test.require.online
   def test_that_cache_is_up_to_date(self):
     """
     Check if the cached fallback directories bundled with Stem are up to date
@@ -236,7 +232,7 @@ class TestDescriptorDownloader(unittest.TestCase):
     if cached_fallback_directories != latest_fallback_directories:
       self.fail("Stem's cached fallback directories are out of date. Please run 'cache_fallback_directories.py'...\n\n%s" % stem.descriptor.remote._fallback_directory_differences(cached_fallback_directories, latest_fallback_directories))
 
-  @require_online
+  @test.require.online
   def test_fallback_directory_reachability(self):
     """
     Fetch information from each fallback directory to confirm that it's
diff --git a/test/integ/descriptor/server_descriptor.py b/test/integ/descriptor/server_descriptor.py
index bd61e70..2a3d897 100644
--- a/test/integ/descriptor/server_descriptor.py
+++ b/test/integ/descriptor/server_descriptor.py
@@ -6,17 +6,14 @@ import os
 import unittest
 
 import stem.descriptor
-
+import test.require
 import test.runner
 
-from test.util import (
-  register_new_capability,
-  only_run_once,
-)
+from test.util import register_new_capability
 
 
 class TestServerDescriptor(unittest.TestCase):
-  @only_run_once
+  @test.require.only_run_once
   def test_cached_descriptor(self):
     """
     Parses the cached descriptor file in our data directory, checking that it
diff --git a/test/integ/installation.py b/test/integ/installation.py
index 4d68988..3f37df3 100644
--- a/test/integ/installation.py
+++ b/test/integ/installation.py
@@ -12,11 +12,9 @@ import unittest
 
 import stem
 import stem.util.system
-
+import test.require
 import test.util
 
-from test.util import only_run_once
-
 INSTALL_MISMATCH_MSG = "Running 'python setup.py sdist' doesn't match our git contents in the following way. The manifest in our setup.py may need to be updated...\n\n"
 
 BASE_INSTALL_PATH = '/tmp/stem_test'
@@ -110,7 +108,7 @@ def _assert_has_all_files(path):
 
 
 class TestInstallation(unittest.TestCase):
-  @only_run_once
+  @test.require.only_run_once
   def test_install(self):
     """
     Installs with 'python setup.py install' and checks we can use what we
@@ -126,7 +124,7 @@ class TestInstallation(unittest.TestCase):
     self.assertEqual(stem.__version__, stem.util.system.call([sys.executable, '-c', "import sys;sys.path.insert(0, '%s');import stem;print(stem.__version__)" % INSTALL_PATH])[0])
     _assert_has_all_files(INSTALL_PATH)
 
-  @only_run_once
+  @test.require.only_run_once
   def test_sdist(self):
     """
     Creates a source distribution tarball with 'python setup.py sdist' and
diff --git a/test/integ/interpreter.py b/test/integ/interpreter.py
index 5e7c94b..42dd01a 100644
--- a/test/integ/interpreter.py
+++ b/test/integ/interpreter.py
@@ -7,12 +7,10 @@ import tempfile
 import unittest
 
 import stem.util.system
-
+import test.require
 import test.runner
 import test.util
 
-from test.util import require_controller
-
 PROMPT_CMD = os.path.join(test.util.STEM_BASE, 'tor-prompt')
 
 
@@ -24,7 +22,7 @@ def _run_prompt(*args):
 
 
 class TestInterpreter(unittest.TestCase):
-  @require_controller
+  @test.require.controller
   def test_running_command(self):
     # We'd need to provide the password to stdin. Fine test to add later but
     # not gonna hassle for now.
@@ -36,7 +34,7 @@ class TestInterpreter(unittest.TestCase):
     expected = ['250-config-file=%s' % test.runner.get_runner().get_torrc_path(), '250 OK']
     self.assertEqual(expected, _run_prompt('--run', 'GETINFO config-file'))
 
-  @require_controller
+  @test.require.controller
   def test_running_file(self):
     if test.runner.Torrc.PASSWORD in test.runner.get_runner().get_options():
       self.skipTest('password auth unsupported')
diff --git a/test/integ/process.py b/test/integ/process.py
index 79797ac..1ca0f70 100644
--- a/test/integ/process.py
+++ b/test/integ/process.py
@@ -20,16 +20,9 @@ import stem.util.str_tools
 import stem.util.system
 import stem.util.tor_tools
 import stem.version
+import test.require
 import test.runner
 
-from test.util import (
-  require_command,
-  require_controller,
-  require_version,
-)
-
-from test.util import only_run_once
-
 try:
   # added in python 3.3
   from unittest.mock import patch, Mock
@@ -53,7 +46,7 @@ class TestProcess(unittest.TestCase):
   def tearDown(self):
     shutil.rmtree(self.data_directory)
 
-  @require_controller
+  @test.require.controller
   def test_version_argument(self):
     """
     Check that 'tor --version' matches 'GETINFO version'.
@@ -199,7 +192,7 @@ class TestProcess(unittest.TestCase):
     self.assertTrue('UseBridges' in output)
     self.assertTrue('SocksPort' in output)
 
-  @require_command('sleep')
+  @test.require.command('sleep')
   @patch('re.compile', Mock(side_effect = KeyboardInterrupt('nope')))
   def test_no_orphaned_process(self):
     """
@@ -255,7 +248,7 @@ class TestProcess(unittest.TestCase):
 
     self.assertEqual(expected, result)
 
-  @require_version(stem.version.Requirement.TORRC_VIA_STDIN)
+  @test.require.version(stem.version.Requirement.TORRC_VIA_STDIN)
   def test_torrc_arguments_via_stdin(self):
     """
     Pass configuration options via stdin.
@@ -278,7 +271,7 @@ class TestProcess(unittest.TestCase):
     self.assertTrue('[notice] Configuration file "/path/that/really/shouldnt/exist" not present, using reasonable defaults.' in output)
     self.assertTrue('Configuration was valid' in output)
 
-  @only_run_once
+  @test.require.only_run_once
   def test_can_run_multithreaded(self):
     """
     Our launch_tor() function uses signal to support its timeout argument.
@@ -321,7 +314,7 @@ class TestProcess(unittest.TestCase):
     self.assertEqual(None, launch_async_with_timeout(None))
     self.assertEqual(None, launch_async_with_timeout(stem.process.DEFAULT_INIT_TIMEOUT))
 
-  @only_run_once
+  @test.require.only_run_once
   @patch('stem.version.get_system_tor_version', Mock(return_value = stem.version.Version('0.0.0.1')))
   def test_launch_tor_with_config_via_file(self):
     """
@@ -359,8 +352,8 @@ class TestProcess(unittest.TestCase):
       tor_process.kill()
       tor_process.wait()
 
-  @only_run_once
-  @require_version(stem.version.Requirement.TORRC_VIA_STDIN)
+  @test.require.only_run_once
+  @test.require.version(stem.version.Requirement.TORRC_VIA_STDIN)
   def test_launch_tor_with_config_via_stdin(self):
     """
     Exercises launch_tor_with_config when we provide our torrc via stdin.
@@ -394,7 +387,7 @@ class TestProcess(unittest.TestCase):
       tor_process.kill()
       tor_process.wait()
 
-  @only_run_once
+  @test.require.only_run_once
   def test_with_invalid_config(self):
     """
     Spawn a tor process with a configuration that should make it dead on arrival.
@@ -417,7 +410,7 @@ class TestProcess(unittest.TestCase):
       },
     )
 
-  @only_run_once
+  @test.require.only_run_once
   def test_launch_tor_with_timeout(self):
     """
     Runs launch_tor where it times out before completing.
@@ -432,9 +425,9 @@ class TestProcess(unittest.TestCase):
     if not (runtime > 0.05 and runtime < 1):
       self.fail('Test should have taken 0.05-1 seconds, took %0.1f instead' % runtime)
 
-  @require_command('sleep')
-  @require_version(stem.version.Requirement.TAKEOWNERSHIP)
-  @only_run_once
+  @test.require.only_run_once
+  @test.require.command('sleep')
+  @test.require.version(stem.version.Requirement.TAKEOWNERSHIP)
   @patch('os.getpid')
   def test_take_ownership_via_pid(self, getpid_mock):
     """
@@ -476,8 +469,8 @@ class TestProcess(unittest.TestCase):
 
     self.fail("tor didn't quit after the process that owned it terminated")
 
-  @require_version(stem.version.Requirement.TAKEOWNERSHIP)
-  @only_run_once
+  @test.require.only_run_once
+  @test.require.version(stem.version.Requirement.TAKEOWNERSHIP)
   def test_take_ownership_via_controller(self):
     """
     Checks that the tor process quits after the controller that owns it
diff --git a/test/integ/response/protocolinfo.py b/test/integ/response/protocolinfo.py
index 8563cf0..1a00671 100644
--- a/test/integ/response/protocolinfo.py
+++ b/test/integ/response/protocolinfo.py
@@ -9,10 +9,11 @@ import stem.connection
 import stem.socket
 import stem.util.system
 import stem.version
+import test.require
 import test.runner
 
 from test.integ.util.system import filter_system_call
-from test.util import require_controller, tor_version
+from test.util import tor_version
 
 try:
   # added in python 3.3
@@ -22,7 +23,7 @@ except ImportError:
 
 
 class TestProtocolInfo(unittest.TestCase):
-  @require_controller
+  @test.require.controller
   def test_parsing(self):
     """
     Makes a PROTOCOLINFO query and processes the response for our control
@@ -44,7 +45,7 @@ class TestProtocolInfo(unittest.TestCase):
 
     self.assert_matches_test_config(protocolinfo_response)
 
-  @require_controller
+  @test.require.controller
   @patch('stem.util.proc.is_available', Mock(return_value = False))
   @patch('stem.util.system.is_available', Mock(return_value = True))
   def test_get_protocolinfo_path_expansion(self):
@@ -87,7 +88,7 @@ class TestProtocolInfo(unittest.TestCase):
       self.assertTrue(control_socket.is_alive())
       control_socket.close()
 
-  @require_controller
+  @test.require.controller
   def test_multiple_protocolinfo_calls(self):
     """
     Tests making repeated PROTOCOLINFO queries. This use case is interesting
@@ -100,7 +101,7 @@ class TestProtocolInfo(unittest.TestCase):
         protocolinfo_response = stem.connection.get_protocolinfo(control_socket)
         self.assert_matches_test_config(protocolinfo_response)
 
-  @require_controller
+  @test.require.controller
   def test_pre_disconnected_query(self):
     """
     Tests making a PROTOCOLINFO query when previous use of the socket had
diff --git a/test/integ/socket/control_message.py b/test/integ/socket/control_message.py
index 646f732..6d16f48 100644
--- a/test/integ/socket/control_message.py
+++ b/test/integ/socket/control_message.py
@@ -7,17 +7,14 @@ import unittest
 
 import stem.socket
 import stem.version
+import test.require
 import test.runner
 
-from test.util import (
-  require_controller,
-  require_version,
-  random_fingerprint,
-)
+from test.util import random_fingerprint
 
 
 class TestControlMessage(unittest.TestCase):
-  @require_controller
+  @test.require.controller
   def test_unestablished_socket(self):
     """
     Checks message parsing when we have a valid but unauthenticated socket.
@@ -58,7 +55,7 @@ class TestControlMessage(unittest.TestCase):
     self.assertRaises(stem.SocketClosed, control_socket.send, 'GETINFO version')
     self.assertRaises(stem.SocketClosed, control_socket.recv)
 
-  @require_controller
+  @test.require.controller
   def test_invalid_command(self):
     """
     Parses the response for a command which doesn't exist.
@@ -72,7 +69,7 @@ class TestControlMessage(unittest.TestCase):
       self.assertEqual('510 Unrecognized command "blarg"\r\n', unrecognized_command_response.raw_content())
       self.assertEqual([('510', ' ', 'Unrecognized command "blarg"')], unrecognized_command_response.content())
 
-  @require_controller
+  @test.require.controller
   def test_invalid_getinfo(self):
     """
     Parses the response for a GETINFO query which doesn't exist.
@@ -86,7 +83,7 @@ class TestControlMessage(unittest.TestCase):
       self.assertEqual('552 Unrecognized key "blarg"\r\n', unrecognized_key_response.raw_content())
       self.assertEqual([('552', ' ', 'Unrecognized key "blarg"')], unrecognized_key_response.content())
 
-  @require_controller
+  @test.require.controller
   def test_getinfo_config_file(self):
     """
     Parses the 'GETINFO config-file' response.
@@ -103,8 +100,8 @@ class TestControlMessage(unittest.TestCase):
       self.assertEqual('250-config-file=%s\r\n250 OK\r\n' % torrc_dst, config_file_response.raw_content())
       self.assertEqual([('250', '-', 'config-file=%s' % torrc_dst), ('250', ' ', 'OK')], config_file_response.content())
 
-  @require_controller
-  @require_version(stem.version.Requirement.GETINFO_CONFIG_TEXT)
+  @test.require.controller
+  @test.require.version(stem.version.Requirement.GETINFO_CONFIG_TEXT)
   def test_getinfo_config_text(self):
     """
     Parses the 'GETINFO config-text' response.
@@ -145,7 +142,7 @@ class TestControlMessage(unittest.TestCase):
         self.assertTrue('%s\r\n' % torrc_entry in config_text_response.raw_content())
         self.assertTrue('%s' % torrc_entry in config_text_response.content()[0][2])
 
-  @require_controller
+  @test.require.controller
   def test_setconf_event(self):
     """
     Issues 'SETEVENTS CONF_CHANGED' and parses an events.
diff --git a/test/integ/socket/control_socket.py b/test/integ/socket/control_socket.py
index a4da321..8e2658a 100644
--- a/test/integ/socket/control_socket.py
+++ b/test/integ/socket/control_socket.py
@@ -14,13 +14,14 @@ import unittest
 import stem.connection
 import stem.control
 import stem.socket
+import test.require
 import test.runner
 
-from test.util import require_controller, tor_version
+from test.util import tor_version
 
 
 class TestControlSocket(unittest.TestCase):
-  @require_controller
+  @test.require.controller
   def test_connection_time(self):
     """
     Checks that our connection_time method tracks when our state's changed.
@@ -54,7 +55,7 @@ class TestControlSocket(unittest.TestCase):
       reconnection_time = control_socket.connection_time()
       self.assertTrue(disconnection_time < reconnection_time <= time.time())
 
-  @require_controller
+  @test.require.controller
   def test_send_buffered(self):
     """
     Sends multiple requests before receiving back any of the replies.
@@ -71,7 +72,7 @@ class TestControlSocket(unittest.TestCase):
         self.assertTrue(str(response).startswith('version=%s' % tor_version()))
         self.assertTrue(str(response).endswith('\nOK'))
 
-  @require_controller
+  @test.require.controller
   def test_send_closed(self):
     """
     Sends a message after we've closed the connection.
@@ -84,7 +85,7 @@ class TestControlSocket(unittest.TestCase):
 
       self.assertRaises(stem.SocketClosed, control_socket.send, 'blarg')
 
-  @require_controller
+  @test.require.controller
   def test_send_disconnected(self):
     """
     Sends a message to a socket that has been disconnected by the other end.
@@ -110,7 +111,7 @@ class TestControlSocket(unittest.TestCase):
         self.assertRaises(stem.SocketClosed, control_socket.send, 'blarg')
         self.assertFalse(control_socket.is_alive())
 
-  @require_controller
+  @test.require.controller
   def test_recv_closed(self):
     """
     Receives a message after we've closed the connection.
@@ -123,7 +124,7 @@ class TestControlSocket(unittest.TestCase):
 
       self.assertRaises(stem.SocketClosed, control_socket.recv)
 
-  @require_controller
+  @test.require.controller
   def test_recv_disconnected(self):
     """
     Receives a message from a socket that has been disconnected by the other
@@ -142,7 +143,7 @@ class TestControlSocket(unittest.TestCase):
       self.assertRaises(stem.SocketClosed, control_socket.recv)
       self.assertFalse(control_socket.is_alive())
 
-  @require_controller
+  @test.require.controller
   def test_connect_repeatedly(self):
     """
     Checks that we can reconnect, use, and disconnect a socket repeatedly.
diff --git a/test/integ/util/connection.py b/test/integ/util/connection.py
index f641a37..1fc08c4 100644
--- a/test/integ/util/connection.py
+++ b/test/integ/util/connection.py
@@ -5,14 +5,14 @@ that we're running.
 
 import unittest
 
+import test.require
 import test.runner
 
 from stem.util.connection import Resolver, get_connections, system_resolvers
-from test.util import require_ptrace
 
 
 class TestConnection(unittest.TestCase):
-  @require_ptrace
+  @test.require.ptrace
   def check_resolver(self, resolver):
     runner = test.runner.get_runner()
 
diff --git a/test/integ/util/proc.py b/test/integ/util/proc.py
index ef3a7c0..da4d6ef 100644
--- a/test/integ/util/proc.py
+++ b/test/integ/util/proc.py
@@ -6,19 +6,15 @@ we're running.
 import os
 import unittest
 
+import test.require
 import test.runner
 
 from stem.util import proc
 
-from test.util import (
-  require_proc,
-  require_ptrace,
-)
-
 
 class TestProc(unittest.TestCase):
-  @require_proc
-  @require_ptrace
+  @test.require.proc
+  @test.require.ptrace
   def test_cwd(self):
     """
     Checks that stem.util.proc.cwd matches our tor instance's cwd.
@@ -28,7 +24,7 @@ class TestProc(unittest.TestCase):
     runner_pid, tor_cwd = runner.get_pid(), runner.get_tor_cwd()
     self.assertEqual(tor_cwd, proc.cwd(runner_pid))
 
-  @require_proc
+  @test.require.proc
   def test_uid(self):
     """
     Checks that stem.util.proc.uid matches our tor instance's uid.
@@ -37,7 +33,7 @@ class TestProc(unittest.TestCase):
     tor_pid = test.runner.get_runner().get_pid()
     self.assertEqual(os.geteuid(), proc.uid(tor_pid))
 
-  @require_proc
+  @test.require.proc
   def test_memory_usage(self):
     """
     Checks that stem.util.proc.memory_usage looks somewhat reasonable.
@@ -50,7 +46,7 @@ class TestProc(unittest.TestCase):
     self.assertTrue(res_size > 1024)
     self.assertTrue(vir_size > 1024)
 
-  @require_proc
+  @test.require.proc
   def test_stats(self):
     """
     Checks that stem.util.proc.stats looks somewhat reasonable.
@@ -65,8 +61,8 @@ class TestProc(unittest.TestCase):
     self.assertTrue(float(stime) >= 0)
     self.assertTrue(float(start_time) > proc.system_start_time())
 
-  @require_proc
-  @require_ptrace
+  @test.require.proc
+  @test.require.ptrace
   def test_connections(self):
     """
     Checks for our control port in the stem.util.proc.connections output if
diff --git a/test/integ/util/system.py b/test/integ/util/system.py
index 88a5529..912251b 100644
--- a/test/integ/util/system.py
+++ b/test/integ/util/system.py
@@ -10,15 +10,9 @@ import unittest
 
 import stem.util.proc
 import stem.util.system
+import test.require
 import test.runner
 
-from test.util import (
-  require,
-  require_command,
-  require_proc,
-  require_ptrace,
-)
-
 try:
   # added in python 3.3
   from unittest.mock import Mock, patch
@@ -68,10 +62,10 @@ def _has_port():
   return test.runner.Torrc.PORT in test.runner.get_runner().get_options()
 
 
-require_single_tor_instance = require(_is_single_tor_running, 'multiple tor instances')
-require_control_port = require(_has_port, 'test instance has no port')
-require_linux = require(_is_linux, 'linux only')
-require_bsd = require(stem.util.system.is_bsd, 'bsd only')
+require_single_tor_instance = test.require.needs(_is_single_tor_running, 'multiple tor instances')
+require_control_port = test.require.needs(_has_port, 'test instance has no port')
+require_linux = test.require.needs(_is_linux, 'linux only')
+require_bsd = test.require.needs(stem.util.system.is_bsd, 'bsd only')
 
 
 class TestSystem(unittest.TestCase):
@@ -91,7 +85,7 @@ class TestSystem(unittest.TestCase):
 
     self.assertFalse(stem.util.system.is_available('blarg_and_stuff'))
 
-  @require_command('ps')
+  @test.require.command('ps')
   def test_is_running(self):
     """
     Checks the stem.util.system.is_running function.
@@ -117,8 +111,8 @@ class TestSystem(unittest.TestCase):
     self.assertEqual(tor_pid, stem.util.system.pid_by_name(tor_cmd))
     self.assertEqual(None, stem.util.system.pid_by_name('blarg_and_stuff'))
 
-  @require_command('pgrep')
   @require_single_tor_instance
+  @test.require.command('pgrep')
   def test_pid_by_name_pgrep(self):
     """
     Tests the pid_by_name function with a pgrep response.
@@ -134,8 +128,8 @@ class TestSystem(unittest.TestCase):
       tor_cmd = test.runner.get_runner().get_tor_command(True)
       self.assertEqual(tor_pid, stem.util.system.pid_by_name(tor_cmd))
 
-  @require_command('pidof')
   @require_single_tor_instance
+  @test.require.command('pidof')
   def test_pid_by_name_pidof(self):
     """
     Tests the pid_by_name function with a pidof response.
@@ -152,8 +146,8 @@ class TestSystem(unittest.TestCase):
       self.assertEqual(tor_pid, stem.util.system.pid_by_name(tor_cmd))
 
   @require_linux
-  @require_command('ps')
   @require_single_tor_instance
+  @test.require.command('ps')
   def test_pid_by_name_ps_linux(self):
     """
     Tests the pid_by_name function with the linux variant of ps.
@@ -170,8 +164,8 @@ class TestSystem(unittest.TestCase):
       self.assertEqual(tor_pid, stem.util.system.pid_by_name(tor_cmd))
 
   @require_bsd
-  @require_command('ps')
   @require_single_tor_instance
+  @test.require.command('ps')
   def test_pid_by_name_ps_bsd(self):
     """
     Tests the pid_by_name function with the bsd variant of ps.
@@ -187,9 +181,9 @@ class TestSystem(unittest.TestCase):
       tor_cmd = test.runner.get_runner().get_tor_command(True)
       self.assertEqual(tor_pid, stem.util.system.pid_by_name(tor_cmd))
 
-  @require_ptrace
-  @require_command('lsof')
   @require_single_tor_instance
+  @test.require.ptrace
+  @test.require.command('lsof')
   def test_pid_by_name_lsof(self):
     """
     Tests the pid_by_name function with a lsof response.
@@ -208,8 +202,8 @@ class TestSystem(unittest.TestCase):
       if len(all_tor_pids) == 1:
         self.assertEqual(our_tor_pid, all_tor_pids[0])
 
-  @require_command('tasklist')
   @require_single_tor_instance
+  @test.require.command('tasklist')
   def test_pid_by_name_tasklist(self):
     """
     Tests the pid_by_name function with a tasklist response.
@@ -218,8 +212,8 @@ class TestSystem(unittest.TestCase):
     runner = test.runner.get_runner()
     self.assertEqual(runner.get_pid(), stem.util.system.pid_by_name(runner.get_tor_command(True)))
 
-  @require_ptrace
   @require_control_port
+  @test.require.ptrace
   def test_pid_by_port(self):
     """
     Checks general usage of the stem.util.system.pid_by_port function.
@@ -243,9 +237,9 @@ class TestSystem(unittest.TestCase):
     self.assertEqual(None, stem.util.system.pid_by_port(99999))
 
   @require_linux
-  @require_ptrace
   @require_control_port
-  @require_command('netstat')
+  @test.require.ptrace
+  @test.require.command('netstat')
   def test_pid_by_port_netstat(self):
     """
     Tests the pid_by_port function with a netstat response.
@@ -266,9 +260,9 @@ class TestSystem(unittest.TestCase):
       self.assertEqual(tor_pid, stem.util.system.pid_by_port(test.runner.CONTROL_PORT))
 
   @require_bsd
-  @require_ptrace
   @require_control_port
-  @require_command('sockstat')
+  @test.require.ptrace
+  @test.require.command('sockstat')
   def test_pid_by_port_sockstat(self):
     """
     Tests the pid_by_port function with a sockstat response.
@@ -283,9 +277,9 @@ class TestSystem(unittest.TestCase):
       tor_pid = test.runner.get_runner().get_pid()
       self.assertEqual(tor_pid, stem.util.system.pid_by_port(test.runner.CONTROL_PORT))
 
-  @require_ptrace
   @require_control_port
-  @require_command('lsof')
+  @test.require.ptrace
+  @test.require.command('lsof')
   def test_pid_by_port_lsof(self):
     """
     Tests the pid_by_port function with a lsof response.
@@ -328,7 +322,7 @@ class TestSystem(unittest.TestCase):
     pids = stem.util.system.pids_by_user(getpass.getuser())
     self.assertTrue(os.getpid() in pids)
 
-  @require_ptrace
+  @test.require.ptrace
   def test_cwd(self):
     """
     Checks general usage of the stem.util.system.cwd function.
@@ -343,8 +337,8 @@ class TestSystem(unittest.TestCase):
     self.assertEqual(tor_cwd, stem.util.system.cwd(runner_pid))
     self.assertEqual(None, stem.util.system.cwd(99999))
 
-  @require_ptrace
-  @require_command('pwdx')
+  @test.require.ptrace
+  @test.require.command('pwdx')
   def test_cwd_pwdx(self):
     """
     Tests the pid_by_cwd function with a pwdx response.
@@ -363,8 +357,8 @@ class TestSystem(unittest.TestCase):
       runner_pid, tor_cwd = runner.get_pid(), runner.get_tor_cwd()
       self.assertEqual(tor_cwd, stem.util.system.cwd(runner_pid))
 
-  @require_ptrace
-  @require_command('lsof')
+  @test.require.ptrace
+  @test.require.command('lsof')
   def test_cwd_lsof(self):
     """
     Tests the pid_by_cwd function with a lsof response.
@@ -392,7 +386,7 @@ class TestSystem(unittest.TestCase):
     self.assertEqual(None, stem.util.system.user(-5))
     self.assertEqual(None, stem.util.system.start_time(98765))
 
-  @require_proc
+  @test.require.proc
   def test_user_proc(self):
     """
     Tests the user function with a proc response.
@@ -408,7 +402,7 @@ class TestSystem(unittest.TestCase):
       pid = test.runner.get_runner().get_pid()
       self.assertTrue(getpass.getuser(), stem.util.system.user(pid))
 
-  @require_command('ps')
+  @test.require.command('ps')
   @patch('stem.util.proc.is_available', Mock(return_value = False))
   def test_user_ps(self):
     """
@@ -427,7 +421,7 @@ class TestSystem(unittest.TestCase):
     self.assertEqual(None, stem.util.system.start_time(-5))
     self.assertEqual(None, stem.util.system.start_time(98765))
 
-  @require_proc
+  @test.require.proc
   def test_start_time_proc(self):
     """
     Tests the start_time function with a proc response.
@@ -441,7 +435,7 @@ class TestSystem(unittest.TestCase):
       pid = test.runner.get_runner().get_pid()
       self.assertTrue(stem.util.system.start_time(pid) >= 0)
 
-  @require_command('ps')
+  @test.require.command('ps')
   @patch('stem.util.proc.is_available', Mock(return_value = False))
   def test_start_time_ps(self):
     """
diff --git a/test/integ/version.py b/test/integ/version.py
index 3cef9b2..b28ee86 100644
--- a/test/integ/version.py
+++ b/test/integ/version.py
@@ -7,16 +7,12 @@ import unittest
 
 import stem.prereq
 import stem.version
+import test.require
 import test.runner
 
-from test.util import (
-  require_command,
-  require_controller,
-)
-
 
 class TestVersion(unittest.TestCase):
-  @require_command('tor')
+  @test.require.command('tor')
   def test_get_system_tor_version(self):
     """
     Basic verification checks for the get_system_tor_version() function.
@@ -34,7 +30,7 @@ class TestVersion(unittest.TestCase):
     # try running against a command that doesn't exist
     self.assertRaises(IOError, stem.version.get_system_tor_version, 'blarg')
 
-  @require_controller
+  @test.require.controller
   def test_getinfo_version_parsing(self):
     """
     Issues a 'GETINFO version' query to our test instance and makes sure that
diff --git a/test/require.py b/test/require.py
new file mode 100644
index 0000000..a0735d8
--- /dev/null
+++ b/test/require.py
@@ -0,0 +1,106 @@
+# Copyright 2012-2017, Damian Johnson and The Tor Project
+# See LICENSE for licensing information
+
+"""
+Testing requirements. This provides annotations to skip tests that shouldn't be
+run.
+
+::
+
+  Test Requirements
+  |- only_run_once - skip test if it has been ran before
+  |- needs - skips the test unless a requirement is met
+  |
+  |- cryptography - skips test unless the cryptography module is present
+  |- pynacl - skips test unless the pynacl module is present
+  |- command - requires a command to be on the path
+  |- proc - requires the platform to have recognized /proc contents
+  |
+  |- controller - skips test unless tor provides a controller endpoint
+  |- version - skips test unless we meet a tor version requirement
+  |- ptrace - requires 'DisableDebuggerAttachment' to be set
+  +- online - skips unless targets allow for online tests
+"""
+
+import stem.util.system
+import stem.version
+import test.runner
+import test.util
+
+RAN_TESTS = []
+
+
+def only_run_once(func):
+  """
+  Skips the test if it has ran before. If it hasn't then flags it as being ran.
+  This is useful to prevent lengthy tests that are independent of integ targets
+  from being run repeatedly with ``RUN_ALL``.
+  """
+
+  def wrapped(self, *args, **kwargs):
+    if self.id() not in RAN_TESTS:
+      RAN_TESTS.append(self.id())
+      return func(self, *args, **kwargs)
+    else:
+      self.skipTest('(already ran)')
+
+  return wrapped
+
+
+def needs(condition, message):
+  """
+  Skips teh test unless the conditional evaluates to 'true'.
+  """
+
+  def decorator(func):
+    def wrapped(self, *args, **kwargs):
+      if condition():
+        return func(self, *args, **kwargs)
+      else:
+        self.skipTest('(%s)' % message)
+
+    return wrapped
+
+  return decorator
+
+
+def _can_access_controller():
+  return test.runner.get_runner().is_accessible()
+
+
+def _can_ptrace():
+  # If we're running a tor version where ptrace is disabled and we didn't
+  # set 'DisableDebuggerAttachment=1' then we can infer that it's disabled.
+
+  has_option = test.util.tor_version() >= stem.version.Requirement.TORRC_DISABLE_DEBUGGER_ATTACHMENT
+  return not has_option or test.runner.Torrc.PTRACE in test.runner.get_runner().get_options()
+
+
+def _is_online():
+  return test.util.Target.ONLINE in test.runner.get_runner().attribute_targets
+
+
+def command(cmd):
+  """
+  Skips the test unless a command is available on the path.
+  """
+
+  return needs(lambda: stem.util.system.is_available(cmd), '%s unavailable' % cmd)
+
+
+def version(req_version):
+  """
+  Skips the test unless we meet the required version.
+
+  :param stem.version.Version req_version: required tor version for the test
+  """
+
+  return needs(lambda: test.util.tor_version() >= req_version, 'requires %s' % req_version)
+
+
+cryptography = needs(stem.prereq.is_crypto_available, 'requires cryptography')
+pynacl = needs(stem.prereq._is_pynacl_available, 'requires pynacl module')
+proc = needs(stem.util.proc.is_available, 'proc unavailable')
+controller = needs(_can_access_controller, 'no connection')
+ptrace = needs(_can_ptrace, 'DisableDebuggerAttachment is set')
+online = needs(_is_online, 'requires online target')
diff --git a/test/unit/descriptor/certificate.py b/test/unit/descriptor/certificate.py
index 0cef45a..19d7c64 100644
--- a/test/unit/descriptor/certificate.py
+++ b/test/unit/descriptor/certificate.py
@@ -10,10 +10,10 @@ import unittest
 import stem.descriptor.certificate
 import stem.util.str_tools
 import stem.prereq
+import test.require
 
 from stem.descriptor.certificate import ED25519_SIGNATURE_LENGTH, CertType, ExtensionType, ExtensionFlag, Ed25519Certificate, Ed25519CertificateV1, Ed25519Extension
 from test.unit.descriptor import get_resource
-from test.util import require_pynacl
 
 ED25519_CERT = """
 AQQABhtZAaW2GoBED1IjY3A6f6GNqBEl5A83fD2Za9upGke51JGqAQAgBABnprVR
@@ -147,7 +147,7 @@ class TestEd25519Certificate(unittest.TestCase):
 
     self.assert_raises(certificate(extension_data = [b'\x00\x02\x04\x07\11\12']), "Ed25519 HAS_SIGNING_KEY extension must be 32 bytes, but was 2.")
 
-  @require_pynacl
+  @test.require.pynacl
   def test_validation_with_descriptor_key(self):
     """
     Validate a descriptor signature using the ed25519 master key within the
@@ -159,7 +159,7 @@ class TestEd25519Certificate(unittest.TestCase):
 
     desc.certificate.validate(desc)
 
-  @require_pynacl
+  @test.require.pynacl
   def test_validation_with_embedded_key(self):
     """
     Validate a descriptor signature using the signing key within the ed25519
@@ -172,7 +172,7 @@ class TestEd25519Certificate(unittest.TestCase):
     desc.ed25519_master_key = None
     desc.certificate.validate(desc)
 
-  @require_pynacl
+  @test.require.pynacl
   def test_validation_with_invalid_descriptor(self):
     """
     Validate a descriptor without a valid signature.
diff --git a/test/unit/descriptor/hidden_service_descriptor.py b/test/unit/descriptor/hidden_service_descriptor.py
index 48f1d29..2f8427f 100644
--- a/test/unit/descriptor/hidden_service_descriptor.py
+++ b/test/unit/descriptor/hidden_service_descriptor.py
@@ -8,8 +8,7 @@ import unittest
 
 import stem.descriptor
 import stem.prereq
-
-from test.util import require_cryptography
+import test.require
 
 from stem.descriptor.hidden_service_descriptor import (
   REQUIRED_FIELDS,
@@ -274,7 +273,7 @@ class TestHiddenServiceDescriptor(unittest.TestCase):
     self.assertEqual(datetime.datetime(2014, 10, 31, 23, 0, 0), desc.published)
     self.assertEqual([2, 3], desc.protocol_versions)
 
-  @require_cryptography
+  @test.require.cryptography
   def test_with_basic_auth(self):
     """
     Parse a descriptor with introduction-points encrypted with basic auth.
@@ -321,7 +320,7 @@ class TestHiddenServiceDescriptor(unittest.TestCase):
     self.assertTrue('MIGJAoGBAM7B/cymp' in point.service_key)
     self.assertEqual([], point.intro_authentication)
 
-  @require_cryptography
+  @test.require.cryptography
   def test_with_stealth_auth(self):
     """
     Parse a descriptor with introduction-points encrypted with stealth auth.
diff --git a/test/unit/manual.py b/test/unit/manual.py
index 6bfcbe7..28a8a8b 100644
--- a/test/unit/manual.py
+++ b/test/unit/manual.py
@@ -11,8 +11,7 @@ import unittest
 import stem.prereq
 import stem.manual
 import stem.util.system
-
-from test.util import require_command
+import test.require
 
 try:
   # account for urllib's change between python 2.x and 3.x
@@ -138,7 +137,7 @@ class TestManual(unittest.TestCase):
     self.assertEqual('', blank.summary)
     self.assertEqual('', blank.description)
 
-  @require_command('man')
+  @test.require.command('man')
   def test_parsing_with_example(self):
     """
     Read a trimmed copy of tor's man page. This gives a good exercise of our
@@ -160,7 +159,7 @@ class TestManual(unittest.TestCase):
     self.assertEqual(EXPECTED_FILES, manual.files)
     self.assertEqual(EXPECTED_CONFIG_OPTIONS, manual.config_options)
 
-  @require_command('man')
+  @test.require.command('man')
   def test_parsing_with_unknown_options(self):
     """
     Check that we can read a local mock man page that contains unrecognized
@@ -189,7 +188,7 @@ class TestManual(unittest.TestCase):
     self.assertEqual('', option.summary)
     self.assertEqual('Description of this new option.', option.description)
 
-  @require_command('man')
+  @test.require.command('man')
   def test_saving_manual(self):
     """
     Check that we can save and reload manuals.
diff --git a/test/util.py b/test/util.py
index e8af8e1..47d90fd 100644
--- a/test/util.py
+++ b/test/util.py
@@ -12,24 +12,6 @@ Helper functions for our test framework.
   get_prereq - provides the tor version required to run the given target
   get_torrc_entries - provides the torrc entries for a given target
 
-This module also provides generally useful test helpers...
-
-::
-
-  Test Requirements
-  |- only_run_once - skip test if it has been ran before
-  |- require - skips the test unless a requirement is met
-  |
-  |- require_cryptography - skips test unless the cryptography module is present
-  |- require_pynacl - skips test unless the pynacl module is present
-  |- require_command - requires a command to be on the path
-  |- require_proc - requires the platform to have recognized /proc contents
-  |
-  |- require_controller - skips test unless tor provides a controller endpoint
-  |- require_version - skips test unless we meet a tor version requirement
-  |- require_ptrace - requires 'DisableDebuggerAttachment' to be set
-  +- require_online - skips unless targets allow for online tests
-
   get_message - provides a ControlMessage instance
   get_protocolinfo_response - provides a ProtocolInfoResponse instance
   get_all_combinations - provides all combinations of attributes
@@ -43,14 +25,10 @@ import re
 import os
 
 import stem
-import stem.prereq
 import stem.util.conf
-import stem.util.system
-import stem.util.test_tools
+import stem.util.enum
 import stem.version
 
-import test.output
-
 CONFIG = stem.util.conf.config_dict('test', {
   'target.prereq': {},
   'target.torrc': {},
@@ -83,7 +61,6 @@ Target = stem.util.enum.UppercaseEnum(
   'RUN_ALL',
 )
 
-RAN_TESTS = []
 TOR_VERSION = None
 
 # We make some paths relative to stem's base directory (the one above us)
@@ -211,82 +188,6 @@ def get_new_capabilities():
   return NEW_CAPABILITIES
 
 
-def only_run_once(func):
-  """
-  Skips the test if it has ran before. If it hasn't then flags it as being ran.
-  This is useful to prevent lengthy tests that are independent of integ targets
-  from being run repeatedly with ``RUN_ALL``.
-  """
-
-  def wrapped(self, *args, **kwargs):
-    if self.id() not in RAN_TESTS:
-      RAN_TESTS.append(self.id())
-      return func(self, *args, **kwargs)
-    else:
-      self.skipTest('(already ran)')
-
-  return wrapped
-
-
-def require(condition, message):
-  """
-  Skips teh test unless the conditional evaluates to 'true'.
-  """
-
-  def decorator(func):
-    def wrapped(self, *args, **kwargs):
-      if condition():
-        return func(self, *args, **kwargs)
-      else:
-        self.skipTest('(%s)' % message)
-
-    return wrapped
-
-  return decorator
-
-
-def _can_access_controller():
-  return test.runner.get_runner().is_accessible()
-
-
-def _can_ptrace():
-  # If we're running a tor version where ptrace is disabled and we didn't
-  # set 'DisableDebuggerAttachment=1' then we can infer that it's disabled.
-
-  has_option = tor_version() >= stem.version.Requirement.TORRC_DISABLE_DEBUGGER_ATTACHMENT
-  return not has_option or test.runner.Torrc.PTRACE in test.runner.get_runner().get_options()
-
-
-def _is_online():
-  return Target.ONLINE in test.runner.get_runner().attribute_targets
-
-
-require_cryptography = require(stem.prereq.is_crypto_available, 'requires cryptography')
-require_pynacl = require(stem.prereq._is_pynacl_available, 'requires pynacl module')
-require_proc = require(stem.util.proc.is_available, 'proc unavailable')
-require_controller = require(_can_access_controller, 'no connection')
-require_ptrace = require(_can_ptrace, 'DisableDebuggerAttachment is set')
-require_online = require(_is_online, 'requires online target')
-
-
-def require_command(cmd):
-  """
-  Skips the test unless a command is available on the path.
-  """
-
-  return require(lambda: stem.util.system.is_available(cmd), '%s unavailable' % cmd)
-
-
-def require_version(req_version):
-  """
-  Skips the test unless we meet the required version.
-
-  :param stem.version.Version req_version: required tor version for the test
-  """
-
-  return require(lambda: tor_version() >= req_version, 'requires %s' % req_version)
-
-
 def register_new_capability(capability_type, msg, suppression_token = None):
   """
   Register new capability found during the tests.





More information about the tor-commits mailing list