Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bignum test case generation script #6093

Merged
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
8b2df74
Add bignum test generation framework
wernerlewis Jul 8, 2022
69a92ce
Add test generation for bignum cmp variant
wernerlewis Jul 18, 2022
86caf85
Add test case generation for bignum add
wernerlewis Jul 18, 2022
a51fe2b
Sort tests when generating cases
wernerlewis Jul 20, 2022
b17ca8a
Remove set() to preserve test case order
wernerlewis Jul 20, 2022
c442f6a
Fix type issues
wernerlewis Jul 20, 2022
265e051
Remove is None from if statement
wernerlewis Jul 20, 2022
6a31396
Fix incorrect indentation
wernerlewis Jul 20, 2022
75ef944
Fix CMake change failures on Windows
wernerlewis Jul 21, 2022
383461c
Separate CMake targets for bignum and PSA
wernerlewis Aug 23, 2022
fbb75e3
Separate common test generation classes/functions
wernerlewis Aug 24, 2022
55e638c
Remove abbreviations and clarify attributes
wernerlewis Aug 23, 2022
92c876a
Remove unneeded list concatenation
wernerlewis Aug 23, 2022
6c70d74
Convert bools to int before arithmetic
wernerlewis Aug 24, 2022
169034a
Add details to docstrings
wernerlewis Aug 23, 2022
699e126
Use ABCMeta for abstract classes
wernerlewis Aug 24, 2022
2b527a3
Split generate_tests to reduce code complexity
wernerlewis Aug 24, 2022
cfd4768
Use __new__() for case counting
wernerlewis Aug 24, 2022
d03d2a3
Remove trailing whitespace in description
wernerlewis Aug 24, 2022
6300b4f
Add missing typing
wernerlewis Aug 24, 2022
9990b30
Use typing casts for fixed-width tuples
wernerlewis Aug 24, 2022
a195ce7
Disable pylint unused arg in __new__
wernerlewis Aug 24, 2022
6d654c6
Raise NotImplementedError in abstract methods
wernerlewis Aug 25, 2022
e3ad22e
Fix TARGET types and code style
wernerlewis Aug 25, 2022
a16b617
Disable abstract check in pylint
wernerlewis Aug 25, 2022
f156c43
Use argparser default for directory
wernerlewis Aug 25, 2022
6ef5436
Clarify documentation
wernerlewis Aug 25, 2022
9df9faa
Use argparser default for targets
wernerlewis Aug 25, 2022
76f4562
Fix trailing whitespace
wernerlewis Aug 25, 2022
3366ebc
Add test_generation.py dependency in builds
wernerlewis Aug 25, 2022
81f2444
Modify wording in docstrings
wernerlewis Aug 25, 2022
a4b7720
Use `combinations_with_replacement` for inputs
wernerlewis Aug 31, 2022
466f036
Add dependencies attribute to BaseTarget
wernerlewis Aug 31, 2022
aaf3b79
Use Python 3.5 style typing for dependencies
wernerlewis Aug 31, 2022
a4668a6
Rework TestGenerator to add file targets
wernerlewis Sep 2, 2022
5601308
Remove unused imports
wernerlewis Sep 2, 2022
855e45c
Use simpler int to hex string conversion
wernerlewis Sep 2, 2022
1fade8a
Move symbol definition out of __init__
wernerlewis Sep 12, 2022
3dc4519
Replace L/R inputs with A/B
wernerlewis Sep 12, 2022
34d6d3e
Update comments/docstrings in TestGenerator
wernerlewis Sep 14, 2022
858cffd
Add toggle for test case count in descriptions
wernerlewis Sep 14, 2022
00d0242
Remove argparser default for directory
wernerlewis Sep 14, 2022
b6e8091
Use typing.cast instead of unqualified cast
wernerlewis Sep 14, 2022
ac446c8
Add combination_pairs helper function
wernerlewis Sep 14, 2022
52ae326
Update references to file targets in docstrings
wernerlewis Sep 14, 2022
07c830c
Fix setting for default test suite directory
wernerlewis Sep 15, 2022
c2fb540
Use a script specific description in CLI help
wernerlewis Sep 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 32 additions & 14 deletions scripts/mbedtls_dev/test_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@
class BaseTarget(metaclass=ABCMeta):
"""Base target for test case generation.

Derive directly from this class when adding new file Targets, setting
`target_basename`.
Child classes of this class represent an output file, and can be referred
to as file targets. These indicate where test cases will be written to for
all subclasses of the file target, which is set by `target_basename`.

Attributes:
count: Counter for test cases from this class.
case_description: Short description of the test case. This may be
automatically generated using the class, or manually set.
dependencies: A list of dependencies required for the test case.
show_test_count: Toggle for inclusion of `count` in the test description.
target_basename: Basename of file to write generated tests to. This
should be specified in a child class of BaseTarget.
test_function: Test function which the class generates cases for.
Expand All @@ -53,6 +55,7 @@ class BaseTarget(metaclass=ABCMeta):
count = 0
case_description = ""
dependencies = [] # type: List[str]
show_test_count = True
target_basename = ""
test_function = ""
test_name = ""
Expand All @@ -78,16 +81,19 @@ def description(self) -> str:
"""Create a test case description.

