[tor-commits] [stem/master] Helper to asynchronously run tests

atagar at torproject.org atagar at torproject.org
Thu Jun 8 17:17:55 UTC 2017


commit ad72e524bd1402ca74a96437d5f745932b650e7a
Author: Damian Johnson <atagar at 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)





More information about the tor-commits mailing list