16
16
17
17
import logging
18
18
import os
19
+ import re
19
20
import subprocess
20
21
import sys
21
22
import tempfile
@@ -75,6 +76,26 @@ def __exit__(self, exception_type, exception_value, traceback):
75
76
self .process .wait ()
76
77
77
78
79
+ class LogValueExtractor :
80
+ """A utility class for extracting values from log lines.
81
+
82
+ This class provides a centralized way to extract values from log lines and manage the error handling and logging process.
83
+ """
84
+
85
+ def __init__ (self , casting_state : str , log_paths : List [str ]):
86
+ self .casting_state = casting_state
87
+ self .log_paths = log_paths
88
+
89
+ def extract_from (self , line : str , value_name : str ):
90
+ if value_name in line :
91
+ try :
92
+ return extract_value_from_string (line , value_name , self .casting_state , self .log_paths )
93
+ except ValueError :
94
+ logging .error (f'Failed to extract `{ value_name } ` value from line: { line } ' )
95
+ handle_casting_failure (self .casting_state , self .log_paths )
96
+ return None
97
+
98
+
78
99
def dump_temporary_logs_to_console (log_file_path : str ):
79
100
"""Dump log file to the console; log file will be removed once the function exits."""
80
101
"""Write the entire content of `log_file_path` to the console."""
@@ -115,25 +136,32 @@ def extract_value_from_string(line: str, value_name: str, casting_state: str, lo
115
136
\x1b [0;34m[1714583616179] [7029:2386956] [SVR] device Name: Test TV casting app\x1b [0m
116
137
The substring to be extracted here is 'Test TV casting app'.
117
138
"""
118
- if ':' in line :
119
- if '=' in line :
139
+ log_line_pattern = r'\x1b\[0;\d+m\[\d+\] \[\d+:\d+\] \[[A-Z]{1,3}\] (.+?)\x1b\[0m'
140
+ log_line_match = re .search (log_line_pattern , line )
141
+
142
+ if log_line_match :
143
+ log_text_of_interest = log_line_match .group (1 )
144
+
145
+ if '=' in log_text_of_interest :
120
146
delimiter = '='
121
- elif '#' in line :
147
+ elif '#' in log_text_of_interest :
122
148
delimiter = '#'
123
149
else :
124
150
delimiter = ':'
125
151
126
- value = line .split (delimiter )[- 1 ].strip (). replace ( ' \x1b [0m' , '' ). rstrip ( ' ,' )
152
+ return log_text_of_interest .split (delimiter )[- 1 ].strip (' ,' )
127
153
else :
128
- logging .error (f'Could not extract { value_name } from the following line: { line } ' )
129
- handle_casting_failure (casting_state , log_paths )
130
-
131
- return value
154
+ raise ValueError (f'Could not extract { value_name } from the following line: { line } ' )
132
155
133
156
134
157
def validate_value (casting_state : str , expected_value : Union [str , int ], log_paths : List [str ], line : str , value_name : str ) -> Optional [str ]:
135
158
"""Validate a value in a string against an expected value during a given casting state."""
136
- value = extract_value_from_string (line , value_name , casting_state , log_paths )
159
+ log_value_extractor = LogValueExtractor (casting_state , log_paths )
160
+ value = log_value_extractor .extract_from (line , value_name )
161
+ if not value :
162
+ logging .error (f'Failed to extract { value_name } value from the following line: { line } ' )
163
+ logging .error (f'Failed to validate against the expected { value_name } value: { expected_value } !' )
164
+ handle_casting_failure (casting_state , log_paths )
137
165
138
166
if isinstance (expected_value , int ):
139
167
value = int (value )
@@ -203,6 +231,7 @@ def initiate_cast_request_success(tv_casting_app_info: Tuple[subprocess.Popen, T
203
231
def extract_device_info_from_tv_casting_app (tv_casting_app_info : Tuple [subprocess .Popen , TextIO ], casting_state : str , log_paths : List [str ]) -> Tuple [Optional [str ], Optional [int ], Optional [int ]]:
204
232
"""Extract device information from the 'Identification Declaration' block in the Linux tv-casting-app output."""
205
233
tv_casting_app_process , linux_tv_casting_app_log_file = tv_casting_app_info
234
+ log_value_extractor = LogValueExtractor (casting_state , log_paths )
206
235
207
236
device_name = None
208
237
vendor_id = None
@@ -212,14 +241,12 @@ def extract_device_info_from_tv_casting_app(tv_casting_app_info: Tuple[subproces
212
241
linux_tv_casting_app_log_file .write (line )
213
242
linux_tv_casting_app_log_file .flush ()
214
243
215
- if 'device Name' in line :
216
- device_name = extract_value_from_string (line , 'device Name' , casting_state , log_paths )
217
- elif 'vendor id' in line :
218
- vendor_id = extract_value_from_string (line , 'vendor id' , casting_state , log_paths )
219
- vendor_id = int (vendor_id )
220
- elif 'product id' in line :
221
- product_id = extract_value_from_string (line , 'product id' , casting_state , log_paths )
222
- product_id = int (product_id )
244
+ if value := log_value_extractor .extract_from (line , 'device Name' ):
245
+ device_name = value
246
+ elif value := log_value_extractor .extract_from (line , 'vendor id' ):
247
+ vendor_id = int (value )
248
+ elif value := log_value_extractor .extract_from (line , 'product id' ):
249
+ product_id = int (value )
223
250
224
251
if device_name and vendor_id and product_id :
225
252
break
@@ -340,6 +367,7 @@ def validate_commissioning_success(tv_casting_app_info: Tuple[subprocess.Popen,
340
367
def parse_tv_casting_app_for_report_data_msg (tv_casting_app_info : Tuple [subprocess .Popen , TextIO ], log_paths : List [str ]):
341
368
"""Parse the Linux tv-casting-app for `ReportDataMessage` block and return the first message block with valid `Cluster` and `Attribute` values."""
342
369
tv_casting_app_process , linux_tv_casting_app_log_file = tv_casting_app_info
370
+ log_value_extractor = LogValueExtractor ('Testing subscription' , log_paths )
343
371
344
372
continue_parsing = False
345
373
report_data_message = []
@@ -366,15 +394,12 @@ def parse_tv_casting_app_for_report_data_msg(tv_casting_app_info: Tuple[subproce
366
394
elif continue_parsing :
367
395
report_data_message .append (tv_casting_line .rstrip ('\n ' ))
368
396
369
- if 'Cluster =' in tv_casting_line :
370
- cluster_value = extract_value_from_string (tv_casting_line , 'Cluster value' , 'Testing subscription' , log_paths )
397
+ if cluster_value := log_value_extractor .extract_from (tv_casting_line , 'Cluster =' ):
371
398
if cluster_value != CLUSTER_MEDIA_PLAYBACK :
372
399
report_data_message .clear ()
373
400
continue_parsing = False
374
401
375
- elif 'Attribute =' in tv_casting_line :
376
- attribute_value = extract_value_from_string (
377
- tv_casting_line , 'Attribute value' , 'Testing subscription' , log_paths )
402
+ elif attribute_value := log_value_extractor .extract_from (tv_casting_line , 'Attribute =' ):
378
403
if attribute_value != ATTRIBUTE_CURRENT_PLAYBACK_STATE :
379
404
report_data_message .clear ()
380
405
continue_parsing = False
@@ -515,7 +540,7 @@ def test_commissioning_fn(valid_discovered_commissioner_number, tv_casting_app_i
515
540
tv_casting_app_info , 'Commissioning' , log_paths )
516
541
517
542
if not expected_device_name or not expected_vendor_id or not expected_product_id :
518
- logging .error ('The is an error with the expected device info values that were extracted from the `Identification Declaration` block.' )
543
+ logging .error ('There is an error with the expected device info values that were extracted from the `Identification Declaration` block.' )
519
544
logging .error (
520
545
f'expected_device_name: { expected_device_name } , expected_vendor_id: { expected_vendor_id } , expected_product_id: { expected_product_id } ' )
521
546
handle_casting_failure ('Commissioning' , log_paths )
@@ -601,8 +626,12 @@ def test_casting_fn(tv_app_rel_path, tv_casting_app_rel_path):
601
626
handle_casting_failure ('Discovery' , log_paths )
602
627
603
628
# We need the valid discovered commissioner number to continue with commissioning.
604
- valid_discovered_commissioner_number = extract_value_from_string (
605
- valid_discovered_commissioner , 'Discovered Commissioner number' , 'Commissioning' , log_paths )
629
+ log_value_extractor = LogValueExtractor ('Commissioning' , log_paths )
630
+ valid_discovered_commissioner_number = log_value_extractor .extract_from (
631
+ valid_discovered_commissioner , 'Discovered Commissioner #' )
632
+ if not valid_discovered_commissioner_number :
633
+ logging .error (f'Failed to find `Discovered Commissioner #` in line: { valid_discovered_commissioner } ' )
634
+ handle_casting_failure ('Commissioning' , log_paths )
606
635
607
636
test_commissioning_fn (valid_discovered_commissioner_number , tv_casting_app_info , tv_app_info , log_paths )
608
637
test_subscription_fn (tv_casting_app_info , log_paths )
0 commit comments