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
					Joshua Harlow
				
			
				
					committed by
					
						 Joshua Harlow
						Joshua Harlow
					
				
			
			
				
	
			
			
			 Joshua Harlow
						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