Skip to content

Commit

Permalink
pytest_plugin pybuilder#13(pybuilder#448)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexeySanko committed Feb 7, 2017
1 parent 121cff0 commit fe1e8e4
Show file tree
Hide file tree
Showing 8 changed files with 304 additions and 39 deletions.
1 change: 1 addition & 0 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
use_plugin("source_distribution")

use_plugin("python.unittest")
use_plugin("python.pytest")

if sys.platform != 'win32':
use_plugin("python.cram")
Expand Down
15 changes: 14 additions & 1 deletion src/main/python/pybuilder/plugins/python/coverage_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,14 @@ def _delete_non_essential_modules():
for module_name in list(sys.modules.keys()):
module = sys.modules[module_name]
if module:
if not _is_module_essential(module.__name__, sys_packages, sys_modules):
try:
# if we try to remove non-module object
module__name__ = module.__name__
except:
module__name__ = None
if module__name__ is None:
module__name__ = module_name
if not _is_module_essential(module__name__, sys_packages, sys_modules):
_delete_module(module_name, module)


Expand All @@ -326,6 +333,9 @@ def _delete_module(module_name, module):
delattr(module, module_name)
except AttributeError:
pass
# if we try to remove non-class object
except ImportError:
pass


def _is_module_essential(module_name, sys_packages, sys_modules):
Expand All @@ -338,6 +348,9 @@ def _is_module_essential(module_name, sys_packages, sys_modules):
# Essential since we're in a fork for communicating exceptions back
sys_packages.append("tblib")
sys_packages.append("pybuilder.errors")
# External modules
sys_packages.append("pkg_resources")
sys_packages.append("_pytest")

for package in sys_packages:
if module_name == package or module_name.startswith(package + "."):
Expand Down
66 changes: 66 additions & 0 deletions src/main/python/pybuilder/plugins/python/pytest_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 Alexey Sanko
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from sys import path as sys_path

from pybuilder.core import task, init, use_plugin, after
from pybuilder.errors import MissingPrerequisiteException, BuildFailedException
from pybuilder.utils import register_test_and_source_path_and_return_test_dir

__author__ = 'Alexey Sanko'

use_plugin("python.core")


@init
def initialize_pytest_plugin(project):
""" Init default plugin project properties. """
project.plugin_depends_on('pytest')
project.set_property_if_unset("dir_source_pytest_python", "src/unittest/python")
project.set_property_if_unset("pytest_extra_args", [])


@after("prepare")
def assert_pytest_available(logger):
""" Asserts that the pytest module is available. """
logger.debug("Checking if pytest module is available.")

try:
import pytest
logger.debug("Found pytest version %s" % pytest.__version__)
except ImportError:
raise MissingPrerequisiteException(prerequisite="pytest module", caller="plugin python.pytest")


@task
def run_unit_tests(project, logger):
""" Call pytest for the sources of the given project. """
logger.info('pytest: Run unittests.')
from pytest import main as pytest_main
test_dir = register_test_and_source_path_and_return_test_dir(project, sys_path, 'pytest')
extra_args = project.get_property("pytest_extra_args")
try:
pytest_args = [test_dir] + (extra_args if extra_args else [])
if project.get_property('verbose'):
pytest_args.append('-s')
pytest_args.append('-v')
ret = pytest_main(pytest_args)
if ret:
raise BuildFailedException('pytest: unittests failed')
else:
logger.info('pytest: All unittests passed.')
except:
raise
15 changes: 5 additions & 10 deletions src/main/python/pybuilder/plugins/python/unittest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@

from pybuilder.core import init, task, description, use_plugin
from pybuilder.errors import BuildFailedException
from pybuilder.utils import discover_modules_matching, render_report, fork_process
from pybuilder.utils import (discover_modules_matching,
render_report,
fork_process,
register_test_and_source_path_and_return_test_dir)
from pybuilder.ci_server_interaction import test_proxy_for
from pybuilder.terminal import print_text_line
from types import MethodType, FunctionType
Expand Down Expand Up @@ -77,7 +80,7 @@ def run_tests(project, logger, execution_prefix, execution_name):


def do_run_tests(project, logger, execution_prefix, execution_name):
test_dir = _register_test_and_source_path_and_return_test_dir(project, sys.path, execution_prefix)
test_dir = register_test_and_source_path_and_return_test_dir(project, sys.path, execution_prefix)

