blob: db7b8a501c395daf89f7d92f716db5591681d171 [file] [log] [blame]
Gilles Peskinebd5147c2022-09-16 22:02:37 +02001"""Library for constructing an Mbed TLS test case.
Gilles Peskinedb2f5752021-01-26 21:27:22 +01002"""
3
4# Copyright The Mbed TLS Contributors
Dave Rodgman7ff79652023-11-03 12:04:52 +00005# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Gilles Peskinedb2f5752021-01-26 21:27:22 +01006#
Gilles Peskinedb2f5752021-01-26 21:27:22 +01007
8import binascii
Gilles Peskine505cc642021-01-27 18:30:40 +01009import os
Gilles Peskine8ffb5852021-01-26 21:35:01 +010010import sys
Gilles Peskine4fbffcd2021-02-16 18:06:59 +010011from typing import Iterable, List, Optional
Gilles Peskinedb2f5752021-01-26 21:27:22 +010012
Gilles Peskine239765a2022-09-16 22:35:18 +020013from . import typing_util
Gilles Peskinedb2f5752021-01-26 21:27:22 +010014
15def hex_string(data: bytes) -> str:
16 return '"' + binascii.hexlify(data).decode('ascii') + '"'
17
18
19class MissingDescription(Exception):
20 pass
21
22class MissingFunction(Exception):
23 pass
24
25class TestCase:
26 """An Mbed TLS test case."""
27
28 def __init__(self, description: Optional[str] = None):
29 self.comments = [] #type: List[str]
30 self.description = description #type: Optional[str]
31 self.dependencies = [] #type: List[str]
32 self.function = None #type: Optional[str]
33 self.arguments = [] #type: List[str]
Gilles Peskine696b7ee2024-04-11 11:17:58 +020034 self.skip_reason = ''
Gilles Peskinedb2f5752021-01-26 21:27:22 +010035
36 def add_comment(self, *lines: str) -> None:
37 self.comments += lines
38
39 def set_description(self, description: str) -> None:
40 self.description = description
41
42 def set_dependencies(self, dependencies: List[str]) -> None:
43 self.dependencies = dependencies
44
45 def set_function(self, function: str) -> None:
46 self.function = function
47
48 def set_arguments(self, arguments: List[str]) -> None:
49 self.arguments = arguments
50
Gilles Peskine696b7ee2024-04-11 11:17:58 +020051 def skip_because(self, reason: str) -> None:
52 """Skip this test case.
53
54 It will be included in the output, but commented out.
55
56 This is intended for test cases that are obtained from a
57 systematic enumeration, but that have dependencies that cannot
58 be fulfilled. Since we don't want to have test cases that are
59 never executed, we arrange not to have actual test cases. But
60 we do include comments to make it easier to understand the output
61 of test case generation.
62
63 reason must be a non-empty string explaining to humans why this
64 test case is skipped.
65 """
66 self.skip_reason = reason
67
Gilles Peskinedb2f5752021-01-26 21:27:22 +010068 def check_completeness(self) -> None:
69 if self.description is None:
70 raise MissingDescription
71 if self.function is None:
72 raise MissingFunction
73
Gilles Peskine4fbffcd2021-02-16 18:06:59 +010074 def write(self, out: typing_util.Writable) -> None:
Gilles Peskinedb2f5752021-01-26 21:27:22 +010075 """Write the .data file paragraph for this test case.
76
77 The output starts and ends with a single newline character. If the
78 surrounding code writes lines (consisting of non-newline characters
79 and a final newline), you will end up with a blank line before, but
80 not after the test case.
81 """
82 self.check_completeness()
83 assert self.description is not None # guide mypy
84 assert self.function is not None # guide mypy
85 out.write('\n')
86 for line in self.comments:
87 out.write('# ' + line + '\n')
Gilles Peskine696b7ee2024-04-11 11:17:58 +020088 prefix = ''
89 if self.skip_reason:
90 prefix = '## '
91 out.write('## # skipped because: ' + self.skip_reason + '\n')
92 out.write(prefix + self.description + '\n')
Gilles Peskinedb2f5752021-01-26 21:27:22 +010093 if self.dependencies:
Gilles Peskine696b7ee2024-04-11 11:17:58 +020094 out.write(prefix + 'depends_on:' +
95 ':'.join(self.dependencies) + '\n')
96 out.write(prefix + self.function + ':' +
97 ':'.join(self.arguments) + '\n')
Gilles Peskine8ffb5852021-01-26 21:35:01 +010098
99def write_data_file(filename: str,
100 test_cases: Iterable[TestCase],
101 caller: Optional[str] = None) -> None:
102 """Write the test cases to the specified file.
103
104 If the file already exists, it is overwritten.
105 """
106 if caller is None:
Gilles Peskine505cc642021-01-27 18:30:40 +0100107 caller = os.path.basename(sys.argv[0])
Gilles Peskineeca29e42022-09-21 22:00:06 +0200108 tempfile = filename + '.new'
109 with open(tempfile, 'w') as out:
Gilles Peskine8ffb5852021-01-26 21:35:01 +0100110 out.write('# Automatically generated by {}. Do not edit!\n'
111 .format(caller))
112 for tc in test_cases:
113 tc.write(out)
114 out.write('\n# End of automatically generated file.\n')
Gilles Peskineeca29e42022-09-21 22:00:06 +0200115 os.replace(tempfile, filename)