diff --git a/unit-tests/py/rspy/devices.py b/unit-tests/py/rspy/devices.py index 3dbd14f077..ab0145f6fb 100644 --- a/unit-tests/py/rspy/devices.py +++ b/unit-tests/py/rspy/devices.py @@ -389,16 +389,17 @@ def load_specs_from_file( filename ): return exceptions -def by_configuration( config, exceptions = None ): +def by_configuration( config, exceptions=None, inclusions=None ): """ Yields the serial numbers fitting the given configuration. If configuration includes an 'each' directive will yield all fitting serial numbers one at a time. Otherwise yields one set of serial numbers fitting the configuration :param config: A test:device line collection of arguments (e.g., [L515 D400*]) or serial numbers :param exceptions: A collection of serial-numbers that serve as exceptions that will never get matched + :param inclusions: A collection of serial-numbers from which to match - nothing else will get matched - If no device matches the configuration devices specified, a RuntimeError will be - raised! + If no device matches the configuration devices specified, a RuntimeError will be raised unless + 'inclusions' is provided and the configuration is simple, and an empty set yielded to signify. """ exceptions = exceptions or set() # split the current config to two lists: @@ -413,11 +414,16 @@ def by_configuration( config, exceptions = None ): else: new_config.append(p) + nothing_matched = True if len( new_config ) > 0 and re.fullmatch( r'each\(.+\)', new_config[0], re.IGNORECASE ): spec = new_config[0][5:-1] for sn in by_spec( spec, ignored_products ): - if sn not in exceptions: - yield { sn } + if sn in exceptions: + continue + if inclusions and sn not in inclusions: + continue + nothing_matched = False + yield { sn } else: sns = set() for spec in new_config: @@ -425,21 +431,32 @@ def by_configuration( config, exceptions = None ): for sn in by_spec( spec, ignored_products ): if sn in exceptions: continue + if inclusions and sn not in inclusions: + continue if sn not in sns: sns.add( sn ) break new_len = len(sns) if new_len == old_len: - error = 'no device matches configuration "' + spec + '"' - if old_len: - error += ' (after already matching ' + str(sns) + ')' - if ignored_products: - error += ' (!' + str(ignored_products) + ')' - if exceptions: - error += ' (-' + str(exceptions) + ')' - raise RuntimeError( error ) + # No new device matches the spec: + # - if no inclusions were specified, this is always an error + # - with inclusions, it's not an error only if it's the only spec + if not inclusions or len(new_config) > 1: + error = 'no device matches configuration "' + spec + '"' + if old_len: + error += ' (after already matching ' + str(sns) + ')' + if ignored_products: + error += ' (!' + str(ignored_products) + ')' + if exceptions: + error += ' (-' + str(exceptions) + ')' + if inclusions: + error += ' (+' + str(inclusions) + ')' + raise RuntimeError( error ) if sns: + nothing_matched = False yield sns + if nothing_matched and inclusions: + yield set() # let the caller decide how to deal with it def get_first( sns ): diff --git a/unit-tests/run-unit-tests.py b/unit-tests/run-unit-tests.py index 674a27f1a1..bcaeb409dc 100644 --- a/unit-tests/run-unit-tests.py +++ b/unit-tests/run-unit-tests.py @@ -249,7 +249,7 @@ def serial_numbers_to_string( sns ): return ' '.join( [f'{devices.get(sn).name}_{sn}' for sn in sns] ) -def configuration_str( configuration, repetition=1, retry=0, sns=None, prefix='', suffix='' ): +def configuration_str( configuration, repetition=0, retry=0, sns=None, prefix='', suffix='' ): """ Return a string repr (with a prefix and/or suffix) of the configuration or '' if it's None """ s = '' if configuration is not None: @@ -394,20 +394,11 @@ def devices_by_test_config( test, exceptions ): global forced_configurations, device_set for configuration in ( forced_configurations or test.config.configurations ): try: - for serial_numbers in devices.by_configuration( configuration, exceptions ): - if device_set is not None: - # We're asked to look only at certain devices and ignore configurations that do - # not use them - without any errors! - for sn in serial_numbers: - dev = devices.get(sn) - if dev is not None and dev.serial_number in device_set: - break # Device was asked for; use this config - else: - # No device in the configuration is part of the set that was asked for; - # skip this configuration! - log.d( f'configuration: {configuration} devices do not match --device directive: {serial_numbers_to_string( serial_numbers )}' ) - continue - yield configuration, serial_numbers + for serial_numbers in devices.by_configuration( configuration, exceptions, device_set ): + if not serial_numbers: + log.d( 'configuration:', configuration_str( configuration ), 'has no matching device; ignoring' ) + else: + yield configuration, serial_numbers except RuntimeError as e: if devices.acroname: log.e( log.red + test.name + log.reset + ': ' + str( e ) ) @@ -490,9 +481,12 @@ def test_wrapper( test, configuration=None, repetition=1, sns=None ): sns = set() # convert the list of specs to a list of serial numbers ignored_list = list() for spec in device_set: - sns.update( devices.by_spec( spec, ignored_list ) ) + included_devices = [sn for sn in devices.by_spec( spec, ignored_list )] + if not included_devices: + log.f( f'No match for --device "{spec}"' ) + sns.update( included_devices ) device_set = sns - log.d( f'ignoring any configuration not using --device serial numbers: {" ".join( device_set )}' ) + log.d( f'ignoring devices other than: {serial_numbers_to_string( device_set )}' ) # log.progress() # @@ -575,7 +569,7 @@ def test_wrapper( test, configuration=None, repetition=1, sns=None ): for configuration, serial_numbers in devices_by_test_config( test, exceptions ): for repetition in range(repeat): try: - log.d( 'configuration:', configuration_str( configuration, repetition=repetition, sns=serial_numbers ) ) + log.d( 'configuration:', configuration_str( configuration, repetition, sns=serial_numbers ) ) log.debug_indent() if not no_reset: devices.enable_only( serial_numbers, recycle=True )