file_suffix = project.get_property("%s_file_suffix" % execution_prefix)
if file_suffix is not None:
Expand Down Expand Up @@ -202,14 +205,6 @@ def addFailure(self, test, err):
return result


def _register_test_and_source_path_and_return_test_dir(project, system_path, execution_prefix):
test_dir = project.expand_path("$dir_source_%s_python" % execution_prefix)
system_path.insert(0, test_dir)
system_path.insert(0, project.expand_path("$dir_source_main_python"))

return test_dir


def write_report(name, project, logger, result, console_out):
project.write_report("%s" % name, console_out)

Expand Down
7 changes: 7 additions & 0 deletions src/main/python/pybuilder/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,3 +612,10 @@ def safe_log_file_name(file_name):
# per https://support.microsoft.com/en-us/kb/177506
# per https://msdn.microsoft.com/en-us/library/aa365247
return re.sub(r'\\|/|:|\*|\?|\"|<|>|\|', '_', file_name)


def register_test_and_source_path_and_return_test_dir(project, system_path, execution_prefix):
test_dir = project.expand_path("$dir_source_%s_python" % execution_prefix)
system_path.insert(0, test_dir)
system_path.insert(0, project.expand_path("$dir_source_main_python"))
return test_dir
182 changes: 182 additions & 0 deletions src/unittest/python/plugins/python/pytest_plugin_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 Alexey Sanko
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from os import mkdir
from os.path import join as path_join
from mock import Mock
from shutil import rmtree
from sys import path as sys_path
from tempfile import mkdtemp
from unittest import TestCase

from pybuilder.core import Project
from pybuilder.errors import BuildFailedException
from pybuilder.plugins.python.pytest_plugin import initialize_pytest_plugin, run_unit_tests


class PytestPluginInitializationTests(TestCase):
def setUp(self):
self.project = Project("basedir")

def test_should_set_dependency(self):
mock_project = Mock(Project)
initialize_pytest_plugin(mock_project)
mock_project.plugin_depends_on.assert_called_with('pytest')

def test_should_set_default_properties(self):
initialize_pytest_plugin(self.project)
expected_default_properties = {
"dir_source_pytest_python": "src/unittest/python"
}
for property_name, property_value in expected_default_properties.items():
self.assertEquals(self.project.get_property(property_name), property_value)

def test_should_leave_user_specified_properties_when_initializing_plugin(self):
expected_properties = {
"dir_source_pytest_python": "some/path"
}
for property_name, property_value in expected_properties.items():
self.project.set_property(property_name, property_value)

initialize_pytest_plugin(self.project)

for property_name, property_value in expected_properties.items():
self.assertEquals(self.project.get_property(property_name), property_value)


pytest_file_success = """
def test_pytest_base_success():
assert True
"""

pytest_file_failure = """
def test_pytest_base_failure():
assert False
"""

pytest_conftest_result_to_file = """
from os.path import abspath, dirname, join as path_join
curr_dir = dirname(abspath(__file__))
def pytest_collection_modifyitems(config, items):
verbose_flag = config.getoption('verbose')
capture = config.getoption('capture')
tests_list = []
for item in items:
tests_list.append(item.name)
f = open(path_join(curr_dir, 'pytest_collected_config.out'), 'w')
f.write(str(verbose_flag) + '\\n')
f.write(str(capture) + '\\n')
f.write(','.join(tests_list))
f.flush()
f.close()
"""

pytest_file_sucess_with_extra_args = """
def test_success_with_extra_args(test_arg):
assert test_arg == "test_value"
"""

pytest_conftest_test_arg = """
import pytest
def pytest_addoption(parser):
parser.addoption("--test-arg", action="store")
@pytest.fixture
def test_arg(request):
return request.config.getoption("--test-arg")
"""


class PytestPluginRunningTests(TestCase):
def setUp(self):
self.tmp_test_folder = mkdtemp()

def create_test_project(self, name, content_dict):
project_dir = path_join(self.tmp_test_folder, name)
mkdir(project_dir)
test_project = Project(project_dir)
tests_dir = path_join(project_dir, 'tests')
mkdir(tests_dir)
test_project.set_property('dir_source_pytest_python',
'tests')
src_dir = path_join(project_dir, 'src')
mkdir(src_dir)
test_project.set_property('dir_source_main_python',
'src')
for file_name, content in content_dict.items():
f = open(path_join(tests_dir, file_name), 'w')
f.write(content)
f.flush()
f.close()
return test_project

