check_test_cases: move "walk" functions into a class

Make the structure more Pythonic: use classes for abstraction and
refinement, rather than higher-order functions.

Convert walk(function, state, data) into instance.walk(data) where
instance has a method that implements function and state is a field of
instance.

No behavior change.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
This commit is contained in:
Gilles Peskine 2020-06-25 16:34:11 +02:00
parent d34e9e450f
commit 78c45dbb0f

View File

@ -76,13 +76,41 @@ def check_description(results, seen, file_name, line_number, description):
len(description)) len(description))
seen[description] = line_number seen[description] = line_number
def walk_test_suite(function, results, descriptions, data_file_name): class TestDescriptionExplorer:
"""Iterate over the test cases in the given unit test data file. """An iterator over test cases with descriptions.
Call function(results, descriptions, data_file_name, line_number, description) The test cases that have descriptions are:
on each description. * Individual unit tests (entries in a .data file) in test suites.
* Individual test cases in ssl-opt.sh.
This is an abstract class. To use it, derive a class that implements
the process_test_case method, and call walk_all().
""" """
def process_test_case(self, per_file_state,
file_name, line_number, description):
"""Process a test case.
per_file_state: a new object returned by per_file_state() for each file.
file_name: a relative path to the file containing the test case.
line_number: the line number in the given file.
description: the test case description as a byte string.
"""
raise NotImplementedError
def per_file_state(self):
"""Return a new per-file state object.
The default per-file state object is None. Child classes that require per-file
state may override this method.
"""
#pylint: disable=no-self-use
return None
def walk_test_suite(self, data_file_name):
"""Iterate over the test cases in the given unit test data file."""
in_paragraph = False in_paragraph = False
descriptions = self.per_file_state() # pylint: disable=assignment-from-none
with open(data_file_name, 'rb') as data_file: with open(data_file_name, 'rb') as data_file:
for line_number, line in enumerate(data_file, 1): for line_number, line in enumerate(data_file, 1):
line = line.rstrip(b'\r\n') line = line.rstrip(b'\r\n')
@ -93,16 +121,13 @@ on each description.
continue continue
if not in_paragraph: if not in_paragraph:
# This is a test case description line. # This is a test case description line.
function(results, descriptions, self.process_test_case(descriptions,
data_file_name, line_number, line) data_file_name, line_number, line)
in_paragraph = True in_paragraph = True
def walk_ssl_opt_sh(function, results, descriptions, file_name): def walk_ssl_opt_sh(self, file_name):
"""Iterate over the test cases in ssl-opt.sh or a file with a similar format. """Iterate over the test cases in ssl-opt.sh or a file with a similar format."""
descriptions = self.per_file_state() # pylint: disable=assignment-from-none
Call function(results, descriptions, file_name, line_number, description)
on each description.
"""
with open(file_name, 'rb') as file_contents: with open(file_name, 'rb') as file_contents:
for line_number, line in enumerate(file_contents, 1): for line_number, line in enumerate(file_contents, 1):
# Assume that all run_test calls have the same simple form # Assume that all run_test calls have the same simple form
@ -112,23 +137,37 @@ on each description.
if not m: if not m:
continue continue
description = m.group(1) description = m.group(1)
function(results, descriptions, self.process_test_case(descriptions,
file_name, line_number, description) file_name, line_number, description)
def walk_all(function, results): def walk_all(self):
"""Iterate over all named test cases. """Iterate over all named test cases."""
Call function(results, {}, file_name, line_number, description)
on each description.
"""
test_directories = collect_test_directories() test_directories = collect_test_directories()
for directory in test_directories: for directory in test_directories:
for data_file_name in glob.glob(os.path.join(directory, 'suites', for data_file_name in glob.glob(os.path.join(directory, 'suites',
'*.data')): '*.data')):
walk_test_suite(function, results, {}, data_file_name) self.walk_test_suite(data_file_name)
ssl_opt_sh = os.path.join(directory, 'ssl-opt.sh') ssl_opt_sh = os.path.join(directory, 'ssl-opt.sh')
if os.path.exists(ssl_opt_sh): if os.path.exists(ssl_opt_sh):
walk_ssl_opt_sh(function, results, {}, ssl_opt_sh) self.walk_ssl_opt_sh(ssl_opt_sh)
class DescriptionChecker(TestDescriptionExplorer):
"""Check all test case descriptions.
* Check that each description is valid (length, allowed character set, etc.).
* Check that there is no duplicated description inside of one test suite.
"""
def __init__(self, results):
self.results = results
def per_file_state(self):
return {}
def process_test_case(self, per_file_state,
file_name, line_number, description):
check_description(self.results, per_file_state,
file_name, line_number, description)
def main(): def main():
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
@ -140,7 +179,8 @@ def main():
help='Show warnings (default: on; undoes --quiet)') help='Show warnings (default: on; undoes --quiet)')
options = parser.parse_args() options = parser.parse_args()
results = Results(options) results = Results(options)
walk_all(check_description, results) checker = DescriptionChecker(results)
checker.walk_all()
if (results.warnings or results.errors) and not options.quiet: if (results.warnings or results.errors) and not options.quiet:
sys.stderr.write('{}: {} errors, {} warnings\n' sys.stderr.write('{}: {} errors, {} warnings\n'
.format(sys.argv[0], results.errors, results.warnings)) .format(sys.argv[0], results.errors, results.warnings))