[PATCH v2 1/4] test: dynamically generate parser tests
Jani Nikula
jani at nikula.org
Sun Jan 27 14:07:12 PST 2019
Apologies for the noise, I sent the entire series to the wrong list. :(
BR,
Jani.
On Mon, 28 Jan 2019, Jani Nikula <jani at nikula.org> wrote:
> It's impossible to have expected failures or other unittest decorators
> at subtest granularity. They only work at the test method level. On the
> other hand we don't want to be manually adding test methods when all of
> the tests are defined in terms of input files and expected results.
>
> Generate test methods dynamically from the input files, and assign to
> the test class. Running code at import time to do this is less than
> stellar, but it needs to be done early to have unittest test discovery
> find the methods.
>
> The alternative would be to add a load_tests protocol function [1], but
> that seems like more boilerplate. Can be added later as needed.
>
> Finally, one massive upside to this is the ability to run individual
> named testcases. For example, to test enum.c and typedef-enum.c, use:
>
> $ test/test_hawkmoth.py ParserTest.test_enum ParserTest.test_typedef_enum
>
> [1] https://docs.python.org/3/library/unittest.html#load-tests-protocol
> ---
> test/test_hawkmoth.py | 26 +++++++-------------------
> test/testenv.py | 29 +++++++++++++++++++++++++++++
> 2 files changed, 36 insertions(+), 19 deletions(-)
>
> diff --git a/test/test_hawkmoth.py b/test/test_hawkmoth.py
> index 1fe02efc004d..75eebbe35eef 100755
> --- a/test/test_hawkmoth.py
> +++ b/test/test_hawkmoth.py
> @@ -8,28 +8,16 @@ import unittest
> import testenv
> from hawkmoth import hawkmoth
>
> -class ParserTest(unittest.TestCase):
> - def _run_test(self, input_filename):
> - # sanity check
> - self.assertTrue(os.path.isfile(input_filename))
> -
> - options = testenv.get_testcase_options(input_filename)
> - output = hawkmoth.parse_to_string(input_filename, False, **options)
> - expected = testenv.read_file(input_filename, ext='stdout')
> +def _get_output(input_filename, **options):
> + return hawkmoth.parse_to_string(input_filename, False, **options)
>
> - self.assertEqual(expected, output)
> +def _get_expected(input_filename, **options):
> + return testenv.read_file(input_filename, ext='stdout')
>
> - def _run_dir(self, path):
> - # sanity check
> - self.assertTrue(os.path.isdir(path))
> -
> - with self.subTest(path=path):
> - for f in testenv.get_testcases(path):
> - with self.subTest(source=os.path.basename(f)):
> - self._run_test(f)
> +class ParserTest(unittest.TestCase):
> + pass
>
> - def test_parser(self):
> - self._run_dir(testenv.testdir)
> +testenv.assign_test_methods(ParserTest, _get_output, _get_expected)
>
> if __name__ == '__main__':
> unittest.main()
> diff --git a/test/testenv.py b/test/testenv.py
> index f026aead8c07..cc80ef2218ed 100644
> --- a/test/testenv.py
> +++ b/test/testenv.py
> @@ -3,6 +3,7 @@
>
> import sys
> import os
> +import unittest
>
> testext = '.c'
> testdir = os.path.dirname(os.path.abspath(__file__))
> @@ -10,6 +11,16 @@ rootdir = os.path.dirname(testdir)
>
> sys.path.insert(0, rootdir)
>
> +def _testcase_name(testcase):
> + """Convert a testcase filename into a test case identifier."""
> + name = os.path.splitext(os.path.basename(testcase))[0]
> + name = name.replace('-', '_')
> + name = 'test_{name}'.format(name=name)
> +
> + assert name.isidentifier()
> +
> + return name
> +
> def get_testcases(path):
> for f in sorted(os.listdir(path)):
> if f.endswith(testext):
> @@ -52,3 +63,21 @@ def read_file(filename, **kwargs):
> expected = file.read()
>
> return expected
> +
> +def _test_generator(get_output, get_expected, input_filename, **options):
> + """Return a function that compares output/expected results on input_filename."""
> + def test(self):
> + output = get_output(input_filename, **options)
> + expected = get_expected(input_filename, **options)
> +
> + self.assertEqual(expected, output)
> +
> + return test
> +
> +def assign_test_methods(cls, get_output, get_expected):
> + """Assign test case functions to the given class."""
> + for f in get_testcases(testdir):
> + options = get_testcase_options(f)
> + method = _test_generator(get_output, get_expected, f, **options)
> +
> + setattr(cls, _testcase_name(f), method)
> --
> 2.20.1
More information about the notmuch
mailing list