Creates a description of the test case, including a name for the test
function, a case number, and a description the specific test case.
This should inform a reader what is being tested, and provide context
for the test case.
function, an optional case count, and a description of the specific
test case. This should inform a reader what is being tested, and
provide context for the test case.

Returns:
Description for the test case.
"""
return "{} #{} {}".format(
self.test_name, self.count, self.case_description
).strip()
if self.show_test_count:
return "{} #{} {}".format(
self.test_name, self.count, self.case_description
).strip()
else:
return "{} {}".format(self.test_name, self.case_description).strip()


def create_test_case(self) -> test_case.TestCase:
Expand Down Expand Up @@ -130,15 +136,23 @@ def generate_tests(cls) -> Iterator[test_case.TestCase]:


class TestGenerator:
"""Generate test data."""
"""Generate test cases and write to data files."""
def __init__(self, options) -> None:
self.test_suite_directory = getattr(options, 'directory')
# Add file Targets which have been declared in other modules
self.test_suite_directory = self.get_option(options, 'directory',
'tests/suites')
# Update `targets` with an entry for each child class of BaseTarget.
# Each entry represents a file generated by the BaseTarget framework,
# and enables generating the .data files using the CLI.
self.targets.update({
subclass.target_basename: subclass.generate_tests
for subclass in BaseTarget.__subclasses__()
})

@staticmethod
def get_option(options, name: str, default: T) -> T:
value = getattr(options, name, None)
return default if value is None else value

def filename_for(self, basename: str) -> str:
"""The location of the data file with the specified base name."""
return posixpath.join(self.test_suite_directory, basename + '.data')
Expand All @@ -165,15 +179,19 @@ def generate_target(self, name: str, *target_args) -> None:
test_cases = self.targets[name](*target_args)
self.write_test_data_file(name, test_cases)

def main(args, generator_class: Type[TestGenerator] = TestGenerator):
def main(args, description: str, generator_class: Type[TestGenerator] = TestGenerator):
"""Command line entry point."""
parser = argparse.ArgumentParser(description=__doc__)
parser = argparse.ArgumentParser(description=description)
parser.add_argument('--list', action='store_true',
help='List available targets and exit')
parser.add_argument('--list-for-cmake', action='store_true',
help='Print \';\'-separated list of available targets and exit')
parser.add_argument('--directory', default="tests/suites", metavar='DIR',
parser.add_argument('--directory', metavar='DIR',
help='Output directory (default: tests/suites)')
# The `--directory` option is interpreted relative to the directory from
# which the script is invoked, but the default is relative to the root of
# the mbedtls tree. The default should not be set above, but instead after
# `build_tree.chdir_to_root()` is called.
Comment on lines +191 to +194
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, not quite. --directory is still interpreted relative to the root of the mbedtls tree. An abspath call is missing somewhere (or alternatively we could stop using chdir, but that would be annoying).

But since this is preexisting (I know it worked when I originally wrote it, but I might have broken it before I even committed), this is a non-blocker.

parser.add_argument('targets', nargs='*', metavar='TARGET',
help='Target file to generate (default: all; "-": none)')
options = parser.parse_args(args)
Expand Down
46 changes: 26 additions & 20 deletions tests/scripts/generate_bignum_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

Class structure:

minosgalanakis marked this conversation as resolved.
Show resolved Hide resolved
Child classes of test_generation.BaseTarget (file Targets) represent a target
Child classes of test_generation.BaseTarget (file targets) represent an output
file. These indicate where test cases will be written to, for all subclasses of
this Target. Multiple Target classes should not reuse a `target_basename`.
this target. Multiple file targets should not reuse a `target_basename`.

Each subclass derived from a file Target can either be:
Each subclass derived from a file target can either be:
- A concrete class, representing a test function, which generates test cases.
- An abstract class containing shared methods and attributes, not associated
with a test function. An example is BignumOperation, which provides
Expand All @@ -24,7 +24,7 @@
Adding test case generation for a function:

A subclass representing the test function should be added, deriving from a
file Target such as BignumTarget. This test class must set/implement the
file target such as BignumTarget. This test class must set/implement the
following:
- test_function: the function name from the associated .function file.
- test_name: a descriptive name or brief summary to refer to the test
Expand Down Expand Up @@ -56,9 +56,10 @@

import itertools
import sys
import typing

from abc import ABCMeta, abstractmethod
from typing import Iterator, List, Tuple, TypeVar, cast
from typing import Iterator, List, Tuple, TypeVar

import scripts_path # pylint: disable=unused-import
from mbedtls_dev import test_case
Expand All @@ -72,6 +73,17 @@ def hex_to_int(val: str) -> int:
def quote_str(val) -> str:
return "\"{}\"".format(val)

def combination_pairs(values: List[T]) -> List[Tuple[T, T]]:
"""Return all pair combinations from input values.

