From 5a2543efea8215fb49d74ca70fcd79a584698d8f Mon Sep 17 00:00:00 2001 From: Felipe Arcaro Date: Mon, 20 May 2024 20:55:56 +0100 Subject: [PATCH 1/3] Added new parameter to allow running tests with custom runner --- README.md | 5 +++- setup.cfg | 1 + src/tests/test_main.py | 48 +++++++++++++++++++++++++++++++++++ src/unittest_parallel/main.py | 11 +++++++- 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a741b21..faaf513 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ your project. ~~~ usage: unittest-parallel [-h] [-v] [-q] [-f] [-b] [-k TESTNAMEPATTERNS] - [-s START] [-p PATTERN] [-t TOP] [-j COUNT] + [-s START] [-p PATTERN] [-t TOP] [-j COUNT] [-r RUNNER] [--level {module,class,test}] [--disable-process-pooling] [--coverage] [--coverage-branch] [--coverage-rcfile RCFILE] @@ -79,6 +79,9 @@ options: -t TOP, --top-level-directory TOP Top level directory of project (defaults to start directory) + -r RUNNER, --runner RUNNER + Custom unittest runner module and class (Supported: TeamCity) + E.g.: `-r teamcity.unittestpy TeamcityTestRunner parallelization options: -j COUNT, --jobs COUNT diff --git a/setup.cfg b/setup.cfg index 7ce6d5b..5e32ab5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,7 @@ package_dir = = src install_requires = coverage >= 5.1 + teamcity-messages == 1.32 [options.entry_points] console_scripts = diff --git a/src/tests/test_main.py b/src/tests/test_main.py index 594f263..61d4108 100644 --- a/src/tests/test_main.py +++ b/src/tests/test_main.py @@ -1607,3 +1607,51 @@ def test_coverage_other(self): Total coverage is 100.00% ''') + + + def test_invalid_custom_runner(self): + discover_suite = unittest.TestSuite(tests=[ + unittest.TestSuite(tests=[ + unittest.TestSuite(tests=[SuccessTestCase('mock_1'), SuccessTestCase('mock_2'), SuccessTestCase('mock_3')]), + ]) + ]) + with patch('multiprocessing.cpu_count', Mock(return_value=2)), \ + patch('multiprocessing.get_context', new=MockMultiprocessingContext), \ + patch('multiprocessing.Manager', new=MockMultiprocessingManager), \ + patch('sys.stdout', StringIO()) as stdout, \ + patch('sys.stderr', StringIO()) as stderr, \ + patch('unittest.TestLoader.discover', Mock(return_value=discover_suite)): + with self.assertRaises(ModuleNotFoundError): + main(['-v', '--runner', 'invalid_method', 'InvalidClass']) + + def test_teamcity_custom_runner(self): + discover_suite = unittest.TestSuite(tests=[ + unittest.TestSuite(tests=[ + unittest.TestSuite(tests=[SuccessTestCase('mock_1'), SuccessTestCase('mock_2'), SuccessTestCase('mock_3')]), + ]) + ]) + with patch('multiprocessing.cpu_count', Mock(return_value=2)), \ + patch('multiprocessing.get_context', new=MockMultiprocessingContext), \ + patch('multiprocessing.Manager', new=MockMultiprocessingManager), \ + patch('sys.stdout', StringIO()) as stdout, \ + patch('sys.stderr', StringIO()) as stderr, \ + patch('unittest.TestLoader.discover', Mock(return_value=discover_suite)): + main(['-v', '--runner', 'teamcity.unittestpy', 'TeamcityTestRunner']) + + self.assertEqual(stdout.getvalue(), '') + + def test_unittest_custom_runner_as_param(self): + discover_suite = unittest.TestSuite(tests=[ + unittest.TestSuite(tests=[ + unittest.TestSuite(tests=[SuccessTestCase('mock_1'), SuccessTestCase('mock_2'), SuccessTestCase('mock_3')]), + ]) + ]) + with patch('multiprocessing.cpu_count', Mock(return_value=2)), \ + patch('multiprocessing.get_context', new=MockMultiprocessingContext), \ + patch('multiprocessing.Manager', new=MockMultiprocessingManager), \ + patch('sys.stdout', StringIO()) as stdout, \ + patch('sys.stderr', StringIO()) as stderr, \ + patch('unittest.TestLoader.discover', Mock(return_value=discover_suite)): + main(['-v', '--runner', 'unittest', 'TextTestRunner']) + + self.assertEqual(stdout.getvalue(), '') \ No newline at end of file diff --git a/src/unittest_parallel/main.py b/src/unittest_parallel/main.py index adab699..d419c78 100644 --- a/src/unittest_parallel/main.py +++ b/src/unittest_parallel/main.py @@ -14,6 +14,7 @@ import tempfile import time import unittest +import importlib import coverage @@ -41,6 +42,8 @@ def main(argv=None): help="Pattern to match tests ('test*.py' default)") parser.add_argument('-t', '--top-level-directory', metavar='TOP', help='Top level directory of project (defaults to start directory)') + parser.add_argument('-r', '--runner', nargs=2, metavar='RUNNER', default=unittest.TextTestRunner, + help='Custom unittest runner module and class') group_parallel = parser.add_argument_group('parallelization options') group_parallel.add_argument('-j', '--jobs', metavar='COUNT', type=int, default=0, help='The number of test processes (default is 0, all cores)') @@ -104,6 +107,12 @@ def main(argv=None): if args.verbose > 1: print(file=sys.stderr) + # Load the customer runner module (if provided) + if args.runner != unittest.TextTestRunner: + custom_module = importlib.import_module(args.runner[0]) + args.runner = getattr(custom_module, args.runner[1]) + + # Run the tests in parallel start_time = time.perf_counter() multiprocessing_context = multiprocessing.get_context(method='spawn') @@ -274,7 +283,7 @@ def run_tests(self, test_suite): # Run unit tests with _coverage(self.args, self.temp_dir): - runner = unittest.TextTestRunner( + runner = self.args.runner( stream=StringIO(), resultclass=ParallelTextTestResult, verbosity=self.args.verbose, From 3774f2fe22e342224ed65cd2d200e18686229b6a Mon Sep 17 00:00:00 2001 From: Felipe Arcaro Date: Mon, 20 May 2024 21:00:45 +0100 Subject: [PATCH 2/3] Removed extra space --- src/unittest_parallel/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/unittest_parallel/main.py b/src/unittest_parallel/main.py index d419c78..63b8f63 100644 --- a/src/unittest_parallel/main.py +++ b/src/unittest_parallel/main.py @@ -112,7 +112,6 @@ def main(argv=None): custom_module = importlib.import_module(args.runner[0]) args.runner = getattr(custom_module, args.runner[1]) - # Run the tests in parallel start_time = time.perf_counter() multiprocessing_context = multiprocessing.get_context(method='spawn') From 77031444e6847d413c8ac4eb7023ae295c9bc941 Mon Sep 17 00:00:00 2001 From: Felipe Arcaro Date: Mon, 20 May 2024 22:08:23 +0100 Subject: [PATCH 3/3] Adjusted unit tests --- src/tests/test_main.py | 72 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/src/tests/test_main.py b/src/tests/test_main.py index 61d4108..2bc1e49 100644 --- a/src/tests/test_main.py +++ b/src/tests/test_main.py @@ -1612,7 +1612,7 @@ def test_coverage_other(self): def test_invalid_custom_runner(self): discover_suite = unittest.TestSuite(tests=[ unittest.TestSuite(tests=[ - unittest.TestSuite(tests=[SuccessTestCase('mock_1'), SuccessTestCase('mock_2'), SuccessTestCase('mock_3')]), + unittest.TestSuite(tests=[SuccessTestCase('mock_1')]), ]) ]) with patch('multiprocessing.cpu_count', Mock(return_value=2)), \ @@ -1627,7 +1627,7 @@ def test_invalid_custom_runner(self): def test_teamcity_custom_runner(self): discover_suite = unittest.TestSuite(tests=[ unittest.TestSuite(tests=[ - unittest.TestSuite(tests=[SuccessTestCase('mock_1'), SuccessTestCase('mock_2'), SuccessTestCase('mock_3')]), + unittest.TestSuite(tests=[SuccessTestCase('mock_1'), SuccessTestCase('mock_2'), SuccessTestCase('mock_3')]) ]) ]) with patch('multiprocessing.cpu_count', Mock(return_value=2)), \ @@ -1639,11 +1639,43 @@ def test_teamcity_custom_runner(self): main(['-v', '--runner', 'teamcity.unittestpy', 'TeamcityTestRunner']) self.assertEqual(stdout.getvalue(), '') + if sys.version_info < (3, 11): # pragma: no cover + self.assertEqual(re.sub(r'\d+\.\d{3}s', 's', stderr.getvalue()), '''\ +Running 1 test suites (3 total tests) across 1 processes + +mock_1 (tests.test_main.SuccessTestCase) ... +mock_1 (tests.test_main.SuccessTestCase) ... ok +mock_2 (tests.test_main.SuccessTestCase) ... +mock_2 (tests.test_main.SuccessTestCase) ... ok +mock_3 (tests.test_main.SuccessTestCase) ... +mock_3 (tests.test_main.SuccessTestCase) ... ok + +---------------------------------------------------------------------- +Ran 3 tests in s + +OK +''') + else: # pragma: no cover + self.assertEqual(re.sub(r'\d+\.\d{3}s', 's', stderr.getvalue()), '''\ +Running 1 test suites (3 total tests) across 1 processes + +mock_1 (tests.test_main.SuccessTestCase.mock_1) ... +mock_1 (tests.test_main.SuccessTestCase.mock_1) ... ok +mock_2 (tests.test_main.SuccessTestCase.mock_2) ... +mock_2 (tests.test_main.SuccessTestCase.mock_2) ... ok +mock_3 (tests.test_main.SuccessTestCase.mock_3) ... +mock_3 (tests.test_main.SuccessTestCase.mock_3) ... ok + +---------------------------------------------------------------------- +Ran 3 tests in s + +OK +''') def test_unittest_custom_runner_as_param(self): discover_suite = unittest.TestSuite(tests=[ unittest.TestSuite(tests=[ - unittest.TestSuite(tests=[SuccessTestCase('mock_1'), SuccessTestCase('mock_2'), SuccessTestCase('mock_3')]), + unittest.TestSuite(tests=[SuccessTestCase('mock_1'), SuccessTestCase('mock_2'), SuccessTestCase('mock_3')]) ]) ]) with patch('multiprocessing.cpu_count', Mock(return_value=2)), \ @@ -1654,4 +1686,36 @@ def test_unittest_custom_runner_as_param(self): patch('unittest.TestLoader.discover', Mock(return_value=discover_suite)): main(['-v', '--runner', 'unittest', 'TextTestRunner']) - self.assertEqual(stdout.getvalue(), '') \ No newline at end of file + self.assertEqual(stdout.getvalue(), '') + if sys.version_info < (3, 11): # pragma: no cover + self.assertEqual(re.sub(r'\d+\.\d{3}s', 's', stderr.getvalue()), '''\ +Running 1 test suites (3 total tests) across 1 processes + +mock_1 (tests.test_main.SuccessTestCase) ... +mock_1 (tests.test_main.SuccessTestCase) ... ok +mock_2 (tests.test_main.SuccessTestCase) ... +mock_2 (tests.test_main.SuccessTestCase) ... ok +mock_3 (tests.test_main.SuccessTestCase) ... +mock_3 (tests.test_main.SuccessTestCase) ... ok + +---------------------------------------------------------------------- +Ran 3 tests in s + +OK +''') + else: # pragma: no cover + self.assertEqual(re.sub(r'\d+\.\d{3}s', 's', stderr.getvalue()), '''\ +Running 1 test suites (3 total tests) across 1 processes + +mock_1 (tests.test_main.SuccessTestCase.mock_1) ... +mock_1 (tests.test_main.SuccessTestCase.mock_1) ... ok +mock_2 (tests.test_main.SuccessTestCase.mock_2) ... +mock_2 (tests.test_main.SuccessTestCase.mock_2) ... ok +mock_3 (tests.test_main.SuccessTestCase.mock_3) ... +mock_3 (tests.test_main.SuccessTestCase.mock_3) ... ok + +---------------------------------------------------------------------- +Ran 3 tests in s + +OK +''') \ No newline at end of file