blob: 4486d49584ee366a1f68f10decbff6971ef60325 [file] [log] [blame]
Werner Lewis545911f2022-07-08 13:54:57 +01001#!/usr/bin/env python3
2"""Generate test data for bignum functions.
3
4With no arguments, generate all test data. With non-option arguments,
5generate only the specified files.
Werner Lewis008d90d2022-08-23 16:07:37 +01006
7Class structure:
8
Werner Lewisb03420f2022-08-25 12:29:46 +01009Child classes of test_generation.BaseTarget (file Targets) represent a target
10file. These indicate where test cases will be written to, for all subclasses of
11this Target. Multiple Target classes should not reuse a `target_basename`.
Werner Lewis008d90d2022-08-23 16:07:37 +010012
Werner Lewisb03420f2022-08-25 12:29:46 +010013Each subclass derived from a file Target can either be:
Werner Lewis008d90d2022-08-23 16:07:37 +010014 - A concrete class, representing a test function, which generates test cases.
15 - An abstract class containing shared methods and attributes, not associated
Werner Lewisb03420f2022-08-25 12:29:46 +010016 with a test function. An example is BignumOperation, which provides
17 common features used for bignum binary operations.
18
19Both concrete and abstract subclasses can be derived from, to implement
20additional test cases (see BignumCmp and BignumCmpAbs for examples of deriving
21from abstract and concrete classes).
Werner Lewis008d90d2022-08-23 16:07:37 +010022
23
Werner Lewisb03420f2022-08-25 12:29:46 +010024Adding test case generation for a function:
Werner Lewis008d90d2022-08-23 16:07:37 +010025
26A subclass representing the test function should be added, deriving from a
Werner Lewisb03420f2022-08-25 12:29:46 +010027file Target. This test class must set/implement the following:
Werner Lewis008d90d2022-08-23 16:07:37 +010028 - test_function: the function name from the associated .function file.
Werner Lewisb03420f2022-08-25 12:29:46 +010029 - test_name: a descriptive name or brief summary to refer to the test
30 function.
31 - arguments(): a method to generate the list of arguments required for the
32 test_function.
33 - generate_function_test(): a method to generate TestCases for the function.
34 This should create instances of the class with required input data, and
35 call `.create_test_case()` to yield the TestCase.
Werner Lewis008d90d2022-08-23 16:07:37 +010036
37Additional details and other attributes/methods are given in the documentation
38of BaseTarget in test_generation.py.
Werner Lewis545911f2022-07-08 13:54:57 +010039"""
40
41# Copyright The Mbed TLS Contributors
42# SPDX-License-Identifier: Apache-2.0
43#
44# Licensed under the Apache License, Version 2.0 (the "License"); you may
45# not use this file except in compliance with the License.
46# You may obtain a copy of the License at
47#
48# http://www.apache.org/licenses/LICENSE-2.0
49#
50# Unless required by applicable law or agreed to in writing, software
51# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
52# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
53# See the License for the specific language governing permissions and
54# limitations under the License.
55
Werner Lewis545911f2022-07-08 13:54:57 +010056import itertools
Werner Lewis545911f2022-07-08 13:54:57 +010057import sys
Werner Lewis008d90d2022-08-23 16:07:37 +010058
Werner Lewis47e37b32022-08-24 12:18:25 +010059from abc import ABCMeta, abstractmethod
Werner Lewis412c4972022-08-25 10:02:06 +010060from typing import Callable, Dict, Iterable, Iterator, List, Tuple, TypeVar, cast
Werner Lewis545911f2022-07-08 13:54:57 +010061
62import scripts_path # pylint: disable=unused-import
Werner Lewis545911f2022-07-08 13:54:57 +010063from mbedtls_dev import test_case
Werner Lewisdcad1e92022-08-24 11:30:03 +010064from mbedtls_dev import test_generation
Werner Lewis545911f2022-07-08 13:54:57 +010065
66T = TypeVar('T') #pylint: disable=invalid-name
67
Werner Lewis9509f442022-08-24 17:46:22 +010068def hex_to_int(val: str) -> int:
Werner Lewis545911f2022-07-08 13:54:57 +010069 return int(val, 16) if val else 0
70
Werner Lewis9509f442022-08-24 17:46:22 +010071def quote_str(val) -> str:
Werner Lewis545911f2022-07-08 13:54:57 +010072 return "\"{}\"".format(val)
73
74
Werner Lewis47e37b32022-08-24 12:18:25 +010075class BignumTarget(test_generation.BaseTarget, metaclass=ABCMeta):
Werner Lewisb29f59f2022-08-25 11:17:35 +010076 #pylint: disable=abstract-method
Werner Lewis545911f2022-07-08 13:54:57 +010077 """Target for bignum (mpi) test case generation."""
Werner Lewis70d3f3d2022-08-23 14:21:53 +010078 target_basename = 'test_suite_mpi.generated'
Werner Lewis545911f2022-07-08 13:54:57 +010079
80
Werner Lewis47e37b32022-08-24 12:18:25 +010081class BignumOperation(BignumTarget, metaclass=ABCMeta):
Werner Lewisb03420f2022-08-25 12:29:46 +010082 """Common features for bignum binary operations.
Werner Lewis008d90d2022-08-23 16:07:37 +010083
84 This adds functionality common in binary operation tests. This includes
85 generation of case descriptions, using descriptions of values and symbols
86 to represent the operation or result.
Werner Lewis545911f2022-07-08 13:54:57 +010087
88 Attributes:
Werner Lewis008d90d2022-08-23 16:07:37 +010089 symbol: Symbol used for the operation in case description.
90 input_values: List of values to use as test case inputs. These are
91 combined to produce pairs of values.
Werner Lewis70d3f3d2022-08-23 14:21:53 +010092 input_cases: List of tuples containing pairs of test case inputs. This
Werner Lewis545911f2022-07-08 13:54:57 +010093 can be used to implement specific pairs of inputs.
94 """
Werner Lewis70d3f3d2022-08-23 14:21:53 +010095 symbol = ""
96 input_values = [
Werner Lewis545911f2022-07-08 13:54:57 +010097 "", "0", "7b", "-7b",
98 "0000000000000000123", "-0000000000000000123",
99 "1230000000000000000", "-1230000000000000000"
Werner Lewisd76c5ed2022-07-20 14:13:44 +0100100 ] # type: List[str]
Werner Lewis478a4ce2022-08-24 18:03:30 +0100101 input_cases = cast(List[Tuple[str, str]], []) # type: List[Tuple[str, str]]
Werner Lewis545911f2022-07-08 13:54:57 +0100102
103 def __init__(self, val_l: str, val_r: str) -> None:
Werner Lewis545911f2022-07-08 13:54:57 +0100104 self.arg_l = val_l
105 self.arg_r = val_r
106 self.int_l = hex_to_int(val_l)
107 self.int_r = hex_to_int(val_r)
108
Werner Lewis9509f442022-08-24 17:46:22 +0100109 def arguments(self) -> List[str]:
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100110 return [quote_str(self.arg_l), quote_str(self.arg_r), self.result()]
Werner Lewis545911f2022-07-08 13:54:57 +0100111
Werner Lewis9509f442022-08-24 17:46:22 +0100112 def description(self) -> str:
Werner Lewis008d90d2022-08-23 16:07:37 +0100113 """Generate a description for the test case.
114
115 If not set, case_description uses the form A `symbol` B, where symbol
116 is used to represent the operation. Descriptions of each value are
117 generated to provide some context to the test case.
118 """
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100119 if not self.case_description:
120 self.case_description = "{} {} {}".format(
121 self.value_description(self.arg_l),
122 self.symbol,
123 self.value_description(self.arg_r)
124 )
125 return super().description()
Werner Lewis545911f2022-07-08 13:54:57 +0100126
Werner Lewis008d90d2022-08-23 16:07:37 +0100127 @abstractmethod
Werner Lewis47e37b32022-08-24 12:18:25 +0100128 def result(self) -> str:
Werner Lewis008d90d2022-08-23 16:07:37 +0100129 """Get the result of the operation.
130
131 This may be calculated during initialization and stored as `_result`,
132 or calculated when the method is called.
133 """
Werner Lewisd77d33d2022-08-25 09:56:51 +0100134 raise NotImplementedError
Werner Lewis545911f2022-07-08 13:54:57 +0100135
136 @staticmethod
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100137 def value_description(val) -> str:
Werner Lewis008d90d2022-08-23 16:07:37 +0100138 """Generate a description of the argument val.
139
140 This produces a simple description of the value, which are used in test
Werner Lewisb03420f2022-08-25 12:29:46 +0100141 case naming, to add context.
Werner Lewis008d90d2022-08-23 16:07:37 +0100142 """
Werner Lewis545911f2022-07-08 13:54:57 +0100143 if val == "":
144 return "0 (null)"
145 if val == "0":
146 return "0 (1 limb)"
147
148 if val[0] == "-":
149 tmp = "negative"
150 val = val[1:]
151 else:
152 tmp = "positive"
153 if val[0] == "0":
154 tmp += " with leading zero limb"
155 elif len(val) > 10:
156 tmp = "large " + tmp
157 return tmp
158
159 @classmethod
Werner Lewis9509f442022-08-24 17:46:22 +0100160 def get_value_pairs(cls) -> Iterator[Tuple[str, str]]:
Werner Lewisb03420f2022-08-25 12:29:46 +0100161 """Generator to yield pairs of inputs.
Werner Lewis008d90d2022-08-23 16:07:37 +0100162
163 Combinations are first generated from all input values, and then
164 specific cases provided.
165 """
Werner Lewis478a4ce2022-08-24 18:03:30 +0100166 yield from cast(
167 Iterator[Tuple[str, str]],
168 itertools.combinations(cls.input_values, 2)
169 )
Werner Lewis02998c42022-08-23 16:07:19 +0100170 yield from cls.input_cases
Werner Lewis545911f2022-07-08 13:54:57 +0100171
172 @classmethod
Werner Lewisc34d0372022-08-24 12:42:00 +0100173 def generate_function_tests(cls) -> Iterator[test_case.TestCase]:
174 for l_value, r_value in cls.get_value_pairs():
175 cur_op = cls(l_value, r_value)
176 yield cur_op.create_test_case()
Werner Lewis545911f2022-07-08 13:54:57 +0100177
178
179class BignumCmp(BignumOperation):
Werner Lewisb03420f2022-08-25 12:29:46 +0100180 """Test cases for bignum value comparison."""
Werner Lewis545911f2022-07-08 13:54:57 +0100181 count = 0
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100182 test_function = "mbedtls_mpi_cmp_mpi"
183 test_name = "MPI compare"
Werner Lewis545911f2022-07-08 13:54:57 +0100184 input_cases = [
185 ("-2", "-3"),
186 ("-2", "-2"),
187 ("2b4", "2b5"),
188 ("2b5", "2b6")
189 ]
190
Werner Lewis9509f442022-08-24 17:46:22 +0100191 def __init__(self, val_l, val_r) -> None:
Werner Lewis545911f2022-07-08 13:54:57 +0100192 super().__init__(val_l, val_r)
Werner Lewis1c2a7322022-08-24 16:37:44 +0100193 self._result = int(self.int_l > self.int_r) - int(self.int_l < self.int_r)
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100194 self.symbol = ["<", "==", ">"][self._result + 1]
Werner Lewis545911f2022-07-08 13:54:57 +0100195
Werner Lewis9509f442022-08-24 17:46:22 +0100196 def result(self) -> str:
Werner Lewis545911f2022-07-08 13:54:57 +0100197 return str(self._result)
198
199
Werner Lewis423f99b2022-07-18 15:49:43 +0100200class BignumCmpAbs(BignumCmp):
Werner Lewisb03420f2022-08-25 12:29:46 +0100201 """Test cases for absolute bignum value comparison."""
Werner Lewis423f99b2022-07-18 15:49:43 +0100202 count = 0
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100203 test_function = "mbedtls_mpi_cmp_abs"
204 test_name = "MPI compare (abs)"
Werner Lewis423f99b2022-07-18 15:49:43 +0100205
Werner Lewis9509f442022-08-24 17:46:22 +0100206 def __init__(self, val_l, val_r) -> None:
Werner Lewis423f99b2022-07-18 15:49:43 +0100207 super().__init__(val_l.strip("-"), val_r.strip("-"))
208
209
Werner Lewis5c1173b2022-07-18 17:22:58 +0100210class BignumAdd(BignumOperation):
Werner Lewisb03420f2022-08-25 12:29:46 +0100211 """Test cases for bignum value addition."""
Werner Lewis5c1173b2022-07-18 17:22:58 +0100212 count = 0
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100213 test_function = "mbedtls_mpi_add_mpi"
214 test_name = "MPI add"
Werner Lewis478a4ce2022-08-24 18:03:30 +0100215 input_cases = cast(
216 List[Tuple[str, str]],
217 list(itertools.combinations(
Werner Lewis412c4972022-08-25 10:02:06 +0100218 [
219 "1c67967269c6", "9cde3",
220 "-1c67967269c6", "-9cde3",
221 ], 2
Werner Lewis478a4ce2022-08-24 18:03:30 +0100222 ))
223 )
Werner Lewis5c1173b2022-07-18 17:22:58 +0100224
Werner Lewis9509f442022-08-24 17:46:22 +0100225 def __init__(self, val_l, val_r) -> None:
Werner Lewis5c1173b2022-07-18 17:22:58 +0100226 super().__init__(val_l, val_r)
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100227 self.symbol = "+"
Werner Lewis5c1173b2022-07-18 17:22:58 +0100228
Werner Lewis9509f442022-08-24 17:46:22 +0100229 def result(self) -> str:
Werner Lewis5c1173b2022-07-18 17:22:58 +0100230 return quote_str(hex(self.int_l + self.int_r).replace("0x", "", 1))
231
232
Werner Lewisdcad1e92022-08-24 11:30:03 +0100233class BignumTestGenerator(test_generation.TestGenerator):
Werner Lewisb03420f2022-08-25 12:29:46 +0100234 """Test generator subclass setting bignum targets."""
Werner Lewis545911f2022-07-08 13:54:57 +0100235 TARGETS = {
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100236 subclass.target_basename: subclass.generate_tests for subclass in
Werner Lewisdcad1e92022-08-24 11:30:03 +0100237 test_generation.BaseTarget.__subclasses__()
Werner Lewis412c4972022-08-25 10:02:06 +0100238 } # type: Dict[str, Callable[[], Iterable[test_case.TestCase]]]
Werner Lewis545911f2022-07-08 13:54:57 +0100239
240if __name__ == '__main__':
Werner Lewisdcad1e92022-08-24 11:30:03 +0100241 test_generation.main(sys.argv[1:], BignumTestGenerator)