build: add directory-oriented make workflows
Host SITL smoke / Deterministic Gazebo command path (push) Has been cancelled
Host SITL smoke / Deterministic Gazebo command path (push) Has been cancelled
This commit is contained in:
1 parent
4bc98adbc6
commit
7dc3ccb619
40 files changed
+1367
-278
No files matched your search
@@ -166,18 +166,22 @@ def validation_errors(layout, section_list, load_segments):
|
||||
)
|
||||
|
||||
for index, segment in enumerate(load_segments):
|
||||
if segment.memory_size and region_for_span(
|
||||
segment.virtual_address, segment.memory_size, layout
|
||||
) is None:
|
||||
if (
|
||||
segment.memory_size
|
||||
and region_for_span(segment.virtual_address, segment.memory_size, layout)
|
||||
is None
|
||||
):
|
||||
errors.append(
|
||||
f"PT_LOAD[{index}] virtual span "
|
||||
f"0x{segment.virtual_address:08X}.."
|
||||
f"0x{segment.virtual_address + segment.memory_size:08X} "
|
||||
"is outside declared memory"
|
||||
)
|
||||
if segment.file_size and region_for_span(
|
||||
segment.physical_address, segment.file_size, layout
|
||||
) is None:
|
||||
if (
|
||||
segment.file_size
|
||||
and region_for_span(segment.physical_address, segment.file_size, layout)
|
||||
is None
|
||||
):
|
||||
errors.append(
|
||||
f"PT_LOAD[{index}] physical span "
|
||||
f"0x{segment.physical_address:08X}.."
|
||||
@@ -200,9 +204,7 @@ def flash_operation_section_errors(layout, section_list):
|
||||
return []
|
||||
|
||||
|
||||
INSTRUCTION = re.compile(
|
||||
r"^\s*([0-9A-Fa-f]+):\s+([A-Za-z][A-Za-z0-9.]*)\s*(.*?)\s*$"
|
||||
)
|
||||
INSTRUCTION = re.compile(r"^\s*([0-9A-Fa-f]+):\s+([A-Za-z][A-Za-z0-9.]*)\s*(.*?)\s*$")
|
||||
DIRECT_BRANCH = re.compile(
|
||||
r"^(?:b(?:eq|ne|cs|hs|cc|lo|mi|pl|vs|vc|hi|ls|ge|lt|gt|le)?"
|
||||
r"(?:\.[nw])?|blx?|bx|cbz|cbnz)$"
|
||||
@@ -321,7 +323,9 @@ def main():
|
||||
|
||||
errors = flash_operation_section_errors(layout, section_list)
|
||||
if errors:
|
||||
raise ValueError("Flash-operation placement validation failed:\n " + "\n ".join(errors))
|
||||
raise ValueError(
|
||||
"Flash-operation placement validation failed:\n " + "\n ".join(errors)
|
||||
)
|
||||
data = next(section for section in section_list if section.name == ".data")
|
||||
objdump = llvm_tool("llvm-objdump")
|
||||
errors = sram_control_flow_errors(
|
||||
@@ -337,7 +341,9 @@ def main():
|
||||
),
|
||||
)
|
||||
if errors:
|
||||
raise ValueError("Flash-operation control-flow validation failed:\n " + "\n ".join(errors))
|
||||
raise ValueError(
|
||||
"Flash-operation control-flow validation failed:\n " + "\n ".join(errors)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -lt 1 || $# -gt 2 ]]; then
|
||||
printf 'Usage: %s <firmware.elf> [memory.x]\n' "$0" >&2
|
||||
exit 2
|
||||
printf 'Usage: %s <firmware.elf> [memory.x]\n' "$0" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
readonly input_elf="$1"
|
||||
@@ -15,18 +15,18 @@ readonly host="$(rustc -vV | sed -n 's/^host: //p')"
|
||||
readonly llvm_objcopy="$(rustc --print sysroot)/lib/rustlib/$host/bin/llvm-objcopy"
|
||||
|
||||
if [[ ! -f "$input_elf" ]]; then
|
||||
printf 'firmware ELF does not exist: %s\n' "$input_elf" >&2
|
||||
exit 1
|
||||
printf 'firmware ELF does not exist: %s\n' "$input_elf" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$memory_x" ]]; then
|
||||
printf 'cannot find linker layout: %s\n' "$memory_x" >&2
|
||||
exit 1
|
||||
printf 'cannot find linker layout: %s\n' "$memory_x" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -x "$llvm_objcopy" ]]; then
|
||||
printf 'llvm-objcopy is unavailable; install it with: rustup component add llvm-tools-preview\n' >&2
|
||||
exit 1
|
||||
printf 'llvm-objcopy is unavailable; install it with: rustup component add llvm-tools-preview\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"$script_directory/analyze-fw.py" "$input_elf" "$memory_x"
|
||||
|
||||
@@ -14,18 +14,18 @@
|
||||
set -eu
|
||||
|
||||
if ! cargo llvm-cov --version >/dev/null 2>&1; then
|
||||
echo "cargo-llvm-cov is required; install it with: cargo install cargo-llvm-cov --locked" >&2
|
||||
exit 127
|
||||
echo "cargo-llvm-cov is required; install it with: cargo install cargo-llvm-cov --locked" >&2
|
||||
exit 127
|
||||
fi
|
||||
|
||||
if ! rustup toolchain list | grep -q '^nightly'; then
|
||||
echo "nightly with llvm-tools-preview is required for branch coverage" >&2
|
||||
exit 127
|
||||
echo "nightly with llvm-tools-preview is required for branch coverage" >&2
|
||||
exit 127
|
||||
fi
|
||||
|
||||
if ! python3 --version >/dev/null 2>&1; then
|
||||
echo "python3 is required for strict source-level coverage accounting" >&2
|
||||
exit 127
|
||||
echo "python3 is required for strict source-level coverage accounting" >&2
|
||||
exit 127
|
||||
fi
|
||||
|
||||
python3 scripts/test-summarize-test-coverage.py
|
||||
@@ -46,50 +46,50 @@ coverage_root=$CARGO_TARGET_DIR
|
||||
core_incomplete=0
|
||||
|
||||
report_package() {
|
||||
package=$1
|
||||
source_path=$2
|
||||
shift 2
|
||||
case "$package" in
|
||||
kaptan-math|kaptan-navigation|kaptan-control|kaptan-safety)
|
||||
package_target_dir="$coverage_root/$package"
|
||||
lcov_report="$coverage_root/$package.lcov"
|
||||
json_report="$coverage_root/$package.json"
|
||||
report_format=strict-core
|
||||
;;
|
||||
*)
|
||||
package_target_dir=$coverage_root
|
||||
lcov_report="$coverage_root/$package.lcov"
|
||||
report_format=lcov
|
||||
;;
|
||||
esac
|
||||
package=$1
|
||||
source_path=$2
|
||||
shift 2
|
||||
case "$package" in
|
||||
kaptan-math | kaptan-navigation | kaptan-control | kaptan-safety)
|
||||
package_target_dir="$coverage_root/$package"
|
||||
lcov_report="$coverage_root/$package.lcov"
|
||||
json_report="$coverage_root/$package.json"
|
||||
report_format=strict-core
|
||||
;;
|
||||
*)
|
||||
package_target_dir=$coverage_root
|
||||
lcov_report="$coverage_root/$package.lcov"
|
||||
report_format=lcov
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$report_format" in
|
||||
strict-core)
|
||||
# Keep the strict core isolated, then export both representations
|
||||
# from the same test run. LCOV `DA` defines source lines; JSON
|
||||
# coordinates define branch identities across generic instances.
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov \
|
||||
--branch \
|
||||
--no-report \
|
||||
-p "$package" \
|
||||
"$@"
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov report \
|
||||
--lcov \
|
||||
--output-path "$lcov_report"
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov report \
|
||||
--json \
|
||||
--output-path "$json_report"
|
||||
totals=$(python3 scripts/summarize-test-coverage.py \
|
||||
"$lcov_report" "$json_report" "$source_path")
|
||||
;;
|
||||
lcov)
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov \
|
||||
--branch \
|
||||
--lcov \
|
||||
--output-path "$lcov_report" \
|
||||
-p "$package" \
|
||||
"$@"
|
||||
totals=$(awk -v source_path="$source_path" '
|
||||
case "$report_format" in
|
||||
strict-core)
|
||||
# Keep the strict core isolated, then export both representations
|
||||
# from the same test run. LCOV `DA` defines source lines; JSON
|
||||
# coordinates define branch identities across generic instances.
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov \
|
||||
--branch \
|
||||
--no-report \
|
||||
-p "$package" \
|
||||
"$@"
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov report \
|
||||
--lcov \
|
||||
--output-path "$lcov_report"
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov report \
|
||||
--json \
|
||||
--output-path "$json_report"
|
||||
totals=$(python3 scripts/summarize-test-coverage.py \
|
||||
"$lcov_report" "$json_report" "$source_path")
|
||||
;;
|
||||
lcov)
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov \
|
||||
--branch \
|
||||
--lcov \
|
||||
--output-path "$lcov_report" \
|
||||
-p "$package" \
|
||||
"$@"
|
||||
totals=$(awk -v source_path="$source_path" '
|
||||
/^SF:/ { selected = index($0, source_path) != 0 }
|
||||
selected && /^LF:/ { lines_found += substr($0, 4) }
|
||||
selected && /^LH:/ { lines_hit += substr($0, 4) }
|
||||
@@ -99,18 +99,18 @@ report_package() {
|
||||
printf "%d %d %d %d", lines_hit, lines_found, branches_hit, branches_found
|
||||
}
|
||||
' "$lcov_report")
|
||||
;;
|
||||
esac
|
||||
set -- $totals
|
||||
printf '%-26s lines %s/%s, branches %s/%s\n' "$package" "$1" "$2" "$3" "$4"
|
||||
;;
|
||||
esac
|
||||
set -- $totals
|
||||
printf '%-26s lines %s/%s, branches %s/%s\n' "$package" "$1" "$2" "$3" "$4"
|
||||
|
||||
case "$package" in
|
||||
kaptan-math|kaptan-navigation|kaptan-control|kaptan-safety)
|
||||
if [ "$1" -ne "$2" ] || [ "$3" -ne "$4" ]; then
|
||||
core_incomplete=1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
case "$package" in
|
||||
kaptan-math | kaptan-navigation | kaptan-control | kaptan-safety)
|
||||
if [ "$1" -ne "$2" ] || [ "$3" -ne "$4" ]; then
|
||||
core_incomplete=1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
echo "Host test coverage (reports: $CARGO_TARGET_DIR; strict core unions LCOV DA lines and JSON branches)"
|
||||
@@ -135,6 +135,6 @@ echo "kaptan-geprc-f722-v2 target-only/N/A (run its thumbv7em-none-eabihf c
|
||||
echo "kaptan-kakute-f7-mini-v3 target-only/N/A (run its thumbv7em-none-eabihf check)"
|
||||
|
||||
if [ "$core_incomplete" -ne 0 ]; then
|
||||
echo "Strict core coverage is incomplete; reports above identify each crate." >&2
|
||||
exit 1
|
||||
echo "Strict core coverage is incomplete; reports above identify each crate." >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -34,7 +34,9 @@ class CoverageFormatError(ValueError):
|
||||
"""Coverage records cannot be safely summarized by source location."""
|
||||
|
||||
|
||||
def selected_files(report: dict[object, object], source_path: str) -> Iterable[dict[object, object]]:
|
||||
def selected_files(
|
||||
report: dict[object, object], source_path: str
|
||||
) -> Iterable[dict[object, object]]:
|
||||
"""Yield selected file records after rejecting ambiguous macro expansion."""
|
||||
exports = report.get("data")
|
||||
if not isinstance(exports, list):
|
||||
@@ -78,7 +80,11 @@ def lcov_line_totals(report: str, source_path: str) -> tuple[int, int, set[str]]
|
||||
if record == "end_of_record":
|
||||
filename = None
|
||||
continue
|
||||
if filename is None or source_path not in filename or not record.startswith("DA:"):
|
||||
if (
|
||||
filename is None
|
||||
or source_path not in filename
|
||||
or not record.startswith("DA:")
|
||||
):
|
||||
continue
|
||||
fields = record[3:].split(",", 2)
|
||||
if len(fields) < 2:
|
||||
@@ -87,7 +93,9 @@ def lcov_line_totals(report: str, source_path: str) -> tuple[int, int, set[str]]
|
||||
line = int(fields[0])
|
||||
count = int(fields[1])
|
||||
except ValueError as error:
|
||||
raise CoverageFormatError(f"{filename}: non-integer LCOV DA record") from error
|
||||
raise CoverageFormatError(
|
||||
f"{filename}: non-integer LCOV DA record"
|
||||
) from error
|
||||
if line <= 0 or count < 0:
|
||||
raise CoverageFormatError(f"{filename}: invalid LCOV DA location or count")
|
||||
key = (filename, line)
|
||||
@@ -100,7 +108,9 @@ def lcov_line_totals(report: str, source_path: str) -> tuple[int, int, set[str]]
|
||||
return sum(lines.values()), len(lines), files
|
||||
|
||||
|
||||
def branch_totals(report: dict[object, object], source_path: str) -> tuple[int, int, set[str]]:
|
||||
def branch_totals(
|
||||
report: dict[object, object], source_path: str
|
||||
) -> tuple[int, int, set[str]]:
|
||||
"""Return the union of selected JSON branch outcomes and source files."""
|
||||
branches: dict[tuple[str, int, int, int, int], list[bool]] = {}
|
||||
files: set[str] = set()
|
||||
@@ -142,7 +152,9 @@ def branch_totals(report: dict[object, object], source_path: str) -> tuple[int,
|
||||
or file_id < 0
|
||||
or expanded_file_id < 0
|
||||
):
|
||||
raise CoverageFormatError(f"{filename}: invalid branch location or count")
|
||||
raise CoverageFormatError(
|
||||
f"{filename}: invalid branch location or count"
|
||||
)
|
||||
if file_id != expanded_file_id:
|
||||
raise CoverageFormatError(
|
||||
f"{filename}:{start_line}:{start_column}: macro-expanded branch is present; "
|
||||
@@ -175,9 +187,15 @@ def coverage_totals(
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("lcov_report", type=Path, help="LCOV report from cargo llvm-cov")
|
||||
parser.add_argument("json_report", type=Path, help="LLVM JSON report from cargo llvm-cov")
|
||||
parser.add_argument("source_path", help="workspace-relative path fragment to include")
|
||||
parser.add_argument(
|
||||
"lcov_report", type=Path, help="LCOV report from cargo llvm-cov"
|
||||
)
|
||||
parser.add_argument(
|
||||
"json_report", type=Path, help="LLVM JSON report from cargo llvm-cov"
|
||||
)
|
||||
parser.add_argument(
|
||||
"source_path", help="workspace-relative path fragment to include"
|
||||
)
|
||||
arguments = parser.parse_args()
|
||||
|
||||
try:
|
||||
|
||||
@@ -122,9 +122,7 @@ class FirmwareMemoryValidationTests(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
ANALYZER.region_usage("FLASH", LAYOUT, sections, segments), 0x50
|
||||
)
|
||||
self.assertEqual(
|
||||
ANALYZER.region_usage("RAM", LAYOUT, sections, segments), 0x10
|
||||
)
|
||||
self.assertEqual(ANALYZER.region_usage("RAM", LAYOUT, sections, segments), 0x10)
|
||||
|
||||
def test_rejects_loadable_virtual_and_physical_spans_outside_memory(self):
|
||||
segments = [
|
||||
@@ -138,9 +136,7 @@ class FirmwareMemoryValidationTests(unittest.TestCase):
|
||||
|
||||
def test_requires_one_executable_data_section_in_ram(self):
|
||||
valid = [ANALYZER.Section(".data", 0x20, 0x20000000, True, True)]
|
||||
non_executable = [
|
||||
ANALYZER.Section(".data", 0x20, 0x20000000, True, False)
|
||||
]
|
||||
non_executable = [ANALYZER.Section(".data", 0x20, 0x20000000, True, False)]
|
||||
in_flash = [ANALYZER.Section(".data", 0x20, 0x08000000, True, True)]
|
||||
|
||||
self.assertEqual(ANALYZER.flash_operation_section_errors(LAYOUT, valid), [])
|
||||
|
||||
Reference in new issue
Block a user