Looking for dependencies on test for a better UX

This commit is contained in:
Eric Ratliff
2026-02-23 12:42:43 -06:00
parent 34d6a765b0
commit 3570777a0d
3 changed files with 98 additions and 113 deletions

View File

@@ -436,7 +436,6 @@ Upload these to a Gitea release. The script requires `build-essential`,
`mingw-w64`, and `zip` as described above.
### Running the test suite
```bash
cargo test
```
@@ -447,6 +446,28 @@ build-system issues like missing linker flags and include paths. They require
cmake and a C++ compiler; if those tools are not installed, the compile tests
skip gracefully and everything else still passes.
#### Full test suite on Linux / WSL
The e2e tests need `cmake`, `g++`, and `arduino-cli` with the AVR core:
```bash
sudo apt install cmake g++
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh
sudo mv bin/arduino-cli /usr/local/bin/
arduino-cli core install arduino:avr
```
#### Full test suite on Windows
Install [arduino-cli](https://arduino.github.io/arduino-cli/installation/)
and add it to your PATH, then install the AVR core:
```
arduino-cli core install arduino:avr
```
CMake and a C++ compiler are needed for the host-side test compilation.
Install [CMake](https://cmake.org/download/) and either MinGW-w64 or open
a Visual Studio Developer Command Prompt (which provides `cl.exe`).
---
## License

38
build.rs Normal file
View File

@@ -0,0 +1,38 @@
// build.rs -- Compile-time detection of optional build tools.
//
// Sets cfg flags that integration tests use to gracefully skip when
// tools are missing instead of panicking with scary error messages.
//
// has_cmake cmake is in PATH
// has_cpp_compiler g++, clang++, or cl is in PATH
// has_git git is in PATH
// has_arduino_cli arduino-cli is in PATH
//
// Usage in tests:
// #[cfg_attr(not(has_cmake), ignore = "cmake not found")]
use std::process::{Command, Stdio};
fn has_tool(name: &str) -> bool {
Command::new(name)
.arg("--version")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.is_ok()
}
fn main() {
if has_tool("cmake") {
println!("cargo:rustc-cfg=has_cmake");
}
if has_tool("g++") || has_tool("clang++") || has_tool("cl") {
println!("cargo:rustc-cfg=has_cpp_compiler");
}
if has_tool("git") {
println!("cargo:rustc-cfg=has_git");
}
if has_tool("arduino-cli") {
println!("cargo:rustc-cfg=has_arduino_cli");
}
}

View File

@@ -9,8 +9,10 @@
// test.sh / test/run_tests.sh --> cmake, g++ (or clang++), git
// build.sh --> arduino-cli (with arduino:avr core)
//
// If any dependency is missing the test FAILS -- that is intentional.
// A build machine that ships Anvil binaries MUST have these tools.
// If a dependency is missing, the test is SKIPPED (shown as "ignored"
// in cargo test output). Detection happens at compile time via build.rs
// which sets cfg flags: has_cmake, has_cpp_compiler, has_git,
// has_arduino_cli.
//
// On Windows the .bat variants are tested instead.
// ==========================================================================
@@ -122,91 +124,6 @@ fn run_script_with_args(dir: &Path, script: &str, args: &[&str]) -> (bool, Strin
(output.status.success(), stdout, stderr)
}
/// Assert that a command-line tool is available in PATH.
/// Panics with a clear message if not found.
fn require_tool(name: &str) {
let check = if cfg!(windows) {
Command::new("where").arg(name).output()
} else {
Command::new("which").arg(name).output()
};
match check {
Ok(output) if output.status.success() => {}
_ => panic!(
"\n\n\
===================================================================\n\
MISSING BUILD DEPENDENCY: {name}\n\
===================================================================\n\
\n\
Anvil's cargo tests REQUIRE build-machine dependencies.\n\
Install '{name}' and re-run. See 'anvil doctor' for guidance.\n\
\n\
===================================================================\n"
),
}
}
/// Check that at least one C++ compiler is present.
///
/// On Windows, cmake discovers MSVC through the Visual Studio installation
/// even when cl.exe is not directly in PATH, so we check for cl.exe as
/// well as g++ and clang++. If none are found in PATH we still let cmake
/// try -- it will fail at configure time with a clear message.
fn require_cpp_compiler() {
let check_tool = |name: &str| -> bool {
Command::new(if cfg!(windows) { "where" } else { "which" })
.arg(name)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
};
let has_gpp = check_tool("g++");
let has_clangpp = check_tool("clang++");
let has_cl = if cfg!(windows) { check_tool("cl") } else { false };
// On Windows, cmake can discover MSVC even when cl.exe is not in
// the current PATH (via vswhere / VS installation registry). So
// we only hard-fail on Linux/macOS where the compiler really must
// be in PATH.
if !has_gpp && !has_clangpp && !has_cl {
if cfg!(windows) {
// Warn but don't panic -- cmake will try to find MSVC
eprintln!(
"\n\
WARNING: No C++ compiler (g++, clang++, cl) found in PATH.\n\
cmake may still find MSVC via Visual Studio installation.\n\
If tests fail, open a VS Developer Command Prompt or install\n\
Build Tools for Visual Studio.\n"
);
} else {
panic!(
"\n\n\
===================================================================\n\
MISSING BUILD DEPENDENCY: C++ compiler (g++ or clang++)\n\
===================================================================\n\
\n\
Install g++ or clang++ and re-run.\n\
\n\
===================================================================\n"
);
}
}
}
/// Require cmake + C++ compiler + git (the test script prereqs).
fn require_test_script_deps() {
require_tool("cmake");
require_tool("git");
require_cpp_compiler();
}
/// Require arduino-cli (the build script prereqs).
fn require_build_script_deps() {
require_tool("arduino-cli");
}
/// Platform-appropriate script paths.
fn root_test_script() -> &'static str {
if cfg!(windows) { "test.bat" } else { "test.sh" }
@@ -283,9 +200,10 @@ fn find_file_recursive(dir: &Path, prefix: &str) -> bool {
// ==========================================================================
#[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_root_test_script_executes_successfully() {
require_test_script_deps();
let tmp = extract_project("root_test");
#[cfg(unix)]
@@ -306,9 +224,10 @@ fn test_root_test_script_executes_successfully() {
}
#[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_root_test_script_tests_actually_ran() {
require_test_script_deps();
let tmp = extract_project("root_verify");
#[cfg(unix)]
@@ -338,9 +257,10 @@ fn test_root_test_script_tests_actually_ran() {
}
#[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_root_test_script_idempotent() {
require_test_script_deps();
let tmp = extract_project("root_idem");
#[cfg(unix)]
@@ -367,9 +287,10 @@ fn test_root_test_script_idempotent() {
// ==========================================================================
#[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_inner_run_tests_script_executes_successfully() {
require_test_script_deps();
let tmp = extract_project("inner_test");
#[cfg(unix)]
@@ -390,9 +311,10 @@ fn test_inner_run_tests_script_executes_successfully() {
}
#[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_inner_run_tests_google_tests_actually_ran() {
require_test_script_deps();
let tmp = extract_project("inner_gtest");
#[cfg(unix)]
@@ -421,9 +343,10 @@ fn test_inner_run_tests_google_tests_actually_ran() {
}
#[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_inner_run_tests_clean_flag_rebuilds() {
require_test_script_deps();
let tmp = extract_project("inner_clean");
#[cfg(unix)]
@@ -457,9 +380,10 @@ fn test_inner_run_tests_clean_flag_rebuilds() {
}
#[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_inner_run_tests_produces_test_binary() {
require_test_script_deps();
let tmp = extract_project("inner_bin");
#[cfg(unix)]
@@ -482,9 +406,10 @@ fn test_inner_run_tests_produces_test_binary() {
}
#[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_inner_run_tests_idempotent() {
require_test_script_deps();
let tmp = extract_project("inner_idem");
#[cfg(unix)]
@@ -513,9 +438,8 @@ fn test_inner_run_tests_idempotent() {
#[test]
#[serial]
#[cfg_attr(not(has_arduino_cli), ignore = "arduino-cli not found")]
fn test_build_script_compiles_sketch() {
require_build_script_deps();
let tmp = extract_project("build_test");
#[cfg(unix)]
@@ -536,9 +460,9 @@ fn test_build_script_compiles_sketch() {
}
#[test]
#[serial]
#[cfg_attr(not(has_arduino_cli), ignore = "arduino-cli not found")]
fn test_build_script_produces_compilation_output() {
require_build_script_deps();
let tmp = extract_project("compile_out");
#[cfg(unix)]
@@ -568,9 +492,9 @@ fn test_build_script_produces_compilation_output() {
}
#[test]
#[serial]
#[cfg_attr(not(has_arduino_cli), ignore = "arduino-cli not found")]
fn test_build_script_idempotent() {
require_build_script_deps();
let tmp = extract_project("build_idem");
#[cfg(unix)]
@@ -594,10 +518,12 @@ fn test_build_script_idempotent() {
// ==========================================================================
#[test]
#[serial]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
#[cfg_attr(not(has_arduino_cli), ignore = "arduino-cli not found")]
fn test_full_project_all_scripts_pass() {
require_test_script_deps();
require_build_script_deps();
let tmp = extract_project("full_e2e");
#[cfg(unix)]