|
1 | 1 | #!/usr/bin/env python3
|
2 |
| - |
3 | 2 | import re
|
4 |
| -import sys |
5 | 3 | import time
|
| 4 | +from datetime import datetime |
6 | 5 | from typing import Optional
|
7 | 6 |
|
8 | 7 | from flipper.app import App
|
|
11 | 10 |
|
12 | 11 |
|
13 | 12 | class Main(App):
|
14 |
| - # this is basic use without sub-commands, simply to reboot flipper / power it off, not meant as a full CLI wrapper |
| 13 | + def __init__(self, no_exit=False): |
| 14 | + super().__init__(no_exit) |
| 15 | + self.test_results = None |
| 16 | + |
15 | 17 | def init(self):
|
16 | 18 | self.parser.add_argument("-p", "--port", help="CDC Port", default="auto")
|
17 | 19 | self.parser.add_argument(
|
@@ -67,64 +69,108 @@ def run_units(self):
|
67 | 69 | self.logger.info("Running unit tests")
|
68 | 70 | flipper.send("unit_tests" + "\r")
|
69 | 71 | self.logger.info("Waiting for unit tests to complete")
|
70 |
| - data = flipper.read.until(">: ") |
71 |
| - self.logger.info("Parsing result") |
72 |
| - |
73 |
| - lines = data.decode().split("\r\n") |
74 |
| - |
75 |
| - tests_re = r"Failed tests: \d{0,}" |
76 |
| - time_re = r"Consumed: \d{0,}" |
77 |
| - leak_re = r"Leaked: \d{0,}" |
78 |
| - status_re = r"Status: \w{3,}" |
79 |
| - |
80 |
| - tests_pattern = re.compile(tests_re) |
81 |
| - time_pattern = re.compile(time_re) |
82 |
| - leak_pattern = re.compile(leak_re) |
83 |
| - status_pattern = re.compile(status_re) |
84 | 72 |
|
85 | 73 | tests, elapsed_time, leak, status = None, None, None, None
|
86 | 74 | total = 0
|
87 |
| - |
88 |
| - for line in lines: |
89 |
| - self.logger.info(line) |
90 |
| - if "()" in line: |
91 |
| - total += 1 |
92 |
| - |
93 |
| - if not tests: |
94 |
| - tests = re.match(tests_pattern, line) |
95 |
| - if not elapsed_time: |
96 |
| - elapsed_time = re.match(time_pattern, line) |
97 |
| - if not leak: |
98 |
| - leak = re.match(leak_pattern, line) |
99 |
| - if not status: |
100 |
| - status = re.match(status_pattern, line) |
101 |
| - |
102 |
| - if None in (tests, elapsed_time, leak, status): |
103 |
| - self.logger.error( |
104 |
| - f"Failed to parse output: {tests} {elapsed_time} {leak} {status}" |
| 75 | + all_required_found = False |
| 76 | + |
| 77 | + full_output = [] |
| 78 | + |
| 79 | + tests_pattern = re.compile(r"Failed tests: \d{0,}") |
| 80 | + time_pattern = re.compile(r"Consumed: \d{0,}") |
| 81 | + leak_pattern = re.compile(r"Leaked: \d{0,}") |
| 82 | + status_pattern = re.compile(r"Status: \w{3,}") |
| 83 | + |
| 84 | + try: |
| 85 | + while not all_required_found: |
| 86 | + try: |
| 87 | + line = flipper.read.until("\r\n", cut_eol=True).decode() |
| 88 | + self.logger.info(line) |
| 89 | + if "command not found," in line: |
| 90 | + self.logger.error(f"Command not found: {line}") |
| 91 | + return 1 |
| 92 | + |
| 93 | + if "()" in line: |
| 94 | + total += 1 |
| 95 | + self.logger.debug(f"Test completed: {line}") |
| 96 | + |
| 97 | + if not tests: |
| 98 | + tests = tests_pattern.match(line) |
| 99 | + if not elapsed_time: |
| 100 | + elapsed_time = time_pattern.match(line) |
| 101 | + if not leak: |
| 102 | + leak = leak_pattern.match(line) |
| 103 | + if not status: |
| 104 | + status = status_pattern.match(line) |
| 105 | + |
| 106 | + pattern = re.compile( |
| 107 | + r"(\[-]|\[\\]|\[\|]|\[/-]|\[[^\]]*\]|\x1b\[\d+D)" |
| 108 | + ) |
| 109 | + line_to_append = pattern.sub("", line) |
| 110 | + pattern = re.compile(r"\[3D[^\]]*") |
| 111 | + line_to_append = pattern.sub("", line_to_append) |
| 112 | + line_to_append = f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S,%f')} {line_to_append}" |
| 113 | + |
| 114 | + full_output.append(line_to_append) |
| 115 | + |
| 116 | + if tests and elapsed_time and leak and status: |
| 117 | + all_required_found = True |
| 118 | + try: |
| 119 | + remaining = flipper.read.until(">: ", cut_eol=True).decode() |
| 120 | + if remaining.strip(): |
| 121 | + full_output.append(remaining) |
| 122 | + except: |
| 123 | + pass |
| 124 | + break |
| 125 | + |
| 126 | + except Exception as e: |
| 127 | + self.logger.error(f"Error reading output: {e}") |
| 128 | + raise |
| 129 | + |
| 130 | + if None in (tests, elapsed_time, leak, status): |
| 131 | + raise RuntimeError( |
| 132 | + f"Failed to parse output: {tests} {elapsed_time} {leak} {status}" |
| 133 | + ) |
| 134 | + |
| 135 | + leak = int(re.findall(r"[- ]\d+", leak.group(0))[0]) |
| 136 | + status = re.findall(r"\w+", status.group(0))[1] |
| 137 | + tests = int(re.findall(r"\d+", tests.group(0))[0]) |
| 138 | + elapsed_time = int(re.findall(r"\d+", elapsed_time.group(0))[0]) |
| 139 | + |
| 140 | + test_results = { |
| 141 | + "full_output": "\n".join(full_output), |
| 142 | + "total_tests": total, |
| 143 | + "failed_tests": tests, |
| 144 | + "elapsed_time_ms": elapsed_time, |
| 145 | + "memory_leak_bytes": leak, |
| 146 | + "status": status, |
| 147 | + } |
| 148 | + |
| 149 | + self.test_results = test_results |
| 150 | + |
| 151 | + output_file = "unit_tests_output.txt" |
| 152 | + with open(output_file, "w") as f: |
| 153 | + f.write(test_results["full_output"]) |
| 154 | + |
| 155 | + print( |
| 156 | + f"::notice:: Total tests: {total} Failed tests: {tests} Status: {status} Elapsed time: {elapsed_time / 1000} s Memory leak: {leak} bytes" |
105 | 157 | )
|
106 |
| - sys.exit(1) |
107 |
| - |
108 |
| - leak = int(re.findall(r"[- ]\d+", leak.group(0))[0]) |
109 |
| - status = re.findall(r"\w+", status.group(0))[1] |
110 |
| - tests = int(re.findall(r"\d+", tests.group(0))[0]) |
111 |
| - elapsed_time = int(re.findall(r"\d+", elapsed_time.group(0))[0]) |
112 |
| - |
113 |
| - if tests > 0 or status != "PASSED": |
114 |
| - self.logger.error(f"Got {tests} failed tests.") |
115 |
| - self.logger.error(f"Leaked (not failing on this stat): {leak}") |
116 |
| - self.logger.error(f"Status: {status}") |
117 |
| - self.logger.error(f"Time: {elapsed_time/1000} seconds") |
118 |
| - flipper.stop() |
119 |
| - return 1 |
120 | 158 |
|
121 |
| - self.logger.info(f"Leaked (not failing on this stat): {leak}") |
122 |
| - self.logger.info( |
123 |
| - f"Tests ran successfully! Time elapsed {elapsed_time/1000} seconds. Passed {total} tests." |
124 |
| - ) |
| 159 | + if tests > 0 or status != "PASSED": |
| 160 | + self.logger.error(f"Got {tests} failed tests.") |
| 161 | + self.logger.error(f"Leaked (not failing on this stat): {leak}") |
| 162 | + self.logger.error(f"Status: {status}") |
| 163 | + self.logger.error(f"Time: {elapsed_time / 1000} seconds") |
| 164 | + return 1 |
125 | 165 |
|
126 |
| - flipper.stop() |
127 |
| - return 0 |
| 166 | + self.logger.info(f"Leaked (not failing on this stat): {leak}") |
| 167 | + self.logger.info( |
| 168 | + f"Tests ran successfully! Time elapsed {elapsed_time / 1000} seconds. Passed {total} tests." |
| 169 | + ) |
| 170 | + return 0 |
| 171 | + |
| 172 | + finally: |
| 173 | + flipper.stop() |
128 | 174 |
|
129 | 175 |
|
130 | 176 | if __name__ == "__main__":
|
|
0 commit comments