44
44
import chip .testing .conversions as conversions
45
45
import chip .testing .matchers as matchers
46
46
import chip .testing .timeoperations as timeoperations
47
+ import chip .testing .runner as runner
47
48
from chip .tlv import uint
48
49
49
50
# isort: off
69
70
from chip .storage import PersistentStorage
70
71
from chip .testing .commissioning import CommissioningInfo , CustomCommissioningParameters , SetupPayloadInfo , commission_devices
71
72
from chip .testing .global_attribute_ids import GlobalAttributeIds
73
+ from chip .testing .runner import get_test_info , run_tests_no_exit , run_tests , TestStep , TestInfo , TestRunnerHooks
72
74
from chip .testing .pics import read_pics_from_file
73
75
from chip .tracing import TracingContext
74
76
from mobly import asserts , base_test , signals , utils
75
- from mobly .config_parser import ENV_MOBLY_LOGPATH , TestRunConfig
76
- from mobly .test_runner import TestRunner
77
-
78
- try :
79
- from matter_yamltests .hooks import TestRunnerHooks
80
- except ImportError :
81
- class TestRunnerHooks :
82
- pass
83
77
84
78
85
79
# TODO: Add utility to commission a device if needed
@@ -498,51 +492,6 @@ def flush_reports(self) -> None:
498
492
return
499
493
500
494
501
- class InternalTestRunnerHooks (TestRunnerHooks ):
502
-
503
- def start (self , count : int ):
504
- logging .info (f'Starting test set, running { count } tests' )
505
-
506
- def stop (self , duration : int ):
507
- logging .info (f'Finished test set, ran for { duration } ms' )
508
-
509
- def test_start (self , filename : str , name : str , count : int , steps : list [str ] = []):
510
- logging .info (f'Starting test from { filename } : { name } - { count } steps' )
511
-
512
- def test_stop (self , exception : Exception , duration : int ):
513
- logging .info (f'Finished test in { duration } ms' )
514
-
515
- def step_skipped (self , name : str , expression : str ):
516
- # TODO: Do we really need the expression as a string? We can evaluate this in code very easily
517
- logging .info (f'\t \t **** Skipping: { name } ' )
518
-
519
- def step_start (self , name : str ):
520
- # The way I'm calling this, the name is already includes the step number, but it seems like it might be good to separate these
521
- logging .info (f'\t \t ***** Test Step { name } ' )
522
-
523
- def step_success (self , logger , logs , duration : int , request ):
524
- pass
525
-
526
- def step_failure (self , logger , logs , duration : int , request , received ):
527
- # TODO: there's supposed to be some kind of error message here, but I have no idea where it's meant to come from in this API
528
- logging .info ('\t \t ***** Test Failure : ' )
529
-
530
- def step_unknown (self ):
531
- """
532
- This method is called when the result of running a step is unknown. For example during a dry-run.
533
- """
534
- pass
535
-
536
- def show_prompt (self ,
537
- msg : str ,
538
- placeholder : Optional [str ] = None ,
539
- default_value : Optional [str ] = None ) -> None :
540
- pass
541
-
542
- def test_skipped (self , filename : str , name : str ):
543
- logging .info (f"Skipping test from { filename } : { name } " )
544
-
545
-
546
495
@dataclass
547
496
class MatterTestConfig :
548
497
storage_path : pathlib .Path = pathlib .Path ("." )
@@ -840,25 +789,6 @@ def stack(self) -> ChipStack:
840
789
return builtins .chipStack
841
790
842
791
843
- @dataclass
844
- class TestStep :
845
- test_plan_number : typing .Union [int , str ]
846
- description : str
847
- expectation : str = ""
848
- is_commissioning : bool = False
849
-
850
- def __str__ (self ):
851
- return f'{ self .test_plan_number } : { self .description } \t Expected outcome: { self .expectation } '
852
-
853
-
854
- @dataclass
855
- class TestInfo :
856
- function : str
857
- desc : str
858
- steps : list [TestStep ]
859
- pics : list [str ]
860
-
861
-
862
792
class MatterBaseTest (base_test .BaseTestClass ):
863
793
def __init__ (self , * args ):
864
794
super ().__init__ (* args )
@@ -1587,26 +1517,6 @@ def wait_for_user_input(self,
1587
1517
return None
1588
1518
1589
1519
1590
- def generate_mobly_test_config (matter_test_config : MatterTestConfig ):
1591
- test_run_config = TestRunConfig ()
1592
- # We use a default name. We don't use Mobly YAML configs, so that we can be
1593
- # freestanding without relying
1594
- test_run_config .testbed_name = "MatterTest"
1595
-
1596
- log_path = matter_test_config .logs_path
1597
- log_path = _DEFAULT_LOG_PATH if log_path is None else log_path
1598
- if ENV_MOBLY_LOGPATH in os .environ :
1599
- log_path = os .environ [ENV_MOBLY_LOGPATH ]
1600
-
1601
- test_run_config .log_path = log_path
1602
- # TODO: For later, configure controllers
1603
- test_run_config .controller_configs = {}
1604
-
1605
- test_run_config .user_params = matter_test_config .global_test_params
1606
-
1607
- return test_run_config
1608
-
1609
-
1610
1520
def _find_test_class ():
1611
1521
"""Finds the test class in a test script.
1612
1522
Walk through module members and find the subclass of MatterBaseTest. Only
@@ -2276,156 +2186,6 @@ def test_run_commissioning(self):
2276
2186
raise signals .TestAbortAll ("Failed to commission node(s)" )
2277
2187
2278
2188
2279
- def default_matter_test_main ():
2280
- """Execute the test class in a test module.
2281
- This is the default entry point for running a test script file directly.
2282
- In this case, only one test class in a test script is allowed.
2283
- To make your test script executable, add the following to your file:
2284
- .. code-block:: python
2285
- from chip.testing.matter_testing import default_matter_test_main
2286
- ...
2287
- if __name__ == '__main__':
2288
- default_matter_test_main()
2289
- """
2290
-
2291
- matter_test_config = parse_matter_test_args ()
2292
-
2293
- # Find the test class in the test script.
2294
- test_class = _find_test_class ()
2295
-
2296
- hooks = InternalTestRunnerHooks ()
2297
-
2298
- run_tests (test_class , matter_test_config , hooks )
2299
-
2300
-
2301
- def get_test_info (test_class : MatterBaseTest , matter_test_config : MatterTestConfig ) -> list [TestInfo ]:
2302
- test_config = generate_mobly_test_config (matter_test_config )
2303
- base = test_class (test_config )
2304
-
2305
- if len (matter_test_config .tests ) > 0 :
2306
- tests = matter_test_config .tests
2307
- else :
2308
- tests = base .get_existing_test_names ()
2309
-
2310
- info = []
2311
- for t in tests :
2312
- info .append (TestInfo (t , steps = base .get_test_steps (t ), desc = base .get_test_desc (t ), pics = base .get_test_pics (t )))
2313
-
2314
- return info
2315
-
2316
-
2317
- def run_tests_no_exit (test_class : MatterBaseTest , matter_test_config : MatterTestConfig ,
2318
- event_loop : asyncio .AbstractEventLoop , hooks : TestRunnerHooks ,
2319
- default_controller = None , external_stack = None ) -> bool :
2320
-
2321
- # NOTE: It's not possible to pass event loop via Mobly TestRunConfig user params, because the
2322
- # Mobly deep copies the user params before passing them to the test class and the event
2323
- # loop is not serializable. So, we are setting the event loop as a test class member.
2324
- CommissionDeviceTest .event_loop = event_loop
2325
- test_class .event_loop = event_loop
2326
-
2327
- get_test_info (test_class , matter_test_config )
2328
-
2329
- # Load test config file.
2330
- test_config = generate_mobly_test_config (matter_test_config )
2331
-
2332
- # Parse test specifiers if exist.
2333
- tests = None
2334
- if len (matter_test_config .tests ) > 0 :
2335
- tests = matter_test_config .tests
2336
-
2337
- if external_stack :
2338
- stack = external_stack
2339
- else :
2340
- stack = MatterStackState (matter_test_config )
2341
-
2342
- with TracingContext () as tracing_ctx :
2343
- for destination in matter_test_config .trace_to :
2344
- tracing_ctx .StartFromString (destination )
2345
-
2346
- test_config .user_params ["matter_stack" ] = stash_globally (stack )
2347
-
2348
- # TODO: Steer to right FabricAdmin!
2349
- # TODO: If CASE Admin Subject is a CAT tag range, then make sure to issue NOC with that CAT tag
2350
- if not default_controller :
2351
- default_controller = stack .certificate_authorities [0 ].adminList [0 ].NewController (
2352
- nodeId = matter_test_config .controller_node_id ,
2353
- paaTrustStorePath = str (matter_test_config .paa_trust_store_path ),
2354
- catTags = matter_test_config .controller_cat_tags ,
2355
- dacRevocationSetPath = str (matter_test_config .dac_revocation_set_path ),
2356
- )
2357
- test_config .user_params ["default_controller" ] = stash_globally (default_controller )
2358
-
2359
- test_config .user_params ["matter_test_config" ] = stash_globally (matter_test_config )
2360
- test_config .user_params ["hooks" ] = stash_globally (hooks )
2361
-
2362
- # Execute the test class with the config
2363
- ok = True
2364
-
2365
- test_config .user_params ["certificate_authority_manager" ] = stash_globally (stack .certificate_authority_manager )
2366
-
2367
- # Execute the test class with the config
2368
- ok = True
2369
-
2370
- runner = TestRunner (log_dir = test_config .log_path ,
2371
- testbed_name = test_config .testbed_name )
2372
-
2373
- with runner .mobly_logger ():
2374
- if matter_test_config .commissioning_method is not None :
2375
- runner .add_test_class (test_config , CommissionDeviceTest , None )
2376
-
2377
- # Add the tests selected unless we have a commission-only request
2378
- if not matter_test_config .commission_only :
2379
- runner .add_test_class (test_config , test_class , tests )
2380
-
2381
- if hooks :
2382
- # Right now, we only support running a single test class at once,
2383
- # but it's relatively easy to expand that to make the test process faster
2384
- # TODO: support a list of tests
2385
- hooks .start (count = 1 )
2386
- # Mobly gives the test run time in seconds, lets be a bit more precise
2387
- runner_start_time = datetime .now (timezone .utc )
2388
-
2389
- try :
2390
- runner .run ()
2391
- ok = runner .results .is_all_pass and ok
2392
- if matter_test_config .fail_on_skipped_tests and runner .results .skipped :
2393
- ok = False
2394
- except TimeoutError :
2395
- ok = False
2396
- except signals .TestAbortAll :
2397
- ok = False
2398
- except Exception :
2399
- logging .exception ('Exception when executing %s.' , test_config .testbed_name )
2400
- ok = False
2401
-
2402
- if hooks :
2403
- duration = (datetime .now (timezone .utc ) - runner_start_time ) / timedelta (microseconds = 1 )
2404
- hooks .stop (duration = duration )
2405
-
2406
- if not external_stack :
2407
- async def shutdown ():
2408
- stack .Shutdown ()
2409
- # Shutdown the stack when all done. Use the async runner to ensure that
2410
- # during the shutdown callbacks can use tha same async context which was used
2411
- # during the initialization.
2412
- event_loop .run_until_complete (shutdown ())
2413
-
2414
- if ok :
2415
- logging .info ("Final result: PASS !" )
2416
- else :
2417
- logging .error ("Final result: FAIL !" )
2418
- return ok
2419
-
2420
-
2421
- def run_tests (test_class : MatterBaseTest , matter_test_config : MatterTestConfig ,
2422
- hooks : TestRunnerHooks , default_controller = None , external_stack = None ) -> None :
2423
- with asyncio .Runner () as runner :
2424
- if not run_tests_no_exit (test_class , matter_test_config , runner .get_loop (),
2425
- hooks , default_controller , external_stack ):
2426
- sys .exit (1 )
2427
-
2428
-
2429
2189
# TODO(#37537): Remove these temporary aliases after transition period
2430
2190
type_matches = matchers .is_type
2431
2191
utc_time_in_matter_epoch = timeoperations .utc_time_in_matter_epoch
@@ -2437,3 +2197,5 @@ def run_tests(test_class: MatterBaseTest, matter_test_config: MatterTestConfig,
2437
2197
hex_from_bytes = conversions .hex_from_bytes
2438
2198
id_str = conversions .format_decimal_and_hex
2439
2199
cluster_id_str = conversions .cluster_id_with_name
2200
+
2201
+ default_matter_test_main = runner .default_matter_test_main
0 commit comments