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:
committed by
Joshua Harlow
parent
afe2a9339c
commit
265181f573
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user