mirror of
https://github.com/Mbed-TLS/mbedtls-framework.git
synced 2026-10-02 06:47:23 +00:00
Merge pull request #104 from gilles-peskine-arm/psa-storage-test-cases-never-supported-negative-framework
Switch generate_psa_test.py to automatic dependencies for negative test cases
This commit is contained in:
@@ -11,7 +11,7 @@ generate only the specified files.
|
|||||||
import enum
|
import enum
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional
|
from typing import Callable, Dict, Iterable, Iterator, List, Optional
|
||||||
|
|
||||||
from mbedtls_framework import crypto_data_tests
|
from mbedtls_framework import crypto_data_tests
|
||||||
from mbedtls_framework import crypto_knowledge
|
from mbedtls_framework import crypto_knowledge
|
||||||
@@ -26,7 +26,7 @@ from mbedtls_framework import test_data_generation
|
|||||||
|
|
||||||
def test_case_for_key_type_not_supported(
|
def test_case_for_key_type_not_supported(
|
||||||
verb: str, key_type: str, bits: int,
|
verb: str, key_type: str, bits: int,
|
||||||
dependencies: List[str],
|
not_supported_mechanism: str,
|
||||||
*args: str,
|
*args: str,
|
||||||
param_descr: str = ''
|
param_descr: str = ''
|
||||||
) -> test_case.TestCase:
|
) -> test_case.TestCase:
|
||||||
@@ -35,17 +35,16 @@ def test_case_for_key_type_not_supported(
|
|||||||
"""
|
"""
|
||||||
tc = psa_test_case.TestCase()
|
tc = psa_test_case.TestCase()
|
||||||
short_key_type = crypto_knowledge.short_expression(key_type)
|
short_key_type = crypto_knowledge.short_expression(key_type)
|
||||||
adverb = 'not' if dependencies else 'never'
|
tc.set_description('PSA {} {} {}-bit{} not supported'
|
||||||
if param_descr:
|
.format(verb, short_key_type, bits,
|
||||||
adverb = param_descr + ' ' + adverb
|
' ' + param_descr if param_descr else ''))
|
||||||
tc.set_description('PSA {} {} {}-bit {} supported'
|
# if tc.description == 'PSA import RSA_KEY_PAIR 1024-bit not supported':
|
||||||
.format(verb, short_key_type, bits, adverb))
|
# import pdb; pdb.set_trace()
|
||||||
tc.set_function(verb + '_not_supported')
|
tc.set_function(verb + '_not_supported')
|
||||||
tc.set_key_bits(bits)
|
tc.set_key_bits(bits)
|
||||||
tc.set_key_pair_usage(verb.upper())
|
tc.set_key_pair_usage([verb.upper()])
|
||||||
|
tc.assumes_not_supported(not_supported_mechanism)
|
||||||
tc.set_arguments([key_type] + list(args))
|
tc.set_arguments([key_type] + list(args))
|
||||||
tc.set_dependencies(dependencies)
|
|
||||||
tc.skip_if_any_not_implemented(dependencies)
|
|
||||||
return tc
|
return tc
|
||||||
|
|
||||||
class KeyTypeNotSupported:
|
class KeyTypeNotSupported:
|
||||||
@@ -77,37 +76,27 @@ class KeyTypeNotSupported:
|
|||||||
# Don't generate test cases for key types that are always supported.
|
# Don't generate test cases for key types that are always supported.
|
||||||
# They would be skipped in all configurations, which is noise.
|
# They would be skipped in all configurations, which is noise.
|
||||||
return
|
return
|
||||||
import_dependencies = [('!' if param is None else '') +
|
if param is None:
|
||||||
psa_information.psa_want_symbol(kt.name)]
|
not_supported_mechanism = kt.name
|
||||||
if kt.params is not None:
|
|
||||||
import_dependencies += [('!' if param == i else '') +
|
|
||||||
psa_information.psa_want_symbol(sym)
|
|
||||||
for i, sym in enumerate(kt.params)]
|
|
||||||
if kt.name.endswith('_PUBLIC_KEY'):
|
|
||||||
generate_dependencies = []
|
|
||||||
else:
|
else:
|
||||||
generate_dependencies = \
|
assert kt.params is not None
|
||||||
psa_information.fix_key_pair_dependencies(import_dependencies, 'GENERATE')
|
not_supported_mechanism = kt.params[param]
|
||||||
import_dependencies = \
|
|
||||||
psa_information.fix_key_pair_dependencies(import_dependencies, 'BASIC')
|
|
||||||
for bits in kt.sizes_to_test():
|
for bits in kt.sizes_to_test():
|
||||||
yield test_case_for_key_type_not_supported(
|
yield test_case_for_key_type_not_supported(
|
||||||
'import', kt.expression, bits,
|
'import', kt.expression, bits,
|
||||||
psa_information.finish_family_dependencies(import_dependencies, bits),
|
not_supported_mechanism,
|
||||||
test_case.hex_string(kt.key_material(bits)),
|
test_case.hex_string(kt.key_material(bits)),
|
||||||
param_descr=param_descr,
|
param_descr=param_descr,
|
||||||
)
|
)
|
||||||
if not generate_dependencies and param is not None:
|
# Don't generate not-supported test cases for key generation of
|
||||||
# If generation is impossible for this key type, rather than
|
# public keys. Our implementation always returns
|
||||||
# supported or not depending on implementation capabilities,
|
# PSA_ERROR_INVALID_ARGUMENT when attempting to generate a
|
||||||
# only generate the test case once.
|
# public key, so we cover this together with the positive cases
|
||||||
continue
|
# in the KeyGenerate class.
|
||||||
# For public key we expect that key generation fails with
|
|
||||||
# INVALID_ARGUMENT. It is handled by KeyGenerate class.
|
|
||||||
if not kt.is_public():
|
if not kt.is_public():
|
||||||
yield test_case_for_key_type_not_supported(
|
yield test_case_for_key_type_not_supported(
|
||||||
'generate', kt.expression, bits,
|
'generate', kt.expression, bits,
|
||||||
psa_information.finish_family_dependencies(generate_dependencies, bits),
|
not_supported_mechanism,
|
||||||
str(bits),
|
str(bits),
|
||||||
param_descr=param_descr,
|
param_descr=param_descr,
|
||||||
)
|
)
|
||||||
@@ -155,7 +144,7 @@ def test_case_for_key_generation(
|
|||||||
.format(short_key_type, bits))
|
.format(short_key_type, bits))
|
||||||
tc.set_function('generate_key')
|
tc.set_function('generate_key')
|
||||||
tc.set_key_bits(bits)
|
tc.set_key_bits(bits)
|
||||||
tc.set_key_pair_usage('GENERATE')
|
tc.set_key_pair_usage(['GENERATE'])
|
||||||
tc.set_arguments([key_type] + list(args) + [result])
|
tc.set_arguments([key_type] + list(args) + [result])
|
||||||
return tc
|
return tc
|
||||||
|
|
||||||
@@ -234,16 +223,19 @@ class OpFail:
|
|||||||
category: crypto_knowledge.AlgorithmCategory,
|
category: crypto_knowledge.AlgorithmCategory,
|
||||||
reason: 'Reason',
|
reason: 'Reason',
|
||||||
kt: Optional[crypto_knowledge.KeyType] = None,
|
kt: Optional[crypto_knowledge.KeyType] = None,
|
||||||
not_deps: FrozenSet[str] = frozenset(),
|
not_supported: Optional[str] = None,
|
||||||
) -> test_case.TestCase:
|
) -> test_case.TestCase:
|
||||||
"""Construct a failure test case for a one-key or keyless operation."""
|
"""Construct a failure test case for a one-key or keyless operation.
|
||||||
|
|
||||||
|
If `reason` is `Reason.NOT_SUPPORTED`, pass the not-supported
|
||||||
|
dependency symbol as the `not_supported` argument.
|
||||||
|
"""
|
||||||
#pylint: disable=too-many-arguments,too-many-locals
|
#pylint: disable=too-many-arguments,too-many-locals
|
||||||
tc = psa_test_case.TestCase()
|
tc = psa_test_case.TestCase()
|
||||||
pretty_alg = alg.short_expression()
|
pretty_alg = alg.short_expression()
|
||||||
if reason == self.Reason.NOT_SUPPORTED:
|
if reason == self.Reason.NOT_SUPPORTED:
|
||||||
short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
|
assert not_supported is not None
|
||||||
for dep in not_deps]
|
pretty_reason = '!' + re.sub(r'PSA_WANT_[A-Z]+_', r'', not_supported)
|
||||||
pretty_reason = '!' + '&'.join(sorted(short_deps))
|
|
||||||
else:
|
else:
|
||||||
pretty_reason = reason.name.lower()
|
pretty_reason = reason.name.lower()
|
||||||
if kt:
|
if kt:
|
||||||
@@ -257,16 +249,12 @@ class OpFail:
|
|||||||
pretty_alg,
|
pretty_alg,
|
||||||
pretty_reason,
|
pretty_reason,
|
||||||
' with ' + pretty_type if pretty_type else ''))
|
' with ' + pretty_type if pretty_type else ''))
|
||||||
dependencies = psa_information.automatic_dependencies(alg.base_expression, key_type)
|
|
||||||
dependencies = psa_information.fix_key_pair_dependencies(dependencies, 'BASIC')
|
|
||||||
for i, dep in enumerate(dependencies):
|
|
||||||
if dep in not_deps:
|
|
||||||
dependencies[i] = '!' + dep
|
|
||||||
tc.set_function(category.name.lower() + '_fail')
|
tc.set_function(category.name.lower() + '_fail')
|
||||||
arguments = [] # type: List[str]
|
arguments = [] # type: List[str]
|
||||||
if kt:
|
if kt:
|
||||||
bits = kt.sizes_to_test()[0]
|
bits = kt.sizes_to_test()[0]
|
||||||
tc.set_key_bits(bits)
|
tc.set_key_bits(bits)
|
||||||
|
tc.set_key_pair_usage(['IMPORT'])
|
||||||
key_material = kt.key_material(bits)
|
key_material = kt.key_material(bits)
|
||||||
arguments += [key_type, test_case.hex_string(key_material)]
|
arguments += [key_type, test_case.hex_string(key_material)]
|
||||||
arguments.append(alg.expression)
|
arguments.append(alg.expression)
|
||||||
@@ -275,8 +263,25 @@ class OpFail:
|
|||||||
error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
|
error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
|
||||||
'INVALID_ARGUMENT')
|
'INVALID_ARGUMENT')
|
||||||
arguments.append('PSA_ERROR_' + error)
|
arguments.append('PSA_ERROR_' + error)
|
||||||
|
if reason == self.Reason.NOT_SUPPORTED:
|
||||||
|
assert not_supported is not None
|
||||||
|
tc.assumes_not_supported(not_supported)
|
||||||
|
# Special case: if one of deterministic/randomized
|
||||||
|
# ECDSA is supported but not the other, then the one
|
||||||
|
# that is not supported in the signature direction is
|
||||||
|
# still supported in the verification direction,
|
||||||
|
# because the two verification algorithms are
|
||||||
|
# identical. This property is how Mbed TLS chooses to
|
||||||
|
# behave, the specification would also allow it to
|
||||||
|
# reject the algorithm. In the generated test cases,
|
||||||
|
# we avoid this difficulty by not running the
|
||||||
|
# not-supported test case when exactly one of the
|
||||||
|
# two variants is supported.
|
||||||
|
if not_supported == 'PSA_WANT_ALG_ECDSA':
|
||||||
|
tc.add_dependencies(['!PSA_WANT_ALG_DETERMINISTIC_ECDSA'])
|
||||||
|
if not_supported == 'PSA_WANT_ALG_DETERMINISTIC_ECDSA':
|
||||||
|
tc.add_dependencies(['!PSA_WANT_ALG_ECDSA'])
|
||||||
tc.set_arguments(arguments)
|
tc.set_arguments(arguments)
|
||||||
tc.set_dependencies(dependencies)
|
|
||||||
return tc
|
return tc
|
||||||
|
|
||||||
def no_key_test_cases(
|
def no_key_test_cases(
|
||||||
@@ -290,7 +295,7 @@ class OpFail:
|
|||||||
for dep in psa_information.automatic_dependencies(alg.base_expression):
|
for dep in psa_information.automatic_dependencies(alg.base_expression):
|
||||||
yield self.make_test_case(alg, category,
|
yield self.make_test_case(alg, category,
|
||||||
self.Reason.NOT_SUPPORTED,
|
self.Reason.NOT_SUPPORTED,
|
||||||
not_deps=frozenset([dep]))
|
not_supported=dep)
|
||||||
else:
|
else:
|
||||||
# Incompatible operation, supported algorithm
|
# Incompatible operation, supported algorithm
|
||||||
yield self.make_test_case(alg, category, self.Reason.INVALID)
|
yield self.make_test_case(alg, category, self.Reason.INVALID)
|
||||||
@@ -308,7 +313,7 @@ class OpFail:
|
|||||||
for dep in psa_information.automatic_dependencies(alg.base_expression):
|
for dep in psa_information.automatic_dependencies(alg.base_expression):
|
||||||
yield self.make_test_case(alg, category,
|
yield self.make_test_case(alg, category,
|
||||||
self.Reason.NOT_SUPPORTED,
|
self.Reason.NOT_SUPPORTED,
|
||||||
kt=kt, not_deps=frozenset([dep]))
|
kt=kt, not_supported=dep)
|
||||||
# Public key for a private-key operation
|
# Public key for a private-key operation
|
||||||
if category.is_asymmetric() and kt.is_public():
|
if category.is_asymmetric() and kt.is_public():
|
||||||
yield self.make_test_case(alg, category,
|
yield self.make_test_case(alg, category,
|
||||||
@@ -481,7 +486,7 @@ class StorageFormat:
|
|||||||
tc.add_dependencies(psa_information.generate_deps_from_description(key.description))
|
tc.add_dependencies(psa_information.generate_deps_from_description(key.description))
|
||||||
tc.set_function('key_storage_' + verb)
|
tc.set_function('key_storage_' + verb)
|
||||||
tc.set_key_bits(key.bits)
|
tc.set_key_bits(key.bits)
|
||||||
tc.set_key_pair_usage('BASIC')
|
tc.set_key_pair_usage(['IMPORT'] if self.forward else ['EXPORT'])
|
||||||
if self.forward:
|
if self.forward:
|
||||||
extra_arguments = []
|
extra_arguments = []
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -139,29 +139,18 @@ def generate_deps_from_description(
|
|||||||
|
|
||||||
return dep_list
|
return dep_list
|
||||||
|
|
||||||
def tweak_key_pair_dependency(dep: str, usage: str):
|
def tweak_key_pair_dependency(dep: str, usages: List[str]) -> List[str]:
|
||||||
"""
|
"""
|
||||||
This helper function add the proper suffix to PSA_WANT_KEY_TYPE_xxx_KEY_PAIR
|
This helper function add the proper suffix to PSA_WANT_KEY_TYPE_xxx_KEY_PAIR
|
||||||
symbols according to the required usage.
|
symbols according to the required usage.
|
||||||
"""
|
"""
|
||||||
ret_list = list()
|
|
||||||
if dep.endswith('KEY_PAIR'):
|
if dep.endswith('KEY_PAIR'):
|
||||||
if usage == "BASIC":
|
return [dep + '_' + usage for usage in usages]
|
||||||
# BASIC automatically includes IMPORT and EXPORT for test purposes (see
|
return [dep]
|
||||||
# config_psa.h).
|
|
||||||
ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_BASIC', dep))
|
|
||||||
ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_IMPORT', dep))
|
|
||||||
ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_EXPORT', dep))
|
|
||||||
elif usage == "GENERATE":
|
|
||||||
ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_GENERATE', dep))
|
|
||||||
else:
|
|
||||||
# No replacement to do in this case
|
|
||||||
ret_list.append(dep)
|
|
||||||
return ret_list
|
|
||||||
|
|
||||||
def fix_key_pair_dependencies(dep_list: List[str], usage: str):
|
def fix_key_pair_dependencies(dep_list: List[str], usages: List[str]) -> List[str]:
|
||||||
new_list = [new_deps
|
new_list = [new_deps
|
||||||
for dep in dep_list
|
for dep in dep_list
|
||||||
for new_deps in tweak_key_pair_dependency(dep, usage)]
|
for new_deps in tweak_key_pair_dependency(dep, usages)]
|
||||||
|
|
||||||
return new_list
|
return new_list
|
||||||
|
|||||||
@@ -14,11 +14,14 @@ from . import psa_information
|
|||||||
from . import test_case
|
from . import test_case
|
||||||
|
|
||||||
|
|
||||||
# A temporary hack: at the time of writing, not all dependency symbols
|
# Skip test cases for which the dependency symbols are not defined.
|
||||||
# are implemented yet. Skip test cases for which the dependency symbols are
|
# We assume that this means that a required mechanism is not implemented.
|
||||||
# not available. Once all dependency symbols are available, this hack must
|
# Note that if we erroneously skip generating test cases for
|
||||||
# be removed so that a bug in the dependency symbols properly leads to a test
|
# mechanisms that are not implemented, this should be caught
|
||||||
# failure.
|
# by the NOT_SUPPORTED test cases generated by generate_psa_tests.py
|
||||||
|
# in test_suite_psa_crypto_not_supported and test_suite_psa_crypto_op_fail:
|
||||||
|
# those emit tests with negative dependencies, which will not be skipped here.
|
||||||
|
|
||||||
def read_implemented_dependencies(acc: Set[str], filename: str) -> None:
|
def read_implemented_dependencies(acc: Set[str], filename: str) -> None:
|
||||||
with open(filename) as input_stream:
|
with open(filename) as input_stream:
|
||||||
for line in input_stream:
|
for line in input_stream:
|
||||||
@@ -51,8 +54,8 @@ def find_dependencies_not_implemented(dependencies: List[str]) -> List[str]:
|
|||||||
_implemented_dependencies = frozenset(acc)
|
_implemented_dependencies = frozenset(acc)
|
||||||
return [dep
|
return [dep
|
||||||
for dep in dependencies
|
for dep in dependencies
|
||||||
if (dep.lstrip('!') not in _implemented_dependencies and
|
if (dep not in _implemented_dependencies and
|
||||||
dep.lstrip('!').startswith('PSA_WANT'))]
|
dep.startswith('PSA_WANT'))]
|
||||||
|
|
||||||
|
|
||||||
class TestCase(test_case.TestCase):
|
class TestCase(test_case.TestCase):
|
||||||
@@ -75,8 +78,9 @@ class TestCase(test_case.TestCase):
|
|||||||
self.manual_dependencies = [] #type: List[str]
|
self.manual_dependencies = [] #type: List[str]
|
||||||
self.automatic_dependencies = set() #type: Set[str]
|
self.automatic_dependencies = set() #type: Set[str]
|
||||||
self.dependency_prefix = dependency_prefix #type: Optional[str]
|
self.dependency_prefix = dependency_prefix #type: Optional[str]
|
||||||
|
self.negated_dependencies = set() #type: Set[str]
|
||||||
self.key_bits = None #type: Optional[int]
|
self.key_bits = None #type: Optional[int]
|
||||||
self.key_pair_usage = None #type: Optional[str]
|
self.key_pair_usage = None #type: Optional[List[str]]
|
||||||
|
|
||||||
def set_key_bits(self, key_bits: Optional[int]) -> None:
|
def set_key_bits(self, key_bits: Optional[int]) -> None:
|
||||||
"""Use the given key size for automatic dependency generation.
|
"""Use the given key size for automatic dependency generation.
|
||||||
@@ -88,8 +92,8 @@ class TestCase(test_case.TestCase):
|
|||||||
"""
|
"""
|
||||||
self.key_bits = key_bits
|
self.key_bits = key_bits
|
||||||
|
|
||||||
def set_key_pair_usage(self, key_pair_usage: Optional[str]) -> None:
|
def set_key_pair_usage(self, key_pair_usage: Optional[List[str]]) -> None:
|
||||||
"""Use the given suffix for key pair dependencies.
|
"""Use the given suffixes for key pair dependencies.
|
||||||
|
|
||||||
Call this function before set_arguments() if relevant.
|
Call this function before set_arguments() if relevant.
|
||||||
|
|
||||||
@@ -109,16 +113,62 @@ class TestCase(test_case.TestCase):
|
|||||||
dependencies = psa_information.fix_key_pair_dependencies(dependencies,
|
dependencies = psa_information.fix_key_pair_dependencies(dependencies,
|
||||||
self.key_pair_usage)
|
self.key_pair_usage)
|
||||||
if 'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE' in dependencies and \
|
if 'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE' in dependencies and \
|
||||||
|
'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE' not in self.negated_dependencies and \
|
||||||
self.key_bits is not None:
|
self.key_bits is not None:
|
||||||
size_dependency = ('PSA_VENDOR_RSA_GENERATE_MIN_KEY_BITS <= ' +
|
size_dependency = ('PSA_VENDOR_RSA_GENERATE_MIN_KEY_BITS <= ' +
|
||||||
str(self.key_bits))
|
str(self.key_bits))
|
||||||
dependencies.append(size_dependency)
|
dependencies.append(size_dependency)
|
||||||
return dependencies
|
return dependencies
|
||||||
|
|
||||||
|
def assumes_not_supported(self, name: str) -> None:
|
||||||
|
"""Negate the given mechanism for automatic dependency generation.
|
||||||
|
|
||||||
|
`name` can be either a dependency symbol (``PSA_WANT_xxx``) or
|
||||||
|
a mechanism name (``PSA_KEY_TYPE_xxx``, etc.).
|
||||||
|
|
||||||
|
Call this function before set_arguments() for a test case that should
|
||||||
|
run if the given mechanism is not supported.
|
||||||
|
|
||||||
|
Call modifiers such as set_key_bits() and set_key_pair_usage() before
|
||||||
|
calling this method, if applicable.
|
||||||
|
|
||||||
|
A mechanism is a PSA_XXX symbol, e.g. PSA_KEY_TYPE_AES, PSA_ALG_HMAC,
|
||||||
|
etc. For mechanisms like ECC curves where the support status includes
|
||||||
|
the key bit-size, this class assumes that only one bit-size is
|
||||||
|
involved in a given test case.
|
||||||
|
"""
|
||||||
|
if name.startswith('PSA_WANT_'):
|
||||||
|
self.negated_dependencies.add(name)
|
||||||
|
return
|
||||||
|
if name == 'PSA_KEY_TYPE_RSA_KEY_PAIR' and \
|
||||||
|
self.key_bits is not None and \
|
||||||
|
self.key_pair_usage == ['GENERATE']:
|
||||||
|
# When RSA key pair generation is not supported, it could be
|
||||||
|
# due to the specific key size is out of range, or because
|
||||||
|
# RSA key pair generation itself is not supported. Assume the
|
||||||
|
# latter.
|
||||||
|
dep = psa_information.psa_want_symbol(name, prefix=self.dependency_prefix)
|
||||||
|
|
||||||
|
self.negated_dependencies.add(dep + '_GENERATE')
|
||||||
|
return
|
||||||
|
dependencies = self.infer_dependencies([name])
|
||||||
|
# * If we have more than one dependency to negate, the result would
|
||||||
|
# say that all of the dependencies are disabled, which is not
|
||||||
|
# a desirable outcome: the negation of (A and B) is (!A or !B),
|
||||||
|
# not (!A and !B).
|
||||||
|
# * If we have no dependency to negate, the result wouldn't be a
|
||||||
|
# not-supported case.
|
||||||
|
# Assert that we don't reach either such case.
|
||||||
|
assert len(dependencies) == 1
|
||||||
|
self.negated_dependencies.add(dependencies[0])
|
||||||
|
|
||||||
def set_arguments(self, arguments: List[str]) -> None:
|
def set_arguments(self, arguments: List[str]) -> None:
|
||||||
"""Set test case arguments and automatically infer dependencies."""
|
"""Set test case arguments and automatically infer dependencies."""
|
||||||
super().set_arguments(arguments)
|
super().set_arguments(arguments)
|
||||||
dependencies = self.infer_dependencies(arguments)
|
dependencies = self.infer_dependencies(arguments)
|
||||||
|
for i in range(len(dependencies)): #pylint: disable=consider-using-enumerate
|
||||||
|
if dependencies[i] in self.negated_dependencies:
|
||||||
|
dependencies[i] = '!' + dependencies[i]
|
||||||
self.skip_if_any_not_implemented(dependencies)
|
self.skip_if_any_not_implemented(dependencies)
|
||||||
self.automatic_dependencies.update(dependencies)
|
self.automatic_dependencies.update(dependencies)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user