Skip to content

Commit

Permalink
Unittests need to run in the 'build' environment.
Browse files Browse the repository at this point in the history
fixes #685
  • Loading branch information
arcivanov committed Apr 9, 2020
1 parent 9f71484 commit 51182ae
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 18 deletions.
Expand Up @@ -17,7 +17,7 @@
# limitations under the License.


from pybuilder.remote import ctx, PipeShutdownError, RemoteObjectPipe, logger, log_to_stderr
from pybuilder.remote import Process, PipeShutdownError, RemoteObjectPipe, logger, log_to_stderr

__all__ = ["RemoteObjectPipe", "start_tool", "Tool", "PipeShutdownError", "logger"]

Expand All @@ -32,7 +32,7 @@ def stop(self, pipe):
pass


def start_tool(tools, group=None, name=None, logging=None):
def start_tool(pyenv, tools, group=None, name=None, logging=None):
"""
Starts a tool process
"""
Expand All @@ -42,7 +42,8 @@ def start_tool(tools, group=None, name=None, logging=None):
logger.setLevel(int(logging))

pipe = RemoteObjectPipe.new_pipe()
proc = ctx.Process(group=group, target=_instrumented_tool, name=name, args=(tools, pipe))
proc = Process(pyenv, group=group, target=_instrumented_tool, name=name, args=(tools, pipe))

try:
proc.start()
finally:
Expand Down
Expand Up @@ -42,6 +42,6 @@ def stop(self, pipe):
pipe.hide("unittest_tests")


def start_unittest_tool(tools, test_modules, test_method_prefix, logging=0):
def start_unittest_tool(pyenv, tools, test_modules, test_method_prefix, logging=0):
tool = UnitTestTool(test_modules, test_method_prefix)
return start_tool(tools + [tool], name="unittest", logging=logging)
return start_tool(pyenv, tools + [tool], name="unittest", logging=logging)
18 changes: 11 additions & 7 deletions src/main/python/pybuilder/plugins/python/unittest_plugin.py
Expand Up @@ -46,6 +46,7 @@ def init_test_source_directory(project):
project.set_property_if_unset("unittest_module_glob", "*_tests")
project.set_property_if_unset("unittest_file_suffix", None) # deprecated, use unittest_module_glob.
project.set_property_if_unset("unittest_test_method_prefix", None)
project.set_property_if_unset("unittest_python_env", "build")
project.set_property_if_unset("unittest_runner", (
lambda stream: __import__("xmlrunner").XMLTestRunner(output=project.expand_path("$dir_target/reports"),
stream=stream), "_make_result"))
Expand All @@ -59,6 +60,7 @@ def coverage_init(project, logger, reactor):
project.get_property("_coverage_tasks").append(run_unit_tests)
project.get_property("_coverage_config_prefixes")[run_unit_tests] = "ut"
project.set_property("ut_coverage_name", "Python unit test")
# project.set_property("ut_coverage_python_env", project.get_property("unittest_python_env"))


@task
Expand Down Expand Up @@ -88,9 +90,11 @@ def run_tests(project, logger, reactor, execution_prefix, execution_name):
try:
test_method_prefix = project.get_property("%s_test_method_prefix" % execution_prefix)
runner_generator = project.get_property("%s_runner" % execution_prefix)
result, console_out = execute_tests_matching(reactor.tools, runner_generator, logger, test_dir, module_glob,
test_method_prefix,
project.get_property("remote_debug"))
result, console_out = execute_tests_matching(
reactor.python_env_registry[project.get_property("unittest_python_env")],
reactor.tools, runner_generator, logger, test_dir, module_glob,
test_method_prefix,
project.get_property("remote_debug"))

if result.testsRun == 0:
logger.warn("No %s executed.", execution_name)
Expand Down Expand Up @@ -119,12 +123,12 @@ def run_tests(project, logger, reactor, execution_prefix, execution_name):
raise BuildFailedException("Unable to execute %s." % execution_name)


