mirror of
https://github.com/paparazzi/paparazzi.git
synced 2026-03-24 07:53:10 +08:00
309 lines
11 KiB
Python
Executable File
309 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (C) 2021 Fabien Bonneval <fabien.bonneval@enac.fr>
|
|
# Gautier Hattenberger <gautier.hattenberger@enac.fr>
|
|
#
|
|
# This file is part of paparazzi.
|
|
#
|
|
# paparazzi is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2, or (at your option)
|
|
# any later version.
|
|
#
|
|
# paparazzi is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with paparazzi; see the file COPYING. If not, see
|
|
# <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
from lxml import etree
|
|
from typing import List, Optional, Tuple, Dict
|
|
import re
|
|
from os import path, getenv
|
|
import os
|
|
import argparse
|
|
import subprocess
|
|
import shlex
|
|
|
|
import TAP
|
|
|
|
PPRZ_HOME = getenv("PAPARAZZI_HOME", path.normpath(path.join(path.dirname(path.abspath(__file__)), '../../')))
|
|
|
|
ARCHS = ["chibios", "linux", "sim", "stm32"]
|
|
|
|
ALT_DIRS = ["../../var/share", "../ext"]
|
|
|
|
GCC_PARAMS: List[str] = ["-c", "-o", "/dev/null", "-W", "-Wall"]
|
|
OTHER_PARAMS: List[str] = [
|
|
"-I./", "-I../include",
|
|
"-I../../var/include",
|
|
"-I../../tests/modules",
|
|
"-Imodules", "-DPERIODIC_TELEMETRY",
|
|
"-DPPRZ_TESTS", "-DDOWNLINK",
|
|
"-DBOARD_CONFIG=\"../../tests/modules/dummy.h\""]
|
|
|
|
|
|
class Test:
|
|
def __init__(self, tst, files, files_arch, includes, test_name, test_id):
|
|
self.configure_regex = re.compile(r"(\$\([a-zA-Z_][a-zA-Z_0-9]*\))")
|
|
self.files = files # type: List[str]
|
|
self.files_arch = files_arch # type: List[str]
|
|
self.includes = includes # type: List[str]
|
|
self.firmware = None # type: Optional[str]
|
|
self.archs = [] # type: List[str]
|
|
self.defines = [] # type: List[Tuple[str,str, str]]
|
|
self.configures = {} # type: Dict[str:str]
|
|
self.shells = [] # type: List[str]
|
|
self.test_name = test_name
|
|
self.test_id = test_id
|
|
|
|
self.parse(tst)
|
|
|
|
def parse(self, tst):
|
|
self.firmware = tst.attrib.get("firmware")
|
|
archs = tst.attrib.get("arch")
|
|
if archs is None:
|
|
# self.archs = ARCHS
|
|
self.archs = []
|
|
else:
|
|
self.archs = archs.split("|")
|
|
if len(self.archs) == 1:
|
|
if self.archs[0] == "":
|
|
self.archs = []
|
|
elif self.archs[0][0] == "!":
|
|
excluded = self.archs[0][1:]
|
|
self.archs = ARCHS
|
|
self.archs.remove(excluded)
|
|
print(f"archs : {self.archs}")
|
|
|
|
for define in tst.findall("define"):
|
|
def_name = define.attrib["name"]
|
|
def_val = define.attrib.get("value")
|
|
def_type = define.attrib.get("type")
|
|
self.defines.append((def_name, def_val, def_type))
|
|
|
|
for configure in tst.findall("configure"):
|
|
conf_name = configure.attrib["name"]
|
|
conf_value = configure.attrib["value"]
|
|
self.configures[conf_name] = conf_value
|
|
|
|
for include in tst.findall("include"):
|
|
self.includes.append(include.attrib["name"])
|
|
|
|
for shell in tst.findall("shell"):
|
|
self.shells.append(shell.attrib["cmd"])
|
|
|
|
if self.firmware is not None:
|
|
self.configures["SRC_FIRMWARE"] = f"firmwares/{self.firmware}"
|
|
self.includes.append(f"firmwares/{self.firmware}")
|
|
|
|
def substitute_configures(self):
|
|
def substitute(string: str):
|
|
if string is None:
|
|
return None
|
|
for m in re.findall(self.configure_regex, string):
|
|
if m[2:-1] in self.configures.keys():
|
|
string = string.replace(m, self.configures[m[2:-1]])
|
|
else:
|
|
env_val = getenv(m[2:-1])
|
|
if env_val is not None:
|
|
string = string.replace(m, env_val)
|
|
return string
|
|
self.files = map(substitute, self.files)
|
|
self.files_arch = map(substitute, self.files_arch)
|
|
self.defines = map(lambda x: (substitute(x[0]), substitute(x[1]), x[2]), self.defines)
|
|
self.includes = map(substitute, self.includes)
|
|
|
|
def make_commands(self) -> List[List[str]]:
|
|
self.substitute_configures()
|
|
self.files = list(self.files)
|
|
self.files_arch = list(self.files_arch)
|
|
|
|
gcc = "gcc"
|
|
for f in self.files + self.files_arch:
|
|
if re.match("cpp$", f) is not None:
|
|
gcc = "g++"
|
|
|
|
defines = list(map(self.define_str, self.defines))
|
|
includes = list((map(self.include_str, self.includes)))
|
|
module_id = [f"-DMODULE_{self.test_name.upper()}_ID=1"]
|
|
shells = []
|
|
for shell in self.shells:
|
|
shell_args = shlex.split(shell)
|
|
p = subprocess.run(shell_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
|
|
if p.returncode == 0:
|
|
shells += shlex.split(p.stdout)
|
|
else:
|
|
print(p.stderr)
|
|
return [["exit", "1"]]
|
|
|
|
cmds = []
|
|
for file in self.files:
|
|
# build with the "test" arch.
|
|
arch_include = f"-I../../tests/modules/test_arch"
|
|
arch_modules_include = f"-I../../tests/modules/test_arch/modules"
|
|
cmd_args = [gcc] + GCC_PARAMS + [file] + OTHER_PARAMS + module_id + defines + includes + [arch_include, arch_modules_include] + shells
|
|
cmds.append(cmd_args)
|
|
|
|
for file in self.files_arch:
|
|
for arch in self.archs:
|
|
file_path = f"arch/{arch}/{file}"
|
|
arch_include = f"-Iarch/{arch}"
|
|
cmd_args = [gcc] + GCC_PARAMS + [file_path] + OTHER_PARAMS + module_id + defines + includes + [arch_include] + shells
|
|
# TODO: uncomment to build <file_arch/> files.
|
|
# cmds.append(cmd_args)
|
|
return cmds
|
|
|
|
def run(self):
|
|
cmds = self.make_commands()
|
|
diagnostic = []
|
|
returncode = 0
|
|
for cmd_args in cmds:
|
|
p = subprocess.run(cmd_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
|
|
if p.stderr != "":
|
|
# diagnostic.append(str(cmd_args))
|
|
diagnostic.append(p.stderr)
|
|
|
|
if p.returncode != 0:
|
|
returncode = 1
|
|
return returncode, f'{self.test_name}_{self.test_id}', diagnostic
|
|
|
|
|
|
@staticmethod
|
|
def define_str(define: Tuple[str, Optional[str], Optional[str]]) -> str:
|
|
name, value, _type = define
|
|
if value is not None:
|
|
if _type is not None and _type == "string":
|
|
return f'-D{name}="{value}"'
|
|
else:
|
|
return f"-D{name}={value}"
|
|
else:
|
|
return f"-D{name}"
|
|
|
|
@staticmethod
|
|
def include_str(name: str) -> str:
|
|
return f"-I{name}"
|
|
|
|
def __repr__(self):
|
|
res = ""
|
|
res += "Test{\n"
|
|
res += "\tdefines: "
|
|
for define in self.defines:
|
|
res += define[0] + "=" + (define[1] if define[1] is not None else "None") + " "
|
|
res += "\n\tconfigures: "
|
|
for key, value in self.configures.items():
|
|
res += key + "=" + (value if value is not None else "None") + " "
|
|
res += "\n\tincludes: " + " ".join(self.includes)
|
|
res += "\n\tfiles: "
|
|
res += " ".join(self.files)
|
|
res += "}"
|
|
return res
|
|
|
|
|
|
class Module:
|
|
|
|
def __init__(self, filename):
|
|
m_tree = etree.parse(filename)
|
|
mod_elt = m_tree.getroot()
|
|
self.name = mod_elt.attrib["name"]
|
|
# if dir is not specified, the name of the module is used as default directory name
|
|
self.dir = mod_elt.attrib.get("dir", self.name)
|
|
self.tests = []
|
|
|
|
for mkf in mod_elt.findall("makefile"):
|
|
files = self.get_files(mkf)
|
|
files_arch = self.get_files_arch(mkf)
|
|
includes = self.get_includes(mkf)
|
|
for i, tst in enumerate(mkf.findall("test")):
|
|
test = Test(tst, files, files_arch, includes, f"{self.name}", i)
|
|
self.tests.append(test)
|
|
|
|
def get_files(self, mkf):
|
|
files = []
|
|
for file in mkf.findall("file"):
|
|
name = file.attrib["name"]
|
|
dir = file.attrib.get("dir")
|
|
if dir is None:
|
|
file_path = "/".join(["modules", self.dir, name])
|
|
else:
|
|
file_path = "/".join([dir, name])
|
|
if not path.exists(file_path):
|
|
for alt_dir in ALT_DIRS:
|
|
alt_path = f"{alt_dir}/{file_path}"
|
|
if path.exists(alt_path):
|
|
file_path = alt_path
|
|
break
|
|
|
|
files.append(file_path)
|
|
return files
|
|
|
|
def get_files_arch(self, mkf):
|
|
files = []
|
|
for file in mkf.findall("file_arch"):
|
|
name = file.attrib["name"]
|
|
dir = file.attrib.get("dir")
|
|
if dir is None:
|
|
file_path = "/".join(["modules", self.dir, name])
|
|
else:
|
|
file_path = "/".join([dir, name])
|
|
files.append(file_path)
|
|
return files
|
|
|
|
def get_includes(self, mkf):
|
|
includes = []
|
|
for include in mkf.findall("include"):
|
|
name = include.attrib["name"]
|
|
includes.append(name)
|
|
return includes
|
|
|
|
|
|
|
|
def get_modules():
|
|
files = os.listdir(PPRZ_HOME+"/conf/modules")
|
|
|
|
def is_xml(filename):
|
|
filename, extension = os.path.splitext(filename)
|
|
return extension == ".xml"
|
|
|
|
files = filter(is_xml, files)
|
|
return files
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="test modules")
|
|
parser.add_argument('--file', '-f', default=None, help="module to be tested")
|
|
parser.add_argument('-w', action='store_false')
|
|
args = parser.parse_args()
|
|
|
|
def is_xml(filename):
|
|
filename, extension = os.path.splitext(filename)
|
|
return extension == ".xml"
|
|
|
|
if args.file is None:
|
|
files = os.listdir(PPRZ_HOME+"/conf/modules")
|
|
files = map(lambda name: PPRZ_HOME+"/conf/modules/"+name, files)
|
|
else:
|
|
files = [args.file]
|
|
|
|
files = filter(is_xml, files)
|
|
|
|
all_tests = []
|
|
os.chdir(f"{PPRZ_HOME}/sw/airborne")
|
|
for f in files:
|
|
mod = Module(f)
|
|
all_tests += mod.tests
|
|
|
|
ok = TAP.Builder.create(len(all_tests)).ok
|
|
for test in all_tests:
|
|
returncode, comment, diagnotics = test.run()
|
|
ok(not returncode, comment)
|
|
if args.w or returncode:
|
|
for d in diagnotics:
|
|
lines = d.split("\n")
|
|
for line in lines:
|
|
print(f"# {line}")
|