blob: b4915d8460724d3830bc4dbedc88e1ad02faca3f [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 Lewis64334d92022-09-14 16:26:54 +01009Child classes of test_generation.BaseTarget (file targets) represent an output
Werner Lewisb03420f2022-08-25 12:29:46 +010010file. These indicate where test cases will be written to, for all subclasses of
Werner Lewis64334d92022-09-14 16:26:54 +010011this target. Multiple file targets should not reuse a `target_basename`.
Werner Lewis008d90d2022-08-23 16:07:37 +010012
Werner Lewis64334d92022-09-14 16:26:54 +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 Lewis64334d92022-09-14 16:26:54 +010027file target such as BignumTarget. This test class must set/implement the
Werner Lewis2b0f7d82022-08-25 16:27:05 +010028following:
Werner Lewis008d90d2022-08-23 16:07:37 +010029 - test_function: the function name from the associated .function file.
Werner Lewisb03420f2022-08-25 12:29:46 +010030 - test_name: a descriptive name or brief summary to refer to the test
31 function.
32 - arguments(): a method to generate the list of arguments required for the
33 test_function.
34 - generate_function_test(): a method to generate TestCases for the function.
35 This should create instances of the class with required input data, and
36 call `.create_test_case()` to yield the TestCase.
Werner Lewis008d90d2022-08-23 16:07:37 +010037
38Additional details and other attributes/methods are given in the documentation
39of BaseTarget in test_generation.py.
Werner Lewis545911f2022-07-08 13:54:57 +010040"""
41
42# Copyright The Mbed TLS Contributors
43# SPDX-License-Identifier: Apache-2.0
44#
45# Licensed under the Apache License, Version 2.0 (the "License"); you may
46# not use this file except in compliance with the License.
47# You may obtain a copy of the License at
48#
49# http://www.apache.org/licenses/LICENSE-2.0
50#
51# Unless required by applicable law or agreed to in writing, software
52# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
53# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
54# See the License for the specific language governing permissions and
55# limitations under the License.
56
Werner Lewis545911f2022-07-08 13:54:57 +010057import itertools
Werner Lewis545911f2022-07-08 13:54:57 +010058import sys
Werner Lewis1965d482022-09-14 15:00:22 +010059import typing
Werner Lewis008d90d2022-08-23 16:07:37 +010060
Werner Lewis47e37b32022-08-24 12:18:25 +010061from abc import ABCMeta, abstractmethod
Werner Lewis1965d482022-09-14 15:00:22 +010062from typing import Iterator, List, Tuple, TypeVar
Werner Lewis545911f2022-07-08 13:54:57 +010063
64import scripts_path # pylint: disable=unused-import
Werner Lewis545911f2022-07-08 13:54:57 +010065from mbedtls_dev import test_case
Werner Lewisdcad1e92022-08-24 11:30:03 +010066from mbedtls_dev import test_generation
Werner Lewis545911f2022-07-08 13:54:57 +010067
68T = TypeVar('T') #pylint: disable=invalid-name
69
Werner Lewis9509f442022-08-24 17:46:22 +010070def hex_to_int(val: str) -> int:
Werner Lewis545911f2022-07-08 13:54:57 +010071 return int(val, 16) if val else 0
72
Werner Lewis9509f442022-08-24 17:46:22 +010073def quote_str(val) -> str:
Werner Lewis545911f2022-07-08 13:54:57 +010074 return "\"{}\"".format(val)
75
Werner Lewis38c24912022-09-14 15:12:46 +010076def combination_pairs(values: List[T]) -> List[Tuple[T, T]]:
77 """Return all pair combinations from input values.
78
79 The return value is cast, as older versions of mypy are unable to derive
80 the specific type returned by itertools.combinations_with_replacement.
81 """
82 return typing.cast(
83 List[Tuple[T, T]],
84 list(itertools.combinations_with_replacement(values, 2))
85 )
86
Werner Lewis545911f2022-07-08 13:54:57 +010087
Werner Lewis47e37b32022-08-24 12:18:25 +010088class BignumTarget(test_generation.BaseTarget, metaclass=ABCMeta):
Werner Lewisb29f59f2022-08-25 11:17:35 +010089 #pylint: disable=abstract-method
Werner Lewis545911f2022-07-08 13:54:57 +010090 """Target for bignum (mpi) test case generation."""
Werner Lewis70d3f3d2022-08-23 14:21:53 +010091 target_basename = 'test_suite_mpi.generated'
Werner Lewis545911f2022-07-08 13:54:57 +010092
93
Werner Lewis47e37b32022-08-24 12:18:25 +010094class BignumOperation(BignumTarget, metaclass=ABCMeta):
Werner Lewisb03420f2022-08-25 12:29:46 +010095 """Common features for bignum binary operations.
Werner Lewis008d90d2022-08-23 16:07:37 +010096
97 This adds functionality common in binary operation tests. This includes
98 generation of case descriptions, using descriptions of values and symbols
99 to represent the operation or result.
Werner Lewis545911f2022-07-08 13:54:57 +0100100
101 Attributes:
Werner Lewis008d90d2022-08-23 16:07:37 +0100102 symbol: Symbol used for the operation in case description.
103 input_values: List of values to use as test case inputs. These are
104 combined to produce pairs of values.
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100105 input_cases: List of tuples containing pairs of test case inputs. This
Werner Lewis545911f2022-07-08 13:54:57 +0100106 can be used to implement specific pairs of inputs.
107 """
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100108 symbol = ""
109 input_values = [
Werner Lewis545911f2022-07-08 13:54:57 +0100110 "", "0", "7b", "-7b",
111 "0000000000000000123", "-0000000000000000123",
112 "1230000000000000000", "-1230000000000000000"
Werner Lewisd76c5ed2022-07-20 14:13:44 +0100113 ] # type: List[str]
Werner Lewis1965d482022-09-14 15:00:22 +0100114 input_cases = [] # type: List[Tuple[str, str]]
Werner Lewis545911f2022-07-08 13:54:57 +0100115
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100116 def __init__(self, val_a: str, val_b: str) -> None:
117 self.arg_a = val_a
118 self.arg_b = val_b
119 self.int_a = hex_to_int(val_a)
120 self.int_b = hex_to_int(val_b)
Werner Lewis545911f2022-07-08 13:54:57 +0100121
Werner Lewis9509f442022-08-24 17:46:22 +0100122 def arguments(self) -> List[str]:
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100123 return [quote_str(self.arg_a), quote_str(self.arg_b), self.result()]
Werner Lewis545911f2022-07-08 13:54:57 +0100124
Werner Lewis9509f442022-08-24 17:46:22 +0100125 def description(self) -> str:
Werner Lewis008d90d2022-08-23 16:07:37 +0100126 """Generate a description for the test case.
127
128 If not set, case_description uses the form A `symbol` B, where symbol
129 is used to represent the operation. Descriptions of each value are
130 generated to provide some context to the test case.
131 """
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100132 if not self.case_description:
133 self.case_description = "{} {} {}".format(
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100134 self.value_description(self.arg_a),
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100135 self.symbol,
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100136 self.value_description(self.arg_b)
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100137 )
138 return super().description()
Werner Lewis545911f2022-07-08 13:54:57 +0100139
Werner Lewis008d90d2022-08-23 16:07:37 +0100140 @abstractmethod
Werner Lewis47e37b32022-08-24 12:18:25 +0100141 def result(self) -> str:
Werner Lewis008d90d2022-08-23 16:07:37 +0100142 """Get the result of the operation.
143
Werner Lewis2b0f7d82022-08-25 16:27:05 +0100144 This could be calculated during initialization and stored as `_result`
145 and then returned, or calculated when the method is called.
Werner Lewis008d90d2022-08-23 16:07:37 +0100146 """
Werner Lewisd77d33d2022-08-25 09:56:51 +0100147 raise NotImplementedError
Werner Lewis545911f2022-07-08 13:54:57 +0100148
149 @staticmethod
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100150 def value_description(val) -> str:
Werner Lewis008d90d2022-08-23 16:07:37 +0100151 """Generate a description of the argument val.
152
Werner Lewis2b0f7d82022-08-25 16:27:05 +0100153 This produces a simple description of the value, which is used in test
154 case naming to add context.
Werner Lewis008d90d2022-08-23 16:07:37 +0100155 """
Werner Lewis545911f2022-07-08 13:54:57 +0100156 if val == "":
157 return "0 (null)"
158 if val == "0":
159 return "0 (1 limb)"
160
161 if val[0] == "-":
162 tmp = "negative"
163 val = val[1:]
164 else:
165 tmp = "positive"
166 if val[0] == "0":
167 tmp += " with leading zero limb"
168 elif len(val) > 10:
169 tmp = "large " + tmp
170 return tmp
171
172 @classmethod
Werner Lewis9509f442022-08-24 17:46:22 +0100173 def get_value_pairs(cls) -> Iterator[Tuple[str, str]]:
Werner Lewisb03420f2022-08-25 12:29:46 +0100174 """Generator to yield pairs of inputs.
Werner Lewis008d90d2022-08-23 16:07:37 +0100175
176 Combinations are first generated from all input values, and then
177 specific cases provided.
178 """
Werner Lewis38c24912022-09-14 15:12:46 +0100179 yield from combination_pairs(cls.input_values)
Werner Lewis02998c42022-08-23 16:07:19 +0100180 yield from cls.input_cases
Werner Lewis545911f2022-07-08 13:54:57 +0100181
182 @classmethod
Werner Lewisc34d0372022-08-24 12:42:00 +0100183 def generate_function_tests(cls) -> Iterator[test_case.TestCase]:
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100184 for a_value, b_value in cls.get_value_pairs():
185 cur_op = cls(a_value, b_value)
Werner Lewisc34d0372022-08-24 12:42:00 +0100186 yield cur_op.create_test_case()
Werner Lewis545911f2022-07-08 13:54:57 +0100187
188
189class BignumCmp(BignumOperation):
Werner Lewisb03420f2022-08-25 12:29:46 +0100190 """Test cases for bignum value comparison."""
Werner Lewis545911f2022-07-08 13:54:57 +0100191 count = 0
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100192 test_function = "mbedtls_mpi_cmp_mpi"
193 test_name = "MPI compare"
Werner Lewis545911f2022-07-08 13:54:57 +0100194 input_cases = [
195 ("-2", "-3"),
196 ("-2", "-2"),
197 ("2b4", "2b5"),
198 ("2b5", "2b6")
199 ]
200
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100201 def __init__(self, val_a, val_b) -> None:
202 super().__init__(val_a, val_b)
203 self._result = int(self.int_a > self.int_b) - int(self.int_a < self.int_b)
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100204 self.symbol = ["<", "==", ">"][self._result + 1]
Werner Lewis545911f2022-07-08 13:54:57 +0100205
Werner Lewis9509f442022-08-24 17:46:22 +0100206 def result(self) -> str:
Werner Lewis545911f2022-07-08 13:54:57 +0100207 return str(self._result)
208
209
Werner Lewis423f99b2022-07-18 15:49:43 +0100210class BignumCmpAbs(BignumCmp):
Werner Lewisb03420f2022-08-25 12:29:46 +0100211 """Test cases for absolute bignum value comparison."""
Werner Lewis423f99b2022-07-18 15:49:43 +0100212 count = 0
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100213 test_function = "mbedtls_mpi_cmp_abs"
214 test_name = "MPI compare (abs)"
Werner Lewis423f99b2022-07-18 15:49:43 +0100215
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100216 def __init__(self, val_a, val_b) -> None:
217 super().__init__(val_a.strip("-"), val_b.strip("-"))
Werner Lewis423f99b2022-07-18 15:49:43 +0100218
219
Werner Lewis5c1173b2022-07-18 17:22:58 +0100220class BignumAdd(BignumOperation):
Werner Lewisb03420f2022-08-25 12:29:46 +0100221 """Test cases for bignum value addition."""
Werner Lewis5c1173b2022-07-18 17:22:58 +0100222 count = 0
Werner Lewis46c09a62022-09-12 17:34:15 +0100223 symbol = "+"
Werner Lewis70d3f3d2022-08-23 14:21:53 +0100224 test_function = "mbedtls_mpi_add_mpi"
225 test_name = "MPI add"
Werner Lewis38c24912022-09-14 15:12:46 +0100226 input_cases = combination_pairs(
227 [
228 "1c67967269c6", "9cde3",
229 "-1c67967269c6", "-9cde3",
230 ]
Werner Lewis478a4ce2022-08-24 18:03:30 +0100231 )
Werner Lewis5c1173b2022-07-18 17:22:58 +0100232
Werner Lewis9509f442022-08-24 17:46:22 +0100233 def result(self) -> str:
Werner Lewis8b2d14b2022-09-12 17:35:27 +0100234 return quote_str("{:x}".format(self.int_a + self.int_b))
Werner Lewis5c1173b2022-07-18 17:22:58 +0100235
236
Werner Lewis545911f2022-07-08 13:54:57 +0100237if __name__ == '__main__':
Werner Lewis0d07e862022-09-02 11:56:34 +0100238 test_generation.main(sys.argv[1:])