unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
From: Jani Nikula <jani@nikula.org>
To: notmuch@notmuchmail.org
Subject: [PATCH v2 1/4] test: dynamically generate parser tests
Date: Mon, 28 Jan 2019 00:04:26 +0200	[thread overview]
Message-ID: <20190127220429.9206-1-jani@nikula.org> (raw)

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

             reply	other threads:[~2019-01-27 22:04 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-01-27 22:04 Jani Nikula [this message]
2019-01-27 22:04 ` [PATCH v2 2/4] test: dynamically generate directive tests Jani Nikula
2019-01-27 22:04 ` [PATCH v2 3/4] test: add support for flagging expected failures in testcase options Jani Nikula
2019-01-27 22:04 ` [PATCH v2 4/4] test: use html builder for directive tests Jani Nikula
2019-01-27 22:07 ` [PATCH v2 1/4] test: dynamically generate parser tests Jani Nikula

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://notmuchmail.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20190127220429.9206-1-jani@nikula.org \
    --to=jani@nikula.org \
    --cc=notmuch@notmuchmail.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://yhetil.org/notmuch.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).