def read_pytest_conftest_result_file(self, dir):
out_file = path_join(dir, 'pytest_collected_config.out')
f = open(out_file, 'r')
out = dict()
out.update({'verbose': f.readline().strip()})
out.update({'capture': f.readline().strip()})
out.update({'tests_list': f.readline().strip().split(',')})
f.close()
return out

def test_should_run_pytest_tests(self):
test_project = self.create_test_project('pytest_sucess', {'test_success.py': pytest_file_success})
run_unit_tests(test_project, Mock())
self.assertTrue(test_project.expand_path('$dir_source_main_python') in sys_path)
self.assertTrue(test_project.expand_path('$dir_source_pytest_python') in sys_path)

def test_should_run_pytest_tests_without_verbose(self):
test_project = self.create_test_project('pytest_sucess_without_verbose',
{'test_success_without_verbose.py': pytest_file_success,
'conftest.py': pytest_conftest_result_to_file})
run_unit_tests(test_project, Mock())
cfg = self.read_pytest_conftest_result_file(test_project.expand_path('$dir_source_pytest_python'))
self.assertEqual(cfg['verbose'], '0')
self.assertEqual(cfg['capture'], 'fd')

def test_should_run_pytest_tests_with_verbose(self):
test_project = self.create_test_project('pytest_sucess_with_verbose',
{'test_success_with_verbose.py': pytest_file_success,
'conftest.py': pytest_conftest_result_to_file})
test_project.set_property('verbose', True)
run_unit_tests(test_project, Mock())
cfg = self.read_pytest_conftest_result_file(test_project.expand_path('$dir_source_pytest_python'))
self.assertEqual(cfg['verbose'], '1')
self.assertEqual(cfg['capture'], 'no')

def test_should_correct_get_pytest_failure(self):
test_project = self.create_test_project('pytest_failure', {'test_failure.py': pytest_file_failure})
self.assertRaises(BuildFailedException, run_unit_tests, test_project, Mock())
# has to be compatible with Python 2.6
# with self.assertRaises(BuildFailedException) as context:
# run_unit_tests(test_project, Mock())

def test_should_run_pytest_tests_with_extra_args(self):
test_project = self.create_test_project('pytest_sucess_with_extra_args',
{'test_success_with_extra_args.py': pytest_file_sucess_with_extra_args,
'conftest.py': pytest_conftest_test_arg})
initialize_pytest_plugin(test_project)
test_project.set_property("pytest_extra_args",
test_project.get_property("pytest_extra_args")
+ ["--test-arg", "test_value"])
run_unit_tests(test_project, Mock())
self.assertTrue(True)

def tearDown(self):
rmtree(self.tmp_test_folder)
27 changes: 0 additions & 27 deletions src/unittest/python/plugins/python/unittest_plugin_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

from pybuilder.core import Project
from pybuilder.plugins.python.unittest_plugin import (execute_tests, execute_tests_matching,
_register_test_and_source_path_and_return_test_dir,
_instrument_result,
_create_runner,
_get_make_result_method_name,
Expand All @@ -33,32 +32,6 @@
__author__ = 'Michael Gruber'


class PythonPathTests(TestCase):
def setUp(self):
self.project = Project('/path/to/project')
self.project.set_property('dir_source_unittest_python', 'unittest')
self.project.set_property('dir_source_main_python', 'src')

def test_should_register_source_paths(self):
system_path = ['some/python/path']

_register_test_and_source_path_and_return_test_dir(self.project, system_path, "unittest")

self.assertTrue('/path/to/project/unittest' in system_path)
self.assertTrue('/path/to/project/src' in system_path)

def test_should_put_project_sources_before_other_sources(self):
system_path = ['irrelevant/sources']

_register_test_and_source_path_and_return_test_dir(self.project, system_path, "unittest")

test_sources_index_in_path = system_path.index('/path/to/project/unittest')
main_sources_index_in_path = system_path.index('/path/to/project/src')
irrelevant_sources_index_in_path = system_path.index('irrelevant/sources')
self.assertTrue(test_sources_index_in_path < irrelevant_sources_index_in_path and
main_sources_index_in_path < irrelevant_sources_index_in_path)


class ExecuteTestsTests(TestCase):
def setUp(self):
self.mock_result = Mock()
Expand Down

0 comments on commit fe1e8e4

Please sign in to comment.