Use a metaclass to dynamically add testcases to example runner

Instead of using a custom update() mechanism just take advantage
of a metaclass that can dynamically add our desired test functions
on in a more pythonic manner.

Change-Id: I8f4940f85dd7b5255c181795606ac76ca5605baa
This commit is contained in:
Joshua Harlow
2014-11-18 18:07:37 -08:00
committed by Joshua Harlow
parent afe2a9339c
commit 265181f573

View File

@@ -28,22 +28,37 @@ examples is indeterministic (due to hash randomization for example).
""" """
import keyword
import os import os
import re import re
import subprocess import subprocess
import sys import sys
import taskflow.test import six
from taskflow import test
ROOT_DIR = os.path.abspath( ROOT_DIR = os.path.abspath(
os.path.dirname( os.path.dirname(
os.path.dirname( os.path.dirname(
os.path.dirname(__file__)))) os.path.dirname(__file__))))
# This is used so that any uuid like data being output is removed (since it
# will change per test run and will invalidate the deterministic output that
# we expect to be able to check).
UUID_RE = re.compile('XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' UUID_RE = re.compile('XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
.replace('X', '[0-9a-f]')) .replace('X', '[0-9a-f]'))
def safe_filename(filename):
# Translates a filename into a method name, returns falsey if not
# possible to perform this translation...
name = re.sub("[^a-zA-Z0-9_]+", "_", filename)
if not name or re.match(r"^[_]+$", name) or keyword.iskeyword(name):
return False
return name
def root_path(*args): def root_path(*args):
return os.path.join(ROOT_DIR, *args) return os.path.join(ROOT_DIR, *args)
@@ -71,7 +86,7 @@ def expected_output_path(name):
return root_path('taskflow', 'examples', '%s.out.txt' % name) return root_path('taskflow', 'examples', '%s.out.txt' % name)
def list_examples(): def iter_examples():
examples_dir = root_path('taskflow', 'examples') examples_dir = root_path('taskflow', 'examples')
for filename in os.listdir(examples_dir): for filename in os.listdir(examples_dir):
path = os.path.join(examples_dir, filename) path = os.path.join(examples_dir, filename)
@@ -80,38 +95,34 @@ def list_examples():
name, ext = os.path.splitext(filename) name, ext = os.path.splitext(filename)
if ext != ".py": if ext != ".py":
continue continue
bad_endings = [] if not any(name.endswith(i) for i in ("utils", "no_test")):
for i in ("utils", "no_test"): safe_name = safe_filename(name)
if name.endswith(i): if safe_name:
bad_endings.append(True) yield name, safe_name
if not any(bad_endings):
yield name
class ExamplesTestCase(taskflow.test.TestCase): class ExampleAdderMeta(type):
@classmethod """Translates examples into test cases/methods."""
def update(cls):
"""For each example, adds on a test method.
This newly created test method will then be activated by the testing def __new__(cls, name, parents, dct):
framework when it scans for and runs tests. This makes for a elegant
and simple way to ensure that all of the provided examples def generate_test(example_name):
actually work.
"""
def add_test_method(name, method_name):
def test_example(self): def test_example(self):
self._check_example(name) self._check_example(example_name)
test_example.__name__ = method_name return test_example
setattr(cls, method_name, test_example)
for name in list_examples(): for example_name, safe_name in iter_examples():
safe_name = str(re.sub("[^a-zA-Z0-9_]+", "_", name)) test_name = 'test_%s' % safe_name
if re.match(r"^[_]+$", safe_name): dct[test_name] = generate_test(example_name)
continue
add_test_method(name, 'test_%s' % safe_name) return type.__new__(cls, name, parents, dct)
@six.add_metaclass(ExampleAdderMeta)
class ExamplesTestCase(test.TestCase):
"""Runs the examples, and checks the outputs against expected outputs."""
def _check_example(self, name): def _check_example(self, name):
"""Runs the example, and checks the output against expected output."""
output = run_example(name) output = run_example(name)
eop = expected_output_path(name) eop = expected_output_path(name)
if os.path.isfile(eop): if os.path.isfile(eop):
@@ -123,14 +134,14 @@ class ExamplesTestCase(taskflow.test.TestCase):
expected_output = UUID_RE.sub('<SOME UUID>', expected_output) expected_output = UUID_RE.sub('<SOME UUID>', expected_output)
self.assertEqual(output, expected_output) self.assertEqual(output, expected_output)
ExamplesTestCase.update()
def make_output_files(): def make_output_files():
"""Generate output files for all examples.""" """Generate output files for all examples."""
for name in list_examples(): for example_name, _safe_name in iter_examples():
output = run_example(name) print("Running %s" % example_name)
with open(expected_output_path(name), 'w') as f: print("Please wait...")
output = run_example(example_name)
with open(expected_output_path(example_name), 'w') as f:
f.write(output) f.write(output)