diff options
author | Yilei Yang <yileiyang@google.com> | 2021-10-07 12:49:41 -0700 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2021-10-07 12:50:14 -0700 |
commit | 1910e64fe1b6e2a276afe0170f382c3b56802402 (patch) | |
tree | 25ba513ed68a290310ab935a5e87f714f0a3a15f /absl | |
parent | 5bc02408a1ef380b606f43fc463018f243611c72 (diff) | |
download | absl-py-1910e64fe1b6e2a276afe0170f382c3b56802402.tar.gz |
#128: support matching substring and glob patterns using bazel's `--test_filter=` flag (Python 3.7+ only).
Previously, the `--test_filter=` arguments are passed to Python's unittest as positional arguments. This only matches by class or method's full names.
After this, `--test_filter=` has the same meaning as unittest's `-k` flag: https://docs.python.org/3/library/unittest.html#cmdoption-unittest-k
One notable behavior difference: previously if the filter doesn't match any tests, the test would fail; now it doesn't fail.
This also makes filtering `absltest.parameterized` tests significantly more user friendly.
This change is only for Python 3.7+.
PiperOrigin-RevId: 401582298
Change-Id: I074e9283706fe9c88a8dbb392084fb6548cebc6f
Diffstat (limited to 'absl')
-rw-r--r-- | absl/CHANGELOG.md | 5 | ||||
-rw-r--r-- | absl/testing/BUILD | 3 | ||||
-rw-r--r-- | absl/testing/absltest.py | 14 | ||||
-rw-r--r-- | absl/testing/tests/absltest_filtering_test.py | 70 | ||||
-rw-r--r-- | absl/testing/tests/absltest_filtering_test_helper.py | 16 |
5 files changed, 101 insertions, 7 deletions
diff --git a/absl/CHANGELOG.md b/absl/CHANGELOG.md index 5e2506f..55b5f0f 100644 --- a/absl/CHANGELOG.md +++ b/absl/CHANGELOG.md @@ -6,7 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com). ## Unreleased -Nothing notable unreleased. +### Changed + +* (testing) #128: When running bazel with its `--test_filter=` flag, it now + treats the filters as `unittest`'s `-k` flag in Python 3.7+. ## 0.14.1 (2021-09-30) diff --git a/absl/testing/BUILD b/absl/testing/BUILD index 609d626..14c9a5f 100644 --- a/absl/testing/BUILD +++ b/absl/testing/BUILD @@ -106,7 +106,7 @@ py_library( py_test( name = "tests/absltest_filtering_test", - size = "small", + size = "medium", srcs = ["tests/absltest_filtering_test.py"], data = [":tests/absltest_filtering_test_helper"], python_version = "PY3", @@ -128,6 +128,7 @@ py_binary( srcs_version = "PY3", deps = [ ":absltest", + ":parameterized", "//absl:app", ], ) diff --git a/absl/testing/absltest.py b/absl/testing/absltest.py index a6a8a8c..a79cde7 100644 --- a/absl/testing/absltest.py +++ b/absl/testing/absltest.py @@ -2336,8 +2336,12 @@ def _setup_filtering(argv): The following environment variable is used in this method: TESTBRIDGE_TEST_ONLY: string, if set, is forwarded to the unittest - framework to use as a test filter. Its value is split with shlex - before being passed as positional arguments on argv. + framework to use as a test filter. Its value is split with shlex, then: + 1. On Python 3.6 and before, split values are passed as positional + arguments on argv. + 2. On Python 3.7+, split values are passed to unittest's `-k` flag. Tests + are matched by glob patterns or substring. See + https://docs.python.org/3/library/unittest.html#cmdoption-unittest-k Args: argv: the argv to mutate in-place. @@ -2346,7 +2350,11 @@ def _setup_filtering(argv): if argv is None or not test_filter: return - argv[1:1] = shlex.split(test_filter) + filters = shlex.split(test_filter) + if sys.version_info[:2] >= (3, 7): + filters = ['-k=' + test_filter for test_filter in filters] + + argv[1:1] = filters def _setup_test_runner_fail_fast(argv): diff --git a/absl/testing/tests/absltest_filtering_test.py b/absl/testing/tests/absltest_filtering_test.py index 63e3915..30a81f6 100644 --- a/absl/testing/tests/absltest_filtering_test.py +++ b/absl/testing/tests/absltest_filtering_test.py @@ -18,6 +18,7 @@ from __future__ import division from __future__ import print_function import subprocess +import sys from absl import logging from absl.testing import _bazelize_command @@ -63,7 +64,12 @@ class TestFilteringTest(absltest.TestCase): if use_env_variable: env['TESTBRIDGE_TEST_ONLY'] = test_filter elif test_filter: - additional_args.extend(test_filter.split(' ')) + if sys.version_info[:2] >= (3, 7): + # The -k flags are passed as positional arguments to absl.flags. + additional_args.append('--') + additional_args.extend(['-k=' + f for f in test_filter.split(' ')]) + else: + additional_args.extend(test_filter.split(' ')) proc = subprocess.Popen( args=([_bazelize_command.get_executable_path(self._test_name)] + @@ -115,12 +121,72 @@ class TestFilteringTest(absltest.TestCase): self.assertIn('class B test C', out) self.assertNotIn('class B test A', out) - def test_not_found_filters(self, use_env_variable, use_app_run): + @absltest.skipIf( + sys.version_info[:2] < (3, 7), + 'Only Python 3.7+ does glob and substring matching.') + def test_substring(self, use_env_variable, use_app_run): + out, exit_code = self._run_filtered( + 'testA', use_env_variable, use_app_run) + self.assertEqual(0, exit_code) + self.assertIn('Ran 2 tests', out) + self.assertIn('ClassA.testA', out) + self.assertIn('ClassB.testA', out) + + @absltest.skipIf( + sys.version_info[:2] < (3, 7), + 'Only Python 3.7+ does glob and substring matching.') + def test_glob_pattern(self, use_env_variable, use_app_run): + out, exit_code = self._run_filtered( + '__main__.Class*.testA', use_env_variable, use_app_run) + self.assertEqual(0, exit_code) + self.assertIn('Ran 2 tests', out) + self.assertIn('ClassA.testA', out) + self.assertIn('ClassB.testA', out) + + @absltest.skipIf( + sys.version_info[:2] >= (3, 7), + "Python 3.7+ uses unittest's -k flag and doesn't fail if no tests match.") + def test_not_found_filters_py36(self, use_env_variable, use_app_run): out, exit_code = self._run_filtered('NotExistedClass.not_existed_method', use_env_variable, use_app_run) self.assertEqual(1, exit_code) self.assertIn("has no attribute 'NotExistedClass'", out) + @absltest.skipIf( + sys.version_info[:2] < (3, 7), + 'Python 3.6 passes the filter as positional arguments and fails if no ' + 'tests match.' + ) + def test_not_found_filters_py37(self, use_env_variable, use_app_run): + out, exit_code = self._run_filtered('NotExistedClass.not_existed_method', + use_env_variable, use_app_run) + self.assertEqual(0, exit_code) + self.assertIn('Ran 0 tests', out) + + @absltest.skipIf( + sys.version_info[:2] < (3, 7), + 'Python 3.6 passes the filter as positional arguments and matches by name' + ) + def test_parameterized_unnamed(self, use_env_variable, use_app_run): + out, exit_code = self._run_filtered('ParameterizedTest.test_unnamed', + use_env_variable, use_app_run) + self.assertEqual(0, exit_code) + self.assertIn('Ran 2 tests', out) + self.assertIn('parameterized unnamed 1', out) + self.assertIn('parameterized unnamed 2', out) + + @absltest.skipIf( + sys.version_info[:2] < (3, 7), + 'Python 3.6 passes the filter as positional arguments and matches by name' + ) + def test_parameterized_named(self, use_env_variable, use_app_run): + out, exit_code = self._run_filtered('ParameterizedTest.test_named', + use_env_variable, use_app_run) + self.assertEqual(0, exit_code) + self.assertIn('Ran 2 tests', out) + self.assertIn('parameterized named 1', out) + self.assertIn('parameterized named 2', out) + if __name__ == '__main__': absltest.main() diff --git a/absl/testing/tests/absltest_filtering_test_helper.py b/absl/testing/tests/absltest_filtering_test_helper.py index 2775a62..2b741ed 100644 --- a/absl/testing/tests/absltest_filtering_test_helper.py +++ b/absl/testing/tests/absltest_filtering_test_helper.py @@ -23,6 +23,7 @@ import sys from absl import app from absl.testing import absltest +from absl.testing import parameterized class ClassA(absltest.TestCase): @@ -58,6 +59,21 @@ class ClassB(absltest.TestCase): self.fail('Force failure') +class ParameterizedTest(parameterized.TestCase): + """Helper parameterized test case for absltest_filtering_test.""" + + @parameterized.parameters([1, 2]) + def test_unnamed(self, value): + sys.stderr.write('\nparameterized unnamed %s' % value) + + @parameterized.named_parameters( + ('test1', 1), + ('test2', 2), + ) + def test_named(self, value): + sys.stderr.write('\nparameterized named %s' % value) + + def main(argv): absltest.main(argv=argv) |