blob: f4676cb10c4084750f3bd393b069f700e13820dc [file] [log] [blame]
Gilles Peskine09940492021-01-26 22:16:30 +01001#!/usr/bin/env python3
2"""Generate test data for PSA cryptographic mechanisms.
Gilles Peskine0298bda2021-03-10 02:34:37 +01003
4With no arguments, generate all test data. With non-option arguments,
5generate only the specified files.
Gilles Peskine09940492021-01-26 22:16:30 +01006"""
7
8# Copyright The Mbed TLS Contributors
9# SPDX-License-Identifier: Apache-2.0
10#
11# Licensed under the Apache License, Version 2.0 (the "License"); you may
12# not use this file except in compliance with the License.
13# You may obtain a copy of the License at
14#
15# http://www.apache.org/licenses/LICENSE-2.0
16#
17# Unless required by applicable law or agreed to in writing, software
18# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
19# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20# See the License for the specific language governing permissions and
21# limitations under the License.
22
23import argparse
Gilles Peskine14e428f2021-01-26 22:19:21 +010024import os
25import re
Gilles Peskine09940492021-01-26 22:16:30 +010026import sys
Gilles Peskine3d778392021-02-17 15:11:05 +010027from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional, TypeVar
Gilles Peskine09940492021-01-26 22:16:30 +010028
29import scripts_path # pylint: disable=unused-import
Gilles Peskine14e428f2021-01-26 22:19:21 +010030from mbedtls_dev import crypto_knowledge
Gilles Peskine09940492021-01-26 22:16:30 +010031from mbedtls_dev import macro_collector
Gilles Peskine897dff92021-03-10 15:03:44 +010032from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010033from mbedtls_dev import test_case
Gilles Peskine09940492021-01-26 22:16:30 +010034
35T = TypeVar('T') #pylint: disable=invalid-name
36
Gilles Peskine14e428f2021-01-26 22:19:21 +010037
Gilles Peskine7f756872021-02-16 12:13:12 +010038def psa_want_symbol(name: str) -> str:
Gilles Peskineaf172842021-01-27 18:24:48 +010039 """Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
40 if name.startswith('PSA_'):
41 return name[:4] + 'WANT_' + name[4:]
42 else:
43 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
44
Gilles Peskine7f756872021-02-16 12:13:12 +010045def finish_family_dependency(dep: str, bits: int) -> str:
46 """Finish dep if it's a family dependency symbol prefix.
47
48 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
49 qualified by the key size. If dep is such a symbol, finish it by adjusting
50 the prefix and appending the key size. Other symbols are left unchanged.
51 """
52 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
53
54def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
55 """Finish any family dependency symbol prefixes.
56
57 Apply `finish_family_dependency` to each element of `dependencies`.
58 """
59 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010060
Gilles Peskinef8223ab2021-03-10 15:07:16 +010061def automatic_dependencies(*expressions: str) -> List[str]:
62 """Infer dependencies of a test case by looking for PSA_xxx symbols.
63
64 The arguments are strings which should be C expressions. Do not use
65 string literals or comments as this function is not smart enough to
66 skip them.
67 """
68 used = set()
69 for expr in expressions:
70 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
71 return sorted(psa_want_symbol(name) for name in used)
72
Gilles Peskined169d602021-02-16 14:16:25 +010073# A temporary hack: at the time of writing, not all dependency symbols
74# are implemented yet. Skip test cases for which the dependency symbols are
75# not available. Once all dependency symbols are available, this hack must
76# be removed so that a bug in the dependency symbols proprely leads to a test
77# failure.
78def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
79 return frozenset(symbol
80 for line in open(filename)
81 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
82IMPLEMENTED_DEPENDENCIES = read_implemented_dependencies('include/psa/crypto_config.h')
83def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
84 if not all(dep.lstrip('!') in IMPLEMENTED_DEPENDENCIES
85 for dep in dependencies):
86 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
87
Gilles Peskine14e428f2021-01-26 22:19:21 +010088
Gilles Peskineb94ea512021-03-10 02:12:08 +010089class Information:
90 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +010091
Gilles Peskineb94ea512021-03-10 02:12:08 +010092 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +010093 self.constructors = self.read_psa_interface()
94
95 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +010096 def remove_unwanted_macros(
Gilles Peskine638deee2021-04-19 13:50:25 +020097 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +010098 ) -> None:
Gilles Peskine638deee2021-04-19 13:50:25 +020099 # Mbed TLS doesn't support finite-field DH yet and will not support
100 # finite-field DSA. Don't attempt to generate any related test case.
101 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
102 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100103 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
104 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100105
Gilles Peskine638deee2021-04-19 13:50:25 +0200106 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100107 """Return the list of known key types, algorithms, etc."""
Gilles Peskine66c0b7b2021-03-30 21:46:35 +0200108 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100109 header_file_names = ['include/psa/crypto_values.h',
110 'include/psa/crypto_extra.h']
Gilles Peskine638deee2021-04-19 13:50:25 +0200111 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100112 for header_file_name in header_file_names:
Gilles Peskine638deee2021-04-19 13:50:25 +0200113 constructors.parse_header(header_file_name)
114 for test_cases in test_suites:
115 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100116 self.remove_unwanted_macros(constructors)
Gilles Peskine66c0b7b2021-03-30 21:46:35 +0200117 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100118 return constructors
119
Gilles Peskine14e428f2021-01-26 22:19:21 +0100120
Gilles Peskineb94ea512021-03-10 02:12:08 +0100121def test_case_for_key_type_not_supported(
122 verb: str, key_type: str, bits: int,
123 dependencies: List[str],
124 *args: str,
125 param_descr: str = ''
126) -> test_case.TestCase:
127 """Return one test case exercising a key creation method
128 for an unsupported key type or size.
129 """
130 hack_dependencies_not_implemented(dependencies)
131 tc = test_case.TestCase()
132 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
133 adverb = 'not' if dependencies else 'never'
134 if param_descr:
135 adverb = param_descr + ' ' + adverb
136 tc.set_description('PSA {} {} {}-bit {} supported'
137 .format(verb, short_key_type, bits, adverb))
138 tc.set_dependencies(dependencies)
139 tc.set_function(verb + '_not_supported')
140 tc.set_arguments([key_type] + list(args))
141 return tc
142
143class NotSupported:
144 """Generate test cases for when something is not supported."""
145
146 def __init__(self, info: Information) -> None:
147 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100148
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100149 ALWAYS_SUPPORTED = frozenset([
150 'PSA_KEY_TYPE_DERIVE',
151 'PSA_KEY_TYPE_RAW_DATA',
152 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100153 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100154 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100155 kt: crypto_knowledge.KeyType,
156 param: Optional[int] = None,
157 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100158 ) -> Iterator[test_case.TestCase]:
Gilles Peskineaf172842021-01-27 18:24:48 +0100159 """Return test cases exercising key creation when the given type is unsupported.
160
161 If param is present and not None, emit test cases conditioned on this
162 parameter not being supported. If it is absent or None, emit test cases
163 conditioned on the base type not being supported.
164 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100165 if kt.name in self.ALWAYS_SUPPORTED:
166 # Don't generate test cases for key types that are always supported.
167 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100168 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100169 import_dependencies = [('!' if param is None else '') +
170 psa_want_symbol(kt.name)]
171 if kt.params is not None:
172 import_dependencies += [('!' if param == i else '') +
173 psa_want_symbol(sym)
174 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100175 if kt.name.endswith('_PUBLIC_KEY'):
176 generate_dependencies = []
177 else:
178 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100179 for bits in kt.sizes_to_test():
Gilles Peskine3d778392021-02-17 15:11:05 +0100180 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100181 'import', kt.expression, bits,
182 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100183 test_case.hex_string(kt.key_material(bits)),
184 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100185 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100186 if not generate_dependencies and param is not None:
187 # If generation is impossible for this key type, rather than
188 # supported or not depending on implementation capabilities,
189 # only generate the test case once.
190 continue
Gilles Peskine3d778392021-02-17 15:11:05 +0100191 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100192 'generate', kt.expression, bits,
193 finish_family_dependencies(generate_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100194 str(bits),
195 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100196 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100197 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100198
Gilles Peskine638deee2021-04-19 13:50:25 +0200199 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
200 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
201
Gilles Peskine3d778392021-02-17 15:11:05 +0100202 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100203 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100204 for key_type in sorted(self.constructors.key_types):
Gilles Peskine638deee2021-04-19 13:50:25 +0200205 if key_type in self.ECC_KEY_TYPES:
206 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100207 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100208 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100209 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine638deee2021-04-19 13:50:25 +0200210 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100211 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100212 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100213 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100214 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100215 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100216
217
Gilles Peskine897dff92021-03-10 15:03:44 +0100218class StorageKey(psa_storage.Key):
219 """Representation of a key for storage format testing."""
220
221 def __init__(self, *, description: str, **kwargs) -> None:
222 super().__init__(**kwargs)
223 self.description = description #type: str
224
225class StorageFormat:
226 """Storage format stability test cases."""
227
228 def __init__(self, info: Information, version: int, forward: bool) -> None:
229 """Prepare to generate test cases for storage format stability.
230
231 * `info`: information about the API. See the `Information` class.
232 * `version`: the storage format version to generate test cases for.
233 * `forward`: if true, generate forward compatibility test cases which
234 save a key and check that its representation is as intended. Otherwise
235 generate backward compatibility test cases which inject a key
236 representation and check that it can be read and used.
237 """
238 self.constructors = info.constructors
239 self.version = version
240 self.forward = forward
241
242 def make_test_case(self, key: StorageKey) -> test_case.TestCase:
243 """Construct a storage format test case for the given key.
244
245 If ``forward`` is true, generate a forward compatibility test case:
246 create a key and validate that it has the expected representation.
247 Otherwise generate a backward compatibility test case: inject the
248 key representation into storage and validate that it can be read
249 correctly.
250 """
251 verb = 'save' if self.forward else 'read'
252 tc = test_case.TestCase()
253 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100254 dependencies = automatic_dependencies(
255 key.lifetime.string, key.type.string,
256 key.usage.string, key.alg.string, key.alg2.string,
257 )
258 dependencies = finish_family_dependencies(dependencies, key.bits)
259 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100260 tc.set_function('key_storage_' + verb)
261 if self.forward:
262 extra_arguments = []
263 else:
264 # Some test keys have the RAW_DATA type and attributes that don't
265 # necessarily make sense. We do this to validate numerical
266 # encodings of the attributes.
267 # Raw data keys have no useful exercise anyway so there is no
268 # loss of test coverage.
269 exercise = key.type.string != 'PSA_KEY_TYPE_RAW_DATA'
270 extra_arguments = ['1' if exercise else '0']
271 tc.set_arguments([key.lifetime.string,
272 key.type.string, str(key.bits),
273 key.usage.string, key.alg.string, key.alg2.string,
274 '"' + key.material.hex() + '"',
275 '"' + key.hex() + '"',
276 *extra_arguments])
277 return tc
278
279 def key_for_usage_flags(
280 self,
281 usage_flags: List[str],
282 short: Optional[str] = None
283 ) -> StorageKey:
284 """Construct a test key for the given key usage."""
285 usage = ' | '.join(usage_flags) if usage_flags else '0'
286 if short is None:
287 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
288 description = 'usage: ' + short
289 key = StorageKey(version=self.version,
290 id=1, lifetime=0x00000001,
291 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
292 usage=usage, alg=0, alg2=0,
293 material=b'K',
294 description=description)
295 return key
296
297 def all_keys_for_usage_flags(self) -> Iterator[StorageKey]:
298 """Generate test keys covering usage flags."""
299 known_flags = sorted(self.constructors.key_usage_flags)
300 yield self.key_for_usage_flags(['0'])
301 for usage_flag in known_flags:
302 yield self.key_for_usage_flags([usage_flag])
303 for flag1, flag2 in zip(known_flags,
304 known_flags[1:] + [known_flags[0]]):
305 yield self.key_for_usage_flags([flag1, flag2])
306 yield self.key_for_usage_flags(known_flags, short='all known')
307
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100308 def keys_for_type(
309 self,
310 key_type: str,
311 params: Optional[Iterable[str]] = None
312 ) -> Iterator[StorageKey]:
313 """Generate test keys for the given key type.
314
315 For key types that depend on a parameter (e.g. elliptic curve family),
316 `param` is the parameter to pass to the constructor. Only a single
317 parameter is supported.
318 """
319 kt = crypto_knowledge.KeyType(key_type, params)
320 for bits in kt.sizes_to_test():
321 usage_flags = 'PSA_KEY_USAGE_EXPORT'
322 alg = 0
323 alg2 = 0
324 key_material = kt.key_material(bits)
325 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
326 r'',
327 kt.expression)
328 description = 'type: {} {}-bit'.format(short_expression, bits)
329 key = StorageKey(version=self.version,
330 id=1, lifetime=0x00000001,
331 type=kt.expression, bits=bits,
332 usage=usage_flags, alg=alg, alg2=alg2,
333 material=key_material,
334 description=description)
335 yield key
336
337 def all_keys_for_types(self) -> Iterator[StorageKey]:
338 """Generate test keys covering key types and their representations."""
Gilles Peskine638deee2021-04-19 13:50:25 +0200339 key_types = sorted(self.constructors.key_types)
340 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100341 yield from self.keys_for_type(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100342 ## Diffie-Hellman (FFDH) is not supported yet, either in
343 ## crypto_knowledge.py or in Mbed TLS.
344 # for key_type in sorted(self.constructors.key_types_from_group):
345 # for group in sorted(self.constructors.dh_groups):
346 # yield from self.keys_for_type(key_type, [group])
347
Gilles Peskined86bc522021-03-10 15:08:57 +0100348 def keys_for_algorithm(self, alg: str) -> Iterator[StorageKey]:
349 """Generate test keys for the specified algorithm."""
350 # For now, we don't have information on the compatibility of key
351 # types and algorithms. So we just test the encoding of algorithms,
352 # and not that operations can be performed with them.
353 descr = alg
354 usage = 'PSA_KEY_USAGE_EXPORT'
355 key1 = StorageKey(version=self.version,
356 id=1, lifetime=0x00000001,
357 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
358 usage=usage, alg=alg, alg2=0,
359 material=b'K',
360 description='alg: ' + descr)
361 yield key1
362 key2 = StorageKey(version=self.version,
363 id=1, lifetime=0x00000001,
364 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
365 usage=usage, alg=0, alg2=alg,
366 material=b'L',
367 description='alg2: ' + descr)
368 yield key2
369
370 def all_keys_for_algorithms(self) -> Iterator[StorageKey]:
371 """Generate test keys covering algorithm encodings."""
Gilles Peskine638deee2021-04-19 13:50:25 +0200372 algorithms = sorted(self.constructors.algorithms)
373 for alg in self.constructors.generate_expressions(algorithms):
Gilles Peskined86bc522021-03-10 15:08:57 +0100374 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100375
Gilles Peskine897dff92021-03-10 15:03:44 +0100376 def all_test_cases(self) -> Iterator[test_case.TestCase]:
377 """Generate all storage format test cases."""
Gilles Peskinef07866a2021-04-12 14:43:05 +0200378 # First build a list of all keys, then construct all the corresponding
379 # test cases. This allows all required information to be obtained in
380 # one go, which is a significant performance gain as the information
381 # includes numerical values obtained by compiling a C program.
382 keys = [] #type: List[StorageKey]
383 keys += self.all_keys_for_usage_flags()
384 keys += self.all_keys_for_types()
385 keys += self.all_keys_for_algorithms()
386 for key in keys:
Gilles Peskined86bc522021-03-10 15:08:57 +0100387 yield self.make_test_case(key)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100388 # To do: vary id, lifetime
Gilles Peskine897dff92021-03-10 15:03:44 +0100389
390
Gilles Peskineb94ea512021-03-10 02:12:08 +0100391class TestGenerator:
392 """Generate test data."""
393
394 def __init__(self, options) -> None:
395 self.test_suite_directory = self.get_option(options, 'directory',
396 'tests/suites')
397 self.info = Information()
398
399 @staticmethod
400 def get_option(options, name: str, default: T) -> T:
401 value = getattr(options, name, None)
402 return default if value is None else value
403
Gilles Peskine0298bda2021-03-10 02:34:37 +0100404 def filename_for(self, basename: str) -> str:
405 """The location of the data file with the specified base name."""
406 return os.path.join(self.test_suite_directory, basename + '.data')
407
Gilles Peskineb94ea512021-03-10 02:12:08 +0100408 def write_test_data_file(self, basename: str,
409 test_cases: Iterable[test_case.TestCase]) -> None:
410 """Write the test cases to a .data file.
411
412 The output file is ``basename + '.data'`` in the test suite directory.
413 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100414 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100415 test_case.write_data_file(filename, test_cases)
416
Gilles Peskine0298bda2021-03-10 02:34:37 +0100417 TARGETS = {
418 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100419 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100420 'test_suite_psa_crypto_storage_format.current':
421 lambda info: StorageFormat(info, 0, True).all_test_cases(),
422 'test_suite_psa_crypto_storage_format.v0':
423 lambda info: StorageFormat(info, 0, False).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100424 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
425
426 def generate_target(self, name: str) -> None:
427 test_cases = self.TARGETS[name](self.info)
428 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100429
Gilles Peskine09940492021-01-26 22:16:30 +0100430def main(args):
431 """Command line entry point."""
432 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100433 parser.add_argument('--list', action='store_true',
434 help='List available targets and exit')
435 parser.add_argument('targets', nargs='*', metavar='TARGET',
436 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100437 options = parser.parse_args(args)
438 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100439 if options.list:
440 for name in sorted(generator.TARGETS):
441 print(generator.filename_for(name))
442 return
443 if options.targets:
444 # Allow "-" as a special case so you can run
445 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
446 # ``$targets`` is empty or not.
447 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
448 for target in options.targets
449 if target != '-']
450 else:
451 options.targets = sorted(generator.TARGETS)
452 for target in options.targets:
453 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100454
455if __name__ == '__main__':
456 main(sys.argv[1:])