-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[devel_transport] Async host test execition support #78
Changes from 1 commit
ce92ac2
63b5bcd
0b1cc56
d24242c
fca7efc
71149d3
9de37ed
10d5007
470c733
dcfd047
7210f00
c1219b2
151734f
0dd25c7
836e330
dad9960
43abb3c
c12a3ff
7d231c4
7280188
cd2bd4a
65b7236
3cda486
9625991
e9efa7b
6cdc12b
c7c5f54
8cf5dd0
ce8f693
91492a0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,10 +19,8 @@ | |
|
||
import re | ||
import sys | ||
from time import time, sleep | ||
from Queue import Queue, Empty | ||
from threading import Thread | ||
from subprocess import call, Popen, PIPE | ||
from time import time | ||
from subprocess import call, Popen, PIPE, STDOUT | ||
|
||
from mbed_greentea.mbed_greentea_log import gt_logger | ||
|
||
|
@@ -66,20 +64,18 @@ | |
"build_failed" : TEST_RESULT_BUILD_FAILED | ||
} | ||
|
||
RE_DETECT_TESTCASE_RESULT = re.compile("\\{(" + "|".join(TEST_RESULT_MAPPING.keys()) + ")\\}") | ||
|
||
def get_test_result(output): | ||
"""! Parse test 'output' data | ||
@details If test result not found returns by default TEST_RESULT_TIMEOUT value | ||
@return Returns found test result | ||
""" | ||
result = TEST_RESULT_TIMEOUT | ||
RE_DETECT_TESTCASE_RESULT = re.compile("\\{(" + "|".join(TEST_RESULT_MAPPING.keys()) + ")\\}") | ||
for line in "".join(output).splitlines(): | ||
search_result = RE_DETECT_TESTCASE_RESULT.search(line) | ||
if search_result and search_result.groups(): | ||
result = TEST_RESULT_MAPPING[search_result.groups(0)[0]] | ||
break | ||
return result | ||
return TEST_RESULT_MAPPING[search_result.groups(0)[0]] | ||
return TEST_RESULT_TIMEOUT | ||
|
||
|
||
def run_host_test(image_path, | ||
|
@@ -118,119 +114,15 @@ def run_host_test(image_path, | |
file name was given as switch option) | ||
""" | ||
|
||
class StdInObserver(Thread): | ||
""" Process used to read stdin only as console input from MUT | ||
""" | ||
def __init__(self): | ||
Thread.__init__(self) | ||
self.queue = Queue() | ||
self.daemon = True | ||
self.active = True | ||
self.start() | ||
|
||
def run(self): | ||
while self.active: | ||
c = sys.stdin.read(1) | ||
self.queue.put(c) | ||
|
||
def stop(self): | ||
self.active = False | ||
|
||
class FileObserver(Thread): | ||
""" process used to read file content as console input from MUT | ||
""" | ||
def __init__(self, filename): | ||
Thread.__init__(self) | ||
self.filename = filename | ||
self.queue = Queue() | ||
self.daemon = True | ||
self.active = True | ||
self.start() | ||
|
||
def run(self): | ||
with open(self.filename) as f: | ||
while self.active: | ||
c = f.read(1) | ||
self.queue.put(c) | ||
|
||
def stop(self): | ||
self.active = False | ||
|
||
class ProcessObserver(Thread): | ||
""" Default process used to observe stdout of another process as console input from MUT | ||
""" | ||
def __init__(self, proc): | ||
Thread.__init__(self) | ||
self.proc = proc | ||
self.queue = Queue() | ||
self.daemon = True | ||
self.active = True | ||
self.start() | ||
|
||
def run(self): | ||
while self.active: | ||
c = self.proc.stdout.read(1) | ||
self.queue.put(c) | ||
|
||
def stop(self): | ||
self.active = False | ||
|
||
# Try stopping mbed-host-test | ||
try: | ||
self.proc.stdin.close() | ||
finally: | ||
pass | ||
|
||
# Give 5 sec for mbedhtrun to exit | ||
ret_code = None | ||
for i in range(5): | ||
ret_code = self.proc.poll() | ||
# A None value indicates that the process hasn't terminated yet. | ||
if ret_code is not None: | ||
break | ||
sleep(1) | ||
|
||
if ret_code is None: # Kill it | ||
print 'Terminating mbed-host-test(mbedhtrun) process (PID %s)' % self.proc.pid | ||
try: | ||
self.proc.terminate() | ||
except Exception as e: | ||
print "ProcessObserver.stop(): %s" % str(e) | ||
|
||
def get_char_from_queue(obs): | ||
""" Get character from queue safe way | ||
""" | ||
def run_command(cmd): | ||
"""! Runs command and prints proc stdout on screen """ | ||
try: | ||
c = obs.queue.get(block=True, timeout=0.5) | ||
# signals to queue job is done | ||
obs.queue.task_done() | ||
except Empty: | ||
c = None | ||
except: | ||
raise | ||
return c | ||
|
||
def filter_queue_char(c): | ||
""" Filters out non ASCII characters from serial port | ||
""" | ||
if ord(c) not in range(128): | ||
c = ' ' | ||
return c | ||
|
||
def get_auto_property_value(property_name, line): | ||
"""! Scans auto detection line from MUT and returns scanned parameter 'property_name' | ||
@details Host test case has to print additional properties for test to be set up | ||
@return Returns string or None if property search failed | ||
""" | ||
result = None | ||
if re.search("HOST: Property '%s'"% property_name, line) is not None: | ||
property = re.search("HOST: Property '%s' = '([\w\d _]+)'"% property_name, line) | ||
if property is not None and len(property.groups()) == 1: | ||
result = property.groups()[0] | ||
return result | ||
|
||
# Detect from where input should be taken, if no --digest switch is specified | ||
# normal test execution can be performed | ||
p = Popen(cmd, | ||
stdout=PIPE, | ||
stderr=STDOUT) | ||
except OSError as e: | ||
print "mbedgt: run_command(%s) ret= %d failed: "% (cmd, int(ret)), str(e), e.child_traceback | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's the diff between using print and gt_logger in this module? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When in exception we would like to use minimal IO to error message. Just to be clear, Just to print less noise during exception handle trace IMHO. |
||
return iter(p.stdout.readline, b'') | ||
|
||
if verbose: | ||
gt_logger.gt_log("selecting test case observer...") | ||
|
@@ -240,124 +132,57 @@ def get_auto_property_value(property_name, line): | |
# Select who will digest test case serial port data | ||
if digest_source == 'stdin': | ||
# When we want to scan stdin for test results | ||
obs = StdInObserver() | ||
raise NotImplementedError | ||
elif digest_source is not None: | ||
# When we want to open file to scan for test results | ||
obs = FileObserver(digest_source) | ||
else: | ||
# Command executing CLI for host test supervisor (in detect-mode) | ||
cmd = ["mbedhtrun", | ||
'-d', disk, | ||
'-p', port, | ||
'-f', '"%s"'% image_path, | ||
] | ||
raise NotImplementedError | ||
|
||
# Command executing CLI for host test supervisor (in detect-mode) | ||
cmd = ["mbedhtrun", | ||
'-d', disk, | ||
'-p', port, | ||
'-f', '"%s"'% image_path, | ||
] | ||
|
||
# Add extra parameters to host_test | ||
if program_cycle_s is not None: | ||
cmd += ["-C", str(program_cycle_s)] | ||
if copy_method is not None: | ||
cmd += ["-c", copy_method] | ||
if micro is not None: | ||
cmd += ["-m", micro] | ||
if reset is not None: | ||
cmd += ["-r", reset] | ||
if reset_tout is not None: | ||
cmd += ["-R", str(reset_tout)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is reset_tout = reset_timeout ? I would guess as it would be integer thus casting to str(). Then above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if json_test_cfg is not None: | ||
cmd += ["--test-cfg", '"%s"' % str(json_test_cfg)] | ||
if run_app is not None: | ||
cmd += ["--run"] # -f stores binary name! | ||
if enum_host_tests_path: | ||
cmd += ["-e", '"%s"'% enum_host_tests_path] | ||
|
||
# Add extra parameters to host_test | ||
if program_cycle_s is not None: | ||
cmd += ["-C", str(program_cycle_s)] | ||
if copy_method is not None: | ||
cmd += ["-c", copy_method] | ||
if micro is not None: | ||
cmd += ["-m", micro] | ||
if reset is not None: | ||
cmd += ["-r", reset] | ||
if reset_tout is not None: | ||
cmd += ["-R", str(reset_tout)] | ||
if json_test_cfg is not None: | ||
cmd += ["--test-cfg", '"%s"' % str(json_test_cfg)] | ||
if run_app is not None: | ||
cmd += ["--run"] # -f stores binary name! | ||
if enum_host_tests_path: | ||
cmd += ["-e", '"%s"'% enum_host_tests_path] | ||
|
||
if verbose: | ||
gt_logger.gt_log_tab("calling mbedhtrun: %s"% " ".join(cmd)) | ||
gt_logger.gt_log("mbed-host-test-runner: started") | ||
proc = Popen(cmd, stdin=PIPE, stdout=PIPE) | ||
obs = ProcessObserver(proc) | ||
if verbose: | ||
gt_logger.gt_log_tab("calling mbedhtrun: %s"% " ".join(cmd)) | ||
gt_logger.gt_log("mbed-host-test-runner: started") | ||
|
||
result = None | ||
update_once_flag = {} # Stores flags checking if some auto-parameter was already set | ||
unknown_property_count = 0 | ||
total_duration = 20 # This for flashing, reset and other serial port operations | ||
line = '' | ||
output = [] | ||
htrun_output = '' | ||
start_time = time() | ||
while (time() - start_time) < (total_duration): | ||
try: | ||
c = get_char_from_queue(obs) | ||
except Exception as e: | ||
output.append('get_char_from_queue(obs): %s'% str(e)) | ||
break | ||
if c: | ||
if verbose: | ||
sys.stdout.write(c) | ||
c = filter_queue_char(c) | ||
output.append(c) | ||
# Give the mbed under test a way to communicate the end of the test | ||
if c in ['\n', '\r']: | ||
|
||
# Check for unknown property prints | ||
# If there are too many we will stop test execution and assume test is not ported | ||
if "HOST: Unknown property" in line: | ||
unknown_property_count += 1 | ||
if unknown_property_count >= max_failed_properties: | ||
output.append('{{error}}') | ||
break | ||
|
||
# Checking for auto-detection information from the test about MUT reset moment | ||
if 'reset_target' not in update_once_flag and "HOST: Reset target..." in line: | ||
# We will update this marker only once to prevent multiple time resets | ||
update_once_flag['reset_target'] = True | ||
start_time = time() | ||
total_duration = duration # After reset we gonna use real test case duration | ||
|
||
# Checking for auto-detection information from the test about timeout | ||
auto_timeout_val = get_auto_property_value('timeout', line) | ||
if 'timeout' not in update_once_flag and auto_timeout_val is not None: | ||
# We will update this marker only once to prevent multiple time resets | ||
update_once_flag['timeout'] = True | ||
total_duration = int(auto_timeout_val) | ||
|
||
# Detect mbed assert: | ||
if 'mbed assertation failed: ' in line: | ||
output.append('{{mbed_assert}}') | ||
break | ||
|
||
# Check for test end. Only a '{{end}}' in the start of line indicates a test end. | ||
# A sub string '{{end}}' may also appear in an error message. | ||
if re.search('^\{\{end\}\}', line, re.I): | ||
break | ||
line = '' | ||
else: | ||
line += c | ||
else: | ||
result = TEST_RESULT_TIMEOUT | ||
|
||
if not '{end}' in line: | ||
output.append('{{end}}') | ||
|
||
c = get_char_from_queue(obs) | ||
|
||
if c: | ||
if verbose: | ||
sys.stdout.write(c) | ||
c = filter_queue_char(c) | ||
output.append(c) | ||
|
||
# Stop test process | ||
obs.stop() | ||
for line in run_command(cmd): | ||
htrun_output += line | ||
sys.stdout.write(line) | ||
sys.stdout.flush() | ||
|
||
end_time = time() | ||
testcase_duration = end_time - start_time # Test case duration from reset to {end} | ||
|
||
result = get_test_result(htrun_output) | ||
|
||
if verbose: | ||
gt_logger.gt_log("mbed-host-test-runner: stopped") | ||
if not result: | ||
result = get_test_result(output) | ||
if verbose: | ||
gt_logger.gt_log("mbed-host-test-runner: returned '%s'"% result) | ||
return (result, "".join(output), testcase_duration, duration) | ||
return (result, "".join(htrun_output), testcase_duration, duration) | ||
|
||
def run_cli_command(cmd, shell=True, verbose=False): | ||
"""! Runs command from command line | ||
|
@@ -373,11 +198,13 @@ def run_cli_command(cmd, shell=True, verbose=False): | |
result = False | ||
if verbose: | ||
print "mbedgt: [ret=%d] Command: %s"% (int(ret), cmd) | ||
except Exception as e: | ||
except OSError as e: | ||
result = False | ||
if verbose: | ||
print "mbedgt: [ret=%d] Command: %s"% (int(ret), cmd) | ||
print str(e) | ||
print "mbedgt: traceback..." | ||
print e.child_traceback | ||
return (result, ret) | ||
|
||
def run_cli_process(cmd): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still valid to use capital letter for this local variable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will change it to lower case !