blob: 091630deccfb18c6150e4b634e5f4eb4d375a882 [file] [log] [blame]
Werner Lewis8b2df742022-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 Lewis169034a2022-08-23 16:07:37 +01006
7Class structure:
8
Gilles Peskine64f2efd2022-09-16 21:41:47 +02009Child classes of test_data_generation.BaseTarget (file targets) represent an output
Werner Lewis6ef54362022-08-25 12:29:46 +010010file. These indicate where test cases will be written to, for all subclasses of
Werner Lewis52ae3262022-09-14 16:26:54 +010011this target. Multiple file targets should not reuse a `target_basename`.
Werner Lewis169034a2022-08-23 16:07:37 +010012
Werner Lewis52ae3262022-09-14 16:26:54 +010013Each subclass derived from a file target can either be:
Werner Lewis169034a2022-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 Lewis6ef54362022-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 Lewis169034a2022-08-23 16:07:37 +010022
23
Werner Lewis6ef54362022-08-25 12:29:46 +010024Adding test case generation for a function:
Werner Lewis169034a2022-08-23 16:07:37 +010025
26A subclass representing the test function should be added, deriving from a
Werner Lewis52ae3262022-09-14 16:26:54 +010027file target such as BignumTarget. This test class must set/implement the
Werner Lewis81f24442022-08-25 16:27:05 +010028following:
Werner Lewis169034a2022-08-23 16:07:37 +010029 - test_function: the function name from the associated .function file.
Werner Lewis6ef54362022-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 Lewis169034a2022-08-23 16:07:37 +010037
38Additional details and other attributes/methods are given in the documentation
Gilles Peskine64f2efd2022-09-16 21:41:47 +020039of BaseTarget in test_data_generation.py.
Werner Lewis8b2df742022-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 Lewis8b2df742022-07-08 13:54:57 +010057import itertools
Werner Lewis8b2df742022-07-08 13:54:57 +010058import sys
Werner Lewisb6e80912022-09-14 15:00:22 +010059import typing
Werner Lewis169034a2022-08-23 16:07:37 +010060
Werner Lewis699e1262022-08-24 12:18:25 +010061from abc import ABCMeta, abstractmethod
Werner Lewisb6e80912022-09-14 15:00:22 +010062from typing import Iterator, List, Tuple, TypeVar
Werner Lewis8b2df742022-07-08 13:54:57 +010063
64import scripts_path # pylint: disable=unused-import
Werner Lewis8b2df742022-07-08 13:54:57 +010065from mbedtls_dev import test_case
Gilles Peskine64f2efd2022-09-16 21:41:47 +020066from mbedtls_dev import test_data_generation
Werner Lewis8b2df742022-07-08 13:54:57 +010067
68T = TypeVar('T') #pylint: disable=invalid-name
69
Werner Lewis6300b4f2022-08-24 17:46:22 +010070def hex_to_int(val: str) -> int:
Werner Lewis8b2df742022-07-08 13:54:57 +010071 return int(val, 16) if val else 0
72
Werner Lewis6300b4f2022-08-24 17:46:22 +010073def quote_str(val) -> str:
Werner Lewis8b2df742022-07-08 13:54:57 +010074 return "\"{}\"".format(val)
75
Werner Lewisac446c82022-09-14 15:12:46 +010076def combination_pairs(values: List[T]) -> List[Tuple[T, T]]:
Gilles Peskine4537d6d2022-09-16 22:26:38 +020077 """Return all pair combinations from input values."""
78 # The return value is cast, as older versions of mypy are unable to derive
79 # the specific type returned by itertools.combinations_with_replacement.
Werner Lewisac446c82022-09-14 15:12:46 +010080 return typing.cast(
81 List[Tuple[T, T]],
82 list(itertools.combinations_with_replacement(values, 2))
83 )
84
Werner Lewis8b2df742022-07-08 13:54:57 +010085
Gilles Peskine64f2efd2022-09-16 21:41:47 +020086class BignumTarget(test_data_generation.BaseTarget, metaclass=ABCMeta):
Werner Lewisa16b6172022-08-25 11:17:35 +010087 #pylint: disable=abstract-method
Werner Lewis8b2df742022-07-08 13:54:57 +010088 """Target for bignum (mpi) test case generation."""
Werner Lewis55e638c2022-08-23 14:21:53 +010089 target_basename = 'test_suite_mpi.generated'
Werner Lewis8b2df742022-07-08 13:54:57 +010090
91
Werner Lewis699e1262022-08-24 12:18:25 +010092class BignumOperation(BignumTarget, metaclass=ABCMeta):
Werner Lewis6ef54362022-08-25 12:29:46 +010093 """Common features for bignum binary operations.
Werner Lewis169034a2022-08-23 16:07:37 +010094
95 This adds functionality common in binary operation tests. This includes
96 generation of case descriptions, using descriptions of values and symbols
97 to represent the operation or result.
Werner Lewis8b2df742022-07-08 13:54:57 +010098
99 Attributes:
Werner Lewis169034a2022-08-23 16:07:37 +0100100 symbol: Symbol used for the operation in case description.
101 input_values: List of values to use as test case inputs. These are
102 combined to produce pairs of values.
Werner Lewis55e638c2022-08-23 14:21:53 +0100103 input_cases: List of tuples containing pairs of test case inputs. This
Werner Lewis8b2df742022-07-08 13:54:57 +0100104 can be used to implement specific pairs of inputs.
105 """
Werner Lewis55e638c2022-08-23 14:21:53 +0100106 symbol = ""
107 input_values = [
Werner Lewis8b2df742022-07-08 13:54:57 +0100108 "", "0", "7b", "-7b",
109 "0000000000000000123", "-0000000000000000123",
110 "1230000000000000000", "-1230000000000000000"
Werner Lewisc442f6a2022-07-20 14:13:44 +0100111 ] # type: List[str]
Werner Lewisb6e80912022-09-14 15:00:22 +0100112 input_cases = [] # type: List[Tuple[str, str]]
Werner Lewis8b2df742022-07-08 13:54:57 +0100113
Werner Lewis3dc45192022-09-12 17:35:27 +0100114 def __init__(self, val_a: str, val_b: str) -> None:
115 self.arg_a = val_a
116 self.arg_b = val_b
117 self.int_a = hex_to_int(val_a)
118 self.int_b = hex_to_int(val_b)
Werner Lewis8b2df742022-07-08 13:54:57 +0100119
Werner Lewis6300b4f2022-08-24 17:46:22 +0100120 def arguments(self) -> List[str]:
Werner Lewis3dc45192022-09-12 17:35:27 +0100121 return [quote_str(self.arg_a), quote_str(self.arg_b), self.result()]
Werner Lewis8b2df742022-07-08 13:54:57 +0100122
Werner Lewis6300b4f2022-08-24 17:46:22 +0100123 def description(self) -> str:
Werner Lewis169034a2022-08-23 16:07:37 +0100124 """Generate a description for the test case.
125
126 If not set, case_description uses the form A `symbol` B, where symbol
127 is used to represent the operation. Descriptions of each value are
128 generated to provide some context to the test case.
129 """
Werner Lewis55e638c2022-08-23 14:21:53 +0100130 if not self.case_description:
131 self.case_description = "{} {} {}".format(
Werner Lewis3dc45192022-09-12 17:35:27 +0100132 self.value_description(self.arg_a),
Werner Lewis55e638c2022-08-23 14:21:53 +0100133 self.symbol,
Werner Lewis3dc45192022-09-12 17:35:27 +0100134 self.value_description(self.arg_b)
Werner Lewis55e638c2022-08-23 14:21:53 +0100135 )
136 return super().description()
Werner Lewis8b2df742022-07-08 13:54:57 +0100137
Werner Lewis169034a2022-08-23 16:07:37 +0100138 @abstractmethod
Werner Lewis699e1262022-08-24 12:18:25 +0100139 def result(self) -> str:
Werner Lewis169034a2022-08-23 16:07:37 +0100140 """Get the result of the operation.
141
Werner Lewis81f24442022-08-25 16:27:05 +0100142 This could be calculated during initialization and stored as `_result`
143 and then returned, or calculated when the method is called.
Werner Lewis169034a2022-08-23 16:07:37 +0100144 """
Werner Lewis6d654c62022-08-25 09:56:51 +0100145 raise NotImplementedError
Werner Lewis8b2df742022-07-08 13:54:57 +0100146
147 @staticmethod
Werner Lewis55e638c2022-08-23 14:21:53 +0100148 def value_description(val) -> str:
Werner Lewis169034a2022-08-23 16:07:37 +0100149 """Generate a description of the argument val.
150
Werner Lewis81f24442022-08-25 16:27:05 +0100151 This produces a simple description of the value, which is used in test
152 case naming to add context.
Werner Lewis169034a2022-08-23 16:07:37 +0100153 """
Werner Lewis8b2df742022-07-08 13:54:57 +0100154 if val == "":
155 return "0 (null)"
156 if val == "0":
157 return "0 (1 limb)"
158
159 if val[0] == "-":
160 tmp = "negative"
161 val = val[1:]
162 else:
163 tmp = "positive"
164 if val[0] == "0":
165 tmp += " with leading zero limb"
166 elif len(val) > 10:
167 tmp = "large " + tmp
168 return tmp
169
170 @classmethod
Werner Lewis6300b4f2022-08-24 17:46:22 +0100171 def get_value_pairs(cls) -> Iterator[Tuple[str, str]]:
Werner Lewis6ef54362022-08-25 12:29:46 +0100172 """Generator to yield pairs of inputs.
Werner Lewis169034a2022-08-23 16:07:37 +0100173
174 Combinations are first generated from all input values, and then
175 specific cases provided.
176 """
Werner Lewisac446c82022-09-14 15:12:46 +0100177 yield from combination_pairs(cls.input_values)
Werner Lewis92c876a2022-08-23 16:07:19 +0100178 yield from cls.input_cases
Werner Lewis8b2df742022-07-08 13:54:57 +0100179
180 @classmethod
Werner Lewis2b527a32022-08-24 12:42:00 +0100181 def generate_function_tests(cls) -> Iterator[test_case.TestCase]:
Werner Lewis3dc45192022-09-12 17:35:27 +0100182 for a_value, b_value in cls.get_value_pairs():
183 cur_op = cls(a_value, b_value)
Werner Lewis2b527a32022-08-24 12:42:00 +0100184 yield cur_op.create_test_case()
Werner Lewis8b2df742022-07-08 13:54:57 +0100185
186
187class BignumCmp(BignumOperation):
Werner Lewis6ef54362022-08-25 12:29:46 +0100188 """Test cases for bignum value comparison."""
Werner Lewis8b2df742022-07-08 13:54:57 +0100189 count = 0
Werner Lewis55e638c2022-08-23 14:21:53 +0100190 test_function = "mbedtls_mpi_cmp_mpi"
191 test_name = "MPI compare"
Werner Lewis8b2df742022-07-08 13:54:57 +0100192 input_cases = [
193 ("-2", "-3"),
194 ("-2", "-2"),
195 ("2b4", "2b5"),
196 ("2b5", "2b6")
197 ]
198
Werner Lewis3dc45192022-09-12 17:35:27 +0100199 def __init__(self, val_a, val_b) -> None:
200 super().__init__(val_a, val_b)
201 self._result = int(self.int_a > self.int_b) - int(self.int_a < self.int_b)
Werner Lewis55e638c2022-08-23 14:21:53 +0100202 self.symbol = ["<", "==", ">"][self._result + 1]
Werner Lewis8b2df742022-07-08 13:54:57 +0100203
Werner Lewis6300b4f2022-08-24 17:46:22 +0100204 def result(self) -> str:
Werner Lewis8b2df742022-07-08 13:54:57 +0100205 return str(self._result)
206
207
Werner Lewis69a92ce2022-07-18 15:49:43 +0100208class BignumCmpAbs(BignumCmp):
Werner Lewis6ef54362022-08-25 12:29:46 +0100209 """Test cases for absolute bignum value comparison."""
Werner Lewis69a92ce2022-07-18 15:49:43 +0100210 count = 0
Werner Lewis55e638c2022-08-23 14:21:53 +0100211 test_function = "mbedtls_mpi_cmp_abs"
212 test_name = "MPI compare (abs)"
Werner Lewis69a92ce2022-07-18 15:49:43 +0100213
Werner Lewis3dc45192022-09-12 17:35:27 +0100214 def __init__(self, val_a, val_b) -> None:
215 super().__init__(val_a.strip("-"), val_b.strip("-"))
Werner Lewis69a92ce2022-07-18 15:49:43 +0100216
217
Werner Lewis86caf852022-07-18 17:22:58 +0100218class BignumAdd(BignumOperation):
Werner Lewis6ef54362022-08-25 12:29:46 +0100219 """Test cases for bignum value addition."""
Werner Lewis86caf852022-07-18 17:22:58 +0100220 count = 0
Werner Lewis1fade8a2022-09-12 17:34:15 +0100221 symbol = "+"
Werner Lewis55e638c2022-08-23 14:21:53 +0100222 test_function = "mbedtls_mpi_add_mpi"
223 test_name = "MPI add"
Werner Lewisac446c82022-09-14 15:12:46 +0100224 input_cases = combination_pairs(
225 [
226 "1c67967269c6", "9cde3",
227 "-1c67967269c6", "-9cde3",
228 ]
Werner Lewis9990b302022-08-24 18:03:30 +0100229 )
Werner Lewis86caf852022-07-18 17:22:58 +0100230
Werner Lewis6300b4f2022-08-24 17:46:22 +0100231 def result(self) -> str:
Werner Lewis3dc45192022-09-12 17:35:27 +0100232 return quote_str("{:x}".format(self.int_a + self.int_b))
Werner Lewis86caf852022-07-18 17:22:58 +0100233
Werner Lewis8b2df742022-07-08 13:54:57 +0100234if __name__ == '__main__':
Werner Lewisc2fb5402022-09-16 17:03:54 +0100235 # Use the section of the docstring relevant to the CLI as description
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200236 test_data_generation.main(sys.argv[1:], "\n".join(__doc__.splitlines()[:4]))