diff --git a/src/integrationtest/python/should_correctly_pass_failure_pytest_plugin_tests.py b/src/integrationtest/python/should_correctly_pass_failure_pytest_plugin_tests.py new file mode 100644 index 000000000..edc6c5bf6 --- /dev/null +++ b/src/integrationtest/python/should_correctly_pass_failure_pytest_plugin_tests.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# +# This file is part of PyBuilder +# +# Copyright 2011-2015 PyBuilder Team +# +# 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 sep as os_sep +import unittest + +from integrationtest_support import IntegrationTestSupport +from pybuilder.errors import BuildFailedException + + +failure_pytest_test_content = """ +def test_pytest_base_failure(): + assert False +""" + + +class TestDefaultParamFailure(IntegrationTestSupport): + def test_pytest(self): + self.write_build_file(""" +from pybuilder.core import use_plugin, init + +use_plugin("python.pytest") + +@init +def init (project): + pass + """) + + unittest_path = "src/unittest/python" + unittest_file = unittest_path + os_sep + "failure_test.py" + report_file = "target/reports/junit.xml" + + self.create_directory(unittest_path) + self.write_file(unittest_file, failure_pytest_test_content) + + reactor = self.prepare_reactor() + self.assertRaises(BuildFailedException, reactor.build, "run_unit_tests") + + self.assert_file_exists(unittest_file) + self.assert_file_exists(report_file) + + self.assert_file_contains(report_file, 'tests="1"') + self.assert_file_contains(report_file, 'errors="0"') + self.assert_file_contains(report_file, 'failures="1"') + self.assert_file_contains(report_file, 'skips="0"') + + +if __name__ == "__main__": + unittest.main() diff --git a/src/integrationtest/python/should_run_pytest_plugin_tests.py b/src/integrationtest/python/should_run_pytest_plugin_tests.py new file mode 100644 index 000000000..579fa171a --- /dev/null +++ b/src/integrationtest/python/should_run_pytest_plugin_tests.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# +# This file is part of PyBuilder +# +# Copyright 2011-2015 PyBuilder Team +# +# 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 sep as os_sep +import unittest + +from integrationtest_support import IntegrationTestSupport + + +success_pytest_test_content = """ +def test_pytest_base_success(): + assert True + +def test_pytest_base_skip(): + assert True +""" + + +class TestCustomParamSuccess(IntegrationTestSupport): + def test_pytest(self): + self.write_build_file(""" +from pybuilder.core import use_plugin, init + +use_plugin("python.pytest") + +@init +def init (project): + project.set_property("dir_source_pytest_python", "some_dir/unittest") + project.set_property("pytest_report_file", "some_dir/junit.xml") + project.get_property("pytest_extra_args").append("-k") + project.get_property("pytest_extra_args").append("test_pytest_base_success") + """) + + unittest_path = "some_dir/unittest" + unittest_file = unittest_path + os_sep + "success_test.py" + report_file = "some_dir/junit.xml" + + self.create_directory(unittest_path) + self.write_file(unittest_file, success_pytest_test_content) + + reactor = self.prepare_reactor() + reactor.build("run_unit_tests") + + self.assert_file_exists(unittest_file) + self.assert_file_exists(report_file) + + self.assert_file_contains(report_file, 'tests="1"') + self.assert_file_contains(report_file, 'errors="0"') + self.assert_file_contains(report_file, 'failures="0"') + self.assert_file_contains(report_file, 'skips="0"') + + +if __name__ == "__main__": + unittest.main() diff --git a/src/main/python/pybuilder/plugins/python/coverage_plugin.py b/src/main/python/pybuilder/plugins/python/coverage_plugin.py index ccb731a69..8249378aa 100644 --- a/src/main/python/pybuilder/plugins/python/coverage_plugin.py +++ b/src/main/python/pybuilder/plugins/python/coverage_plugin.py @@ -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) @@ -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): @@ -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 + "."): diff --git a/src/main/python/pybuilder/plugins/python/pytest_plugin.py b/src/main/python/pybuilder/plugins/python/pytest_plugin.py new file mode 100644 index 000000000..4ec2acdbe --- /dev/null +++ b/src/main/python/pybuilder/plugins/python/pytest_plugin.py @@ -0,0 +1,70 @@ +# -*- 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_report_file", "target/reports/junit.xml") + 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] + if project.get_property('verbose'): + pytest_args.append('-s') + pytest_args.append('-v') + pytest_args.append('--junit-xml') + pytest_args.append(project.expand_path('$pytest_report_file')) + pytest_args = pytest_args + (extra_args if extra_args else []) + ret = pytest_main(pytest_args) + if ret: + raise BuildFailedException('pytest: unittests failed') + else: + logger.info('pytest: All unittests passed.') + except: + raise diff --git a/src/main/python/pybuilder/plugins/python/unittest_plugin.py b/src/main/python/pybuilder/plugins/python/unittest_plugin.py index 5bdcac22a..1e253e878 100644 --- a/src/main/python/pybuilder/plugins/python/unittest_plugin.py +++ b/src/main/python/pybuilder/plugins/python/unittest_plugin.py @@ -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 @@ -72,7 +75,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: @@ -197,14 +200,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) diff --git a/src/main/python/pybuilder/utils.py b/src/main/python/pybuilder/utils.py index 844429f5c..78c16e8c6 100644 --- a/src/main/python/pybuilder/utils.py +++ b/src/main/python/pybuilder/utils.py @@ -348,3 +348,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 diff --git a/src/unittest/python/plugins/python/pytest_plugin_tests.py b/src/unittest/python/plugins/python/pytest_plugin_tests.py new file mode 100644 index 000000000..e559df7bc --- /dev/null +++ b/src/unittest/python/plugins/python/pytest_plugin_tests.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# +# This file is part of PyBuilder +# +# Copyright 2011-2015 PyBuilder Team +# +# 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 unittest import TestCase + +from pybuilder.core import Project +from pybuilder.errors import MissingPrerequisiteException +from pybuilder.plugins.python.pytest_plugin import ( + assert_pytest_available, + initialize_pytest_plugin +) +from test_utils import Mock + + +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_values_when_initializing_plugin(self): + initialize_pytest_plugin(self.project) + + self.assertEquals( + self.project.get_property("dir_source_pytest_python"), "src/unittest/python") + self.assertEquals( + self.project.get_property("pytest_report_file"), "target/reports/junit.xml") + self.assertEquals( + self.project.get_property("pytest_extra_args"), []) + + def test_should_leave_user_specified_properties_when_initializing_plugin(self): + + expected_properties = { + "dir_source_pytest_python": "source_dir", + "pytest_report_file": "output_dir/report_file", + "pytest_extra_args": ['-a'] + } + + 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) + + +class PytestPluginAssertAvailableTests(TestCase): + def setUp(self): + self.project = Project("basedir") + + def test_should_raise_assert_unavailable(self): + logger_mock = Mock() + self.assertRaises( + MissingPrerequisiteException, + assert_pytest_available, + logger_mock + ) + logger_mock.debug.call_only_once() + + +# PyByuilder build dependency on pytest was removed by PyBuilder developers' request: +# https://github.com/pybuilder/pybuilder/pull/448#discussion_r93954475 +# So we need to disable next tests and decrease % coverage. +# This functionality is covered by next integreation test: +# should_correctly_pass_failure_pytest_plugin_tests +# should_run_pytest_plugin_tests +# +# class PytestPluginAssertAvailableTests(TestCase): +# def setUp(self): +# self.project = Project("basedir") +# +# @patch('pytest.__version__') +# def test_should_assert_available(self, pytest_version): +# logger_mock = Mock() +# assert_pytest_available(logger_mock) +# self.assertEqual(logger_mock.debug.call_count, 2) +# +# +# class PytestPluginRunTests(TestCase): +# def setUp(self): +# self.project = Project("basedir") +# self.project.set_property('verbose', True) +# self.project.set_property('dir_source_main_python', 'src') +# initialize_pytest_plugin(self.project) +# +# @patch('pytest.main', return_value=None) +# def test_should_call_pytest_main_with_arguments(self, main): +# run_unit_tests(self.project, Mock()) +# main.assert_called_with(['basedir/src/unittest/python', '-s', '-v', '--junit-xml', +# 'basedir/target/reports/junit.xml']) +# +# @patch('pytest.main', return_value=1) +# def test_should_raise_if_pytest_return_code(self, main): +# self.assertRaises( +# BuildFailedException, +# run_unit_tests, +# self.project, +# Mock() +# ) diff --git a/src/unittest/python/plugins/python/unittest_plugin_tests.py b/src/unittest/python/plugins/python/unittest_plugin_tests.py index 08a69268c..3f9b2861c 100644 --- a/src/unittest/python/plugins/python/unittest_plugin_tests.py +++ b/src/unittest/python/plugins/python/unittest_plugin_tests.py @@ -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, @@ -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() diff --git a/src/unittest/python/utils_tests.py b/src/unittest/python/utils_tests.py index 789509719..b7ca39d77 100644 --- a/src/unittest/python/utils_tests.py +++ b/src/unittest/python/utils_tests.py @@ -26,6 +26,7 @@ import unittest from json import loads +from pybuilder.core import Project from pybuilder.errors import PyBuilderException from pybuilder.utils import (GlobExpression, Timer, @@ -40,7 +41,8 @@ render_report, timedelta_in_millis, fork_process, - execute_command) + execute_command, + register_test_and_source_path_and_return_test_dir) from test_utils import patch, Mock @@ -435,3 +437,29 @@ def test_execute_command(self, popen, _): self.assertEquals(execute_command(["test", "commands"], outfile_name="test.out"), 0) self.assertEquals( execute_command(["test", "commands"], outfile_name="test.out", error_file_name="test.out.err"), 0) + + +class PythonPathTests(unittest.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)