Skip to content

Commit 5858a93

Browse files
authored
PR #12471 from Eran: add run-unit-tests --device <>
2 parents c84eead + 8342b63 commit 5858a93

File tree

2 files changed

+72
-27
lines changed

2 files changed

+72
-27
lines changed

unit-tests/py/rspy/devices.py

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -333,16 +333,18 @@ def by_name( name, ignored_products ):
333333
result.add(device.serial_number)
334334
return result
335335

336-
def _get_sns_from_spec( spec, ignored_products ):
336+
def by_spec( spec, ignored_products ):
337337
"""
338338
Helper function for by_configuration. Yields all serial-numbers matching the given spec
339-
:param spec: A product name/line (as a string) we want to get serial number of
339+
:param spec: A product name/line (as a string) we want to get serial number of, or an actual s/n
340340
:param ignored_products: List of products we want to ignore. e.g. ['D455', 'D457', etc.]
341341
:return: A set of device serial-numbers
342342
"""
343343
if spec.endswith( '*' ):
344344
for sn in by_product_line( spec[:-1], ignored_products ):
345345
yield sn
346+
elif get( spec ):
347+
yield spec # the device serial number
346348
else:
347349
for sn in by_name( spec, ignored_products ):
348350
yield sn
@@ -357,7 +359,7 @@ def expand_specs( specs ):
357359
"""
358360
expanded = set()
359361
for spec in specs:
360-
sns = {sn for sn in _get_sns_from_spec( spec )}
362+
sns = {sn for sn in by_spec( spec )}
361363
if sns:
362364
expanded.update( sns )
363365
else:
@@ -387,16 +389,17 @@ def load_specs_from_file( filename ):
387389
return exceptions
388390

389391

390-
def by_configuration( config, exceptions = None ):
392+
def by_configuration( config, exceptions=None, inclusions=None ):
391393
"""
392394
Yields the serial numbers fitting the given configuration. If configuration includes an 'each' directive
393395
will yield all fitting serial numbers one at a time. Otherwise yields one set of serial numbers fitting the configuration
394396
395-
:param config: A test:device line collection of arguments (e.g., [L515 D400*])
397+
:param config: A test:device line collection of arguments (e.g., [L515 D400*]) or serial numbers
396398
:param exceptions: A collection of serial-numbers that serve as exceptions that will never get matched
399+
:param inclusions: A collection of serial-numbers from which to match - nothing else will get matched
397400
398-
If no device matches the configuration devices specified, a RuntimeError will be
399-
raised!
401+
If no device matches the configuration devices specified, a RuntimeError will be raised unless
402+
'inclusions' is provided and the configuration is simple, and an empty set yielded to signify.
400403
"""
401404
exceptions = exceptions or set()
402405
# split the current config to two lists:
@@ -411,33 +414,49 @@ def by_configuration( config, exceptions = None ):
411414
else:
412415
new_config.append(p)
413416

417+
nothing_matched = True
414418
if len( new_config ) > 0 and re.fullmatch( r'each\(.+\)', new_config[0], re.IGNORECASE ):
415419
spec = new_config[0][5:-1]
416-
for sn in _get_sns_from_spec( spec, ignored_products ):
417-
if sn not in exceptions:
418-
yield { sn }
420+
for sn in by_spec( spec, ignored_products ):
421+
if sn in exceptions:
422+
continue
423+
if inclusions and sn not in inclusions:
424+
continue
425+
nothing_matched = False
426+
yield { sn }
419427
else:
420428
sns = set()
421429
for spec in new_config:
422430
old_len = len(sns)
423-
for sn in _get_sns_from_spec( spec, ignored_products ):
431+
for sn in by_spec( spec, ignored_products ):
424432
if sn in exceptions:
425433
continue
434+
if inclusions and sn not in inclusions:
435+
continue
426436
if sn not in sns:
427437
sns.add( sn )
428438
break
429439
new_len = len(sns)
430440
if new_len == old_len:
431-
error = 'no device matches configuration "' + spec + '"'
432-
if old_len:
433-
error += ' (after already matching ' + str(sns) + ')'
434-
if ignored_products:
435-
error += ' (!' + str(ignored_products) + ')'
436-
if exceptions:
437-
error += ' (-' + str(exceptions) + ')'
438-
raise RuntimeError( error )
441+
# No new device matches the spec:
442+
# - if no inclusions were specified, this is always an error
443+
# - with inclusions, it's not an error only if it's the only spec
444+
if not inclusions or len(new_config) > 1:
445+
error = 'no device matches configuration "' + spec + '"'
446+
if old_len:
447+
error += ' (after already matching ' + str(sns) + ')'
448+
if ignored_products:
449+
error += ' (!' + str(ignored_products) + ')'
450+
if exceptions:
451+
error += ' (-' + str(exceptions) + ')'
452+
if inclusions:
453+
error += ' (+' + str(inclusions) + ')'
454+
raise RuntimeError( error )
439455
if sns:
456+
nothing_matched = False
440457
yield sns
458+
if nothing_matched and inclusions:
459+
yield set() # let the caller decide how to deal with it
441460

442461

443462
def get_first( sns ):

unit-tests/run-unit-tests.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def usage():
4747
print( ' --context <> The context to use for test configuration' )
4848
print( ' --repeat <#> Repeat each test <#> times' )
4949
print( ' --config <> Ignore test configurations; use the one provided' )
50+
print( ' --device <> Run only on the specified devices; ignore any test that does not match (implies --live)' )
5051
print( ' --no-reset Do not try to reset any devices, with or without Acroname' )
5152
print( ' --acroname-reset If acroname is available, reset the acroname itself' )
5253
print( ' --rslog Enable LibRS logging (LOG_DEBUG etc.) to console in each test' )
@@ -77,7 +78,7 @@ def usage():
7778
opts, args = getopt.getopt( sys.argv[1:], 'hvqr:st:',
7879
longopts=['help', 'verbose', 'debug', 'quiet', 'regex=', 'stdout', 'tag=', 'list-tags',
7980
'list-tests', 'no-exceptions', 'context=', 'repeat=', 'config=', 'no-reset', 'acroname-reset',
80-
'rslog', 'skip-disconnected', 'live', 'not-live'] )
81+
'rslog', 'skip-disconnected', 'live', 'not-live', 'device='] )
8182
except getopt.GetoptError as err:
8283
log.e( err ) # something like "option -a not recognized"
8384
usage()
@@ -90,6 +91,7 @@ def usage():
9091
context = []
9192
repeat = 1
9293
forced_configurations = None
94+
device_set = None
9395
no_reset = False
9496
acroname_reset = False
9597
skip_disconnected = False
@@ -124,6 +126,12 @@ def usage():
124126
repeat = int(arg)
125127
elif opt == '--config':
126128
forced_configurations = [[arg]]
129+
elif opt == '--device':
130+
if only_not_live:
131+
log.e( "--device and --not-live are mutually exclusive" )
132+
usage()
133+
only_live = True
134+
device_set = arg.split()
127135
elif opt == '--no-reset':
128136
no_reset = True
129137
elif opt == '--acroname-reset':
@@ -237,16 +245,20 @@ def find_build_dir( dir ):
237245
os.environ["PYTHONPATH"] += os.pathsep + dir
238246

239247

240-
def configuration_str( configuration, repetition=1, retry=0, sns=None, prefix='', suffix='' ):
248+
def serial_numbers_to_string( sns ):
249+
return ' '.join( [f'{devices.get(sn).name}_{sn}' for sn in sns] )
250+
251+
252+
def configuration_str( configuration, repetition=0, retry=0, sns=None, prefix='', suffix='' ):
241253
""" Return a string repr (with a prefix and/or suffix) of the configuration or '' if it's None """
242254
s = ''
243255
if configuration is not None:
244256
s += '[' + ' '.join( configuration )
245257
if sns is not None:
246-
s += ' -> ' + ' '.join( [f'{devices.get(sn).name}_{sn}' for sn in sns] )
258+
s += ' -> ' + serial_numbers_to_string( sns )
247259
s += ']'
248260
elif sns is not None:
249-
s += '[' + ' '.join( [f'{devices.get(sn).name}_{sn}' for sn in sns] ) + ']'
261+
s += '[' + serial_numbers_to_string( sns ) + ']'
250262
if repetition:
251263
s += '[' + str(repetition+1) + ']'
252264
if retry:
@@ -379,11 +391,14 @@ def devices_by_test_config( test, exceptions ):
379391
380392
:param test: The test (of class type Test) we're interested in
381393
"""
382-
global forced_configurations
394+
global forced_configurations, device_set
383395
for configuration in ( forced_configurations or test.config.configurations ):
384396
try:
385-
for serial_numbers in devices.by_configuration( configuration, exceptions ):
386-
yield configuration, serial_numbers
397+
for serial_numbers in devices.by_configuration( configuration, exceptions, device_set ):
398+
if not serial_numbers:
399+
log.d( 'configuration:', configuration_str( configuration ), 'has no matching device; ignoring' )
400+
else:
401+
yield configuration, serial_numbers
387402
except RuntimeError as e:
388403
if devices.acroname:
389404
log.e( log.red + test.name + log.reset + ': ' + str( e ) )
@@ -462,6 +477,17 @@ def test_wrapper( test, configuration=None, repetition=1, sns=None ):
462477
finally:
463478
log.debug_unindent()
464479
#
480+
if device_set is not None:
481+
sns = set() # convert the list of specs to a list of serial numbers
482+
ignored_list = list()
483+
for spec in device_set:
484+
included_devices = [sn for sn in devices.by_spec( spec, ignored_list )]
485+
if not included_devices:
486+
log.f( f'No match for --device "{spec}"' )
487+
sns.update( included_devices )
488+
device_set = sns
489+
log.d( f'ignoring devices other than: {serial_numbers_to_string( device_set )}' )
490+
#
465491
log.progress()
466492
#
467493
# Automatically detect github actions based on environment variable
@@ -543,7 +569,7 @@ def test_wrapper( test, configuration=None, repetition=1, sns=None ):
543569
for configuration, serial_numbers in devices_by_test_config( test, exceptions ):
544570
for repetition in range(repeat):
545571
try:
546-
log.d( 'configuration:', configuration )
572+
log.d( 'configuration:', configuration_str( configuration, repetition, sns=serial_numbers ) )
547573
log.debug_indent()
548574
if not no_reset:
549575
devices.enable_only( serial_numbers, recycle=True )

0 commit comments

Comments
 (0)