|
36 | 36 | # The maximum amount of time to test that the launchURL is sent from the Linux tv-casting-app and received on the tv-app before timeout.
|
37 | 37 | TEST_LAUNCHURL_MAX_WAIT_SEC = 10
|
38 | 38 |
|
| 39 | +# The maximum amount of time to verify the subscription state in the Linux tv-casting-app output before timeout. |
| 40 | +VERIFY_SUBSCRIPTION_STATE_MAX_WAIT_SEC = 10 |
| 41 | + |
39 | 42 | # File names of logs for the Linux tv-casting-app and the Linux tv-app.
|
40 | 43 | LINUX_TV_APP_LOGS = 'Linux-tv-app-logs.txt'
|
41 | 44 | LINUX_TV_CASTING_APP_LOGS = 'Linux-tv-casting-app-logs.txt'
|
|
46 | 49 | PRODUCT_ID = 0x8001 # Test product id
|
47 | 50 | DEVICE_TYPE_CASTING_VIDEO_PLAYER = 0x23 # Device type library 10.3: Casting Video Player
|
48 | 51 |
|
| 52 | +# Values to verify the subscription state against from the `ReportDataMessage` in the Linux tv-casting-app output. |
| 53 | +CLUSTER_MEDIA_PLAYBACK = '0x506' # Application Cluster Spec 6.10.3 Cluster ID: Media Playback |
| 54 | +ATTRIBUTE_CURRENT_PLAYBACK_STATE = '0x0000_0000' # Application Cluster Spec 6.10.6 Attribute ID: Current State of Playback |
| 55 | + |
49 | 56 |
|
50 | 57 | class ProcessManager:
|
51 | 58 | """A context manager for managing subprocesses.
|
@@ -96,13 +103,19 @@ def extract_value_from_string(line: str) -> str:
|
96 | 103 |
|
97 | 104 | The string is expected to be in the following format as it is received
|
98 | 105 | from the Linux tv-casting-app output:
|
| 106 | + \x1b[0;34m[1715206773402] [20056:2842184] [DMG] Cluster = 0x506,\x1b[0m |
| 107 | + The substring to be extracted here is '0x506'. |
| 108 | + Or: |
99 | 109 | \x1b[0;34m[1713741926895] [7276:9521344] [DIS] Vendor ID: 65521\x1b[0m
|
100 | 110 | The integer value to be extracted here is 65521.
|
101 | 111 | Or:
|
102 | 112 | \x1b[0;34m[1714583616179] [7029:2386956] [SVR] device Name: Test TV casting app\x1b[0m
|
103 | 113 | The substring to be extracted here is 'Test TV casting app'.
|
104 | 114 | """
|
105 |
| - value = line.split(':')[-1].strip().replace('\x1b[0m', '') |
| 115 | + if '=' in line: |
| 116 | + value = line.split('=')[-1].strip().replace(',\x1b[0m', '') |
| 117 | + else: |
| 118 | + value = line.split(':')[-1].strip().replace('\x1b[0m', '') |
106 | 119 |
|
107 | 120 | return value
|
108 | 121 |
|
@@ -221,7 +234,7 @@ def validate_identification_declaration_message_on_tv_app(tv_app_info: Tuple[sub
|
221 | 234 | linux_tv_app_log_file.flush()
|
222 | 235 |
|
223 | 236 | if 'Identification Declaration Start' in tv_app_line:
|
224 |
| - logging.info('"Identification Declaration" block from the Linux tv-app output:') |
| 237 | + logging.info('Found the `Identification Declaration` block in the Linux tv-app output:') |
225 | 238 | logging.info(tv_app_line.rstrip('\n'))
|
226 | 239 | parsing_identification_block = True
|
227 | 240 | elif parsing_identification_block:
|
@@ -310,6 +323,56 @@ def validate_commissioning_success(tv_casting_app_info: Tuple[subprocess.Popen,
|
310 | 323 | return True
|
311 | 324 |
|
312 | 325 |
|
| 326 | +def parse_tv_casting_app_for_report_data_msg(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]): |
| 327 | + """Parse the Linux tv-casting-app for `ReportDataMessage` block and return the first message block with valid `Cluster` and `Attribute` values.""" |
| 328 | + tv_casting_app_process, linux_tv_casting_app_log_file = tv_casting_app_info |
| 329 | + |
| 330 | + continue_parsing = False |
| 331 | + report_data_message = [] |
| 332 | + |
| 333 | + start_wait_time = time.time() |
| 334 | + |
| 335 | + while True: |
| 336 | + # Check if we exceeded the maximum wait time to parse the Linux tv-casting-app output for `ReportDataMessage` block. |
| 337 | + if time.time() - start_wait_time > VERIFY_SUBSCRIPTION_STATE_MAX_WAIT_SEC: |
| 338 | + logging.error( |
| 339 | + 'The relevant `ReportDataMessage` block was not found in the Linux tv-casting-app process within the timeout.') |
| 340 | + report_data_message.clear() |
| 341 | + return report_data_message |
| 342 | + |
| 343 | + tv_casting_line = tv_casting_app_process.stdout.readline() |
| 344 | + |
| 345 | + if tv_casting_line: |
| 346 | + linux_tv_casting_app_log_file.write(tv_casting_line) |
| 347 | + linux_tv_casting_app_log_file.flush() |
| 348 | + |
| 349 | + if 'ReportDataMessage =' in tv_casting_line: |
| 350 | + report_data_message.append(tv_casting_line.rstrip('\n')) |
| 351 | + continue_parsing = True |
| 352 | + elif continue_parsing: |
| 353 | + report_data_message.append(tv_casting_line.rstrip('\n')) |
| 354 | + |
| 355 | + if 'Cluster =' in tv_casting_line: |
| 356 | + cluster_value = extract_value_from_string(tv_casting_line) |
| 357 | + if cluster_value != CLUSTER_MEDIA_PLAYBACK: |
| 358 | + report_data_message.clear() |
| 359 | + continue_parsing = False |
| 360 | + |
| 361 | + elif 'Attribute =' in tv_casting_line: |
| 362 | + attribute_value = extract_value_from_string(tv_casting_line) |
| 363 | + if attribute_value != ATTRIBUTE_CURRENT_PLAYBACK_STATE: |
| 364 | + report_data_message.clear() |
| 365 | + continue_parsing = False |
| 366 | + |
| 367 | + elif 'InteractionModelRevision' in tv_casting_line: |
| 368 | + # Capture the closing brace `}` of the `ReportDataMessage` block. |
| 369 | + tv_casting_line = tv_casting_app_process.stdout.readline() |
| 370 | + linux_tv_casting_app_log_file.write(tv_casting_line) |
| 371 | + linux_tv_casting_app_log_file.flush() |
| 372 | + report_data_message.append(tv_casting_line.rstrip('\n')) |
| 373 | + return report_data_message |
| 374 | + |
| 375 | + |
313 | 376 | def parse_tv_app_output_for_launchUrl_msg_success(tv_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]):
|
314 | 377 | """Parse the Linux tv-app output for the relevant string indicating that the launchUrl was received."""
|
315 | 378 |
|
@@ -359,7 +422,7 @@ def parse_tv_casting_app_output_for_launchUrl_msg_success(tv_casting_app_info: T
|
359 | 422 | linux_tv_casting_app_log_file.flush()
|
360 | 423 |
|
361 | 424 | if 'InvokeResponseMessage =' in tv_casting_line:
|
362 |
| - logging.info('Found the InvokeResponseMessage block in the Linux tv-casting-app output:') |
| 425 | + logging.info('Found the `InvokeResponseMessage` block in the Linux tv-casting-app output:') |
363 | 426 | logging.info(tv_casting_line.rstrip('\n'))
|
364 | 427 | continue_parsing_invoke_response_msg_block = True
|
365 | 428 |
|
@@ -415,7 +478,7 @@ def test_discovery_fn(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], log_
|
415 | 478 |
|
416 | 479 | # A valid commissioner has VENDOR_ID, PRODUCT_ID, and DEVICE TYPE in its list of entries.
|
417 | 480 | if valid_vendor_id and valid_product_id and valid_device_type:
|
418 |
| - logging.info('Found a valid commissioner in the Linux tv-casting-app logs:') |
| 481 | + logging.info('Found a valid commissioner in the Linux tv-casting-app output:') |
419 | 482 | logging.info(valid_discovered_commissioner)
|
420 | 483 | logging.info(valid_vendor_id)
|
421 | 484 | logging.info(valid_product_id)
|
@@ -454,7 +517,24 @@ def test_launchUrl_fn(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], tv_a
|
454 | 517 | if not parse_tv_casting_app_output_for_launchUrl_msg_success(tv_casting_app_info, log_paths):
|
455 | 518 | handle_casting_failure('Testing launchUrl', log_paths)
|
456 | 519 |
|
457 |
| - logging.info('Testing launchUrl success!\n') |
| 520 | + logging.info('Testing launchUrl success!') |
| 521 | + |
| 522 | + |
| 523 | +def test_subscription_fn(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]): |
| 524 | + """Test the subscription state of the Linux tv-casting-app by validating the `ReportDataMessage` block.""" |
| 525 | + |
| 526 | + valid_report_data_msg = parse_tv_casting_app_for_report_data_msg(tv_casting_app_info, log_paths) |
| 527 | + |
| 528 | + if valid_report_data_msg: |
| 529 | + logging.info('Found the `ReportDataMessage` block in the Linux tv-casting-app output:') |
| 530 | + |
| 531 | + for line in valid_report_data_msg: |
| 532 | + logging.info(line) |
| 533 | + |
| 534 | + logging.info('Testing subscription success!\n') |
| 535 | + valid_report_data_msg.clear() |
| 536 | + else: |
| 537 | + handle_casting_failure('Testing subscription', log_paths) |
458 | 538 |
|
459 | 539 |
|
460 | 540 | @click.command()
|
@@ -504,7 +584,7 @@ def test_casting_fn(tv_app_rel_path, tv_casting_app_rel_path):
|
504 | 584 | valid_discovered_commissioner_number = valid_discovered_commissioner.split('#')[-1].replace('\x1b[0m', '')
|
505 | 585 |
|
506 | 586 | test_commissioning_fn(valid_discovered_commissioner_number, tv_casting_app_info, tv_app_info, log_paths)
|
507 |
| - |
| 587 | + test_subscription_fn(tv_casting_app_info, log_paths) |
508 | 588 | test_launchUrl_fn(tv_casting_app_info, tv_app_info, log_paths)
|
509 | 589 |
|
510 | 590 |
|
|
0 commit comments