def execute_tests(tools, runner_generator, logger, test_source, suffix, test_method_prefix=None, remote_debug=0):
return execute_tests_matching(tools, runner_generator, logger, test_source, "*{0}".format(suffix),
def execute_tests(pyenv, tools, runner_generator, logger, test_source, suffix, test_method_prefix=None, remote_debug=0):
return execute_tests_matching(pyenv, tools, runner_generator, logger, test_source, "*{0}".format(suffix),
test_method_prefix, remote_debug=remote_debug)


def execute_tests_matching(tools, runner_generator, logger, test_source, file_glob, test_method_prefix=None,
def execute_tests_matching(pyenv, tools, runner_generator, logger, test_source, file_glob, test_method_prefix=None,
remote_debug=0):
output_log_file = StringIO()
try:
Expand All @@ -134,7 +138,7 @@ def execute_tests_matching(tools, runner_generator, logger, test_source, file_gl
_create_runner(runner_generator, output_log_file))

try:
proc, pipe = start_unittest_tool(tools, test_modules, test_method_prefix, logging=remote_debug)
proc, pipe = start_unittest_tool(pyenv, tools, test_modules, test_method_prefix, logging=remote_debug)
try:
pipe.register_remote(runner)
pipe.register_remote_type(unittest.result.TestResult)
Expand Down
109 changes: 106 additions & 3 deletions src/main/python/pybuilder/remote/__init__.py
Expand Up @@ -42,7 +42,110 @@
logger = ctx.get_logger()

__all__ = ["RemoteObjectPipe", "RemoteObjectError",
"ctx", "proxy_members", "PipeShutdownError", "log_to_stderr"]
"Process", "proxy_members", "PipeShutdownError", "log_to_stderr"]


class Process:
def __init__(self, pyenv, group=None, target=None, name=None, args=None):
self.pyenv_exec = pyenv.executable
self.proc = ctx.Process(group=group, target=target, name=name, args=args)

def start(self):
if PY2:
if IS_WIN:
from multiprocessing import forking as patch_module
tracker = None
else:
from billiard import spawn as patch_module
from billiard import semaphore_tracker as tracker
else:
from multiprocessing import spawn as patch_module
if not IS_WIN:
try:
from multiprocessing import semaphore_tracker as tracker
except ImportError:
from multiprocessing import resource_tracker as tracker
else:
tracker = None

# This is done to prevent polluting RT's path with our path magic
if tracker:
tracker.getfd()

old_python_exe = patch_module._python_exe
patch_module._python_exe = self.pyenv_exec[0] # pyenv's actual sys.executable

old_get_command_line = patch_module.get_command_line

def patched_get_command_line(**kwds):
cmd_line = old_get_command_line(**kwds)
result = list(self.pyenv_exec) + cmd_line[1:]
logger.debug("Starting process with %r", result)
return result

patch_module.get_command_line = patched_get_command_line

try:
return self.proc.start()
finally:
patch_module._python_exe = old_python_exe
patch_module.get_command_line = old_get_command_line

def terminate(self):
return self.proc.terminate()

def kill(self):
return self.proc.kill()

def join(self, timeout=None):
return self.proc.join(timeout)

def is_alive(self):
return self.proc.is_alive()

def close(self):
return self.proc.close()

@property
def name(self):
return self.proc.name

@name.setter
def name(self, name):
self.proc.name = name

@property
def daemon(self):
return self.proc.daemon

@daemon.setter
def daemon(self, daemonic):
self.proc.daemon = daemonic

@property
def authkey(self):
return self.proc.authkey

@authkey.setter
def authkey(self, authkey):
self.proc.authkey = authkey

@property
def exitcode(self):
return self.proc.exitcode

@property
def ident(self):
return self.proc.ident

pid = ident

@property
def sentinel(self):
return self.proc.sentinel

def __repr__(self):
return repr(self.proc)


class ProxyDef:
Expand Down Expand Up @@ -379,8 +482,8 @@ def _make_proxy_type(proxy_def):
dic = {}

for meth in methods:
exec('''def %s(self, *args, **kwargs):
return self._BaseProxy__rop.call(%r, %r, args, kwargs)''' % (meth, remote_id, meth), dic)
exec("""def %s(self, *args, **kwargs):
return self._BaseProxy__rop.call(%r, %r, args, kwargs)""" % (meth, remote_id, meth), dic)

for field in fields:
exec("""
Expand Down
6 changes: 3 additions & 3 deletions src/unittest/python/plugins/python/unittest_plugin_tests.py
Expand Up @@ -72,7 +72,7 @@ def test_should_discover_modules_by_suffix(self, mock_discover_modules_matching,
pipe = Mock()
pipe.remote_close_cause.return_value = None
tool.return_value = (Mock(), pipe)
execute_tests([], runner, self.mock_logger, "/path/to/test/sources", "_tests.py")
execute_tests(Mock(), [], runner, self.mock_logger, "/path/to/test/sources", "_tests.py")

mock_discover_modules_matching.assert_called_with("/path/to/test/sources", "*_tests.py")

Expand All @@ -84,7 +84,7 @@ def test_should_discover_modules_by_glob(self, mock_discover_modules_matching, m
pipe = Mock()
pipe.remote_close_cause.return_value = None
tool.return_value = (Mock(), pipe)
execute_tests_matching([], runner, self.mock_logger, "/path/to/test/sources", "*_tests.py")
execute_tests_matching(Mock(), [], runner, self.mock_logger, "/path/to/test/sources", "*_tests.py")

mock_discover_modules_matching.assert_called_with("/path/to/test/sources", "*_tests.py")

Expand All @@ -100,7 +100,7 @@ def test_should_return_actual_test_results(self, mock_discover_modules, mock_uni
mock_unittest.defaultTestLoader.loadTestsFromNames.return_value = mock_tests
runner.return_value.run.return_value = self.mock_result

actual, _ = execute_tests([], runner, self.mock_logger, "/path/to/test/sources", "_tests.py")
actual, _ = execute_tests(Mock(), [], runner, self.mock_logger, "/path/to/test/sources", "_tests.py")

self.assertEqual(self.mock_result, actual)

Expand Down

0 comments on commit 51182ae

Please sign in to comment.