The return value is cast, as older versions of mypy are unable to derive
the specific type returned by itertools.combinations_with_replacement.
Comment on lines +79 to +80
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocker: this information is relevant when reading the function's code, but not when using the function. So it should be a comment, not part of the documentation string.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#6296 has fixes for this and the other non-blocker issues I raised in this review.

"""
return typing.cast(
List[Tuple[T, T]],
list(itertools.combinations_with_replacement(values, 2))
)


class BignumTarget(test_generation.BaseTarget, metaclass=ABCMeta):
#pylint: disable=abstract-method
Expand Down Expand Up @@ -99,7 +111,7 @@ class BignumOperation(BignumTarget, metaclass=ABCMeta):
"0000000000000000123", "-0000000000000000123",
"1230000000000000000", "-1230000000000000000"
] # type: List[str]
input_cases = cast(List[Tuple[str, str]], []) # type: List[Tuple[str, str]]
input_cases = [] # type: List[Tuple[str, str]]

def __init__(self, val_a: str, val_b: str) -> None:
self.arg_a = val_a
Expand Down Expand Up @@ -164,10 +176,7 @@ def get_value_pairs(cls) -> Iterator[Tuple[str, str]]:
Combinations are first generated from all input values, and then
specific cases provided.
"""
yield from cast(
Iterator[Tuple[str, str]],
itertools.combinations_with_replacement(cls.input_values, 2)
)
yield from combination_pairs(cls.input_values)
yield from cls.input_cases

@classmethod
Expand Down Expand Up @@ -214,19 +223,16 @@ class BignumAdd(BignumOperation):
symbol = "+"
test_function = "mbedtls_mpi_add_mpi"
test_name = "MPI add"
input_cases = cast(
List[Tuple[str, str]],
list(itertools.combinations_with_replacement(
[
"1c67967269c6", "9cde3",
"-1c67967269c6", "-9cde3",
], 2
))
input_cases = combination_pairs(
[
"1c67967269c6", "9cde3",
"-1c67967269c6", "-9cde3",
]
)

def result(self) -> str:
return quote_str("{:x}".format(self.int_a + self.int_b))


if __name__ == '__main__':
test_generation.main(sys.argv[1:])
# Use the section of the docstring relevant to the CLI as description
test_generation.main(sys.argv[1:], "\n".join(__doc__.splitlines()[:4]))
Copy link
Contributor

@minosgalanakis minosgalanakis Sep 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is precicely why using "doc" was established. The same can be achieved by

__short_doc __ = """Generate test data for bignum functions. ""
__file_help__ = """With no arguments, generate all test data. With non-option arguments,

__doc__ = __short_doc __ + __file_help__
generate only the specified files.

Class structure:

Child classes of test_generation.BaseTarget (file targets) represent an output
"""

.....
.....
.....

test_generation.main(sys.argv[1:], __file_help__ ))


if __name__ == "__main__":
        print(__doc__)

This shall not block this PR, this is just for the purposes of discussion

2 changes: 1 addition & 1 deletion tests/scripts/generate_psa_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -917,4 +917,4 @@ def generate_target(self, name: str, *target_args) -> None:
super().generate_target(name, self.info)

if __name__ == '__main__':
test_generation.main(sys.argv[1:], PSATestGenerator)
test_generation.main(sys.argv[1:], __doc__, PSATestGenerator)