-
Notifications
You must be signed in to change notification settings - Fork 0
/
utSuiteNavigator.py
453 lines (395 loc) · 16.6 KB
/
utSuiteNavigator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
#!/usr/bin/env python3
#** *****************************************************************************
# *
# * If not stated otherwise in this file or this component's LICENSE file the
# * following copyright and licenses apply:
# *
# * Copyright 2024 RDK Management
# *
# * Licensed under the Apache License, Version 2.0 (the "License");
# * you may not use this file except in compliance with the License.
# * You may obtain a copy of the License at
# *
# *
# http://www.apache.org/licenses/LICENSE-2.0
# *
# * Unless required by applicable law or agreed to in writing, software
# * distributed under the License is distributed on an "AS IS" BASIS,
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# * See the License for the specific language governing permissions and
# * limitations under the License.
# *
#* ******************************************************************************
import yaml
import sys
import os
import re
import time
# Helper always exist in the same directory under raft
dir_path = os.path.dirname(os.path.realpath(__file__))
sys.path.append(dir_path+"/../../../")
sys.path.append(dir_path)
from framework.core.logModule import logModule
from framework.core.commandModules.consoleInterface import consoleInterface
from interactiveShell import InteractiveShell
from configRead import ConfigRead
class utCFramework:
"""This module supports the selection of C Type tests
"""
def __init__(self, session:consoleInterface, log:logModule = None):
"""init function
Args:
session (consoleInterface): console interface to operate on
"""
self.prompt=": "
self.session = session
self.log=log
if log is None:
self.log = logModule(self.__class__.__name__)
self.log.setLevel( self.log.DEBUG )
self.commandPrompt = r"command: " # CUnit Prompt
self.selectPrompt = r") : "
def start(self, command:str ):
"""start the suite
Args:
command (str): start command
Returns:
str: prompt result
"""
self.session.write(command)
#result = self.session.read_all()
result = self.session.read_until( self.commandPrompt )
#self.log.debug("start[{}]".format(result))
if result == "":
self.log.error("Failed to start[{}]".format(command))
return result
def stop(self):
"""stops the active suite
Returns:
str: terminal output for debugging
"""
# Quit from any menu we're in
self.session.write("q")
result = self.session.read_all()
self.log.debug(result)
return result
def select(self, suite_name: str, test_name:str = None, promptWithAnswers: list = None ):
"""select a test from the suite to execute and wait for Prompt
Args:
suite_name (str): suite to select
test_name (str, optional): test_name within the suite to select. Defaults to None, whole suite will be ran
input (bool, optional): if set to true then don't wait on last prompt
Raises:
ValueError: Suite {suite_name} not found the suite configuration
ValueError: Test {test_name} not found in suite
Returns:
str: output from the framework
"""
# Ensure we're at the top menu
self.session.write("x")
self.session.write("u")
output = self.session.read_until(self.commandPrompt)
self.log.debug(output)
self.session.write("s")
output = self.session.read_until(self.selectPrompt)
self.log.debug(output)
# Extract test suite index from the output
suite_index = self.find_index_in_output(output, suite_name)
if suite_index is None:
self.log.error(f"Suite [{suite_name}] not found in configuration.")
return None
self.log.info(f"Found Suite: [{suite_name}]")
self.session.write(str(suite_index))
output = self.session.read_until(self.commandPrompt)
self.log.debug(output)
if test_name is None:
# Run the Suite of tests
self.session.write("r")
output = self.session.read_until(self.commandPrompt)
self.log.debug(output)
else:
# Run the specific test
self.session.write("s")
output = self.session.read_until(self.selectPrompt)
self.log.debug(output)
# Extract test index from the output
test_index = self.find_index_in_output(output, test_name)
if test_index is None:
self.log.error(f"Test [{test_name}] not found in suite [{suite_name}].")
raise ValueError(f"Test [{test_name}] not found in the suite.")
self.log.info(f"Found test: [{test_name}] @ [{test_index}]")
# Run the specific test
self.session.write(str(test_index))
# If Input is present we need to then wait on them
if promptWithAnswers is not None:
output = self.inputPrompts( promptWithAnswers )
# Wait for the command prompt if there's no other input required
output += self.session.read_until(self.commandPrompt)
self.log.debug(output)
return output
def inputPrompts(self, promptsWithAnswers: dict):
"""
Sends the ecific prompts and sends corresponding input values.
Args:
promptsWithAnswers A list of prompt strings to wait for.
"""
output=""
for prompt in promptsWithAnswers:
session_output = self.session.read_until(prompt.get("query"))
if prompt.get("query_type") == "list":
value = self.find_index_in_output(session_output, prompt.get("input"))
if value is None:
prompt = prompt.get("input")
self.log.error(f"Test [{prompt}] not found in suite")
raise ValueError(f"Test [{prompt}] not found.")
input = str(value)
else:
input = prompt.get("input")
self.session.write(input)
output += session_output
return output
def find_index_in_output(self, output, target_name):
"""
Finds the index of a target name in the shell output.
Args:
output (str): The shell output to search in.
target_name (str): The name of the suite or test to find.
Returns:
int: The index of the target_name if found, otherwise None.
"""
pattern = r'(\d+)\.\s*\[?' + re.escape(target_name) + r'\]?'
match = re.search(pattern, output)
if match:
return int(match.group(1))
return None
def collect_results(self, output):
"""
Collects and interprets the results from the test execution output.
Args:
output (str): The output from the test execution.
Returns:
bool: True if the test passed successfully, False if the test failed, or None if the output format is unexpected.
"""
# Pattern to detect the number of suites, tests, and asserts with their corresponding results
# Run Summary: Type Total Ran Passed Failed Inactive
# suites 2 0 n/a 0 0
# tests 16 1 1 0 0
# asserts 2 2 2 0 n/a
run_summary_pattern = r"Run Summary:\s+Type\s+Total\s+Ran\s+Passed\s+Failed\s+Inactive"
summary_match = re.search(run_summary_pattern, output)
if summary_match:
# Extract the relevant lines for suites, tests, and asserts
suite_summary_line = re.search(r"suites\s+\d+\s+\d+\s+n/a\s+(\d+)\s+\d+", output)
test_summary_line = re.search(r"tests\s+\d+\s+\d+\s+(\d+)\s+(\d+)\s+\d+", output)
assert_summary_line = re.search(r"asserts\s+\d+\s+\d+\s+(\d+)\s+(\d+)\s+n/a", output)
if suite_summary_line and test_summary_line and assert_summary_line:
suites_failed = int(suite_summary_line.group(1))
tests_failed = int(test_summary_line.group(2))
asserts_failed = int(assert_summary_line.group(2))
if suites_failed == 0 and tests_failed == 0 and asserts_failed == 0:
self.log.info("Test passed successfully.")
return True
else:
self.log.error("Test failed.")
return False
else:
self.log.error("Unexpected output format.")
return None
else:
self.log.error("Run Summary not found.")
return None
class UTSuiteNavigatorClass:
"""
Navigates through the UTcore menu system, trigger the execution of test cases, and collect results.
"""
def __init__(self, config:str, startKey:str, session:consoleInterface, log:logModule=None):
"""
Initializes the UTCoreMenuNavigator object with a menu configuration file and an optional test profile.
Example Format:
```yaml
module: # Prefix must always exist
description: "dsAudio Device Settings testing profile for UT"
test:
execute: "../bin/run.sh -p ../profiles/module_profile.yaml"
type: UT-C # C (UT-C Cunit) / C++ (UT-G (g++ ut-core gtest backend))
suites:
0:
name: "L1 Suite"
1:
name: "L2 Suite"
2:
name: "L3 Suite"
tests:
- "Test 1"
- "Test 2"
- "Test 3"
```
Args:
config (str): The file path to the menu configuration YAML file or a string
startKey (str): Optional Index string into the config
session: (consoleInterface) The console session object to communicate
log: (logModule, optional) Log module to use
"""
self.session = session
self.config = ConfigRead(config, startKey)
if log is None:
self.log = logModule(self.__class__.__name__)
test_type = self.config.test.type
self.log.setLevel( self.log.INFO )
if test_type == "UT-C" or test_type == "C":
# Currently support the C Framework
self.framework = utCFramework(session)
self.log.info("C Framework Selected")
else:
self.log.error("Invalid Menu Type Configuration :{}".format(test_type))
def select(self, suite_name: str, test_name:str = None, promptWithAnswers:dict = None ):
"""Select a menu from an already running system
Args:
suite_name (str): Suite Name
test_id (str): Test name or None for the whole suite
input (list optional): list of input values
Raises:
ValueError: not found in the menu configuration
ValueError: not found in the suite_name
"""
# 1. Find the suite name from the configuration
test_section = self.config.fields.get('test')
if not test_section:
self.log.error("Invalid Format [test:] section not found")
return None
suite_list = test_section.get('suites')
if not suite_list:
self.log.error("Invalid Format [suites:] section not found")
return None
found = False
# Just validate that the test is in the expected list
for index in suite_list:
suite = suite_list.get(index)
if not suite:
self.log.error("Invalid Format [suites.<index>]")
return None
testsList = suite.get("tests")
if not testsList:
continue
if test_name in testsList:
found = True
break
if not found:
self.log.error("Suite:[{}] Test:[{}] Not Found".format(suite_name, test_name))
return None
result = self.framework.select( suite_name, test_name, promptWithAnswers )
return result
def start(self):
command = self.config.test.execute
#TODO: Handle opkg download and install in the future
result = self.framework.start(command)
self.log.debug( "result [{}]".format(result))
def stop(self):
self.framework.stop()
def run(self, suite_name, test_name=None):
"""
Executes the specified test by navigating to it and collecting the results.
Args:
suite_name (str): The name of the test suikte to navigate to.
test_name (str): The name of the specific test case to execute.
Returns:
bool: The result of the test execution, True if the test passed, False otherwise.
"""
self.framework.start()
result = self.select( suite_name, test_name )
results = self.framework.collect_results( result )
return results
# Test and example usage code
if __name__ == '__main__':
suiteConfig="""
dsAudio: # Prefix must always exist
description: "dsAudio Device Settings testing profile for UT"
test:
execute: "cd bin;./run.sh -p ../profiles/sink/Sink_AudioSettings.yaml"
type: UT-C # C (UT-C Cunit) / C++ (UT-G (g++ ut-core gtest backend))
suites:
0:
name: "L1 dsAudio - Sink"
tests:
- None
1:
name: "L2 dsAudio - Sink"
tests:
- None
2:
name: "L3 dsAudio - Sink"
tests:
- "Initialize dsAudio"
- "Enable Audio Port"
- "Disable Audio Port"
- "Headphone Connection"
- "Audio Compression"
- "MS12 DAP Features"
- "Set Stereo Mode"
- "Enable/Disable Stereo Auto"
- "Set Audio Level"
- "Set Audio Gain For Speaker"
- "Audio Mute/UnMute"
- "Set Audio Delay"
- "Get Audio Format"
- "Set ATMOS Output Mode"
- "Get ATMOS Capabilities"
- "Set MS12 Profiles"
- "Set Associate Audio Mixing"
- "Set Audio Mixer Levels"
- "Primary/Secondary Language"
- "Get ARC Type"
- "Set SAD List"
- "Terminate dsAudio"
"""
# test the class
shell = InteractiveShell()
result = shell.open()
print("Shell:[{}]".format(result))
suite = "L3 dsAudio"
# Enable to test file loading assuming that we have a Audio Settings profile for testing
test = UTSuiteNavigatorClass(suiteConfig, "dsAudio:", shell)
test.start()
test.select( suite, "test_error_validation_case" ) # error case
result = test.select( suite, "Initialize dsAudio" ) # valid case
result = test.select( suite, "Terminate dsAudio" ) # valid case
promptWithAnswers = [
{
"query_type": "list",
"query": "Select dsAudio Port:",
"input": "dsAUDIOPORT_TYPE_SPEAKER"
},
{
"query_type": "direct",
"query": "Select dsAudio Port Index[0-10]:",
"input": "0"
}
]
result = test.select( suite, "Enable Audio Port", promptWithAnswers ) # Has non matching inputs and should error
print(result)
promptWithAnswers = [
{
"query_type": "list",
"query": "Select dsAudio Port:",
"input": "dsAUDIOPORT_TYPE_SPEAKER"
},
{
"query_type": "direct",
"query": "Select dsAudio Port Index[0-10]:",
"input": "0"
},
{
"query_type": "direct",
"query": "Enter Gain Level[0.0 to 100.0]:",
"input": "20"
}
]
result = test.select( suite, "Set Audio Mixer Levels", promptWithAnswers ) # Has non matching inputs and should error
print(result)
test.stop()
#test.select( "Parent", "Child" )
#menu_config - is currently not used
# Upgrade the help to support menu navigation input via the yaml file
# test launch_test
# select is working
shell.close()