tor-commits
Threads by month
- ----- 2025 -----
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
June 2017
- 13 participants
- 1918 discussions
commit 267547286e6375a315e6cb34323feddfdafbcedd
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Jun 5 13:21:28 2017 -0700
Use builtin SkipTest if it's available
Only providing a SkipTest class if using python 2.6. This way we can drop it
when we remove python 2.6 support.
---
stem/util/test_tools.py | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/stem/util/test_tools.py b/stem/util/test_tools.py
index b7208e3..5a5813f 100644
--- a/stem/util/test_tools.py
+++ b/stem/util/test_tools.py
@@ -51,8 +51,11 @@ CONFIG = stem.util.conf.config_dict('test', {
TEST_RUNTIMES = {}
-class SkipTest(Exception):
- 'Notes that the test was skipped.'
+if stem.prereq._is_python_26():
+ class SkipTest(Exception):
+ 'Notes that the test was skipped.'
+else:
+ SkipTest = unittest.case.SkipTest
class AsyncTest(object):
1
0
commit 902e10498b4b6aff358f810efcee7678561fdc04
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Jun 5 10:49:50 2017 -0700
Nice pattern for running async tests
Fiddled with a few options and finally came upon a pattern that's really
elegant. This allows asynchronous tests to be very similar to test methods.
---
run_tests.py | 4 +-
stem/util/test_tools.py | 25 ++++++-
test/integ/installation.py | 159 +++++++++++++++++++++------------------------
test/integ/process.py | 108 ++++++++++++++----------------
4 files changed, 146 insertions(+), 150 deletions(-)
diff --git a/run_tests.py b/run_tests.py
index 2798fd4..961600c 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -235,10 +235,10 @@ def main():
if args.run_integ:
if not args.specific_test or 'test.integ.installation'.startswith(args.specific_test):
- test.integ.installation.setup()
+ test.integ.installation.TestInstallation.run_tests()
if not args.specific_test or 'test.integ.process'.startswith(args.specific_test):
- test.integ.process.setup(args.tor_path)
+ test.integ.process.TestProcess.run_tests(args.tor_path)
if args.run_unit:
test.output.print_divider('UNIT TESTS', True)
diff --git a/stem/util/test_tools.py b/stem/util/test_tools.py
index 47fc02a..b7208e3 100644
--- a/stem/util/test_tools.py
+++ b/stem/util/test_tools.py
@@ -55,9 +55,26 @@ class SkipTest(Exception):
'Notes that the test was skipped.'
-class AsyncTestResult(object):
+class AsyncTest(object):
"""
- Test results that can be applied later.
+ Test that's run asychronously. These are functions (no self reference)
+ performed like the following...
+
+ ::
+
+ class MyTest(unittest.TestCase):
+ @staticmethod
+ def run_tests():
+ MyTest.test_addition = stem.util.test_tools.AsyncTest(MyTest.test_addition).method
+
+ @staticmethod
+ def test_addition():
+ if 1 + 1 != 2:
+ raise AssertionError('tisk, tisk')
+
+ MyTest.run()
+
+ .. versionadded:: 1.6.0
"""
def __init__(self, test_runner, *test_runner_args):
@@ -72,6 +89,10 @@ class AsyncTestResult(object):
finally:
conn.close()
+ # method that can be mixed into TestCases
+
+ self.method = lambda test: self.result(test)
+
self._result_type, self._result_msg = None, None
self._result_lock = threading.RLock()
self._results_pipe, child_pipe = multiprocessing.Pipe()
diff --git a/test/integ/installation.py b/test/integ/installation.py
index 2b58e1d..7cf5346 100644
--- a/test/integ/installation.py
+++ b/test/integ/installation.py
@@ -15,91 +15,10 @@ import stem.util.system
import stem.util.test_tools
import test
-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'
DIST_PATH = os.path.join(test.STEM_BASE, 'dist')
PYTHON_EXE = sys.executable if sys.executable else 'python'
-TEST_INSTALL, TEST_SDIST = None, None
-
-
-def setup():
- """
- Performs our tests asynchronously. They take a while due to iops.
- """
-
- global TEST_INSTALL, TEST_SDIST
-
- TEST_INSTALL = stem.util.test_tools.AsyncTestResult(_test_install)
- TEST_SDIST = stem.util.test_tools.AsyncTestResult(_test_sdist, TEST_INSTALL.pid())
-
-
-def _test_install():
- try:
- try:
- stem.util.system.call('%s setup.py install --prefix %s' % (PYTHON_EXE, BASE_INSTALL_PATH), timeout = 60, cwd = test.STEM_BASE)
- stem.util.system.call('%s setup.py clean --all' % PYTHON_EXE, timeout = 60, cwd = test.STEM_BASE) # tidy up the build directory
- site_packages_paths = glob.glob('%s/lib*/*/site-packages' % BASE_INSTALL_PATH)
- except Exception as exc:
- raise AssertionError("Unable to install with 'python setup.py install': %s" % exc)
-
- if len(site_packages_paths) != 1:
- raise AssertionError('We should only have a single site-packages directory, but instead had: %s' % site_packages_paths)
-
- install_path = site_packages_paths[0]
- version_output = stem.util.system.call([PYTHON_EXE, '-c', "import sys;sys.path.insert(0, '%s');import stem;print(stem.__version__)" % install_path])[0]
-
- if stem.__version__ != version_output:
- raise AssertionError('We expected the installed version to be %s but was %s' % (stem.__version__, version_output))
-
- _assert_has_all_files(install_path)
- finally:
- if os.path.exists(BASE_INSTALL_PATH):
- shutil.rmtree(BASE_INSTALL_PATH)
-
-
-def _test_sdist(dependency_pid):
- while stem.util.system.is_running(dependency_pid):
- time.sleep(0.1) # we need to run these tests serially
-
- git_dir = os.path.join(test.STEM_BASE, '.git')
-
- if not stem.util.system.is_available('git'):
- raise stem.util.test_tools.SkipTest('(git unavailable)')
- elif not os.path.exists(git_dir):
- raise stem.util.test_tools.SkipTest('(not a git checkout)')
-
- if os.path.exists(DIST_PATH):
- raise AssertionError("%s already exists, maybe you manually ran 'python setup.py sdist'?" % DIST_PATH)
-
- try:
- try:
- stem.util.system.call('%s setup.py sdist' % PYTHON_EXE, timeout = 60, cwd = test.STEM_BASE)
- except Exception as exc:
- raise AssertionError("Unable to run 'python setup.py sdist': %s" % exc)
-
- git_contents = [line.split()[-1] for line in stem.util.system.call('git --git-dir=%s ls-tree --full-tree -r HEAD' % git_dir)]
-
- # tarball has a prefix 'stem-[verion]' directory so stipping that out
-
- dist_tar = tarfile.open(os.path.join(DIST_PATH, 'stem-dry-run-%s.tar.gz' % stem.__version__))
- tar_contents = ['/'.join(info.name.split('/')[1:]) for info in dist_tar.getmembers() if info.isfile()]
-
- issues = []
-
- for path in git_contents:
- if path not in tar_contents and path not in ['.gitignore']:
- issues.append(' * %s is missing from our release tarball' % path)
-
- for path in tar_contents:
- if path not in git_contents and path not in ['MANIFEST.in', 'PKG-INFO']:
- issues.append(" * %s isn't expected in our release tarball" % path)
-
- if issues:
- raise AssertionError(INSTALL_MISMATCH_MSG + '\n'.join(issues))
- finally:
- if os.path.exists(DIST_PATH):
- shutil.rmtree(DIST_PATH)
+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"
def _assert_has_all_files(path):
@@ -135,19 +54,87 @@ def _assert_has_all_files(path):
class TestInstallation(unittest.TestCase):
- def test_install(self):
+ @staticmethod
+ def run_tests():
+ test_install = stem.util.test_tools.AsyncTest(TestInstallation.test_install)
+ TestInstallation.test_install = test_install.method
+ TestInstallation.test_sdist = stem.util.test_tools.AsyncTest(TestInstallation.test_sdist, test_install.pid()).method
+
+ @staticmethod
+ def test_install():
"""
Installs with 'python setup.py install' and checks we can use what we
install.
"""
- TEST_INSTALL.result(self)
+ try:
+ try:
+ stem.util.system.call('%s setup.py install --prefix %s' % (PYTHON_EXE, BASE_INSTALL_PATH), timeout = 60, cwd = test.STEM_BASE)
+ stem.util.system.call('%s setup.py clean --all' % PYTHON_EXE, timeout = 60, cwd = test.STEM_BASE) # tidy up the build directory
+ site_packages_paths = glob.glob('%s/lib*/*/site-packages' % BASE_INSTALL_PATH)
+ except Exception as exc:
+ raise AssertionError("Unable to install with 'python setup.py install': %s" % exc)
+
+ if len(site_packages_paths) != 1:
+ raise AssertionError('We should only have a single site-packages directory, but instead had: %s' % site_packages_paths)
+
+ install_path = site_packages_paths[0]
+ version_output = stem.util.system.call([PYTHON_EXE, '-c', "import sys;sys.path.insert(0, '%s');import stem;print(stem.__version__)" % install_path])[0]
- def test_sdist(self):
+ if stem.__version__ != version_output:
+ raise AssertionError('We expected the installed version to be %s but was %s' % (stem.__version__, version_output))
+
+ _assert_has_all_files(install_path)
+ finally:
+ if os.path.exists(BASE_INSTALL_PATH):
+ shutil.rmtree(BASE_INSTALL_PATH)
+
+ @staticmethod
+ def test_sdist(dependency_pid):
"""
Creates a source distribution tarball with 'python setup.py sdist' and
checks that it matches the content of our git repository. This primarily is
meant to test that our MANIFEST.in is up to date.
"""
- TEST_SDIST.result(self)
+ while stem.util.system.is_running(dependency_pid):
+ time.sleep(0.1) # we need to run these tests serially
+
+ git_dir = os.path.join(test.STEM_BASE, '.git')
+
+ if not stem.util.system.is_available('git'):
+ raise stem.util.test_tools.SkipTest('(git unavailable)')
+ elif not os.path.exists(git_dir):
+ raise stem.util.test_tools.SkipTest('(not a git checkout)')
+
+ if os.path.exists(DIST_PATH):
+ raise AssertionError("%s already exists, maybe you manually ran 'python setup.py sdist'?" % DIST_PATH)
+
+ try:
+ try:
+ stem.util.system.call('%s setup.py sdist' % PYTHON_EXE, timeout = 60, cwd = test.STEM_BASE)
+ except Exception as exc:
+ raise AssertionError("Unable to run 'python setup.py sdist': %s" % exc)
+
+ git_contents = [line.split()[-1] for line in stem.util.system.call('git --git-dir=%s ls-tree --full-tree -r HEAD' % git_dir)]
+
+ # tarball has a prefix 'stem-[verion]' directory so stipping that out
+
+ dist_tar = tarfile.open(os.path.join(DIST_PATH, 'stem-dry-run-%s.tar.gz' % stem.__version__))
+ tar_contents = ['/'.join(info.name.split('/')[1:]) for info in dist_tar.getmembers() if info.isfile()]
+
+ issues = []
+
+ for path in git_contents:
+ if path not in tar_contents and path not in ['.gitignore']:
+ issues.append(' * %s is missing from our release tarball' % path)
+
+ for path in tar_contents:
+ if path not in git_contents and path not in ['MANIFEST.in', 'PKG-INFO']:
+ issues.append(" * %s isn't expected in our release tarball" % path)
+
+ if issues:
+ raise AssertionError(INSTALL_MISMATCH_MSG + '\n'.join(issues))
+ finally:
+ if os.path.exists(DIST_PATH):
+ shutil.rmtree(DIST_PATH)
diff --git a/test/integ/process.py b/test/integ/process.py
index 5677be1..3d90c1b 100644
--- a/test/integ/process.py
+++ b/test/integ/process.py
@@ -39,66 +39,12 @@ PublishServerDescriptor 0
DataDirectory %s
"""
-TEST_TAKE_OWNERSHIP_BY_PID = None
-
-
-def setup(tor_cmd):
- global TEST_TAKE_OWNERSHIP_BY_PID
-
- TEST_TAKE_OWNERSHIP_BY_PID = stem.util.test_tools.AsyncTestResult(_test_take_ownership_via_pid, tor_cmd)
-
-
-def _test_take_ownership_via_pid(tor_cmd):
- """
- Checks that the tor process quits after we do if we set take_ownership. To
- test this we spawn a process and trick tor into thinking that it is us.
- """
-
- if not stem.util.system.is_available('sleep'):
- raise stem.util.test_tools.SkipTest('(sleep unavailable)')
- elif test.tor_version() < stem.version.Requirement.TAKEOWNERSHIP:
- raise stem.util.test_tools.SkipTest('(requires )' % stem.version.Requirement.TAKEOWNERSHIP)
-
- data_directory = tempfile.mkdtemp()
-
- try:
- sleep_process = subprocess.Popen(['sleep', '60'])
-
- tor_process = stem.process.launch_tor_with_config(
- tor_cmd = tor_cmd,
- config = {
- 'SocksPort': '2779',
- 'ControlPort': '2780',
- 'DataDirectory': data_directory,
- '__OwningControllerProcess': str(sleep_process.pid),
- },
- completion_percent = 5,
- )
-
- # Kill the sleep command. Tor should quit shortly after.
-
- sleep_process.kill()
- sleep_process.communicate()
-
- # tor polls for the process every fifteen seconds so this may take a
- # while...
- #
- # https://trac.torproject.org/projects/tor/ticket/21281
-
- start_time = time.time()
-
- while time.time() - start_time < 30:
- if tor_process.poll() == 0:
- return # tor exited
-
- time.sleep(0.01)
-
- raise AssertionError("tor didn't quit after the process that owned it terminated")
- finally:
- shutil.rmtree(data_directory)
-
class TestProcess(unittest.TestCase):
+ @staticmethod
+ def run_tests(tor_cmd):
+ TestProcess.test_take_ownership_via_pid = stem.util.test_tools.AsyncTest(TestProcess.test_take_ownership_via_pid, tor_cmd).method
+
def setUp(self):
self.data_directory = tempfile.mkdtemp()
@@ -484,13 +430,55 @@ 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)
- def test_take_ownership_via_pid(self):
+ @staticmethod
+ def test_take_ownership_via_pid(tor_cmd):
"""
Checks that the tor process quits after we do if we set take_ownership. To
test this we spawn a process and trick tor into thinking that it is us.
"""
- TEST_TAKE_OWNERSHIP_BY_PID.result(self)
+ if not stem.util.system.is_available('sleep'):
+ raise stem.util.test_tools.SkipTest('(sleep unavailable)')
+ elif test.tor_version() < stem.version.Requirement.TAKEOWNERSHIP:
+ raise stem.util.test_tools.SkipTest('(requires )' % stem.version.Requirement.TAKEOWNERSHIP)
+
+ data_directory = tempfile.mkdtemp()
+
+ try:
+ sleep_process = subprocess.Popen(['sleep', '60'])
+
+ tor_process = stem.process.launch_tor_with_config(
+ tor_cmd = tor_cmd,
+ config = {
+ 'SocksPort': '2779',
+ 'ControlPort': '2780',
+ 'DataDirectory': data_directory,
+ '__OwningControllerProcess': str(sleep_process.pid),
+ },
+ completion_percent = 5,
+ )
+
+ # Kill the sleep command. Tor should quit shortly after.
+
+ sleep_process.kill()
+ sleep_process.communicate()
+
+ # tor polls for the process every fifteen seconds so this may take a
+ # while...
+ #
+ # https://trac.torproject.org/projects/tor/ticket/21281
+
+ start_time = time.time()
+
+ while time.time() - start_time < 30:
+ if tor_process.poll() == 0:
+ return # tor exited
+
+ time.sleep(0.01)
+
+ raise AssertionError("tor didn't quit after the process that owned it terminated")
+ finally:
+ shutil.rmtree(data_directory)
@test.require.only_run_once
@test.require.version(stem.version.Requirement.TAKEOWNERSHIP)
1
0

08 Jun '17
commit fe88bb6123805de0d4fe4c2e619c60f69a643d75
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Jun 5 11:28:07 2017 -0700
Perform more process tests asynchronously
Performing the rests of the tests that take over a second and are easily moved
to be done asynchronously. Some others that use the run_tor() method will take
a bit more thought but this is most of the long ones.
---
test/integ/process.py | 166 +++++++++++++++++++++++++++++---------------------
1 file changed, 98 insertions(+), 68 deletions(-)
diff --git a/test/integ/process.py b/test/integ/process.py
index 3d90c1b..33e3927 100644
--- a/test/integ/process.py
+++ b/test/integ/process.py
@@ -5,6 +5,7 @@ Tests the stem.process functions with various use cases.
import binascii
import hashlib
import os
+import random
import re
import shutil
import subprocess
@@ -40,10 +41,17 @@ DataDirectory %s
"""
+def random_port():
+ return str(random.randint(1024, 65536))
+
+
class TestProcess(unittest.TestCase):
@staticmethod
def run_tests(tor_cmd):
+ TestProcess.test_launch_tor_with_config_via_file = stem.util.test_tools.AsyncTest(TestProcess.test_launch_tor_with_config_via_file, tor_cmd).method
+ TestProcess.test_launch_tor_with_config_via_stdin = stem.util.test_tools.AsyncTest(TestProcess.test_launch_tor_with_config_via_stdin, tor_cmd).method
TestProcess.test_take_ownership_via_pid = stem.util.test_tools.AsyncTest(TestProcess.test_take_ownership_via_pid, tor_cmd).method
+ TestProcess.test_take_ownership_via_controller = stem.util.test_tools.AsyncTest(TestProcess.test_take_ownership_via_controller, tor_cmd).method
def setUp(self):
self.data_directory = tempfile.mkdtemp()
@@ -319,78 +327,92 @@ class TestProcess(unittest.TestCase):
self.assertEqual(None, launch_async_with_timeout(None))
self.assertEqual(None, launch_async_with_timeout(stem.process.DEFAULT_INIT_TIMEOUT))
- @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):
+ @staticmethod
+ def test_launch_tor_with_config_via_file(tor_cmd):
"""
Exercises launch_tor_with_config when we write a torrc to disk.
"""
- # Launch tor without a torrc, but with a control port. Confirms that this
- # works by checking that we're still able to access the new instance.
-
- runner = test.runner.get_runner()
- tor_process = stem.process.launch_tor_with_config(
- tor_cmd = runner.get_tor_command(),
- config = {
- 'SocksPort': '2777',
- 'ControlPort': '2778',
- 'DataDirectory': self.data_directory,
- },
- completion_percent = 5
- )
-
- control_socket = None
+ data_directory = tempfile.mkdtemp()
+ control_port = random_port()
+ control_socket, tor_process = None, None
try:
- control_socket = stem.socket.ControlPort(port = 2778)
- stem.connection.authenticate(control_socket, chroot_path = runner.get_chroot())
+ # Launch tor without a torrc, but with a control port. Confirms that this
+ # works by checking that we're still able to access the new instance.
+
+ with patch('stem.version.get_system_tor_version', Mock(return_value = stem.version.Version('0.0.0.1'))):
+ tor_process = stem.process.launch_tor_with_config(
+ tor_cmd = tor_cmd,
+ config = {
+ 'SocksPort': random_port(),
+ 'ControlPort': control_port,
+ 'DataDirectory': data_directory,
+ },
+ completion_percent = 5
+ )
+
+ control_socket = stem.socket.ControlPort(port = int(control_port))
+ stem.connection.authenticate(control_socket)
# exercises the socket
control_socket.send('GETCONF ControlPort')
getconf_response = control_socket.recv()
- self.assertEqual('ControlPort=2778', str(getconf_response))
+
+ if 'ControlPort=%s' % control_port != str(getconf_response):
+ raise AssertionError('Expected tor to report its ControlPort as %s but was: %s' % (control_port, getconf_response))
finally:
if control_socket:
control_socket.close()
- tor_process.kill()
- tor_process.wait()
+ if tor_process:
+ tor_process.kill()
+ tor_process.wait()
- @test.require.only_run_once
- @test.require.version(stem.version.Requirement.TORRC_VIA_STDIN)
- def test_launch_tor_with_config_via_stdin(self):
+ shutil.rmtree(data_directory)
+
+ @staticmethod
+ def test_launch_tor_with_config_via_stdin(tor_cmd):
"""
Exercises launch_tor_with_config when we provide our torrc via stdin.
"""
- runner = test.runner.get_runner()
- tor_process = stem.process.launch_tor_with_config(
- tor_cmd = runner.get_tor_command(),
- config = {
- 'SocksPort': '2777',
- 'ControlPort': '2778',
- 'DataDirectory': self.data_directory,
- },
- completion_percent = 5
- )
+ if test.tor_version() < stem.version.Requirement.TORRC_VIA_STDIN:
+ raise stem.util.test_tools.SkipTest('(requires )' % stem.version.Requirement.TORRC_VIA_STDIN)
- control_socket = None
+ data_directory = tempfile.mkdtemp()
+ control_port = random_port()
+ control_socket, tor_process = None, None
try:
- control_socket = stem.socket.ControlPort(port = 2778)
- stem.connection.authenticate(control_socket, chroot_path = runner.get_chroot())
+ tor_process = stem.process.launch_tor_with_config(
+ tor_cmd = tor_cmd,
+ config = {
+ 'SocksPort': random_port(),
+ 'ControlPort': control_port,
+ 'DataDirectory': data_directory,
+ },
+ completion_percent = 5
+ )
+
+ control_socket = stem.socket.ControlPort(port = int(control_port))
+ stem.connection.authenticate(control_socket)
# exercises the socket
control_socket.send('GETCONF ControlPort')
getconf_response = control_socket.recv()
- self.assertEqual('ControlPort=2778', str(getconf_response))
+
+ if 'ControlPort=%s' % control_port != str(getconf_response):
+ raise AssertionError('Expected tor to report its ControlPort as %s but was: %s' % (control_port, getconf_response))
finally:
if control_socket:
control_socket.close()
- tor_process.kill()
- tor_process.wait()
+ if tor_process:
+ tor_process.kill()
+ tor_process.wait()
+
+ shutil.rmtree(data_directory)
@test.require.only_run_once
def test_with_invalid_config(self):
@@ -450,8 +472,8 @@ class TestProcess(unittest.TestCase):
tor_process = stem.process.launch_tor_with_config(
tor_cmd = tor_cmd,
config = {
- 'SocksPort': '2779',
- 'ControlPort': '2780',
+ 'SocksPort': random_port(),
+ 'ControlPort': random_port(),
'DataDirectory': data_directory,
'__OwningControllerProcess': str(sleep_process.pid),
},
@@ -480,41 +502,49 @@ class TestProcess(unittest.TestCase):
finally:
shutil.rmtree(data_directory)
- @test.require.only_run_once
- @test.require.version(stem.version.Requirement.TAKEOWNERSHIP)
- def test_take_ownership_via_controller(self):
+ @staticmethod
+ def test_take_ownership_via_controller(tor_cmd):
"""
Checks that the tor process quits after the controller that owns it
connects, then disconnects..
"""
- tor_process = stem.process.launch_tor_with_config(
- tor_cmd = test.runner.get_runner().get_tor_command(),
- config = {
- 'SocksPort': '2777',
- 'ControlPort': '2778',
- 'DataDirectory': self.data_directory,
- },
- completion_percent = 5,
- take_ownership = True,
- )
+ if test.tor_version() < stem.version.Requirement.TAKEOWNERSHIP:
+ raise stem.util.test_tools.SkipTest('(requires )' % stem.version.Requirement.TAKEOWNERSHIP)
- # We're the controlling process. Just need to connect then disconnect.
+ data_directory = tempfile.mkdtemp()
+ control_port = random_port()
- controller = stem.control.Controller.from_port(port = 2778)
- controller.authenticate()
- controller.close()
+ try:
+ tor_process = stem.process.launch_tor_with_config(
+ tor_cmd = tor_cmd,
+ config = {
+ 'SocksPort': random_port(),
+ 'ControlPort': control_port,
+ 'DataDirectory': data_directory,
+ },
+ completion_percent = 5,
+ take_ownership = True,
+ )
- # give tor a few seconds to quit
- start_time = time.time()
+ # We're the controlling process. Just need to connect then disconnect.
- while time.time() - start_time < 5:
- if tor_process.poll() == 0:
- return # tor exited
+ controller = stem.control.Controller.from_port(port = int(control_port))
+ controller.authenticate()
+ controller.close()
- time.sleep(0.01)
+ # give tor a few seconds to quit
+ start_time = time.time()
- self.fail("tor didn't quit after the controller that owned it disconnected")
+ while time.time() - start_time < 5:
+ if tor_process.poll() == 0:
+ return # tor exited
+
+ time.sleep(0.01)
+
+ raise AssertionError("tor didn't quit after the controller that owned it disconnected")
+ finally:
+ shutil.rmtree(data_directory)
def run_tor(self, *args, **kwargs):
# python doesn't allow us to have individual keyword arguments when there's
1
0
commit 4fe31c5fa8444fed4ccb2d5c47a5191ab0f1b730
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Jun 4 11:10:57 2017 -0700
Run asychronous tests in a subprocess
Fully parallelizing asynchronously run tests to avoid the GIL. This doesn't
have a noticeable impact on our installation runtime (it causes the install
test to be slower, but the overall runtime to be the same). That said, in
theory should benefit others. I'll probably experiment back and forth between
this and threads as we expand the tests which do this.
---
run_tests.py | 10 +++-------
stem/util/test_tools.py | 48 ++++++++++++++++++++++++++++++----------------
test/integ/installation.py | 9 ++++++---
3 files changed, 40 insertions(+), 27 deletions(-)
diff --git a/run_tests.py b/run_tests.py
index 49f8465..a174f65 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -232,6 +232,9 @@ def main():
skipped_tests = 0
+ if args.run_integ and (not args.specific_test or 'test.integ.installation'.startswith(args.specific_test)):
+ test.integ.installation.setup()
+
if args.run_unit:
test.output.print_divider('UNIT TESTS', True)
error_tracker.set_category('UNIT TEST')
@@ -251,10 +254,6 @@ def main():
our_version = stem.version.get_system_tor_version(args.tor_path)
skipped_targets = {}
- integ_setup_thread = None
-
- if not args.specific_test or 'test.integ.installation'.startswith(args.specific_test):
- integ_setup_thread = test.integ.installation.setup()
for target in args.run_targets:
# check if we meet this target's tor version prerequisites
@@ -286,9 +285,6 @@ def main():
# We should have joined on all threads. If not then that indicates a
# leak that could both likely be a bug and disrupt further targets.
- if integ_setup_thread:
- integ_setup_thread.join()
-
active_threads = threading.enumerate()
if len(active_threads) > 1:
diff --git a/stem/util/test_tools.py b/stem/util/test_tools.py
index c9912c0..47fc02a 100644
--- a/stem/util/test_tools.py
+++ b/stem/util/test_tools.py
@@ -30,6 +30,7 @@ to match just against the prefix or suffix. For instance...
import collections
import linecache
+import multiprocessing
import os
import re
import time
@@ -59,31 +60,44 @@ class AsyncTestResult(object):
Test results that can be applied later.
"""
- def __init__(self, runner):
- self._failure = None
- self._skip_reason = None
-
- def _wrapper():
+ def __init__(self, test_runner, *test_runner_args):
+ def _wrapper(conn, runner, args):
try:
- runner()
+ runner(*args) if args else runner()
+ conn.send(('success', None))
except AssertionError as exc:
- self._failure = str(exc)
+ conn.send(('failure', str(exc)))
except SkipTest as exc:
- self._skip_reason = str(exc)
+ conn.send(('skipped', str(exc)))
+ finally:
+ conn.close()
+
+ self._result_type, self._result_msg = None, None
+ self._result_lock = threading.RLock()
+ self._results_pipe, child_pipe = multiprocessing.Pipe()
+ self._test_process = multiprocessing.Process(target = _wrapper, args = (child_pipe, test_runner, test_runner_args))
+ self._test_process.start()
- self._thread = threading.Thread(target = _wrapper)
- self._thread.start()
+ def pid(self):
+ with self._result_lock:
+ return self._test_process.pid if self._test_process else None
def join(self):
- self._thread.join()
+ self.result(None)
def result(self, test):
- self.join()
-
- if self._failure:
- test.fail(self._failure)
- elif self._skip_reason:
- test.skipTest(self._skip_reason)
+ with self._result_lock:
+ if self._test_process:
+ self._result_type, self._result_msg = self._results_pipe.recv()
+ self._test_process.join()
+ self._test_process = None
+
+ if not test:
+ return
+ elif self._result_type == 'failure':
+ test.fail(self._result_msg)
+ elif self._result_type == 'skipped':
+ test.skipTest(self._result_msg)
class Issue(collections.namedtuple('Issue', ['line_number', 'message', 'line'])):
diff --git a/test/integ/installation.py b/test/integ/installation.py
index da918a4..8b39e9d 100644
--- a/test/integ/installation.py
+++ b/test/integ/installation.py
@@ -7,6 +7,7 @@ import os
import shutil
import sys
import tarfile
+import time
import unittest
import stem
@@ -29,7 +30,7 @@ def setup():
global TEST_INSTALL, TEST_SDIST
TEST_INSTALL = stem.util.test_tools.AsyncTestResult(_test_install)
- TEST_SDIST = stem.util.test_tools.AsyncTestResult(_test_sdist)
+ TEST_SDIST = stem.util.test_tools.AsyncTestResult(_test_sdist, TEST_INSTALL.pid())
def _test_install():
@@ -56,8 +57,10 @@ def _test_install():
shutil.rmtree(BASE_INSTALL_PATH)
-def _test_sdist():
- TEST_INSTALL.join() # we need to run these tests serially
+def _test_sdist(dependency_pid):
+ while stem.util.system.is_running(dependency_pid):
+ time.sleep(0.1) # we need to run these tests serially
+
git_dir = os.path.join(test.STEM_BASE, '.git')
if not stem.util.system.is_available('git'):
1
0

08 Jun '17
commit 6b70842612cc7913681b13ed09818e21a6afc42f
Author: Damian Johnson <atagar(a)torproject.org>
Date: Wed Jun 7 09:12:52 2017 -0700
Don't use runner's torrc in process tests
Some of our process tests need to wait for us to start a test tor instance
because they use its torrc. This isn't necessary, and dropping this requirement
will let us perform them in the background.
---
test/integ/process.py | 68 +++++++++++++++++++++++++++------------------------
1 file changed, 36 insertions(+), 32 deletions(-)
diff --git a/test/integ/process.py b/test/integ/process.py
index 0aa639d..44bc807 100644
--- a/test/integ/process.py
+++ b/test/integ/process.py
@@ -35,7 +35,7 @@ except ImportError:
from mock import patch, Mock
BASIC_RELAY_TORRC = """\
-ORPort 6000
+SocksPort 9089
ExtORPort 6001
Nickname stemIntegTest
ExitPolicy reject *:*
@@ -55,28 +55,39 @@ def run_tor(tor_cmd, *args, **kwargs):
expect_failure = kwargs.pop('expect_failure', False)
with_torrc = kwargs.pop('with_torrc', False)
stdin = kwargs.pop('stdin', None)
+ data_directory = None
if kwargs:
raise ValueError('Got unexpected keyword arguments: %s' % kwargs)
if with_torrc:
- args = ['-f', with_torrc] + list(args)
+ data_directory = tempfile.mkdtemp()
+ torrc_path = os.path.join(data_directory, 'torrc')
+
+ with open(torrc_path, 'w') as torrc_file:
+ torrc_file.write(BASIC_RELAY_TORRC % data_directory)
- args = [tor_cmd] + list(args)
- tor_process = subprocess.Popen(args, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ args = ['-f', torrc_path] + list(args)
- if stdin:
- tor_process.stdin.write(stem.util.str_tools._to_bytes(stdin))
+ try:
+ args = [tor_cmd] + list(args)
+ tor_process = subprocess.Popen(args, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
- stdout = tor_process.communicate()[0]
- exit_status = tor_process.poll()
+ if stdin:
+ tor_process.stdin.write(stem.util.str_tools._to_bytes(stdin))
- if exit_status and not expect_failure:
- raise AssertionError("Tor failed to start when we ran: %s\n%s" % (' '.join(args), stdout))
- elif not exit_status and expect_failure:
- raise AssertionError("Didn't expect tor to be able to start when we run: %s\n%s" % (' '.join(args), stdout))
+ stdout = tor_process.communicate()[0]
+ exit_status = tor_process.poll()
- return stem.util.str_tools._to_unicode(stdout) if stem.prereq.is_python_3() else stdout
+ if exit_status and not expect_failure:
+ raise AssertionError("Tor failed to start when we ran: %s\n%s" % (' '.join(args), stdout))
+ elif not exit_status and expect_failure:
+ raise AssertionError("Didn't expect tor to be able to start when we run: %s\n%s" % (' '.join(args), stdout))
+
+ return stem.util.str_tools._to_unicode(stdout) if stem.prereq.is_python_3() else stdout
+ finally:
+ if data_directory:
+ shutil.rmtree(data_directory)
class TestProcess(unittest.TestCase):
@@ -89,7 +100,6 @@ class TestProcess(unittest.TestCase):
def setUp(self):
self.data_directory = tempfile.mkdtemp()
self.tor_cmd = test.runner.get_runner().get_tor_command()
- self.torrc_path = test.runner.get_runner().get_torrc_path()
def tearDown(self):
shutil.rmtree(self.data_directory)
@@ -192,25 +202,21 @@ class TestProcess(unittest.TestCase):
Exercises our 'tor --dump-config' arugments.
"""
- short_output = run_tor(self.tor_cmd, '--dump-config', 'short', with_torrc = self.torrc_path)
- non_builtin_output = run_tor(self.tor_cmd, '--dump-config', 'non-builtin', with_torrc = self.torrc_path)
- full_output = run_tor(self.tor_cmd, '--dump-config', 'full', with_torrc = self.torrc_path)
- run_tor(self.tor_cmd, '--dump-config', 'invalid_option', with_torrc = self.torrc_path, expect_failure = True)
-
- torrc_contents = [line for line in test.runner.get_runner().get_torrc_contents().splitlines() if not line.startswith('#')]
-
- self.assertEqual(sorted(torrc_contents), sorted(short_output.strip().splitlines()))
- self.assertEqual(sorted(torrc_contents), sorted(non_builtin_output.strip().splitlines()))
+ short_output = run_tor(self.tor_cmd, '--dump-config', 'short', with_torrc = True)
+ non_builtin_output = run_tor(self.tor_cmd, '--dump-config', 'non-builtin', with_torrc = True)
+ full_output = run_tor(self.tor_cmd, '--dump-config', 'full', with_torrc = True)
+ run_tor(self.tor_cmd, '--dump-config', 'invalid_option', with_torrc = True, expect_failure = True)
- for line in torrc_contents:
- self.assertTrue(line in full_output)
+ self.assertTrue('Nickname stemIntegTest' in short_output)
+ self.assertTrue('Nickname stemIntegTest' in non_builtin_output)
+ self.assertTrue('Nickname stemIntegTest' in full_output)
def test_validate_config_argument(self):
"""
Exercises our 'tor --validate-config' argument.
"""
- valid_output = run_tor(self.tor_cmd, '--verify-config', with_torrc = self.torrc_path)
+ valid_output = run_tor(self.tor_cmd, '--verify-config', with_torrc = True)
self.assertTrue('Configuration was valid\n' in valid_output)
run_tor(self.tor_cmd, '--verify-config', '-f', __file__, expect_failure = True)
@@ -222,13 +228,13 @@ class TestProcess(unittest.TestCase):
# This command should only work with a relay (which our test instance isn't).
- output = run_tor(self.tor_cmd, '--list-fingerprint', with_torrc = self.torrc_path, expect_failure = True)
+ output = run_tor(self.tor_cmd, '--list-fingerprint', with_torrc = True, expect_failure = True)
self.assertTrue("Clients don't have long-term identity keys. Exiting." in output)
torrc_path = os.path.join(self.data_directory, 'torrc')
with open(torrc_path, 'w') as torrc_file:
- torrc_file.write(BASIC_RELAY_TORRC % self.data_directory)
+ torrc_file.write(BASIC_RELAY_TORRC % self.data_directory + '\nORPort 6954')
output = run_tor(self.tor_cmd, '--list-fingerprint', '-f', torrc_path)
nickname, fingerprint_with_spaces = output.splitlines()[-1].split(' ', 1)
@@ -292,8 +298,7 @@ class TestProcess(unittest.TestCase):
torrc_file.write(BASIC_RELAY_TORRC % data_directory)
config_args = [
- '+ORPort', '9003', # appends an extra ORPort
- 'SocksPort', '9090',
+ '+SocksPort', '9090', # append an extra SocksPort
'/ExtORPort', # drops our ExtORPort
'/TransPort', # drops a port we didn't originally have
'+ControlPort', '9005', # appends a ControlPort where we didn't have any before
@@ -306,9 +311,8 @@ class TestProcess(unittest.TestCase):
'ControlPort 9005',
'ExitPolicy reject *:*',
'Nickname stemIntegTest',
- 'ORPort 6000',
- 'ORPort 9003',
'PublishServerDescriptor 0',
+ 'SocksPort 9089',
'SocksPort 9090',
]
1
0
commit 53c660f32babe24da6ea9130ed68688241676792
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Jun 5 22:28:06 2017 -0700
Run most process tests asynchrously
Expanding the number of tests we run asynchronously to cover most of them.
There's three left that require the torrc that's generated when tor starts.
---
test/integ/process.py | 416 ++++++++++++++++++++++++++++++--------------------
1 file changed, 251 insertions(+), 165 deletions(-)
diff --git a/test/integ/process.py b/test/integ/process.py
index 09c51ed..4e59e00 100644
--- a/test/integ/process.py
+++ b/test/integ/process.py
@@ -22,6 +22,7 @@ import stem.util.system
import stem.util.test_tools
import stem.util.tor_tools
import stem.version
+import test
import test.require
import test.runner
@@ -45,72 +46,133 @@ def random_port():
return str(random.randint(1024, 65536))
+def run_tor(tor_cmd, *args, **kwargs):
+ # python doesn't allow us to have individual keyword arguments when there's
+ # an arbitrary number of positional arguments, so explicitly checking
+
+ expect_failure = kwargs.pop('expect_failure', False)
+ with_torrc = kwargs.pop('with_torrc', False)
+ stdin = kwargs.pop('stdin', None)
+
+ if kwargs:
+ raise ValueError('Got unexpected keyword arguments: %s' % kwargs)
+
+ if with_torrc:
+ args = ['-f', with_torrc] + list(args)
+
+ args = [tor_cmd] + list(args)
+ tor_process = subprocess.Popen(args, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+
+ if stdin:
+ tor_process.stdin.write(stem.util.str_tools._to_bytes(stdin))
+
+ stdout = tor_process.communicate()[0]
+ exit_status = tor_process.poll()
+
+ if exit_status and not expect_failure:
+ raise AssertionError("Tor failed to start when we ran: %s\n%s" % (' '.join(args), stdout))
+ elif not exit_status and expect_failure:
+ raise AssertionError("Didn't expect tor to be able to start when we run: %s\n%s" % (' '.join(args), stdout))
+
+ return stem.util.str_tools._to_unicode(stdout) if stem.prereq.is_python_3() else stdout
+
+
class TestProcess(unittest.TestCase):
@staticmethod
def run_tests(tor_cmd):
- TestProcess.test_launch_tor_with_config_via_file = stem.util.test_tools.AsyncTest(TestProcess.test_launch_tor_with_config_via_file, args = (tor_cmd,)).method
- TestProcess.test_launch_tor_with_config_via_stdin = stem.util.test_tools.AsyncTest(TestProcess.test_launch_tor_with_config_via_stdin, args = (tor_cmd,)).method
- TestProcess.test_take_ownership_via_pid = stem.util.test_tools.AsyncTest(TestProcess.test_take_ownership_via_pid, args = (tor_cmd,)).method
- TestProcess.test_take_ownership_via_controller = stem.util.test_tools.AsyncTest(TestProcess.test_take_ownership_via_controller, args = (tor_cmd,)).method
+ async_tests = (
+ 'test_version_argument',
+ 'test_help_argument',
+ 'test_quiet_argument',
+ 'test_hush_argument',
+ 'test_hash_password',
+ 'test_hash_password_requires_argument',
+ 'test_list_torrc_options_argument',
+ 'test_torrc_arguments',
+ 'test_torrc_arguments_via_stdin',
+ 'test_with_missing_torrc',
+ 'test_can_run_multithreaded',
+ 'test_launch_tor_with_config_via_file',
+ 'test_launch_tor_with_config_via_stdin',
+ 'test_with_invalid_config',
+ 'test_launch_tor_with_timeout',
+ 'test_take_ownership_via_pid',
+ 'test_take_ownership_via_controller',
+ )
+
+ for func in async_tests:
+ setattr(TestProcess, func, stem.util.test_tools.AsyncTest(getattr(TestProcess, func), args = (tor_cmd,)).method)
def setUp(self):
self.data_directory = tempfile.mkdtemp()
+ self.tor_cmd = test.runner.get_runner().get_tor_command()
+ self.torrc_path = test.runner.get_runner().get_torrc_path()
def tearDown(self):
shutil.rmtree(self.data_directory)
- @test.require.controller
- def test_version_argument(self):
+ @staticmethod
+ def test_version_argument(tor_cmd):
"""
Check that 'tor --version' matches 'GETINFO version'.
"""
- with test.runner.get_runner().get_tor_controller() as controller:
- self.assertEqual('Tor version %s.\n' % controller.get_version(), self.run_tor('--version'))
+ version_output = run_tor(tor_cmd, '--version')
+
+ if 'Tor version %s.\n' % test.tor_version() != version_output:
+ raise AssertionError('Unexpected response: %s' % version_output)
- def test_help_argument(self):
+ @staticmethod
+ def test_help_argument(tor_cmd):
"""
Check that 'tor --help' provides the expected output.
"""
- help_output = self.run_tor('--help')
+ help_output = run_tor(tor_cmd, '--help')
- self.assertTrue(help_output.startswith('Copyright (c) 2001'))
- self.assertTrue(help_output.endswith('tor -f <torrc> [args]\nSee man page for options, or https://www.torproject.org/ for documentation.\n'))
+ if not help_output.startswith('Copyright (c) 2001') or not help_output.endswith('tor -f <torrc> [args]\nSee man page for options, or https://www.torproject.org/ for documentation.\n'):
+ raise AssertionError("Help output didn't have the expected strings: %s" % help_output)
- # should be an alias for 'tor -h'
+ if help_output != run_tor(tor_cmd, '-h'):
+ raise AssertionError("'tor -h' should simply be an alias for 'tor --help'")
- self.assertEqual(help_output, self.run_tor('-h'))
-
- def test_quiet_argument(self):
+ @staticmethod
+ def test_quiet_argument(tor_cmd):
"""
Check that we don't provide anything on stdout when running 'tor --quiet'.
"""
- self.assertEqual('', self.run_tor('--quiet', '--invalid_argument', 'true', expect_failure = True))
+ if '' != run_tor(tor_cmd, '--quiet', '--invalid_argument', 'true', expect_failure = True):
+ raise AssertionError('No output should be provided with the --quiet argument')
- def test_hush_argument(self):
+ @staticmethod
+ def test_hush_argument(tor_cmd):
"""
Check that we only get warnings and errors when running 'tor --hush'.
"""
- output = self.run_tor('--hush', '--invalid_argument', expect_failure = True)
- self.assertTrue("[warn] Command-line option '--invalid_argument' with no value. Failing." in output)
- self.assertTrue('[err] Reading config failed--see warnings above.' in output)
+ output = run_tor(tor_cmd, '--hush', '--invalid_argument', expect_failure = True)
- output = self.run_tor('--hush', '--invalid_argument', 'true', expect_failure = True)
- self.assertTrue("[warn] Failed to parse/validate config: Unknown option 'invalid_argument'. Failing." in output)
- self.assertTrue('[err] Reading config failed--see warnings above.' in output)
+ if "[warn] Command-line option '--invalid_argument' with no value. Failing." not in output:
+ raise AssertionError('Unexpected response: %s' % output)
- def test_hash_password(self):
+ output = run_tor(tor_cmd, '--hush', '--invalid_argument', 'true', expect_failure = True)
+
+ if "[warn] Failed to parse/validate config: Unknown option 'invalid_argument'. Failing." not in output:
+ raise AssertionError('Unexpected response: %s' % output)
+
+ @staticmethod
+ def test_hash_password(tor_cmd):
"""
Hash a controller password. It's salted so can't assert that we get a
particular value. Also, tor's output is unnecessarily verbose so including
hush to cut it down.
"""
- output = self.run_tor('--hush', '--hash-password', 'my_password').splitlines()[-1]
- self.assertTrue(re.match('^16:[0-9A-F]{58}$', output))
+ output = run_tor(tor_cmd, '--hush', '--hash-password', 'my_password').splitlines()[-1]
+
+ if not re.match('^16:[0-9A-F]{58}$', output):
+ raise AssertionError("Unexpected response from 'tor --hash-password my_password': %s" % output)
# I'm not gonna even pretend to understand the following. Ported directly
# from tor's test_cmdline_args.py.
@@ -127,27 +189,30 @@ class TestProcess(unittest.TestCase):
repetitions = count // len(stuff) + 1
inp = (stuff * repetitions)[:count]
- self.assertEqual(hashlib.sha1(inp).digest(), hashed)
+ if hashlib.sha1(inp).digest() != hashed:
+ raise AssertionError('Password hash not what we expected (%s rather than %s)' % (hashlib.sha1(inp).digest(), hashed))
- def test_hash_password_requires_argument(self):
+ @staticmethod
+ def test_hash_password_requires_argument(tor_cmd):
"""
Check that 'tor --hash-password' balks if not provided with something to
hash.
"""
- output = self.run_tor('--hash-password', expect_failure = True)
- self.assertTrue("[warn] Command-line option '--hash-password' with no value. Failing." in output)
- self.assertTrue('[err] Reading config failed--see warnings above.' in output)
+ output = run_tor(tor_cmd, '--hash-password', expect_failure = True)
+
+ if "[warn] Command-line option '--hash-password' with no value. Failing." not in output:
+ raise AssertionError("'tor --hash-password' should require an argument")
def test_dump_config_argument(self):
"""
Exercises our 'tor --dump-config' arugments.
"""
- short_output = self.run_tor('--dump-config', 'short', with_torrc = True)
- non_builtin_output = self.run_tor('--dump-config', 'non-builtin', with_torrc = True)
- full_output = self.run_tor('--dump-config', 'full', with_torrc = True)
- self.run_tor('--dump-config', 'invalid_option', with_torrc = True, expect_failure = True)
+ short_output = run_tor(self.tor_cmd, '--dump-config', 'short', with_torrc = self.torrc_path)
+ non_builtin_output = run_tor(self.tor_cmd, '--dump-config', 'non-builtin', with_torrc = self.torrc_path)
+ full_output = run_tor(self.tor_cmd, '--dump-config', 'full', with_torrc = self.torrc_path)
+ run_tor(self.tor_cmd, '--dump-config', 'invalid_option', with_torrc = self.torrc_path, expect_failure = True)
torrc_contents = [line for line in test.runner.get_runner().get_torrc_contents().splitlines() if not line.startswith('#')]
@@ -162,10 +227,10 @@ class TestProcess(unittest.TestCase):
Exercises our 'tor --validate-config' argument.
"""
- valid_output = self.run_tor('--verify-config', with_torrc = True)
+ valid_output = run_tor(self.tor_cmd, '--verify-config', with_torrc = self.torrc_path)
self.assertTrue('Configuration was valid\n' in valid_output)
- self.run_tor('--verify-config', '-f', __file__, expect_failure = True)
+ run_tor(self.tor_cmd, '--verify-config', '-f', __file__, expect_failure = True)
def test_list_fingerprint_argument(self):
"""
@@ -174,7 +239,7 @@ class TestProcess(unittest.TestCase):
# This command should only work with a relay (which our test instance isn't).
- output = self.run_tor('--list-fingerprint', with_torrc = True, expect_failure = True)
+ output = run_tor(self.tor_cmd, '--list-fingerprint', with_torrc = self.torrc_path, expect_failure = True)
self.assertTrue("Clients don't have long-term identity keys. Exiting." in output)
torrc_path = os.path.join(self.data_directory, 'torrc')
@@ -182,7 +247,7 @@ class TestProcess(unittest.TestCase):
with open(torrc_path, 'w') as torrc_file:
torrc_file.write(BASIC_RELAY_TORRC % self.data_directory)
- output = self.run_tor('--list-fingerprint', '-f', torrc_path)
+ output = run_tor(self.tor_cmd, '--list-fingerprint', '-f', torrc_path)
nickname, fingerprint_with_spaces = output.splitlines()[-1].split(' ', 1)
fingerprint = fingerprint_with_spaces.replace(' ', '')
@@ -194,16 +259,18 @@ class TestProcess(unittest.TestCase):
expected = 'stemIntegTest %s\n' % fingerprint
self.assertEqual(expected, fingerprint_file.read())
- def test_list_torrc_options_argument(self):
+ @staticmethod
+ def test_list_torrc_options_argument(tor_cmd):
"""
Exercise our 'tor --list-torrc-options' argument.
"""
- output = self.run_tor('--list-torrc-options')
- self.assertTrue(len(output.splitlines()) > 50)
- self.assertTrue(output.splitlines()[0] <= 'AccountingMax')
- self.assertTrue('UseBridges' in output)
- self.assertTrue('SocksPort' in output)
+ output = run_tor(tor_cmd, '--list-torrc-options')
+
+ if len(output.splitlines()) < 50:
+ raise AssertionError("'tor --list-torrc-options' should have numerous entries, but only had %i" % len(output.splitlines()))
+ elif 'UseBridges' not in output or 'SocksPort' not in output:
+ raise AssertionError("'tor --list-torrc-options' didn't have options we expect")
@test.require.command('sleep')
@patch('re.compile', Mock(side_effect = KeyboardInterrupt('nope')))
@@ -228,104 +295,132 @@ class TestProcess(unittest.TestCase):
self.assertEqual('nope', str(exc))
- def test_torrc_arguments(self):
+ @staticmethod
+ def test_torrc_arguments(tor_cmd):
"""
Pass configuration options on the commandline.
"""
- torrc_path = os.path.join(self.data_directory, 'torrc')
-
- with open(torrc_path, 'w') as torrc_file:
- torrc_file.write(BASIC_RELAY_TORRC % self.data_directory)
-
- config_args = [
- '+ORPort', '9003', # appends an extra ORPort
- 'SocksPort', '9090',
- '/ExtORPort', # drops our ExtORPort
- '/TransPort', # drops a port we didn't originally have
- '+ControlPort', '9005', # appends a ControlPort where we didn't have any before
- ]
-
- output = self.run_tor('-f', torrc_path, '--dump-config', 'short', *config_args)
- result = [line for line in output.splitlines() if not line.startswith('DataDirectory')]
-
- expected = [
- 'ControlPort 9005',
- 'ExitPolicy reject *:*',
- 'Nickname stemIntegTest',
- 'ORPort 6000',
- 'ORPort 9003',
- 'PublishServerDescriptor 0',
- 'SocksPort 9090',
- ]
+ data_directory = tempfile.mkdtemp()
+ torrc_path = os.path.join(data_directory, 'torrc')
- self.assertEqual(expected, result)
+ try:
+ with open(torrc_path, 'w') as torrc_file:
+ torrc_file.write(BASIC_RELAY_TORRC % data_directory)
+
+ config_args = [
+ '+ORPort', '9003', # appends an extra ORPort
+ 'SocksPort', '9090',
+ '/ExtORPort', # drops our ExtORPort
+ '/TransPort', # drops a port we didn't originally have
+ '+ControlPort', '9005', # appends a ControlPort where we didn't have any before
+ ]
+
+ output = run_tor(tor_cmd, '-f', torrc_path, '--dump-config', 'short', *config_args)
+ result = [line for line in output.splitlines() if not line.startswith('DataDirectory')]
+
+ expected = [
+ 'ControlPort 9005',
+ 'ExitPolicy reject *:*',
+ 'Nickname stemIntegTest',
+ 'ORPort 6000',
+ 'ORPort 9003',
+ 'PublishServerDescriptor 0',
+ 'SocksPort 9090',
+ ]
+
+ if expected != result:
+ raise AssertionError("Unexpected output from 'tor -f torrc --dump-config short': %s" % result)
+ finally:
+ shutil.rmtree(data_directory)
- @test.require.version(stem.version.Requirement.TORRC_VIA_STDIN)
- def test_torrc_arguments_via_stdin(self):
+ @staticmethod
+ def test_torrc_arguments_via_stdin(tor_cmd):
"""
Pass configuration options via stdin.
"""
- torrc = BASIC_RELAY_TORRC % self.data_directory
- output = self.run_tor('-f', '-', '--dump-config', 'short', stdin = torrc)
- self.assertEqual(sorted(torrc.splitlines()), sorted(output.splitlines()))
+ if test.tor_version() < stem.version.Requirement.TORRC_VIA_STDIN:
+ raise stem.util.test_tools.SkipTest('(requires )' % stem.version.Requirement.TORRC_VIA_STDIN)
+
+ data_directory = tempfile.mkdtemp()
+
+ try:
+ torrc = BASIC_RELAY_TORRC % data_directory
+ output = run_tor(tor_cmd, '-f', '-', '--dump-config', 'short', stdin = torrc)
+
+ if sorted(torrc.splitlines()) != sorted(output.splitlines()):
+ raise AssertionError("Unexpected output from 'tor -f - --dump-config short': %s" % output)
+ finally:
+ shutil.rmtree(data_directory)
- def test_with_missing_torrc(self):
+ @staticmethod
+ def test_with_missing_torrc(tor_cmd):
"""
Provide a torrc path that doesn't exist.
"""
- output = self.run_tor('-f', '/path/that/really/shouldnt/exist', '--verify-config', expect_failure = True)
- self.assertTrue('[warn] Unable to open configuration file "/path/that/really/shouldnt/exist".' in output)
- self.assertTrue('[err] Reading config failed--see warnings above.' in output)
+ output = run_tor(tor_cmd, '-f', '/path/that/really/shouldnt/exist', '--verify-config', expect_failure = True)
+
+ if '[warn] Unable to open configuration file "/path/that/really/shouldnt/exist".' not in output:
+ raise AssertionError('Tor refuse to read a non-existant torrc file')
+
+ output = run_tor(tor_cmd, '-f', '/path/that/really/shouldnt/exist', '--verify-config', '--ignore-missing-torrc')
- output = self.run_tor('-f', '/path/that/really/shouldnt/exist', '--verify-config', '--ignore-missing-torrc')
- self.assertTrue('[notice] Configuration file "/path/that/really/shouldnt/exist" not present, using reasonable defaults.' in output)
- self.assertTrue('Configuration was valid' in output)
+ if '[notice] Configuration file "/path/that/really/shouldnt/exist" not present, using reasonable defaults.' not in output:
+ raise AssertionError('Missing torrc should be allowed with --ignore-missing-torrc')
- @test.require.only_run_once
- def test_can_run_multithreaded(self):
+ @staticmethod
+ def test_can_run_multithreaded(tor_cmd):
"""
Our launch_tor() function uses signal to support its timeout argument.
This only works in the main thread so ensure we give a useful message when
it isn't.
"""
- # Tries running tor in another thread with the given timeout argument. This
- # issues an invalid torrc so we terminate right away if we get to the point
- # of actually invoking tor.
- #
- # Returns None if launching tor is successful, and otherwise returns the
- # exception we raised.
+ data_directory = tempfile.mkdtemp()
- def launch_async_with_timeout(timeout_arg):
- raised_exc, tor_cmd = [None], test.runner.get_runner().get_tor_command()
+ try:
+ # Tries running tor in another thread with the given timeout argument. This
+ # issues an invalid torrc so we terminate right away if we get to the point
+ # of actually invoking tor.
+ #
+ # Returns None if launching tor is successful, and otherwise returns the
+ # exception we raised.
+
+ def launch_async_with_timeout(timeout_arg):
+ raised_exc = [None]
- def short_launch():
- try:
- stem.process.launch_tor_with_config({'SocksPort': 'invalid', 'DataDirectory': self.data_directory}, tor_cmd, 100, None, timeout_arg)
- except Exception as exc:
- raised_exc[0] = exc
+ def short_launch():
+ try:
+ stem.process.launch_tor_with_config({'SocksPort': 'invalid', 'DataDirectory': data_directory}, tor_cmd, 100, None, timeout_arg)
+ except Exception as exc:
+ raised_exc[0] = exc
- t = threading.Thread(target = short_launch)
- t.start()
- t.join()
+ t = threading.Thread(target = short_launch)
+ t.start()
+ t.join()
- if 'Invalid SocksPort' in str(raised_exc[0]):
- return None # got to the point of invoking tor
- else:
- return raised_exc[0]
+ if 'Invalid SocksPort' in str(raised_exc[0]):
+ return None # got to the point of invoking tor
+ else:
+ return raised_exc[0]
- exc = launch_async_with_timeout(0.5)
- self.assertEqual(OSError, type(exc))
- self.assertEqual('Launching tor with a timeout can only be done in the main thread', str(exc))
+ exc = launch_async_with_timeout(0.5)
- # We should launch successfully if no timeout is specified or we specify it
- # to be 'None'.
+ if type(exc) != OSError or str(exc) != 'Launching tor with a timeout can only be done in the main thread':
+ raise AssertionError("Exception isn't what we expected: %s" % exc)
- self.assertEqual(None, launch_async_with_timeout(None))
- self.assertEqual(None, launch_async_with_timeout(stem.process.DEFAULT_INIT_TIMEOUT))
+ # We should launch successfully if no timeout is specified or we specify it
+ # to be 'None'.
+
+ if launch_async_with_timeout(None) is not None:
+ raise AssertionError('Launching tor without a timeout should be successful')
+
+ if launch_async_with_timeout(stem.process.DEFAULT_INIT_TIMEOUT) is not None:
+ raise AssertionError('Launching tor with the default timeout should be successful')
+ finally:
+ shutil.rmtree(data_directory)
@staticmethod
def test_launch_tor_with_config_via_file(tor_cmd):
@@ -414,8 +509,8 @@ class TestProcess(unittest.TestCase):
shutil.rmtree(data_directory)
- @test.require.only_run_once
- def test_with_invalid_config(self):
+ @staticmethod
+ def test_with_invalid_config(tor_cmd):
"""
Spawn a tor process with a configuration that should make it dead on arrival.
"""
@@ -425,32 +520,53 @@ class TestProcess(unittest.TestCase):
# [warn] Failed to parse/validate config: Failed to bind one of the listener ports.
# [err] Reading config failed--see warnings above.
- self.assertRaisesRegexp(
- OSError,
- 'Process terminated: Failed to bind one of the listener ports.',
- stem.process.launch_tor_with_config,
- tor_cmd = test.runner.get_runner().get_tor_command(),
- config = {
- 'SocksPort': '2777',
- 'ControlPort': '2777',
- 'DataDirectory': self.data_directory,
- },
- )
+ data_directory = tempfile.mkdtemp()
+ both_ports = random_port()
+
+ try:
+ stem.process.launch_tor_with_config(
+ tor_cmd = tor_cmd,
+ config = {
+ 'SocksPort': both_ports,
+ 'ControlPort': both_ports,
+ 'DataDirectory': data_directory,
+ },
+ )
+
+ raise AssertionError('Tor should fail to launch')
+ except OSError as exc:
+ if str(exc) != 'Process terminated: Failed to bind one of the listener ports.':
+ raise AssertionError('Unexpected error response from tor: %s' % exc)
+ finally:
+ shutil.rmtree(data_directory)
- @test.require.only_run_once
- def test_launch_tor_with_timeout(self):
+ @staticmethod
+ def test_launch_tor_with_timeout(tor_cmd):
"""
Runs launch_tor where it times out before completing.
"""
- runner = test.runner.get_runner()
+ data_directory = tempfile.mkdtemp()
start_time = time.time()
- config = {'SocksPort': '2777', 'DataDirectory': self.data_directory}
- self.assertRaises(OSError, stem.process.launch_tor_with_config, config, runner.get_tor_command(), 100, None, 0.05)
- runtime = time.time() - start_time
- if not (runtime > 0.05 and runtime < 1):
- self.fail('Test should have taken 0.05-1 seconds, took %0.1f instead' % runtime)
+ try:
+ stem.process.launch_tor_with_config(
+ tor_cmd = tor_cmd,
+ timeout = 0.05,
+ config = {
+ 'SocksPort': random_port(),
+ 'DataDirectory': data_directory,
+ },
+ )
+
+ raise AssertionError('Tor should fail to launch')
+ except OSError:
+ runtime = time.time() - start_time
+
+ if not (runtime > 0.05 and runtime < 1):
+ raise AssertionError('Test should have taken 0.05-1 seconds, took %0.1f instead' % runtime)
+ finally:
+ shutil.rmtree(data_directory)
@staticmethod
def test_take_ownership_via_pid(tor_cmd):
@@ -545,33 +661,3 @@ class TestProcess(unittest.TestCase):
raise AssertionError("tor didn't quit after the controller that owned it disconnected")
finally:
shutil.rmtree(data_directory)
-
- def run_tor(self, *args, **kwargs):
- # python doesn't allow us to have individual keyword arguments when there's
- # an arbitrary number of positional arguments, so explicitly checking
-
- expect_failure = kwargs.pop('expect_failure', False)
- with_torrc = kwargs.pop('with_torrc', False)
- stdin = kwargs.pop('stdin', None)
-
- if kwargs:
- raise ValueError('Got unexpected keyword arguments: %s' % kwargs)
-
- if with_torrc:
- args = ['-f', test.runner.get_runner().get_torrc_path()] + list(args)
-
- args = [test.runner.get_runner().get_tor_command()] + list(args)
- tor_process = subprocess.Popen(args, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
-
- if stdin:
- tor_process.stdin.write(stem.util.str_tools._to_bytes(stdin))
-
- stdout = tor_process.communicate()[0]
- exit_status = tor_process.poll()
-
- if exit_status and not expect_failure:
- self.fail("Tor failed to start when we ran: %s\n%s" % (' '.join(args), stdout))
- elif not exit_status and expect_failure:
- self.fail("Didn't expect tor to be able to start when we run: %s\n%s" % (' '.join(args), stdout))
-
- return stem.util.str_tools._to_unicode(stdout) if stem.prereq.is_python_3() else stdout
1
0
commit ad72e524bd1402ca74a96437d5f745932b650e7a
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Jun 3 17:55:24 2017 -0700
Helper to asynchronously run tests
We already ran our installation tests asynchronously, but in a one-off fashion.
Adding a helper so we can experiment with fully parallelizing it and apply this
to other tests.
---
run_tests.py | 3 --
stem/util/test_tools.py | 37 +++++++++++++
test/integ/installation.py | 130 ++++++++++++++++++++-------------------------
3 files changed, 96 insertions(+), 74 deletions(-)
diff --git a/run_tests.py b/run_tests.py
index 7c894d2..49f8465 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -309,9 +309,6 @@ def main():
except OSError:
error_tracker.register_error()
finally:
- if integ_setup_thread:
- test.integ.installation.clean()
-
println()
integ_runner.stop()
println()
diff --git a/stem/util/test_tools.py b/stem/util/test_tools.py
index 6964572..c9912c0 100644
--- a/stem/util/test_tools.py
+++ b/stem/util/test_tools.py
@@ -33,6 +33,7 @@ import linecache
import os
import re
import time
+import threading
import unittest
import stem.prereq
@@ -49,6 +50,42 @@ CONFIG = stem.util.conf.config_dict('test', {
TEST_RUNTIMES = {}
+class SkipTest(Exception):
+ 'Notes that the test was skipped.'
+
+
+class AsyncTestResult(object):
+ """
+ Test results that can be applied later.
+ """
+
+ def __init__(self, runner):
+ self._failure = None
+ self._skip_reason = None
+
+ def _wrapper():
+ try:
+ runner()
+ except AssertionError as exc:
+ self._failure = str(exc)
+ except SkipTest as exc:
+ self._skip_reason = str(exc)
+
+ self._thread = threading.Thread(target = _wrapper)
+ self._thread.start()
+
+ def join(self):
+ self._thread.join()
+
+ def result(self, test):
+ self.join()
+
+ if self._failure:
+ test.fail(self._failure)
+ elif self._skip_reason:
+ test.skipTest(self._skip_reason)
+
+
class Issue(collections.namedtuple('Issue', ['line_number', 'message', 'line'])):
"""
Issue encountered by pyflakes or pycodestyle.
diff --git a/test/integ/installation.py b/test/integ/installation.py
index dd4eb88..da918a4 100644
--- a/test/integ/installation.py
+++ b/test/integ/installation.py
@@ -7,66 +7,95 @@ import os
import shutil
import sys
import tarfile
-import threading
import unittest
import stem
import stem.util.system
import test
-import test.require
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'
DIST_PATH = os.path.join(test.STEM_BASE, 'dist')
-SETUP_THREAD, INSTALL_FAILURE, INSTALL_PATH, SDIST_FAILURE = None, None, None, None
PYTHON_EXE = sys.executable if sys.executable else 'python'
+TEST_INSTALL, TEST_SDIST = None, None
def setup():
"""
- Performs setup our tests will need. This mostly just needs disk iops so it
- can happen asynchronously with other tests.
+ Performs our tests asynchronously. They take a while due to iops.
"""
- global SETUP_THREAD
+ global TEST_INSTALL, TEST_SDIST
- def _setup():
- global INSTALL_FAILURE, INSTALL_PATH, SDIST_FAILURE
+ TEST_INSTALL = stem.util.test_tools.AsyncTestResult(_test_install)
+ TEST_SDIST = stem.util.test_tools.AsyncTestResult(_test_sdist)
+
+def _test_install():
+ try:
try:
stem.util.system.call('%s setup.py install --prefix %s' % (PYTHON_EXE, BASE_INSTALL_PATH), timeout = 60, cwd = test.STEM_BASE)
stem.util.system.call('%s setup.py clean --all' % PYTHON_EXE, timeout = 60, cwd = test.STEM_BASE) # tidy up the build directory
site_packages_paths = glob.glob('%s/lib*/*/site-packages' % BASE_INSTALL_PATH)
+ except Exception as exc:
+ raise AssertionError("Unable to install with 'python setup.py install': %s" % exc)
- if len(site_packages_paths) != 1:
- raise AssertionError('We should only have a single site-packages directory, but instead had: %s' % site_packages_paths)
+ if len(site_packages_paths) != 1:
+ raise AssertionError('We should only have a single site-packages directory, but instead had: %s' % site_packages_paths)
- INSTALL_PATH = site_packages_paths[0]
- except Exception as exc:
- INSTALL_FAILURE = AssertionError("Unable to install with 'python setup.py install': %s" % exc)
+ install_path = site_packages_paths[0]
+ version_output = stem.util.system.call([PYTHON_EXE, '-c', "import sys;sys.path.insert(0, '%s');import stem;print(stem.__version__)" % install_path])[0]
- if not os.path.exists(DIST_PATH):
- try:
- stem.util.system.call('%s setup.py sdist' % PYTHON_EXE, timeout = 60, cwd = test.STEM_BASE)
- except Exception as exc:
- SDIST_FAILURE = exc
- else:
- SDIST_FAILURE = AssertionError("%s already exists, maybe you manually ran 'python setup.py sdist'?" % DIST_PATH)
+ if stem.__version__ != version_output:
+ raise AssertionError('We expected the installed version to be %s but was %s' % (stem.__version__, version_output))
- if SETUP_THREAD is None:
- SETUP_THREAD = threading.Thread(target = _setup)
- SETUP_THREAD.start()
+ _assert_has_all_files(install_path)
+ finally:
+ if os.path.exists(BASE_INSTALL_PATH):
+ shutil.rmtree(BASE_INSTALL_PATH)
- return SETUP_THREAD
+def _test_sdist():
+ TEST_INSTALL.join() # we need to run these tests serially
+ git_dir = os.path.join(test.STEM_BASE, '.git')
-def clean():
- if os.path.exists(BASE_INSTALL_PATH):
- shutil.rmtree(BASE_INSTALL_PATH)
+ if not stem.util.system.is_available('git'):
+ raise stem.util.test_tools.SkipTest('(git unavailable)')
+ elif not os.path.exists(git_dir):
+ raise stem.util.test_tools.SkipTest('(not a git checkout)')
if os.path.exists(DIST_PATH):
- shutil.rmtree(DIST_PATH)
+ raise AssertionError("%s already exists, maybe you manually ran 'python setup.py sdist'?" % DIST_PATH)
+
+ try:
+ try:
+ stem.util.system.call('%s setup.py sdist' % PYTHON_EXE, timeout = 60, cwd = test.STEM_BASE)
+ except Exception as exc:
+ raise AssertionError("Unable to run 'python setup.py sdist': %s" % exc)
+
+ git_contents = [line.split()[-1] for line in stem.util.system.call('git --git-dir=%s ls-tree --full-tree -r HEAD' % git_dir)]
+
+ # tarball has a prefix 'stem-[verion]' directory so stipping that out
+
+ dist_tar = tarfile.open(os.path.join(DIST_PATH, 'stem-dry-run-%s.tar.gz' % stem.__version__))
+ tar_contents = ['/'.join(info.name.split('/')[1:]) for info in dist_tar.getmembers() if info.isfile()]
+
+ issues = []
+
+ for path in git_contents:
+ if path not in tar_contents and path not in ['.gitignore']:
+ issues.append(' * %s is missing from our release tarball' % path)
+
+ for path in tar_contents:
+ if path not in git_contents and path not in ['MANIFEST.in', 'PKG-INFO']:
+ issues.append(" * %s isn't expected in our release tarball" % path)
+
+ if issues:
+ raise AssertionError(INSTALL_MISMATCH_MSG + '\n'.join(issues))
+ finally:
+ if os.path.exists(DIST_PATH):
+ shutil.rmtree(DIST_PATH)
def _assert_has_all_files(path):
@@ -102,23 +131,14 @@ def _assert_has_all_files(path):
class TestInstallation(unittest.TestCase):
- @test.require.only_run_once
def test_install(self):
"""
Installs with 'python setup.py install' and checks we can use what we
install.
"""
- if not INSTALL_PATH:
- setup().join()
+ TEST_INSTALL.result(self)
- if INSTALL_FAILURE:
- raise INSTALL_FAILURE
-
- self.assertEqual(stem.__version__, stem.util.system.call([PYTHON_EXE, '-c', "import sys;sys.path.insert(0, '%s');import stem;print(stem.__version__)" % INSTALL_PATH])[0])
- _assert_has_all_files(INSTALL_PATH)
-
- @test.require.only_run_once
def test_sdist(self):
"""
Creates a source distribution tarball with 'python setup.py sdist' and
@@ -126,36 +146,4 @@ class TestInstallation(unittest.TestCase):
meant to test that our MANIFEST.in is up to date.
"""
- git_dir = os.path.join(test.STEM_BASE, '.git')
-
- if not stem.util.system.is_available('git'):
- self.skipTest('(git unavailable)')
- return
- elif not os.path.exists(git_dir):
- self.skipTest('(not a git checkout)')
- return
-
- setup().join()
-
- if SDIST_FAILURE:
- raise SDIST_FAILURE
-
- git_contents = [line.split()[-1] for line in stem.util.system.call('git --git-dir=%s ls-tree --full-tree -r HEAD' % git_dir)]
-
- # tarball has a prefix 'stem-[verion]' directory so stipping that out
-
- dist_tar = tarfile.open(os.path.join(DIST_PATH, 'stem-dry-run-%s.tar.gz' % stem.__version__))
- tar_contents = ['/'.join(info.name.split('/')[1:]) for info in dist_tar.getmembers() if info.isfile()]
-
- issues = []
-
- for path in git_contents:
- if path not in tar_contents and path not in ['.gitignore']:
- issues.append(' * %s is missing from our release tarball' % path)
-
- for path in tar_contents:
- if path not in git_contents and path not in ['MANIFEST.in', 'PKG-INFO']:
- issues.append(" * %s isn't expected in our release tarball" % path)
-
- if issues:
- self.fail(INSTALL_MISMATCH_MSG + '\n'.join(issues))
+ TEST_SDIST.result(self)
1
0
commit 06836f52258e0e0d92acf523ac249e9e39cae7f0
Author: Damian Johnson <atagar(a)torproject.org>
Date: Tue Jun 6 09:42:24 2017 -0700
@asynchronous testing decorator
Adding a @asynchronous decorator which registers the function as being
asynchronous yet also allows it to be run normally. This way if the user calls
run() it's run early, but if not it's executed by unittest like a normal test.
---
run_tests.py | 7 +-
stem/util/test_tools.py | 92 ++++++++++++++++++---------
test/integ/descriptor/extrainfo_descriptor.py | 7 +-
test/integ/descriptor/microdescriptor.py | 6 +-
test/integ/descriptor/networkstatus.py | 10 +--
test/integ/descriptor/server_descriptor.py | 6 +-
test/integ/installation.py | 12 ++--
test/integ/process.py | 61 +++++++-----------
8 files changed, 114 insertions(+), 87 deletions(-)
diff --git a/run_tests.py b/run_tests.py
index 0a162eb..973da06 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -347,9 +347,10 @@ def main():
if task:
task.join()
- for path, issues in task.result.items():
- for issue in issues:
- static_check_issues.setdefault(path, []).append(issue)
+ if task.result:
+ for path, issues in task.result.items():
+ for issue in issues:
+ static_check_issues.setdefault(path, []).append(issue)
elif not task.is_available and task.unavailable_msg:
println(task.unavailable_msg, ERROR)
diff --git a/stem/util/test_tools.py b/stem/util/test_tools.py
index 0d167d0..43befd4 100644
--- a/stem/util/test_tools.py
+++ b/stem/util/test_tools.py
@@ -39,6 +39,7 @@ import unittest
import stem.prereq
import stem.util.conf
+import stem.util.enum
import stem.util.system
CONFIG = stem.util.conf.config_dict('test', {
@@ -49,7 +50,13 @@ CONFIG = stem.util.conf.config_dict('test', {
})
TEST_RUNTIMES = {}
+ASYNC_TESTS = {}
+AsyncStatus = stem.util.enum.UppercaseEnum('PENDING', 'RUNNING', 'FINISHED')
+AsyncResult = collections.namedtuple('AsyncResult', 'type msg')
+
+# TODO: Providing a copy of SkipTest that works with python 2.6. This will be
+# dropped when we remove python 2.6 support.
if stem.prereq._is_python_26():
class SkipTest(Exception):
@@ -58,6 +65,12 @@ else:
SkipTest = unittest.case.SkipTest
+def asynchronous(func):
+ test = stem.util.test_tools.AsyncTest(func)
+ ASYNC_TESTS['%s.%s' % (func.__module__, func.__name__)] = test
+ return test.method
+
+
class AsyncTest(object):
"""
Test that's run asychronously. These are functions (no self reference)
@@ -80,51 +93,72 @@ class AsyncTest(object):
.. versionadded:: 1.6.0
"""
- def __init__(self, test_runner, args = None, threaded = False):
- def _wrapper(conn, runner, test_args):
+ def __init__(self, runner, args = None, threaded = False):
+ self._runner = runner
+ self._runner_args = args
+ self._threaded = threaded
+
+ self.method = lambda test: self.result(test) # method that can be mixed into TestCases
+ self.method.async = self
+
+ self._process = None
+ self._process_pipe = None
+ self._process_lock = threading.RLock()
+
+ self._result = None
+ self._status = AsyncStatus.PENDING
+
+ def run(self, *runner_args, **kwargs):
+ def _wrapper(conn, runner, args):
try:
- runner(*test_args) if test_args else runner()
- conn.send(('success', None))
+ runner(*args) if args else runner()
+ conn.send(AsyncResult('success', None))
except AssertionError as exc:
- conn.send(('failure', str(exc)))
+ conn.send(AsyncResult('failure', str(exc)))
except SkipTest as exc:
- conn.send(('skipped', str(exc)))
+ conn.send(AsyncResult('skipped', str(exc)))
finally:
conn.close()
- self.method = lambda test: self.result(test) # method that can be mixed into TestCases
+ with self._process_lock:
+ if self._status == AsyncStatus.PENDING:
+ if runner_args:
+ self._runner_args = runner_args
- self._result_type, self._result_msg = None, None
- self._result_lock = threading.RLock()
- self._results_pipe, child_pipe = multiprocessing.Pipe()
+ if 'threaded' in kwargs:
+ self._threaded = kwargs['threaded']
- if threaded:
- self._test_process = threading.Thread(target = _wrapper, args = (child_pipe, test_runner, args))
- else:
- self._test_process = multiprocessing.Process(target = _wrapper, args = (child_pipe, test_runner, args))
+ self._process_pipe, child_pipe = multiprocessing.Pipe()
+
+ if self._threaded:
+ self._process = threading.Thread(target = _wrapper, args = (child_pipe, self._runner, self._runner_args))
+ else:
+ self._process = multiprocessing.Process(target = _wrapper, args = (child_pipe, self._runner, self._runner_args))
- self._test_process.start()
+ self._process.start()
+ self._status = AsyncStatus.RUNNING
def pid(self):
- with self._result_lock:
- return self._test_process.pid if self._test_process else None
+ with self._process_lock:
+ return self._process.pid if (self._process and not self._threaded) else None
def join(self):
self.result(None)
def result(self, test):
- with self._result_lock:
- if self._test_process:
- self._result_type, self._result_msg = self._results_pipe.recv()
- self._test_process.join()
- self._test_process = None
-
- if not test:
- return
- elif self._result_type == 'failure':
- test.fail(self._result_msg)
- elif self._result_type == 'skipped':
- test.skipTest(self._result_msg)
+ with self._process_lock:
+ if self._status == AsyncStatus.PENDING:
+ self.run()
+
+ if self._status == AsyncStatus.RUNNING:
+ self._result = self._process_pipe.recv()
+ self._process.join()
+ self._status = AsyncStatus.FINISHED
+
+ if test and self._result.type == 'failure':
+ test.fail(self._result.msg)
+ elif test and self._result.type == 'skipped':
+ test.skipTest(self._result.msg)
class Issue(collections.namedtuple('Issue', ['line_number', 'message', 'line'])):
diff --git a/test/integ/descriptor/extrainfo_descriptor.py b/test/integ/descriptor/extrainfo_descriptor.py
index e2350ce..0e13435 100644
--- a/test/integ/descriptor/extrainfo_descriptor.py
+++ b/test/integ/descriptor/extrainfo_descriptor.py
@@ -8,15 +8,16 @@ import unittest
import stem.descriptor
import stem.util.test_tools
import test
-import test.require
+
+from stem.util.test_tools import asynchronous
class TestExtraInfoDescriptor(unittest.TestCase):
@staticmethod
def run_tests(test_dir):
- TestExtraInfoDescriptor.test_cached_descriptor = stem.util.test_tools.AsyncTest(TestExtraInfoDescriptor.test_cached_descriptor, args = (test_dir,), threaded = True).method
+ stem.util.test_tools.ASYNC_TESTS['test.integ.descriptor.extrainfo_descriptor.test_cached_descriptor'].run(test_dir, threaded = True)
- @staticmethod
+ @asynchronous
def test_cached_descriptor(test_dir):
"""
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 cc48fce..8987906 100644
--- a/test/integ/descriptor/microdescriptor.py
+++ b/test/integ/descriptor/microdescriptor.py
@@ -9,13 +9,15 @@ import stem.descriptor
import stem.util.test_tools
import test
+from stem.util.test_tools import asynchronous
+
class TestMicrodescriptor(unittest.TestCase):
@staticmethod
def run_tests(test_dir):
- TestMicrodescriptor.test_cached_microdescriptors = stem.util.test_tools.AsyncTest(TestMicrodescriptor.test_cached_microdescriptors, args = (test_dir,), threaded = True).method
+ stem.util.test_tools.ASYNC_TESTS['test.integ.descriptor.microdescriptor.test_cached_microdescriptors'].run(test_dir, threaded = True)
- @staticmethod
+ @asynchronous
def test_cached_microdescriptors(test_dir):
"""
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 46d7654..dee8e7a 100644
--- a/test/integ/descriptor/networkstatus.py
+++ b/test/integ/descriptor/networkstatus.py
@@ -14,12 +14,14 @@ import test
import test.require
import test.runner
+from stem.util.test_tools import asynchronous
+
class TestNetworkStatus(unittest.TestCase):
@staticmethod
def run_tests(test_dir):
- TestNetworkStatus.test_cached_consensus = stem.util.test_tools.AsyncTest(TestNetworkStatus.test_cached_consensus, args = (test_dir,), threaded = True).method
- TestNetworkStatus.test_cached_microdesc_consensus = stem.util.test_tools.AsyncTest(TestNetworkStatus.test_cached_microdesc_consensus, args = (test_dir,), threaded = True).method
+ stem.util.test_tools.ASYNC_TESTS['test.integ.descriptor.networkstatus.test_cached_consensus'].run(test_dir, threaded = True)
+ stem.util.test_tools.ASYNC_TESTS['test.integ.descriptor.networkstatus.test_cached_microdesc_consensus'].run(test_dir, threaded = True)
@test.require.only_run_once
@test.require.online
@@ -32,7 +34,7 @@ class TestNetworkStatus(unittest.TestCase):
stem.descriptor.remote.get_consensus(document_handler = stem.descriptor.DocumentHandler.DOCUMENT, validate = True).run()
- @staticmethod
+ @asynchronous
def test_cached_consensus(test_dir):
"""
Parses the cached-consensus file in our data directory.
@@ -68,7 +70,7 @@ class TestNetworkStatus(unittest.TestCase):
if count < 100:
raise AssertionError('%s only included %s relays' % (consensus_path, count))
- @staticmethod
+ @asynchronous
def test_cached_microdesc_consensus(test_dir):
"""
Parses the cached-microdesc-consensus file in our data directory.
diff --git a/test/integ/descriptor/server_descriptor.py b/test/integ/descriptor/server_descriptor.py
index 0726c35..a4a78ac 100644
--- a/test/integ/descriptor/server_descriptor.py
+++ b/test/integ/descriptor/server_descriptor.py
@@ -9,13 +9,15 @@ import stem.descriptor
import stem.util.test_tools
import test
+from stem.util.test_tools import asynchronous
+
class TestServerDescriptor(unittest.TestCase):
@staticmethod
def run_tests(test_dir):
- TestServerDescriptor.test_cached_descriptor = stem.util.test_tools.AsyncTest(TestServerDescriptor.test_cached_descriptor, args = (test_dir,), threaded = True).method
+ stem.util.test_tools.ASYNC_TESTS['test.integ.descriptor.server_descriptor.test_cached_descriptor'].run(test_dir, threaded = True)
- @staticmethod
+ @asynchronous
def test_cached_descriptor(test_dir):
"""
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 0253efd..82bc717 100644
--- a/test/integ/installation.py
+++ b/test/integ/installation.py
@@ -15,6 +15,8 @@ import stem.util.system
import stem.util.test_tools
import test
+from stem.util.test_tools import asynchronous
+
BASE_INSTALL_PATH = '/tmp/stem_test'
DIST_PATH = os.path.join(test.STEM_BASE, 'dist')
PYTHON_EXE = sys.executable if sys.executable else 'python'
@@ -56,11 +58,11 @@ def _assert_has_all_files(path):
class TestInstallation(unittest.TestCase):
@staticmethod
def run_tests():
- test_install = stem.util.test_tools.AsyncTest(TestInstallation.test_install)
- TestInstallation.test_install = test_install.method
- TestInstallation.test_sdist = stem.util.test_tools.AsyncTest(TestInstallation.test_sdist, args = (test_install.pid(),)).method
+ test_install = stem.util.test_tools.ASYNC_TESTS['test.integ.installation.test_install']
+ test_install.run()
+ stem.util.test_tools.ASYNC_TESTS['test.integ.installation.test_sdist'].run(test_install.pid())
- @staticmethod
+ @asynchronous
def test_install():
"""
Installs with 'python setup.py install' and checks we can use what we
@@ -89,7 +91,7 @@ class TestInstallation(unittest.TestCase):
if os.path.exists(BASE_INSTALL_PATH):
shutil.rmtree(BASE_INSTALL_PATH)
- @staticmethod
+ @asynchronous
def test_sdist(dependency_pid):
"""
Creates a source distribution tarball with 'python setup.py sdist' and
diff --git a/test/integ/process.py b/test/integ/process.py
index 4e59e00..0aa639d 100644
--- a/test/integ/process.py
+++ b/test/integ/process.py
@@ -26,6 +26,8 @@ import test
import test.require
import test.runner
+from stem.util.test_tools import asynchronous
+
try:
# added in python 3.3
from unittest.mock import patch, Mock
@@ -80,28 +82,9 @@ def run_tor(tor_cmd, *args, **kwargs):
class TestProcess(unittest.TestCase):
@staticmethod
def run_tests(tor_cmd):
- async_tests = (
- 'test_version_argument',
- 'test_help_argument',
- 'test_quiet_argument',
- 'test_hush_argument',
- 'test_hash_password',
- 'test_hash_password_requires_argument',
- 'test_list_torrc_options_argument',
- 'test_torrc_arguments',
- 'test_torrc_arguments_via_stdin',
- 'test_with_missing_torrc',
- 'test_can_run_multithreaded',
- 'test_launch_tor_with_config_via_file',
- 'test_launch_tor_with_config_via_stdin',
- 'test_with_invalid_config',
- 'test_launch_tor_with_timeout',
- 'test_take_ownership_via_pid',
- 'test_take_ownership_via_controller',
- )
-
- for func in async_tests:
- setattr(TestProcess, func, stem.util.test_tools.AsyncTest(getattr(TestProcess, func), args = (tor_cmd,)).method)
+ for func, async_test in stem.util.test_tools.ASYNC_TESTS.items():
+ if func.startswith('test.integ.process.'):
+ async_test.run(tor_cmd)
def setUp(self):
self.data_directory = tempfile.mkdtemp()
@@ -111,7 +94,7 @@ class TestProcess(unittest.TestCase):
def tearDown(self):
shutil.rmtree(self.data_directory)
- @staticmethod
+ @asynchronous
def test_version_argument(tor_cmd):
"""
Check that 'tor --version' matches 'GETINFO version'.
@@ -122,7 +105,7 @@ class TestProcess(unittest.TestCase):
if 'Tor version %s.\n' % test.tor_version() != version_output:
raise AssertionError('Unexpected response: %s' % version_output)
- @staticmethod
+ @asynchronous
def test_help_argument(tor_cmd):
"""
Check that 'tor --help' provides the expected output.
@@ -136,7 +119,7 @@ class TestProcess(unittest.TestCase):
if help_output != run_tor(tor_cmd, '-h'):
raise AssertionError("'tor -h' should simply be an alias for 'tor --help'")
- @staticmethod
+ @asynchronous
def test_quiet_argument(tor_cmd):
"""
Check that we don't provide anything on stdout when running 'tor --quiet'.
@@ -145,7 +128,7 @@ class TestProcess(unittest.TestCase):
if '' != run_tor(tor_cmd, '--quiet', '--invalid_argument', 'true', expect_failure = True):
raise AssertionError('No output should be provided with the --quiet argument')
- @staticmethod
+ @asynchronous
def test_hush_argument(tor_cmd):
"""
Check that we only get warnings and errors when running 'tor --hush'.
@@ -161,7 +144,7 @@ class TestProcess(unittest.TestCase):
if "[warn] Failed to parse/validate config: Unknown option 'invalid_argument'. Failing." not in output:
raise AssertionError('Unexpected response: %s' % output)
- @staticmethod
+ @asynchronous
def test_hash_password(tor_cmd):
"""
Hash a controller password. It's salted so can't assert that we get a
@@ -192,7 +175,7 @@ class TestProcess(unittest.TestCase):
if hashlib.sha1(inp).digest() != hashed:
raise AssertionError('Password hash not what we expected (%s rather than %s)' % (hashlib.sha1(inp).digest(), hashed))
- @staticmethod
+ @asynchronous
def test_hash_password_requires_argument(tor_cmd):
"""
Check that 'tor --hash-password' balks if not provided with something to
@@ -259,7 +242,7 @@ class TestProcess(unittest.TestCase):
expected = 'stemIntegTest %s\n' % fingerprint
self.assertEqual(expected, fingerprint_file.read())
- @staticmethod
+ @asynchronous
def test_list_torrc_options_argument(tor_cmd):
"""
Exercise our 'tor --list-torrc-options' argument.
@@ -295,7 +278,7 @@ class TestProcess(unittest.TestCase):
self.assertEqual('nope', str(exc))
- @staticmethod
+ @asynchronous
def test_torrc_arguments(tor_cmd):
"""
Pass configuration options on the commandline.
@@ -334,7 +317,7 @@ class TestProcess(unittest.TestCase):
finally:
shutil.rmtree(data_directory)
- @staticmethod
+ @asynchronous
def test_torrc_arguments_via_stdin(tor_cmd):
"""
Pass configuration options via stdin.
@@ -354,7 +337,7 @@ class TestProcess(unittest.TestCase):
finally:
shutil.rmtree(data_directory)
- @staticmethod
+ @asynchronous
def test_with_missing_torrc(tor_cmd):
"""
Provide a torrc path that doesn't exist.
@@ -370,7 +353,7 @@ class TestProcess(unittest.TestCase):
if '[notice] Configuration file "/path/that/really/shouldnt/exist" not present, using reasonable defaults.' not in output:
raise AssertionError('Missing torrc should be allowed with --ignore-missing-torrc')
- @staticmethod
+ @asynchronous
def test_can_run_multithreaded(tor_cmd):
"""
Our launch_tor() function uses signal to support its timeout argument.
@@ -422,7 +405,7 @@ class TestProcess(unittest.TestCase):
finally:
shutil.rmtree(data_directory)
- @staticmethod
+ @asynchronous
def test_launch_tor_with_config_via_file(tor_cmd):
"""
Exercises launch_tor_with_config when we write a torrc to disk.
@@ -466,7 +449,7 @@ class TestProcess(unittest.TestCase):
shutil.rmtree(data_directory)
- @staticmethod
+ @asynchronous
def test_launch_tor_with_config_via_stdin(tor_cmd):
"""
Exercises launch_tor_with_config when we provide our torrc via stdin.
@@ -509,7 +492,7 @@ class TestProcess(unittest.TestCase):
shutil.rmtree(data_directory)
- @staticmethod
+ @asynchronous
def test_with_invalid_config(tor_cmd):
"""
Spawn a tor process with a configuration that should make it dead on arrival.
@@ -540,7 +523,7 @@ class TestProcess(unittest.TestCase):
finally:
shutil.rmtree(data_directory)
- @staticmethod
+ @asynchronous
def test_launch_tor_with_timeout(tor_cmd):
"""
Runs launch_tor where it times out before completing.
@@ -568,7 +551,7 @@ class TestProcess(unittest.TestCase):
finally:
shutil.rmtree(data_directory)
- @staticmethod
+ @asynchronous
def test_take_ownership_via_pid(tor_cmd):
"""
Checks that the tor process quits after we do if we set take_ownership. To
@@ -618,7 +601,7 @@ class TestProcess(unittest.TestCase):
finally:
shutil.rmtree(data_directory)
- @staticmethod
+ @asynchronous
def test_take_ownership_via_controller(tor_cmd):
"""
Checks that the tor process quits after the controller that owns it
1
0
commit 30e2a75258e970bcf3a6643d739f62277e6596e6
Merge: 04f45ab 5b18cd5
Author: Damian Johnson <atagar(a)torproject.org>
Date: Thu Jun 8 10:09:56 2017 -0700
Parallelize integration tests
For months I've been striving to improve our test performance but no matter
what I did I ran into walls with python's GIL. At this year's PyCon however I
discovered a module I wasn't aware of before: multiprocessing.
The multiprocessing module allows callers to easily spawn worker processes
similar to threads, bypassing the python GIL. Using this to parallelize our
long running tests to greatly improve our testing runtime on multi-core
systems.
For me this drops the 'run_tests.py --all' runtime from 49s to 26s (47%
faster). I'm on an old dual-core system so speedups should be more pronounced
on anything modern. That said, the tests now saturate both cores for the
duration of the tests so this hits the ceiling for what parallelization can buy
me at present. When I upgrade there's more tests that are good candidates for
parallelization.
docs/change_log.rst | 1 +
run_tests.py | 44 +-
stem/util/system.py | 14 +-
stem/util/test_tools.py | 162 ++++++
test/integ/descriptor/extrainfo_descriptor.py | 28 +-
test/integ/descriptor/microdescriptor.py | 18 +-
test/integ/descriptor/networkstatus.py | 38 +-
test/integ/descriptor/server_descriptor.py | 25 +-
test/integ/installation.py | 141 +++--
test/integ/process.py | 715 ++++++++++++++------------
test/integ/util/system.py | 12 +-
test/settings.cfg | 14 +-
test/task.py | 1 +
13 files changed, 744 insertions(+), 469 deletions(-)
1
0

08 Jun '17
commit 09aeec8f5f6615c618c847feb339b191e302ff67
Author: Damian Johnson <atagar(a)torproject.org>
Date: Wed Jun 7 09:55:53 2017 -0700
Use 'with' keyword for temporary directories
Not sure why python's builtin doesn't support this, but decorating with a
simple context manager so temporary directories clean themselves up. This way
we don't need to do the cleanup in every test.
---
test/integ/process.py | 257 +++++++++++++++++++++++---------------------------
1 file changed, 117 insertions(+), 140 deletions(-)
diff --git a/test/integ/process.py b/test/integ/process.py
index 7e9542a..a047540 100644
--- a/test/integ/process.py
+++ b/test/integ/process.py
@@ -25,6 +25,7 @@ import stem.version
import test
import test.require
+from contextlib import contextmanager
from stem.util.test_tools import asynchronous
try:
@@ -47,6 +48,16 @@ def random_port():
return str(random.randint(1024, 65536))
+@contextmanager
+def tmp_directory():
+ tmp_dir = tempfile.mkdtemp()
+
+ try:
+ yield tmp_dir
+ finally:
+ shutil.rmtree(tmp_dir)
+
+
def run_tor(tor_cmd, *args, **kwargs):
# python doesn't allow us to have individual keyword arguments when there's
# an arbitrary number of positional arguments, so explicitly checking
@@ -54,21 +65,19 @@ def run_tor(tor_cmd, *args, **kwargs):
expect_failure = kwargs.pop('expect_failure', False)
with_torrc = kwargs.pop('with_torrc', False)
stdin = kwargs.pop('stdin', None)
- data_directory = None
if kwargs:
raise ValueError('Got unexpected keyword arguments: %s' % kwargs)
- if with_torrc:
- data_directory = tempfile.mkdtemp()
- torrc_path = os.path.join(data_directory, 'torrc')
+ with tmp_directory() as data_directory:
+ if with_torrc:
+ torrc_path = os.path.join(data_directory, 'torrc')
- with open(torrc_path, 'w') as torrc_file:
- torrc_file.write(BASIC_RELAY_TORRC % data_directory)
+ with open(torrc_path, 'w') as torrc_file:
+ torrc_file.write(BASIC_RELAY_TORRC % data_directory)
- args = ['-f', torrc_path] + list(args)
+ args = ['-f', torrc_path] + list(args)
- try:
args = [tor_cmd] + list(args)
tor_process = subprocess.Popen(args, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
@@ -84,9 +93,6 @@ def run_tor(tor_cmd, *args, **kwargs):
raise AssertionError("Didn't expect tor to be able to start when we run: %s\n%s" % (' '.join(args), stdout))
return stem.util.str_tools._to_unicode(stdout) if stem.prereq.is_python_3() else stdout
- finally:
- if data_directory:
- shutil.rmtree(data_directory)
class TestProcess(unittest.TestCase):
@@ -235,10 +241,9 @@ class TestProcess(unittest.TestCase):
if "Clients don't have long-term identity keys. Exiting." not in output:
raise AssertionError('Should fail to start due to lacking an ORPort')
- data_directory = tempfile.mkdtemp()
- torrc_path = os.path.join(data_directory, 'torrc')
+ with tmp_directory() as data_directory:
+ torrc_path = os.path.join(data_directory, 'torrc')
- try:
with open(torrc_path, 'w') as torrc_file:
torrc_file.write(BASIC_RELAY_TORRC % data_directory + '\nORPort 6954')
@@ -259,8 +264,6 @@ class TestProcess(unittest.TestCase):
if expected != fingerprint_file_content:
raise AssertionError('Unexpected fingerprint file: %s' % fingerprint_file_content)
- finally:
- shutil.rmtree(data_directory)
@asynchronous
def test_list_torrc_options_argument(tor_cmd):
@@ -304,10 +307,9 @@ class TestProcess(unittest.TestCase):
Pass configuration options on the commandline.
"""
- data_directory = tempfile.mkdtemp()
- torrc_path = os.path.join(data_directory, 'torrc')
+ with tmp_directory() as data_directory:
+ torrc_path = os.path.join(data_directory, 'torrc')
- try:
with open(torrc_path, 'w') as torrc_file:
torrc_file.write(BASIC_RELAY_TORRC % data_directory)
@@ -332,8 +334,6 @@ class TestProcess(unittest.TestCase):
if expected != result:
raise AssertionError("Unexpected output from 'tor -f torrc --dump-config short': %s" % result)
- finally:
- shutil.rmtree(data_directory)
@asynchronous
def test_torrc_arguments_via_stdin(tor_cmd):
@@ -344,16 +344,12 @@ class TestProcess(unittest.TestCase):
if test.tor_version() < stem.version.Requirement.TORRC_VIA_STDIN:
raise stem.util.test_tools.SkipTest('(requires )' % stem.version.Requirement.TORRC_VIA_STDIN)
- data_directory = tempfile.mkdtemp()
-
- try:
+ with tmp_directory() as data_directory:
torrc = BASIC_RELAY_TORRC % data_directory
output = run_tor(tor_cmd, '-f', '-', '--dump-config', 'short', stdin = torrc)
if sorted(torrc.splitlines()) != sorted(output.splitlines()):
raise AssertionError("Unexpected output from 'tor -f - --dump-config short': %s" % output)
- finally:
- shutil.rmtree(data_directory)
@asynchronous
def test_with_missing_torrc(tor_cmd):
@@ -379,9 +375,7 @@ class TestProcess(unittest.TestCase):
it isn't.
"""
- data_directory = tempfile.mkdtemp()
-
- try:
+ with tmp_directory() as data_directory:
# Tries running tor in another thread with the given timeout argument. This
# issues an invalid torrc so we terminate right away if we get to the point
# of actually invoking tor.
@@ -420,8 +414,6 @@ class TestProcess(unittest.TestCase):
if launch_async_with_timeout(stem.process.DEFAULT_INIT_TIMEOUT) is not None:
raise AssertionError('Launching tor with the default timeout should be successful')
- finally:
- shutil.rmtree(data_directory)
@asynchronous
def test_launch_tor_with_config_via_file(tor_cmd):
@@ -429,43 +421,41 @@ class TestProcess(unittest.TestCase):
Exercises launch_tor_with_config when we write a torrc to disk.
"""
- data_directory = tempfile.mkdtemp()
- control_port = random_port()
- control_socket, tor_process = None, None
-
- try:
- # Launch tor without a torrc, but with a control port. Confirms that this
- # works by checking that we're still able to access the new instance.
-
- with patch('stem.version.get_system_tor_version', Mock(return_value = stem.version.Version('0.0.0.1'))):
- tor_process = stem.process.launch_tor_with_config(
- tor_cmd = tor_cmd,
- config = {
- 'SocksPort': random_port(),
- 'ControlPort': control_port,
- 'DataDirectory': data_directory,
- },
- completion_percent = 5
- )
-
- control_socket = stem.socket.ControlPort(port = int(control_port))
- stem.connection.authenticate(control_socket)
+ with tmp_directory() as data_directory:
+ control_port = random_port()
+ control_socket, tor_process = None, None
- # exercises the socket
- control_socket.send('GETCONF ControlPort')
- getconf_response = control_socket.recv()
-
- if 'ControlPort=%s' % control_port != str(getconf_response):
- raise AssertionError('Expected tor to report its ControlPort as %s but was: %s' % (control_port, getconf_response))
- finally:
- if control_socket:
- control_socket.close()
-
- if tor_process:
- tor_process.kill()
- tor_process.wait()
-
- shutil.rmtree(data_directory)
+ try:
+ # Launch tor without a torrc, but with a control port. Confirms that this
+ # works by checking that we're still able to access the new instance.
+
+ with patch('stem.version.get_system_tor_version', Mock(return_value = stem.version.Version('0.0.0.1'))):
+ tor_process = stem.process.launch_tor_with_config(
+ tor_cmd = tor_cmd,
+ config = {
+ 'SocksPort': random_port(),
+ 'ControlPort': control_port,
+ 'DataDirectory': data_directory,
+ },
+ completion_percent = 5
+ )
+
+ control_socket = stem.socket.ControlPort(port = int(control_port))
+ stem.connection.authenticate(control_socket)
+
+ # exercises the socket
+ control_socket.send('GETCONF ControlPort')
+ getconf_response = control_socket.recv()
+
+ if 'ControlPort=%s' % control_port != str(getconf_response):
+ raise AssertionError('Expected tor to report its ControlPort as %s but was: %s' % (control_port, getconf_response))
+ finally:
+ if control_socket:
+ control_socket.close()
+
+ if tor_process:
+ tor_process.kill()
+ tor_process.wait()
@asynchronous
def test_launch_tor_with_config_via_stdin(tor_cmd):
@@ -476,39 +466,37 @@ class TestProcess(unittest.TestCase):
if test.tor_version() < stem.version.Requirement.TORRC_VIA_STDIN:
raise stem.util.test_tools.SkipTest('(requires )' % stem.version.Requirement.TORRC_VIA_STDIN)
- data_directory = tempfile.mkdtemp()
- control_port = random_port()
- control_socket, tor_process = None, None
-
- try:
- tor_process = stem.process.launch_tor_with_config(
- tor_cmd = tor_cmd,
- config = {
- 'SocksPort': random_port(),
- 'ControlPort': control_port,
- 'DataDirectory': data_directory,
- },
- completion_percent = 5
- )
+ with tmp_directory() as data_directory:
+ control_port = random_port()
+ control_socket, tor_process = None, None
- control_socket = stem.socket.ControlPort(port = int(control_port))
- stem.connection.authenticate(control_socket)
+ try:
+ tor_process = stem.process.launch_tor_with_config(
+ tor_cmd = tor_cmd,
+ config = {
+ 'SocksPort': random_port(),
+ 'ControlPort': control_port,
+ 'DataDirectory': data_directory,
+ },
+ completion_percent = 5
+ )
- # exercises the socket
- control_socket.send('GETCONF ControlPort')
- getconf_response = control_socket.recv()
+ control_socket = stem.socket.ControlPort(port = int(control_port))
+ stem.connection.authenticate(control_socket)
- if 'ControlPort=%s' % control_port != str(getconf_response):
- raise AssertionError('Expected tor to report its ControlPort as %s but was: %s' % (control_port, getconf_response))
- finally:
- if control_socket:
- control_socket.close()
+ # exercises the socket
+ control_socket.send('GETCONF ControlPort')
+ getconf_response = control_socket.recv()
- if tor_process:
- tor_process.kill()
- tor_process.wait()
+ if 'ControlPort=%s' % control_port != str(getconf_response):
+ raise AssertionError('Expected tor to report its ControlPort as %s but was: %s' % (control_port, getconf_response))
+ finally:
+ if control_socket:
+ control_socket.close()
- shutil.rmtree(data_directory)
+ if tor_process:
+ tor_process.kill()
+ tor_process.wait()
@asynchronous
def test_with_invalid_config(tor_cmd):
@@ -521,25 +509,23 @@ class TestProcess(unittest.TestCase):
# [warn] Failed to parse/validate config: Failed to bind one of the listener ports.
# [err] Reading config failed--see warnings above.
- data_directory = tempfile.mkdtemp()
- both_ports = random_port()
+ with tmp_directory() as data_directory:
+ both_ports = random_port()
- try:
- stem.process.launch_tor_with_config(
- tor_cmd = tor_cmd,
- config = {
- 'SocksPort': both_ports,
- 'ControlPort': both_ports,
- 'DataDirectory': data_directory,
- },
- )
+ try:
+ stem.process.launch_tor_with_config(
+ tor_cmd = tor_cmd,
+ config = {
+ 'SocksPort': both_ports,
+ 'ControlPort': both_ports,
+ 'DataDirectory': data_directory,
+ },
+ )
- raise AssertionError('Tor should fail to launch')
- except OSError as exc:
- if str(exc) != 'Process terminated: Failed to bind one of the listener ports.':
- raise AssertionError('Unexpected error response from tor: %s' % exc)
- finally:
- shutil.rmtree(data_directory)
+ raise AssertionError('Tor should fail to launch')
+ except OSError as exc:
+ if str(exc) != 'Process terminated: Failed to bind one of the listener ports.':
+ raise AssertionError('Unexpected error response from tor: %s' % exc)
@asynchronous
def test_launch_tor_with_timeout(tor_cmd):
@@ -547,27 +533,25 @@ class TestProcess(unittest.TestCase):
Runs launch_tor where it times out before completing.
"""
- data_directory = tempfile.mkdtemp()
- start_time = time.time()
+ with tmp_directory() as data_directory:
+ start_time = time.time()
- try:
- stem.process.launch_tor_with_config(
- tor_cmd = tor_cmd,
- timeout = 0.05,
- config = {
- 'SocksPort': random_port(),
- 'DataDirectory': data_directory,
- },
- )
+ try:
+ stem.process.launch_tor_with_config(
+ tor_cmd = tor_cmd,
+ timeout = 0.05,
+ config = {
+ 'SocksPort': random_port(),
+ 'DataDirectory': data_directory,
+ },
+ )
- raise AssertionError('Tor should fail to launch')
- except OSError:
- runtime = time.time() - start_time
+ raise AssertionError('Tor should fail to launch')
+ except OSError:
+ runtime = time.time() - start_time
- if not (runtime > 0.05 and runtime < 1):
- raise AssertionError('Test should have taken 0.05-1 seconds, took %0.1f instead' % runtime)
- finally:
- shutil.rmtree(data_directory)
+ if not (runtime > 0.05 and runtime < 1):
+ raise AssertionError('Test should have taken 0.05-1 seconds, took %0.1f instead' % runtime)
@asynchronous
def test_take_ownership_via_pid(tor_cmd):
@@ -581,9 +565,7 @@ class TestProcess(unittest.TestCase):
elif test.tor_version() < stem.version.Requirement.TAKEOWNERSHIP:
raise stem.util.test_tools.SkipTest('(requires )' % stem.version.Requirement.TAKEOWNERSHIP)
- data_directory = tempfile.mkdtemp()
-
- try:
+ with tmp_directory() as data_directory:
sleep_process = subprocess.Popen(['sleep', '60'])
tor_process = stem.process.launch_tor_with_config(
@@ -616,8 +598,6 @@ class TestProcess(unittest.TestCase):
time.sleep(0.01)
raise AssertionError("tor didn't quit after the process that owned it terminated")
- finally:
- shutil.rmtree(data_directory)
@asynchronous
def test_take_ownership_via_controller(tor_cmd):
@@ -629,10 +609,9 @@ class TestProcess(unittest.TestCase):
if test.tor_version() < stem.version.Requirement.TAKEOWNERSHIP:
raise stem.util.test_tools.SkipTest('(requires )' % stem.version.Requirement.TAKEOWNERSHIP)
- data_directory = tempfile.mkdtemp()
- control_port = random_port()
+ with tmp_directory() as data_directory:
+ control_port = random_port()
- try:
tor_process = stem.process.launch_tor_with_config(
tor_cmd = tor_cmd,
config = {
@@ -660,5 +639,3 @@ class TestProcess(unittest.TestCase):
time.sleep(0.01)
raise AssertionError("tor didn't quit after the controller that owned it disconnected")
- finally:
- shutil.rmtree(data_directory)
1
0