From 3570777a0d0dc0ec128a2b611667cf8e4cd4cbda Mon Sep 17 00:00:00 2001 From: Eric Ratliff Date: Mon, 23 Feb 2026 12:42:43 -0600 Subject: [PATCH] Looking for dependencies on test for a better UX --- README.md | 23 ++++- build.rs | 38 +++++++++ tests/script_execution_test.rs | 150 +++++++++------------------------ 3 files changed, 98 insertions(+), 113 deletions(-) create mode 100644 build.rs diff --git a/README.md b/README.md index acd0bd5..03b6c95 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..462449c --- /dev/null +++ b/build.rs @@ -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"); + } +} \ No newline at end of file diff --git a/tests/script_execution_test.rs b/tests/script_execution_test.rs index 62f8d1b..761d669 100644 --- a/tests/script_execution_test.rs +++ b/tests/script_execution_test.rs @@ -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)]