Skip to content

Commit

Permalink
Support running tests in parallel
Browse files Browse the repository at this point in the history
Conflicts:
	docs/internals/contributing.rst
  • Loading branch information
Jeremy Bowman committed Jun 5, 2016
1 parent c23c0b2 commit 3e9a418
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 108 deletions.
12 changes: 11 additions & 1 deletion docs/internals/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,17 @@ To capture unexpected successes in test:

$ python setup.py test | grep success

Running the full test suite will take quite a while - it takes 40 minutes on the CI server. If you just want to run a single test, or a single group of tests, you can provide command-line arguments.
Running the full test suite will take quite a while - it takes 40 minutes on the CI server. You can speed this up by running the tests in parallel via pytest:

.. code-block:: bash
$ cd voc
$ pip install -r requirements/tests.txt
$ py.test -n auto
You can specify the number of cores to utilize, or use ``auto`` as shown above to use all available cores.

If you just want to run a single test, or a single group of tests, you can provide command-line arguments.

To run a single test, provide the full dotted-path to the test:

Expand Down
2 changes: 1 addition & 1 deletion python/testdaemon/python/testdaemon/TestDaemon.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static void main(String[] args) {
URL runtime2;
try {
// load the voc jar here so that they can see the runtime classes
voc = new URL("file:" + System.getProperty("user.dir") + "/../dist/python-java.jar");
voc = new URL("file:" + System.getProperty("user.dir") + "/../../dist/python-java.jar");
// need the trailing slash so that the ClassLoader knows it's a
// directory instead of a jar file
runtime1 = new URL("file:" + System.getProperty("user.dir") + "/temp/");
Expand Down
12 changes: 12 additions & 0 deletions requirements/tests.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# execnet dependency
apipkg==1.4

# pytest dependency
py==1.4.31

# pytest-xdist dependencies
execnet==1.4.1
pytest==2.9.2

# Support for parallel test execution
pytest-xdist==1.14
202 changes: 96 additions & 106 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import shutil
import subprocess
import sys
import tempfile
import traceback
from unittest import TestCase

Expand All @@ -15,10 +16,11 @@
from voc.java.attributes import Code as JavaCode
from voc.transpiler import Transpiler

# get path to `tests` directory
tests_dir = os.path.dirname(__file__)

# A state variable to determine if the test environment has been configured.
_suite_configured = False
_jvm = None


def setUpSuite():
Expand Down Expand Up @@ -130,59 +132,6 @@ def runAsPython(test_dir, main_code, extra_code=None, run_in_function=False, arg
return out[0].decode('utf8')


def runAsJava(test_dir, main_code, extra_code=None, run_in_function=False, args=None):
"""Run a block of Python code as a Java program."""
# Output source code into test directory
transpiler = Transpiler(verbosity=0)

# Don't redirect stderr; we want to see any errors from the transpiler
# as top level test failures.
with capture_output(redirect_stderr=False):
transpiler.transpile_string("test.py", adjust(main_code, run_in_function=run_in_function))

if extra_code:
for name, code in extra_code.items():
transpiler.transpile_string("%s.py" % name.replace('.', os.path.sep), adjust(code))

transpiler.write(test_dir)

if args is None:
args = []

if len(args) == 0:
global _jvm

# encode to turn str into bytes-like object
_jvm.stdin.write(("python.test.__init__\n").encode("utf-8"))
_jvm.stdin.flush()
out = ""
while True:
try:
line = _jvm.stdout.readline().decode("utf-8")
if line == ".{0}".format(os.linesep):
break
else:
out += line
except IOError:
continue
else:
classpath = os.pathsep.join([
os.path.join('..', '..', 'dist', 'python-java.jar'),
os.path.join('..', 'java'),
os.curdir,
])
proc = subprocess.Popen(
["java", "-classpath", classpath, "python.test.__init__"] + args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=test_dir
)
out = proc.communicate()[0].decode('utf8')

return out


def compileJava(java_dir, java):
if not java:
return None
Expand Down Expand Up @@ -292,25 +241,32 @@ def cleanse_python(input):


class TranspileTestCase(TestCase):
def setUpClass():

@classmethod
def setUpClass(cls):
setUpSuite()
global _jvm
test_dir = os.path.join(os.path.dirname(__file__))
classpath = os.path.join('..', 'dist', 'python-java-testdaemon.jar')
_jvm = subprocess.Popen(
tests_dir = os.path.join(os.path.dirname(__file__))
cls.output_dir = tempfile.mkdtemp(dir=tests_dir)
cls.temp_dir = os.path.join(cls.output_dir, 'temp')
classpath = os.path.join('..', '..', 'dist', 'python-java-testdaemon.jar')
with open('/Users/jeremy/Downloads/classpath.txt', 'w') as f:
f.write(classpath)
cls.jvm = subprocess.Popen(
["java", "-classpath", classpath, "python.testdaemon.TestDaemon"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=test_dir,
cwd=cls.output_dir,
)
with open('/Users/jeremy/Downloads/voc.txt', 'w') as f:
f.write('Running JVM from ' + cls.temp_dir)

def tearDownClass():
global _jvm

if _jvm is not None:
@classmethod
def tearDownClass(cls):
if cls.jvm is not None:
# use communicate here to wait for process to exit
_jvm.communicate("exit".encode("utf-8"))
cls.jvm.communicate("exit".encode("utf-8"))
shutil.rmtree(cls.output_dir)

def assertBlock(self, python, java):
self.maxDiff = None
Expand Down Expand Up @@ -353,21 +309,15 @@ def assertCodeExecution(self, code, message=None, extra_code=None, run_in_global
#==================================================
if run_in_global:
try:
# Create the temp directory into which code will be placed
test_dir = os.path.join(os.path.dirname(__file__), 'temp')
try:
os.mkdir(test_dir)
except FileExistsError:
pass

self.makeTempDir()
# Run the code as Python and as Java.
py_out = runAsPython(test_dir, code, extra_code, False, args=args)
java_out = runAsJava(test_dir, code, extra_code, False, args=args)
py_out = runAsPython(self.temp_dir, code, extra_code, False, args=args)
java_out = self.runAsJava(code, extra_code, False, args=args)
except Exception as e:
self.fail(e)
finally:
# Clean up the test directory where the class file was written.
shutil.rmtree(test_dir)
shutil.rmtree(self.temp_dir)
# print(java_out)

# Cleanse the Python and Java output, producing a simple
Expand All @@ -387,21 +337,15 @@ def assertCodeExecution(self, code, message=None, extra_code=None, run_in_global
#==================================================
if run_in_function:
try:
# Create the temp directory into which code will be placed
test_dir = os.path.join(os.path.dirname(__file__), 'temp')
try:
os.mkdir(test_dir)
except FileExistsError:
pass

self.makeTempDir()
# Run the code as Python and as Java.
py_out = runAsPython(test_dir, code, extra_code, True, args=args)
java_out = runAsJava(test_dir, code, extra_code, True, args=args)
py_out = runAsPython(self.temp_dir, code, extra_code, True, args=args)
java_out = self.runAsJava(code, extra_code, True, args=args)
except Exception as e:
self.fail(e)
finally:
# Clean up the test directory where the class file was written.
shutil.rmtree(test_dir)
shutil.rmtree(self.temp_dir)
# print(java_out)

# Cleanse the Python and Java output, producing a simple
Expand All @@ -424,10 +368,10 @@ def assertJavaExecution(self, code, out, extra_code=None, java=None, run_in_glob
# Prep - compile any required Java sources
#==================================================
# Create the temp directory into which code will be placed
java_dir = os.path.join(os.path.dirname(__file__), 'java')
java_dir = os.path.join(self.output_dir, 'java')

try:
os.mkdir(java_dir)
os.makedirs(java_dir)
except FileExistsError:
pass

Expand All @@ -446,20 +390,14 @@ def assertJavaExecution(self, code, out, extra_code=None, java=None, run_in_glob
#==================================================
if run_in_global:
try:
# Create the temp directory into which code will be placed
test_dir = os.path.join(os.path.dirname(__file__), 'temp')
try:
os.mkdir(test_dir)
except FileExistsError:
pass

self.makeTempDir()
# Run the code as Java.
java_out = runAsJava(test_dir, code, extra_code, False, args=args)
java_out = self.runAsJava(code, extra_code, False, args=args)
except Exception as e:
self.fail(e)
finally:
# Clean up the test directory where the class file was written.
shutil.rmtree(test_dir)
shutil.rmtree(self.temp_dir)
# print(java_out)

# Cleanse the Java output, producing a simple
Expand All @@ -474,20 +412,14 @@ def assertJavaExecution(self, code, out, extra_code=None, java=None, run_in_glob
#==================================================
if run_in_function:
try:
# Create the temp directory into which code will be placed
test_dir = os.path.join(os.path.dirname(__file__), 'temp')
try:
os.mkdir(test_dir)
except FileExistsError:
pass

self.makeTempDir()
# Run the code as Java.
java_out = runAsJava(test_dir, code, extra_code, True, args=args)
java_out = self.runAsJava(code, extra_code, True, args=args)
except Exception as e:
self.fail(e)
finally:
# Clean up the test directory where the class file was written.
shutil.rmtree(test_dir)
shutil.rmtree(self.temp_dir)
# print(java_out)

# Cleanse the Java output, producing a simple
Expand All @@ -499,7 +431,65 @@ def assertJavaExecution(self, code, out, extra_code=None, java=None, run_in_glob

finally:
# Clean up the java directory where the class file was written.
shutil.rmtree(java_dir)
if os.path.exists(java_dir):
shutil.rmtree(java_dir)

def makeTempDir(self):
"""Create a "temp" subdirectory in the class's generated temporary directory if it doesn't currently exist."""
try:
os.mkdir(self.temp_dir)
except FileExistsError:
pass

def runAsJava(self, main_code, extra_code=None, run_in_function=False, args=None):
"""Run a block of Python code as a Java program."""
# Output source code into test directory
transpiler = Transpiler(verbosity=0)

# Don't redirect stderr; we want to see any errors from the transpiler
# as top level test failures.
with capture_output(redirect_stderr=False):
transpiler.transpile_string("test.py", adjust(main_code, run_in_function=run_in_function))

if extra_code:
for name, code in extra_code.items():
transpiler.transpile_string("%s.py" % name.replace('.', os.path.sep), adjust(code))

transpiler.write(self.temp_dir)

if args is None:
args = []

if len(args) == 0:
# encode to turn str into bytes-like object
self.jvm.stdin.write(("python.test.__init__\n").encode("utf-8"))
self.jvm.stdin.flush()
out = ""
while True:
try:
line = self.jvm.stdout.readline().decode("utf-8")
if line == ".{0}".format(os.linesep):
break
else:
out += line
except IOError:
continue
else:
classpath = os.pathsep.join([
os.path.join('..', '..', '..', 'dist', 'python-java.jar'),
os.path.join('..', '..', 'java'),
os.curdir,
])
proc = subprocess.Popen(
["java", "-classpath", classpath, "python.test.__init__"] + args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=self.temp_dir
)
out = proc.communicate()[0].decode('utf8')

return out


def _unary_test(test_name, operation):
Expand Down

0 comments on commit 3e9a418

Please sign in to comment.