mirror of https://github.com/ARMmbed/mbed-os.git
932 lines
27 KiB
Python
Executable File
932 lines
27 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
"""
|
|
Copyright (c) 2020 ARM Limited
|
|
|
|
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 argparse
|
|
import json
|
|
import pathlib
|
|
import hashlib
|
|
import re
|
|
import sys
|
|
from tabulate import tabulate
|
|
from itertools import chain
|
|
from enum import Enum
|
|
|
|
|
|
class ReturnCode(Enum):
|
|
"""Return codes."""
|
|
|
|
SUCCESS = 0
|
|
ERROR = 1
|
|
INVALID_OPTIONS = 2
|
|
|
|
|
|
class TestCaseError(Exception):
|
|
"""An exception for test case failure."""
|
|
|
|
|
|
class ArgumentParserWithDefaultHelp(argparse.ArgumentParser):
|
|
"""Subclass that always shows the help message on invalid arguments."""
|
|
|
|
def error(self, message):
|
|
"""Error handler."""
|
|
sys.stderr.write("error: {}\n".format(message))
|
|
self.print_help()
|
|
raise SystemExit(ReturnCode.INVALID_OPTIONS.value)
|
|
|
|
def find_target_by_path(target_path):
|
|
"""Find a target by path."""
|
|
mbed_os_root = pathlib.Path(__file__).absolute().parents[3]
|
|
|
|
targets = dict()
|
|
|
|
with open(target_path) as pin_names_file:
|
|
pin_names_file_content = pin_names_file.read()
|
|
|
|
target_list_match = re.search(
|
|
"\/* MBED TARGET LIST: ([0-9A-Z_,* \n]+)*\/",
|
|
pin_names_file_content
|
|
)
|
|
|
|
target_list = []
|
|
if target_list_match:
|
|
target_list = list(
|
|
re.findall(
|
|
r"([0-9A-Z_]{3,})",
|
|
target_list_match.group(1),
|
|
re.MULTILINE,
|
|
)
|
|
)
|
|
|
|
if not target_list:
|
|
print("WARNING: MBED TARGET LIST marker invalid or not found in file " + target_path)
|
|
print("Target could not be determined. Only the generic test suite will run. You can manually specify additional suites.")
|
|
|
|
with (
|
|
mbed_os_root.joinpath("targets", "targets.json")
|
|
).open() as targets_json_file:
|
|
target_data = json.load(targets_json_file)
|
|
|
|
# find target in targets.json
|
|
for target in target_data:
|
|
if "public" in target_data[target]:
|
|
if not target_data[target]["public"]:
|
|
continue
|
|
|
|
if target in target_list:
|
|
targets[target] = target_path
|
|
|
|
if len(targets) == 0:
|
|
targets[target_path] = target_path
|
|
|
|
return targets
|
|
|
|
def find_target_by_name(target_name=""):
|
|
"""Find a target by name."""
|
|
mbed_os_root = pathlib.Path(__file__).absolute().parents[3]
|
|
|
|
targets = dict()
|
|
|
|
for f in mbed_os_root.joinpath('targets').rglob("PinNames.h"):
|
|
with open(f) as pin_names_file:
|
|
pin_names_file_content = pin_names_file.read()
|
|
|
|
target_list_match = re.search(
|
|
"\/* MBED TARGET LIST: ([0-9A-Z_,* \n]+)*\/",
|
|
pin_names_file_content
|
|
)
|
|
|
|
target_list = []
|
|
if target_list_match:
|
|
target_list = list(
|
|
re.findall(
|
|
r"([0-9A-Z_]{3,})",
|
|
target_list_match.group(1),
|
|
re.MULTILINE,
|
|
)
|
|
)
|
|
|
|
if target_name:
|
|
if target_name in target_list:
|
|
targets[target_name] = f
|
|
break
|
|
else:
|
|
for target in target_list:
|
|
targets[target] = f
|
|
|
|
return targets
|
|
|
|
|
|
def check_markers(test_mode=False):
|
|
"""Validate markers in PinNames.h files"""
|
|
mbed_os_root = pathlib.Path(__file__).absolute().parents[3]
|
|
|
|
errors = []
|
|
|
|
with (
|
|
mbed_os_root.joinpath("targets", "targets.json")
|
|
).open() as targets_json_file:
|
|
targets_json = json.load(targets_json_file)
|
|
|
|
if test_mode:
|
|
search_dir = pathlib.Path(__file__).parent.joinpath('test_files').absolute()
|
|
else:
|
|
search_dir = mbed_os_root.joinpath('targets')
|
|
|
|
for f in search_dir.rglob("PinNames.h"):
|
|
with open(f) as pin_names_file:
|
|
pin_names_file_content = pin_names_file.read()
|
|
|
|
target_list_match = re.search(
|
|
"\/* MBED TARGET LIST: ([0-9A-Z_,* \n]+)*\/",
|
|
pin_names_file_content
|
|
)
|
|
|
|
marker_target_list = []
|
|
if target_list_match:
|
|
marker_target_list = list(
|
|
re.findall(
|
|
r"([0-9A-Z_]{3,})",
|
|
target_list_match.group(1),
|
|
re.MULTILINE,
|
|
)
|
|
)
|
|
|
|
if not marker_target_list:
|
|
print("WARNING: MBED TARGET LIST marker invalid or not found in file " + str(f))
|
|
errors.append({ "file": str(f), "error": "marker invalid or not found"})
|
|
continue
|
|
|
|
for target in marker_target_list:
|
|
target_is_valid = False
|
|
if target in targets_json:
|
|
target_is_valid = True
|
|
if "public" in targets_json[target]:
|
|
if targets_json[target]["public"] == False:
|
|
target_is_valid = False
|
|
if not target_is_valid:
|
|
print("WARNING: MBED TARGET LIST in file " + str(f) + " includes target '" + target + "' which doesn't exist in targets.json or is not public")
|
|
errors.append({ "file": str(f), "error": "target not found"})
|
|
|
|
return errors
|
|
|
|
|
|
def check_duplicate_pinnames_files(test_mode=False):
|
|
"""Check for duplicate PinNames.h files"""
|
|
mbed_os_root = pathlib.Path(__file__).absolute().parents[3]
|
|
|
|
errors = []
|
|
|
|
file_hash_dict = dict()
|
|
|
|
if test_mode:
|
|
search_dir = pathlib.Path(__file__).parent.joinpath('test_files').absolute()
|
|
else:
|
|
search_dir = mbed_os_root.joinpath('targets')
|
|
|
|
for f in search_dir.rglob("PinNames.h"):
|
|
with open(f) as pin_names_file:
|
|
pin_names_file_content = pin_names_file.read()
|
|
file_hash_dict[str(f)] = hashlib.md5(pin_names_file_content.encode('utf-8')).hexdigest()
|
|
|
|
rev_dict = {}
|
|
for key, value in file_hash_dict.items():
|
|
rev_dict.setdefault(value, set()).add(key)
|
|
duplicates = [key for key, values in rev_dict.items()
|
|
if len(values) > 1]
|
|
|
|
for duplicate in duplicates:
|
|
print("WARNING: Duplicate files")
|
|
for file_path, file_hash in file_hash_dict.items():
|
|
if file_hash == duplicate:
|
|
errors.append({ "file": file_path, "error": "duplicate file"})
|
|
print("\t" + file_path)
|
|
|
|
return errors
|
|
|
|
def check_duplicate_markers(test_mode=False):
|
|
"""Check target markers in PinNames.h files for duplicates."""
|
|
mbed_os_root = pathlib.Path(__file__).absolute().parents[3]
|
|
|
|
errors = []
|
|
|
|
markers = dict()
|
|
|
|
if test_mode:
|
|
search_dir = pathlib.Path(__file__).parent.joinpath('test_files').absolute()
|
|
else:
|
|
search_dir = mbed_os_root.joinpath('targets')
|
|
|
|
for f in search_dir.rglob("PinNames.h"):
|
|
with open(f) as pin_names_file:
|
|
pin_names_file_content = pin_names_file.read()
|
|
|
|
target_list_match = re.search(
|
|
"\/* MBED TARGET LIST: ([0-9A-Z_,* \n]+)*\/",
|
|
pin_names_file_content
|
|
)
|
|
|
|
marker_target_list = []
|
|
if target_list_match:
|
|
marker_target_list = list(
|
|
re.findall(
|
|
r"([0-9A-Z_]{3,})",
|
|
target_list_match.group(1),
|
|
re.MULTILINE,
|
|
)
|
|
)
|
|
|
|
for target in marker_target_list:
|
|
if target in markers:
|
|
print("WARNING: target duplicate in " + str(f) + ", " + target + " first listed in " + markers[target])
|
|
errors.append({ "file": str(f), "error": "duplicate marker"})
|
|
else:
|
|
markers[target] = str(f)
|
|
|
|
return errors
|
|
|
|
|
|
def target_has_form_factor(target_name, form_factor):
|
|
"""Check if the target has the Arduino form factor."""
|
|
mbed_os_root = pathlib.Path(__file__).absolute().parents[3]
|
|
|
|
with (
|
|
mbed_os_root.joinpath("targets", "targets.json")
|
|
).open() as targets_json_file:
|
|
target_data = json.load(targets_json_file)
|
|
|
|
if target_name in target_data:
|
|
if "supported_form_factors" in target_data[target_name]:
|
|
form_factors = target_data[target_name]["supported_form_factors"]
|
|
if form_factor in form_factors:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def pin_name_to_dict(pin_name_file_content):
|
|
pin_name_enum_dict = dict()
|
|
pin_name_enum_match = re.search(
|
|
"typedef enum {\n([^}]*)\n} PinName;", pin_name_file_content
|
|
)
|
|
if pin_name_enum_match:
|
|
pin_name_enum_body = pin_name_enum_match.group(1)
|
|
pin_name_enum_dict = dict(
|
|
re.findall(
|
|
r"^\s*([a-zA-Z0-9_]+)\s*=\s*([a-zA-Z0-9_]+)",
|
|
pin_name_enum_body,
|
|
re.MULTILINE,
|
|
)
|
|
)
|
|
|
|
pin_name_define_dict = dict(
|
|
re.findall(
|
|
r"^#define\s+([a-zA-Z0-9_]+)\s+([a-zA-Z0-9_]+)",
|
|
pin_name_file_content,
|
|
re.MULTILINE,
|
|
)
|
|
)
|
|
|
|
return {**pin_name_enum_dict, **pin_name_define_dict}
|
|
|
|
|
|
def identity_assignment_check(pin_name_dict):
|
|
invalid_items = []
|
|
for key, val in pin_name_dict.items():
|
|
if val == key:
|
|
message = "cannot assign value to itself"
|
|
invalid_items.append({"key": key, "val": val, "message": message})
|
|
return invalid_items
|
|
|
|
|
|
def nc_assignment_check(pin_name_dict):
|
|
invalid_items = []
|
|
for key, val in pin_name_dict.items():
|
|
if re.match(r"^((LED|BUTTON)\d*|CONSOLE_TX|CONSOLE_RX|USBTX|USBRX)$", key):
|
|
if val == "NC":
|
|
message = "cannot be NC"
|
|
invalid_items.append(
|
|
{"key": key, "val": val, "message": message}
|
|
)
|
|
return invalid_items
|
|
|
|
|
|
def duplicate_assignment_check(pin_name_dict):
|
|
used_pins = []
|
|
used_pins_friendly = []
|
|
invalid_items = []
|
|
|
|
for key, val in pin_name_dict.items():
|
|
if re.match(r"^((LED|BUTTON)\d*|CONSOLE_TX|CONSOLE_RX|USBTX|USBRX)$", key):
|
|
if val == "NC":
|
|
continue
|
|
# resolve to literal
|
|
realval = val
|
|
depth = 0
|
|
while not re.match(
|
|
"(0x[0-9a-fA-F]+|[1-9][0-9]*|0[1-7][0-7]+|0b[01]+)[uUlL]{0,2}",
|
|
realval,
|
|
):
|
|
try:
|
|
realval = pin_name_dict[realval]
|
|
depth += 1
|
|
except KeyError:
|
|
break
|
|
if depth > 10:
|
|
break
|
|
|
|
if realval in used_pins:
|
|
message = (
|
|
"already assigned to "
|
|
+ used_pins_friendly[used_pins.index(realval)]
|
|
)
|
|
invalid_items.append(
|
|
{"key": key, "val": val, "message": message}
|
|
)
|
|
continue
|
|
|
|
used_pins.append(realval)
|
|
used_pins_friendly.append(key + " = " + val)
|
|
return invalid_items
|
|
|
|
|
|
def arduino_duplicate_assignment_check(pin_name_dict):
|
|
used_pins = []
|
|
used_pins_friendly = []
|
|
invalid_items = []
|
|
|
|
for key, val in pin_name_dict.items():
|
|
if re.match(r"^ARDUINO_UNO_[AD]\d+$", key):
|
|
if val == "NC":
|
|
continue
|
|
# resolve to literal
|
|
realval = val
|
|
depth = 0
|
|
while not re.match(
|
|
"(0x[0-9a-fA-F]+|[1-9][0-9]*|0[1-7][0-7]+|0b[01]+)[uUlL]{0,2}",
|
|
realval,
|
|
):
|
|
try:
|
|
realval = pin_name_dict[realval]
|
|
depth += 1
|
|
except KeyError:
|
|
break
|
|
if depth > 10:
|
|
break
|
|
|
|
if realval in used_pins:
|
|
message = (
|
|
"already assigned to "
|
|
+ used_pins_friendly[used_pins.index(realval)]
|
|
)
|
|
invalid_items.append(
|
|
{"key": key, "val": val, "message": message}
|
|
)
|
|
continue
|
|
|
|
used_pins.append(realval)
|
|
used_pins_friendly.append(key + " = " + val)
|
|
return invalid_items
|
|
|
|
|
|
def arduino_existence_check(pin_name_dict):
|
|
analog_pins = (f"ARDUINO_UNO_A{i}" for i in range(6))
|
|
digital_pins = (f"ARDUINO_UNO_D{i}" for i in range(16))
|
|
|
|
return [
|
|
{"key": pin, "val": "", "message": pin + " not defined"}
|
|
for pin in chain(analog_pins, digital_pins)
|
|
if pin not in pin_name_dict
|
|
]
|
|
|
|
|
|
def arduino_nc_assignment_check(pin_name_dict):
|
|
invalid_items = []
|
|
for key, val in pin_name_dict.items():
|
|
if re.match(r"^ARDUINO_UNO_[AD]\d+$", key):
|
|
if val == "NC":
|
|
message = "cannot be NC"
|
|
invalid_items.append(
|
|
{"key": key, "val": val, "message": message}
|
|
)
|
|
return invalid_items
|
|
|
|
|
|
def legacy_assignment_check(pin_name_content):
|
|
invalid_items = []
|
|
legacy_assignments = dict(
|
|
re.findall(
|
|
r"^\s*((?:LED|BUTTON)\d*)\s*=\s*([a-zA-Z0-9_]+)",
|
|
pin_name_content,
|
|
re.MULTILINE,
|
|
)
|
|
)
|
|
for key, val in legacy_assignments.items():
|
|
message = "legacy assignment; LEDs and BUTTONs must be #define'd"
|
|
invalid_items.append({"key": key, "val": val, "message": message})
|
|
return invalid_items
|
|
|
|
|
|
def legacy_alias_check(pin_name_content):
|
|
invalid_items = []
|
|
legacy_assignments = dict(
|
|
re.findall(
|
|
r"^\s*((?:SPI|I2C)_\w*)\s*=\s*([a-zA-Z0-9_]+)",
|
|
pin_name_content,
|
|
re.MULTILINE,
|
|
)
|
|
)
|
|
for key, val in legacy_assignments.items():
|
|
message = "legacy assignment; SPI_xxx and I2C_xxx must be #define'd"
|
|
invalid_items.append({"key": key, "val": val, "message": message})
|
|
return invalid_items
|
|
|
|
|
|
def legacy_uart_check(pin_name_dict):
|
|
invalid_items = []
|
|
if "CONSOLE_TX" not in pin_name_dict or "CONSOLE_RX" not in pin_name_dict:
|
|
message = "CONSOLE_TX or CONSOLE_RX are not defined; USBTX and USBRX are deprecated"
|
|
invalid_items.append({"key": "", "val": "", "message": message})
|
|
return invalid_items
|
|
|
|
|
|
def legacy_arduino_uno_check(arduino_form_factor):
|
|
invalid_items = []
|
|
if arduino_form_factor == True:
|
|
message = "ARDUINO form factor is deprecated, should be replaced by ARDUINO_UNO"
|
|
invalid_items.append({"key": "", "val": "", "message": message})
|
|
return invalid_items
|
|
|
|
def print_summary(report):
|
|
targets = set([case["platform_name"] for case in report])
|
|
|
|
table = []
|
|
for target in targets:
|
|
error_count = 0
|
|
|
|
for case in report:
|
|
if (
|
|
case["platform_name"] == target
|
|
and case["result"] == "FAILED"
|
|
):
|
|
error_count += 1
|
|
|
|
table.append(
|
|
[target, "FAILED" if error_count else "PASSED", error_count]
|
|
)
|
|
|
|
return tabulate(
|
|
table,
|
|
headers=["Platform name", "Result", "Error count"],
|
|
tablefmt="grid",
|
|
)
|
|
|
|
|
|
def print_suite_summary(report):
|
|
targets = set([case["platform_name"] for case in report])
|
|
|
|
table = []
|
|
for target in targets:
|
|
suites = set([
|
|
case["suite_name"]
|
|
for case in report
|
|
if case["platform_name"] == target
|
|
])
|
|
|
|
for suite in suites:
|
|
result = "PASSED"
|
|
error_count = 0
|
|
for case in report:
|
|
if case["platform_name"] != target:
|
|
continue
|
|
if case["suite_name"] != suite:
|
|
continue
|
|
if case["result"] == "FAILED":
|
|
result = "FAILED"
|
|
error_count += 1
|
|
table.append([target, suite, result, error_count])
|
|
|
|
return tabulate(
|
|
table,
|
|
headers=["Platform name", "Test suite", "Result", "Error count"],
|
|
tablefmt="grid",
|
|
)
|
|
|
|
|
|
def print_report(report, print_error_detail, tablefmt="grid"):
|
|
table = []
|
|
for case in report:
|
|
errors_str = []
|
|
for error in case["errors"]:
|
|
errors_str.append("\n")
|
|
if error["key"]:
|
|
errors_str.append(error["key"])
|
|
if error["val"]:
|
|
errors_str.append(" = ")
|
|
errors_str.append(error["val"])
|
|
if error["message"]:
|
|
errors_str.append(" <-- ")
|
|
errors_str.append(error["message"])
|
|
|
|
if not errors_str:
|
|
errors_str = "None"
|
|
|
|
if print_error_detail:
|
|
table.append(
|
|
(
|
|
case["platform_name"],
|
|
case["suite_name"],
|
|
case["case_name"],
|
|
case["result"],
|
|
len(case["errors"]),
|
|
"None" if not errors_str else "".join(errors_str).lstrip(),
|
|
)
|
|
)
|
|
else:
|
|
table.append(
|
|
(
|
|
case["platform_name"],
|
|
case["suite_name"],
|
|
case["case_name"],
|
|
case["result"],
|
|
len(case["errors"]),
|
|
)
|
|
)
|
|
|
|
return tabulate(
|
|
table,
|
|
headers=[
|
|
"Platform name",
|
|
"Test suite",
|
|
"Test case",
|
|
"Result",
|
|
"Error count",
|
|
"Errors",
|
|
],
|
|
tablefmt=tablefmt,
|
|
)
|
|
|
|
|
|
def print_pretty_html_report(report):
|
|
output = []
|
|
output.append("<html><head><style>table, td, tr { border: 2px solid black; border-collapse: collapse; padding: 5px; font-family: Helvetica, serif; }</style></head>")
|
|
output.append('<body><p><button onclick=\'e=document.getElementsByTagName("details");for(var i=0;i<e.length;i++){e[i].setAttribute("open","true")};\'>Expand all errors</button></p><table>')
|
|
output.append("<tr><th>Platform name</th><th>Test suite</th><th>Test case</th><th>Result</th><th>Error count</th><th>Errors</th></tr>")
|
|
|
|
for case in report:
|
|
output.append("<tr>")
|
|
|
|
if case["errors"]:
|
|
error_details = ["<details><summary>View errors</summary><table>"]
|
|
for error in case["errors"]:
|
|
error_details.append("<tr>")
|
|
if error["key"]:
|
|
error_details.append("<td>")
|
|
error_details.append(error["key"])
|
|
error_details.append("</td>")
|
|
if error["val"]:
|
|
error_details.append("<td>")
|
|
error_details.append(error["val"])
|
|
error_details.append("</td>")
|
|
if error["message"]:
|
|
error_details.append("<td>")
|
|
error_details.append(error["message"])
|
|
error_details.append("</td>")
|
|
error_details.append("</tr>")
|
|
error_details.append("</table></details>")
|
|
else:
|
|
error_details = []
|
|
|
|
output.append("<td>")
|
|
output.append(case["platform_name"])
|
|
output.append("</td>")
|
|
output.append("<td>")
|
|
output.append(case["suite_name"])
|
|
output.append("</td>")
|
|
output.append("<td>")
|
|
output.append(case["case_name"])
|
|
output.append("</td>")
|
|
|
|
if case["result"] == "PASSED":
|
|
color = "green"
|
|
count_color = "black"
|
|
else:
|
|
color = "red"
|
|
count_color = "red"
|
|
|
|
output.append("<td style='color:")
|
|
output.append(color)
|
|
output.append("'>")
|
|
output.append(case["result"])
|
|
output.append("</td>")
|
|
|
|
output.append("<td style='color:")
|
|
output.append(count_color)
|
|
output.append("'>")
|
|
output.append(str(len(case["errors"])))
|
|
output.append("</td>")
|
|
|
|
output.append("<td>")
|
|
output.extend(error_details)
|
|
output.append("</td>")
|
|
|
|
output.append("</tr>")
|
|
|
|
output.append("</table></body></table>")
|
|
|
|
return "".join(output)
|
|
|
|
|
|
def has_passed_all_test_cases(report):
|
|
"""Check that all test cases passed."""
|
|
for case in report:
|
|
if case["result"] == "FAILED":
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
test_cases = [
|
|
{
|
|
"suite_name": "generic",
|
|
"case_name": "identity",
|
|
"case_function": identity_assignment_check,
|
|
"case_input": "dict",
|
|
},
|
|
{
|
|
"suite_name": "generic",
|
|
"case_name": "nc",
|
|
"case_function": nc_assignment_check,
|
|
"case_input": "dict",
|
|
},
|
|
{
|
|
"suite_name": "generic",
|
|
"case_name": "duplicate",
|
|
"case_function": duplicate_assignment_check,
|
|
"case_input": "dict",
|
|
},
|
|
{
|
|
"suite_name": "generic",
|
|
"case_name": "legacy",
|
|
"case_function": legacy_assignment_check,
|
|
"case_input": "content",
|
|
},
|
|
{
|
|
"suite_name": "generic",
|
|
"case_name": "alias",
|
|
"case_function": legacy_alias_check,
|
|
"case_input": "content",
|
|
},
|
|
{
|
|
"suite_name": "generic",
|
|
"case_name": "uart",
|
|
"case_function": legacy_uart_check,
|
|
"case_input": "content",
|
|
},
|
|
{
|
|
"suite_name": "generic",
|
|
"case_name": "arduino_formfactor",
|
|
"case_function": legacy_arduino_uno_check,
|
|
"case_input": "arduino_form_factor",
|
|
},
|
|
{
|
|
"suite_name": "arduino_uno",
|
|
"case_name": "duplicate",
|
|
"case_function": arduino_duplicate_assignment_check,
|
|
"case_input": "dict",
|
|
},
|
|
{
|
|
"suite_name": "arduino_uno",
|
|
"case_name": "existence",
|
|
"case_function": arduino_existence_check,
|
|
"case_input": "dict",
|
|
},
|
|
{
|
|
"suite_name": "arduino_uno",
|
|
"case_name": "nc",
|
|
"case_function": arduino_nc_assignment_check,
|
|
"case_input": "dict",
|
|
},
|
|
]
|
|
|
|
|
|
def validate_pin_names(args):
|
|
"""Entry point for validating the Pin names."""
|
|
suites = []
|
|
if args.suite_names:
|
|
suites = args.suite_names.split(",")
|
|
|
|
targets = dict()
|
|
if args.paths:
|
|
paths = args.paths.split(",")
|
|
for path in paths:
|
|
targets = {**targets, **find_target_by_path(path)}
|
|
elif args.targets:
|
|
target_names = args.targets.split(",")
|
|
for target_name in target_names:
|
|
targets = {**targets, **find_target_by_name(target_name)}
|
|
elif args.all:
|
|
targets = find_target_by_name()
|
|
elif args.check_markers:
|
|
check_markers()
|
|
check_duplicate_pinnames_files()
|
|
check_duplicate_markers()
|
|
return
|
|
|
|
report = []
|
|
for target, path in targets.items():
|
|
pin_name_content = open(path).read()
|
|
|
|
pin_name_dict = pin_name_to_dict(pin_name_content)
|
|
|
|
arduino_uno_support = target_has_form_factor(target, "ARDUINO_UNO")
|
|
|
|
arduino_support = target_has_form_factor(target, "ARDUINO")
|
|
|
|
for case in test_cases:
|
|
if suites:
|
|
if case["suite_name"] not in suites:
|
|
continue
|
|
else:
|
|
if not arduino_uno_support and case["suite_name"] == "arduino_uno":
|
|
continue
|
|
if not arduino_uno_support and not arduino_support and case["case_name"] == "arduino_formfactor":
|
|
continue
|
|
|
|
|
|
if case["case_input"] == "dict":
|
|
case_input = pin_name_dict
|
|
elif case["case_input"] == "content":
|
|
case_input = pin_name_content
|
|
elif case["case_input"] == "arduino_form_factor":
|
|
case_input = arduino_support
|
|
case_output = case["case_function"](case_input)
|
|
|
|
case_result = "FAILED" if case_output else "PASSED"
|
|
|
|
platform_name = target
|
|
if not args.full_name and args.output_format == "prettytext":
|
|
if len(platform_name) > 40:
|
|
platform_name = "..." + platform_name[-40:]
|
|
|
|
report.append(
|
|
{
|
|
"platform_name": platform_name,
|
|
"suite_name": case["suite_name"],
|
|
"case_name": case["case_name"],
|
|
"result": case_result,
|
|
"errors": case_output,
|
|
}
|
|
)
|
|
|
|
generate_output(report, args.output_format, args.verbose, args.output_file)
|
|
|
|
if not has_passed_all_test_cases(report):
|
|
raise TestCaseError("One or more test cases failed")
|
|
|
|
|
|
def generate_output(report, output_format, verbosity, output_file):
|
|
"""Generate the output."""
|
|
if output_format == "json":
|
|
output = json.dumps(report)
|
|
elif output_format == "html":
|
|
output = print_pretty_html_report(report)
|
|
else:
|
|
if verbosity == 0:
|
|
output = print_summary(report)
|
|
elif verbosity == 1:
|
|
output = print_suite_summary(report)
|
|
elif verbosity == 2:
|
|
output = print_report(report, False)
|
|
elif verbosity > 2:
|
|
output = print_report(report, True)
|
|
|
|
if output_file:
|
|
with open(output_file, "w") as out_file:
|
|
out_file.write(output)
|
|
else:
|
|
print(output)
|
|
|
|
|
|
def parse_args():
|
|
"""Parse the command line args."""
|
|
parser = ArgumentParserWithDefaultHelp(
|
|
description="Pin names validation",
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
)
|
|
|
|
parser.add_argument(
|
|
"-n",
|
|
"--suite_names",
|
|
help="Run specific test suite. Use comma to seperate multiple suites.",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"-v",
|
|
"--verbose",
|
|
action="count",
|
|
default=0,
|
|
help=(
|
|
"Verbosity of the report (none to -vvv)."
|
|
" Only applies for 'prettytext' output format."
|
|
),
|
|
)
|
|
|
|
parser.add_argument(
|
|
"-f",
|
|
"--full_name",
|
|
action="store_true",
|
|
help=(
|
|
"Don't truncate long platform names in"
|
|
" human-readable output formats"
|
|
),
|
|
)
|
|
|
|
parser.add_argument(
|
|
"-o",
|
|
"--output_format",
|
|
default="prettytext",
|
|
help="Set the output format: prettytext (default), json or html",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"-w",
|
|
"--output_file",
|
|
help="File to write output to, instead of printing to stdout",
|
|
)
|
|
|
|
group = parser.add_mutually_exclusive_group(required=True)
|
|
|
|
group.add_argument(
|
|
"-t",
|
|
"--targets",
|
|
help=(
|
|
"Target name. Use comma to seperate multiple targets."
|
|
"THIS FEATURE IS EXPERIMENTAL!"
|
|
),
|
|
)
|
|
|
|
group.add_argument(
|
|
"-p",
|
|
"--paths",
|
|
help="Path to PinNames.h file. Use comma to seperate multiple paths.",
|
|
)
|
|
|
|
group.add_argument(
|
|
"-a", "--all", action="store_true", help="Run tests on all targets."
|
|
)
|
|
|
|
group.add_argument(
|
|
"-m",
|
|
"--check-markers",
|
|
action="store_true",
|
|
help="Check all PinNames.h for the MBED TARGET LIST marker."
|
|
)
|
|
|
|
parser.set_defaults(func=validate_pin_names)
|
|
|
|
args_namespace = parser.parse_args()
|
|
|
|
# We want to fail gracefully, with a consistent
|
|
# help message, in the no argument case.
|
|
# So here's an obligatory hasattr hack.
|
|
if not hasattr(args_namespace, "func"):
|
|
parser.error("No arguments given!")
|
|
else:
|
|
return args_namespace
|
|
|
|
|
|
def run_pin_validate():
|
|
"""Application main algorithm."""
|
|
args = parse_args()
|
|
args.func(args)
|
|
|
|
|
|
def _main():
|
|
"""Run pinvalidate."""
|
|
try:
|
|
run_pin_validate()
|
|
except Exception as error:
|
|
print(error)
|
|
return ReturnCode.ERROR.value
|
|
else:
|
|
return ReturnCode.SUCCESS.value
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(_main())
|