Trying to fix tests in project, not done yet
This commit is contained in:
750
tests/test_template_weather.rs
Normal file
750
tests/test_template_weather.rs
Normal file
@@ -0,0 +1,750 @@
|
||||
use anvil::commands;
|
||||
use anvil::library;
|
||||
use anvil::project::config::ProjectConfig;
|
||||
use anvil::templates::{TemplateManager, TemplateContext};
|
||||
use std::fs;
|
||||
use tempfile::TempDir;
|
||||
|
||||
// =========================================================================
|
||||
// Template registry
|
||||
// =========================================================================
|
||||
|
||||
#[test]
|
||||
fn test_list_templates_includes_weather() {
|
||||
let templates = TemplateManager::list_templates();
|
||||
assert!(
|
||||
templates.iter().any(|t| t.name == "weather"),
|
||||
"Weather template should be listed"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_template_exists() {
|
||||
assert!(TemplateManager::template_exists("weather"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_is_not_default() {
|
||||
let templates = TemplateManager::list_templates();
|
||||
let weather = templates.iter().find(|t| t.name == "weather").unwrap();
|
||||
assert!(!weather.is_default);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_lists_tmp36_library() {
|
||||
let templates = TemplateManager::list_templates();
|
||||
let weather = templates.iter().find(|t| t.name == "weather").unwrap();
|
||||
assert!(weather.libraries.contains(&"tmp36".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_lists_analog_capability() {
|
||||
let templates = TemplateManager::list_templates();
|
||||
let weather = templates.iter().find(|t| t.name == "weather").unwrap();
|
||||
assert!(weather.board_capabilities.contains(&"analog".to_string()));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Composed metadata
|
||||
// =========================================================================
|
||||
|
||||
#[test]
|
||||
fn test_weather_composed_meta_exists() {
|
||||
let meta = TemplateManager::composed_meta("weather");
|
||||
assert!(meta.is_some(), "Weather should have composed metadata");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_composed_meta_base_is_basic() {
|
||||
let meta = TemplateManager::composed_meta("weather").unwrap();
|
||||
assert_eq!(meta.base, "basic");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_composed_meta_requires_tmp36() {
|
||||
let meta = TemplateManager::composed_meta("weather").unwrap();
|
||||
assert!(meta.libraries.contains(&"tmp36".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_composed_meta_has_pin_defaults() {
|
||||
let meta = TemplateManager::composed_meta("weather").unwrap();
|
||||
assert!(!meta.pins.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_pins_for_uno() {
|
||||
let meta = TemplateManager::composed_meta("weather").unwrap();
|
||||
let pins = meta.pins_for_board("uno");
|
||||
assert_eq!(pins.len(), 1);
|
||||
assert_eq!(pins[0].name, "tmp36_data");
|
||||
assert_eq!(pins[0].pin, "A0");
|
||||
assert_eq!(pins[0].mode, "analog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_pins_fallback_to_default() {
|
||||
let meta = TemplateManager::composed_meta("weather").unwrap();
|
||||
// "micro" is not explicitly listed, should fall back to "default"
|
||||
let pins = meta.pins_for_board("micro");
|
||||
assert!(!pins.is_empty());
|
||||
assert!(pins.iter().any(|p| p.name == "tmp36_data"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_has_no_composed_meta() {
|
||||
assert!(TemplateManager::composed_meta("basic").is_none());
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Template extraction -- file overlay
|
||||
// =========================================================================
|
||||
|
||||
fn extract_weather(name: &str) -> TempDir {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let ctx = TemplateContext {
|
||||
project_name: name.to_string(),
|
||||
anvil_version: "1.0.0".to_string(),
|
||||
board_name: "uno".to_string(),
|
||||
fqbn: "arduino:avr:uno".to_string(),
|
||||
baud: 115200,
|
||||
};
|
||||
TemplateManager::extract("weather", tmp.path(), &ctx).unwrap();
|
||||
tmp
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_has_basic_scaffold() {
|
||||
let tmp = extract_weather("wx");
|
||||
assert!(tmp.path().join(".anvil.toml").exists());
|
||||
assert!(tmp.path().join("build.sh").exists());
|
||||
assert!(tmp.path().join("build.bat").exists());
|
||||
assert!(tmp.path().join("upload.sh").exists());
|
||||
assert!(tmp.path().join("monitor.sh").exists());
|
||||
assert!(tmp.path().join(".gitignore").exists());
|
||||
assert!(tmp.path().join("lib").join("hal").join("hal.h").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_has_weather_app() {
|
||||
let tmp = extract_weather("wx");
|
||||
let app_path = tmp.path().join("lib").join("app").join("wx_app.h");
|
||||
assert!(app_path.exists(), "Weather app header should exist");
|
||||
let content = fs::read_to_string(&app_path).unwrap();
|
||||
assert!(content.contains("WeatherApp"));
|
||||
assert!(content.contains("TempSensor"));
|
||||
assert!(content.contains("readCelsius"));
|
||||
assert!(content.contains("readFahrenheit"));
|
||||
assert!(content.contains("READ_INTERVAL_MS"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_app_replaces_basic_blink() {
|
||||
let tmp = extract_weather("wx");
|
||||
let app_path = tmp.path().join("lib").join("app").join("wx_app.h");
|
||||
let content = fs::read_to_string(&app_path).unwrap();
|
||||
// Should NOT contain basic template's BlinkApp
|
||||
assert!(
|
||||
!content.contains("BlinkApp"),
|
||||
"Weather app should replace basic blink, not include it"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_has_weather_sketch() {
|
||||
let tmp = extract_weather("wx");
|
||||
let ino_path = tmp.path().join("wx").join("wx.ino");
|
||||
assert!(ino_path.exists());
|
||||
let content = fs::read_to_string(&ino_path).unwrap();
|
||||
assert!(content.contains("Tmp36Analog"));
|
||||
assert!(content.contains("WeatherApp"));
|
||||
assert!(content.contains("hal_arduino.h"));
|
||||
assert!(content.contains("tmp36_analog.h"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_sketch_replaces_basic_sketch() {
|
||||
let tmp = extract_weather("wx");
|
||||
let ino_path = tmp.path().join("wx").join("wx.ino");
|
||||
let content = fs::read_to_string(&ino_path).unwrap();
|
||||
assert!(
|
||||
!content.contains("BlinkApp"),
|
||||
"Weather sketch should replace basic, not extend it"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_has_unit_tests() {
|
||||
let tmp = extract_weather("wx");
|
||||
let test_path = tmp.path().join("test").join("test_unit.cpp");
|
||||
assert!(test_path.exists());
|
||||
let content = fs::read_to_string(&test_path).unwrap();
|
||||
assert!(content.contains("Tmp36Mock"));
|
||||
assert!(content.contains("WeatherUnitTest"));
|
||||
assert!(content.contains("wx_app.h"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_has_system_tests() {
|
||||
let tmp = extract_weather("wx");
|
||||
let test_path = tmp.path().join("test").join("test_system.cpp");
|
||||
assert!(test_path.exists());
|
||||
let content = fs::read_to_string(&test_path).unwrap();
|
||||
assert!(content.contains("Tmp36Sim"));
|
||||
assert!(content.contains("WeatherSystemTest"));
|
||||
assert!(content.contains("SimHal"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_no_template_toml_in_output() {
|
||||
let tmp = extract_weather("wx");
|
||||
assert!(
|
||||
!tmp.path().join("template.toml").exists(),
|
||||
"template.toml should be stripped from output"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_preserves_cmake() {
|
||||
let tmp = extract_weather("wx");
|
||||
let cmake = tmp.path().join("test").join("CMakeLists.txt");
|
||||
assert!(cmake.exists());
|
||||
let content = fs::read_to_string(&cmake).unwrap();
|
||||
assert!(content.contains("wx"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_variable_substitution() {
|
||||
let tmp = extract_weather("my_weather");
|
||||
// App header should use project name
|
||||
let app_path = tmp
|
||||
.path()
|
||||
.join("lib")
|
||||
.join("app")
|
||||
.join("my_weather_app.h");
|
||||
assert!(app_path.exists());
|
||||
|
||||
// Sketch should use project name
|
||||
let ino_path = tmp
|
||||
.path()
|
||||
.join("my_weather")
|
||||
.join("my_weather.ino");
|
||||
assert!(ino_path.exists());
|
||||
|
||||
// Config should use project name
|
||||
let config = fs::read_to_string(tmp.path().join(".anvil.toml")).unwrap();
|
||||
assert!(config.contains("my_weather"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_all_files_ascii() {
|
||||
let tmp = extract_weather("wx");
|
||||
let mut checked = 0;
|
||||
for entry in walkdir(tmp.path()) {
|
||||
let content = fs::read(&entry).unwrap();
|
||||
for (i, &byte) in content.iter().enumerate() {
|
||||
assert!(
|
||||
byte < 128,
|
||||
"Non-ASCII byte {} at offset {} in {}",
|
||||
byte,
|
||||
i,
|
||||
entry.display()
|
||||
);
|
||||
}
|
||||
checked += 1;
|
||||
}
|
||||
assert!(checked > 0, "Should have checked some files");
|
||||
}
|
||||
|
||||
/// Walk all files in a directory recursively.
|
||||
fn walkdir(dir: &std::path::Path) -> Vec<std::path::PathBuf> {
|
||||
let mut files = vec![];
|
||||
if let Ok(entries) = fs::read_dir(dir) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
files.extend(walkdir(&path));
|
||||
} else {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
files
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Library installation via install_library
|
||||
// =========================================================================
|
||||
|
||||
#[test]
|
||||
fn test_install_library_adds_tmp36_to_project() {
|
||||
let tmp = extract_weather("wx");
|
||||
let written =
|
||||
commands::lib::install_library("tmp36", tmp.path()).unwrap();
|
||||
assert!(!written.is_empty(), "Should write at least one file");
|
||||
|
||||
// Verify driver directory created
|
||||
let driver_dir = tmp.path().join("lib").join("drivers").join("tmp36");
|
||||
assert!(driver_dir.exists());
|
||||
assert!(driver_dir.join("tmp36.h").exists());
|
||||
assert!(driver_dir.join("tmp36_analog.h").exists());
|
||||
assert!(driver_dir.join("tmp36_mock.h").exists());
|
||||
assert!(driver_dir.join("tmp36_sim.h").exists());
|
||||
|
||||
// Verify config updated
|
||||
let config = ProjectConfig::load(tmp.path()).unwrap();
|
||||
assert!(config.libraries.contains_key("tmp36"));
|
||||
assert!(config.build.include_dirs.contains(
|
||||
&"lib/drivers/tmp36".to_string()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_library_idempotent() {
|
||||
let tmp = extract_weather("wx");
|
||||
let first =
|
||||
commands::lib::install_library("tmp36", tmp.path()).unwrap();
|
||||
assert!(!first.is_empty());
|
||||
|
||||
// Second call should be a no-op
|
||||
let second =
|
||||
commands::lib::install_library("tmp36", tmp.path()).unwrap();
|
||||
assert!(second.is_empty(), "Second install should skip");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Pin assignment via install_pin_assignment
|
||||
// =========================================================================
|
||||
|
||||
#[test]
|
||||
fn test_install_pin_assignment_creates_assignment() {
|
||||
let tmp = extract_weather("wx");
|
||||
// Need library installed first (for the include_dirs, not strictly
|
||||
// needed for pin assignment but mirrors real flow)
|
||||
commands::lib::install_library("tmp36", tmp.path()).unwrap();
|
||||
|
||||
commands::pin::install_pin_assignment(
|
||||
"tmp36_data",
|
||||
"A0",
|
||||
"analog",
|
||||
"uno",
|
||||
tmp.path(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Verify config has the assignment
|
||||
let config = ProjectConfig::load(tmp.path()).unwrap();
|
||||
let pins = config.pins.get("uno").expect("Should have uno pins");
|
||||
assert!(pins.assignments.contains_key("tmp36_data"));
|
||||
let a = &pins.assignments["tmp36_data"];
|
||||
assert_eq!(a.pin, 14); // A0 = pin 14 on Uno
|
||||
assert_eq!(a.mode, "analog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_pin_assignment_rejects_bad_pin() {
|
||||
let tmp = extract_weather("wx");
|
||||
let result = commands::pin::install_pin_assignment(
|
||||
"tmp36_data",
|
||||
"Z99",
|
||||
"analog",
|
||||
"uno",
|
||||
tmp.path(),
|
||||
);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_pin_assignment_rejects_bad_mode() {
|
||||
let tmp = extract_weather("wx");
|
||||
let result = commands::pin::install_pin_assignment(
|
||||
"tmp36_data",
|
||||
"A0",
|
||||
"bogus",
|
||||
"uno",
|
||||
tmp.path(),
|
||||
);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Full composed template flow (extract + install libs + assign pins)
|
||||
// =========================================================================
|
||||
|
||||
#[test]
|
||||
fn test_weather_full_flow() {
|
||||
let tmp = extract_weather("my_wx");
|
||||
|
||||
// Install libraries (as the new command would)
|
||||
let meta = TemplateManager::composed_meta("weather").unwrap();
|
||||
for lib_name in &meta.libraries {
|
||||
commands::lib::install_library(lib_name, tmp.path()).unwrap();
|
||||
}
|
||||
|
||||
// Assign pins (as the new command would)
|
||||
let pin_defaults = meta.pins_for_board("uno");
|
||||
for pin_def in &pin_defaults {
|
||||
commands::pin::install_pin_assignment(
|
||||
&pin_def.name,
|
||||
&pin_def.pin,
|
||||
&pin_def.mode,
|
||||
"uno",
|
||||
tmp.path(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Verify everything is in place
|
||||
let config = ProjectConfig::load(tmp.path()).unwrap();
|
||||
|
||||
// Library installed
|
||||
assert!(config.libraries.contains_key("tmp36"));
|
||||
assert!(config.build.include_dirs.contains(
|
||||
&"lib/drivers/tmp36".to_string()
|
||||
));
|
||||
|
||||
// Pin assigned
|
||||
let pins = config.pins.get("uno").expect("Should have uno pins");
|
||||
assert!(pins.assignments.contains_key("tmp36_data"));
|
||||
|
||||
// Driver files present
|
||||
assert!(tmp
|
||||
.path()
|
||||
.join("lib")
|
||||
.join("drivers")
|
||||
.join("tmp36")
|
||||
.join("tmp36.h")
|
||||
.exists());
|
||||
|
||||
// App code present (weather-specific, not blink)
|
||||
let app_content = fs::read_to_string(
|
||||
tmp.path().join("lib").join("app").join("my_wx_app.h"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(app_content.contains("WeatherApp"));
|
||||
assert!(!app_content.contains("BlinkApp"));
|
||||
|
||||
// Test files present (weather-specific)
|
||||
let unit_content = fs::read_to_string(
|
||||
tmp.path().join("test").join("test_unit.cpp"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(unit_content.contains("Tmp36Mock"));
|
||||
|
||||
// Sketch wires everything together
|
||||
let ino_content = fs::read_to_string(
|
||||
tmp.path().join("my_wx").join("my_wx.ino"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(ino_content.contains("Tmp36Analog"));
|
||||
assert!(ino_content.contains("WeatherApp"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weather_flow_with_mega() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let ctx = TemplateContext {
|
||||
project_name: "mega_wx".to_string(),
|
||||
anvil_version: "1.0.0".to_string(),
|
||||
board_name: "mega".to_string(),
|
||||
fqbn: "arduino:avr:mega:cpu=atmega2560".to_string(),
|
||||
baud: 115200,
|
||||
};
|
||||
TemplateManager::extract("weather", tmp.path(), &ctx).unwrap();
|
||||
|
||||
let meta = TemplateManager::composed_meta("weather").unwrap();
|
||||
for lib_name in &meta.libraries {
|
||||
commands::lib::install_library(lib_name, tmp.path()).unwrap();
|
||||
}
|
||||
|
||||
let pin_defaults = meta.pins_for_board("mega");
|
||||
for pin_def in &pin_defaults {
|
||||
commands::pin::install_pin_assignment(
|
||||
&pin_def.name,
|
||||
&pin_def.pin,
|
||||
&pin_def.mode,
|
||||
"mega",
|
||||
tmp.path(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let config = ProjectConfig::load(tmp.path()).unwrap();
|
||||
assert!(config.libraries.contains_key("tmp36"));
|
||||
let pins = config.pins.get("mega").expect("Should have mega pins");
|
||||
assert!(pins.assignments.contains_key("tmp36_data"));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Audit integration -- after template creation, audit should be clean
|
||||
// =========================================================================
|
||||
|
||||
#[test]
|
||||
fn test_weather_audit_clean_after_full_setup() {
|
||||
let tmp = extract_weather("wx");
|
||||
|
||||
let meta = TemplateManager::composed_meta("weather").unwrap();
|
||||
for lib_name in &meta.libraries {
|
||||
commands::lib::install_library(lib_name, tmp.path()).unwrap();
|
||||
}
|
||||
for pin_def in meta.pins_for_board("uno") {
|
||||
commands::pin::install_pin_assignment(
|
||||
&pin_def.name,
|
||||
&pin_def.pin,
|
||||
&pin_def.mode,
|
||||
"uno",
|
||||
tmp.path(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Load config and check that library pins are all assigned
|
||||
let config = ProjectConfig::load(tmp.path()).unwrap();
|
||||
let lib_meta = library::find_library("tmp36").unwrap();
|
||||
let pins = config.pins.get("uno").unwrap();
|
||||
let assigned_names: Vec<String> =
|
||||
pins.assignments.keys().cloned().collect();
|
||||
let unassigned =
|
||||
library::unassigned_pins(&lib_meta, &assigned_names);
|
||||
assert!(
|
||||
unassigned.is_empty(),
|
||||
"All library pins should be assigned after full setup, \
|
||||
but these are missing: {:?}",
|
||||
unassigned
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// API compatibility -- template C++ must match library headers
|
||||
// =========================================================================
|
||||
|
||||
/// Extract public method names from a C++ header (simple regex-free scan).
|
||||
/// Looks for lines like: "void methodName(" or "float methodName(" or
|
||||
/// "ClassName(" (constructors).
|
||||
fn extract_public_methods(header: &str) -> Vec<String> {
|
||||
let mut methods = Vec::new();
|
||||
for line in header.lines() {
|
||||
let trimmed = line.trim();
|
||||
// Skip comments, preprocessor, blank
|
||||
if trimmed.starts_with("//")
|
||||
|| trimmed.starts_with("/*")
|
||||
|| trimmed.starts_with("*")
|
||||
|| trimmed.starts_with('#')
|
||||
|| trimmed.is_empty()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Look for "word(" patterns that are method declarations
|
||||
// e.g. "void setBaseTemperature(float celsius)"
|
||||
// e.g. "Tmp36Sim(float base_temp = 22.0f, float noise = 0.5f)"
|
||||
if let Some(paren_pos) = trimmed.find('(') {
|
||||
let before = trimmed[..paren_pos].trim();
|
||||
// Last word before the paren is the method name
|
||||
if let Some(name) = before.split_whitespace().last() {
|
||||
// Skip class/struct declarations
|
||||
if name == "class" || name == "struct" || name == "if"
|
||||
|| name == "for" || name == "while"
|
||||
|| name == "override"
|
||||
{
|
||||
continue;
|
||||
}
|
||||
methods.push(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
methods
|
||||
}
|
||||
|
||||
/// Count the number of default parameters in a constructor signature.
|
||||
/// e.g. "Tmp36Sim(float base_temp = 22.0f, float noise = 0.5f)"
|
||||
/// has 2 params, both with defaults.
|
||||
fn count_constructor_params(header: &str, class_name: &str) -> (usize, usize) {
|
||||
for line in header.lines() {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.contains(&format!("{}(", class_name)) {
|
||||
continue;
|
||||
}
|
||||
if let Some(start) = trimmed.find('(') {
|
||||
if let Some(end) = trimmed.find(')') {
|
||||
let params_str = &trimmed[start + 1..end];
|
||||
if params_str.trim().is_empty() {
|
||||
return (0, 0);
|
||||
}
|
||||
let params: Vec<&str> = params_str.split(',').collect();
|
||||
let total = params.len();
|
||||
let with_defaults =
|
||||
params.iter().filter(|p| p.contains('=')).count();
|
||||
return (total, with_defaults);
|
||||
}
|
||||
}
|
||||
}
|
||||
(0, 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_system_tests_use_valid_sim_api() {
|
||||
let tmp = extract_weather("wx");
|
||||
commands::lib::install_library("tmp36", tmp.path()).unwrap();
|
||||
|
||||
let sim_header = fs::read_to_string(
|
||||
tmp.path()
|
||||
.join("lib")
|
||||
.join("drivers")
|
||||
.join("tmp36")
|
||||
.join("tmp36_sim.h"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let test_source = fs::read_to_string(
|
||||
tmp.path().join("test").join("test_system.cpp"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let methods = extract_public_methods(&sim_header);
|
||||
|
||||
// Verify test code only calls methods that exist in the header
|
||||
// Check for common method-call patterns: "sensor.methodName("
|
||||
// or "exact_sensor.methodName(" etc.
|
||||
for line in test_source.lines() {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.starts_with("//") || trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
// Find "identifier.methodName(" patterns
|
||||
if let Some(dot_pos) = trimmed.find('.') {
|
||||
let after_dot = &trimmed[dot_pos + 1..];
|
||||
if let Some(paren_pos) = after_dot.find('(') {
|
||||
let method_name = after_dot[..paren_pos].trim();
|
||||
// Only check methods on sensor/sim-like objects
|
||||
let before_dot = trimmed[..dot_pos].trim();
|
||||
let before_dot = before_dot
|
||||
.split_whitespace()
|
||||
.last()
|
||||
.unwrap_or(before_dot);
|
||||
if before_dot.contains("sensor") || before_dot.contains("sim") {
|
||||
assert!(
|
||||
methods.contains(&method_name.to_string()),
|
||||
"test_system.cpp calls '{}.{}()' but '{}' \
|
||||
is not in tmp36_sim.h.\n \
|
||||
Available methods: {:?}",
|
||||
before_dot,
|
||||
method_name,
|
||||
method_name,
|
||||
methods
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_unit_tests_use_valid_mock_api() {
|
||||
let tmp = extract_weather("wx");
|
||||
commands::lib::install_library("tmp36", tmp.path()).unwrap();
|
||||
|
||||
let mock_header = fs::read_to_string(
|
||||
tmp.path()
|
||||
.join("lib")
|
||||
.join("drivers")
|
||||
.join("tmp36")
|
||||
.join("tmp36_mock.h"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let test_source = fs::read_to_string(
|
||||
tmp.path().join("test").join("test_unit.cpp"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let methods = extract_public_methods(&mock_header);
|
||||
|
||||
for line in test_source.lines() {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.starts_with("//") || trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Some(dot_pos) = trimmed.find('.') {
|
||||
let after_dot = &trimmed[dot_pos + 1..];
|
||||
if let Some(paren_pos) = after_dot.find('(') {
|
||||
let method_name = after_dot[..paren_pos].trim();
|
||||
let before_dot = trimmed[..dot_pos].trim();
|
||||
let before_dot = before_dot
|
||||
.split_whitespace()
|
||||
.last()
|
||||
.unwrap_or(before_dot);
|
||||
if before_dot.contains("sensor") {
|
||||
assert!(
|
||||
methods.contains(&method_name.to_string()),
|
||||
"test_unit.cpp calls '{}.{}()' but '{}' \
|
||||
is not in tmp36_mock.h.\n \
|
||||
Available methods: {:?}",
|
||||
before_dot,
|
||||
method_name,
|
||||
method_name,
|
||||
methods
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_sim_constructor_arg_count() {
|
||||
let tmp = extract_weather("wx");
|
||||
commands::lib::install_library("tmp36", tmp.path()).unwrap();
|
||||
|
||||
let sim_header = fs::read_to_string(
|
||||
tmp.path()
|
||||
.join("lib")
|
||||
.join("drivers")
|
||||
.join("tmp36")
|
||||
.join("tmp36_sim.h"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (total_params, default_params) =
|
||||
count_constructor_params(&sim_header, "Tmp36Sim");
|
||||
|
||||
let test_source = fs::read_to_string(
|
||||
tmp.path().join("test").join("test_system.cpp"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Check every "Tmp36Sim(" call in test code
|
||||
let min_args = total_params - default_params;
|
||||
for (line_num, line) in test_source.lines().enumerate() {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.starts_with("//") {
|
||||
continue;
|
||||
}
|
||||
// Find "Tmp36Sim(" constructor calls (not #include or class decl)
|
||||
if let Some(pos) = trimmed.find("Tmp36Sim(") {
|
||||
// Skip if it's a forward decl or class line
|
||||
if trimmed.contains("class ") || trimmed.contains("#include") {
|
||||
continue;
|
||||
}
|
||||
let after = &trimmed[pos + 9..]; // after "Tmp36Sim("
|
||||
if let Some(close) = after.find(')') {
|
||||
let args_str = &after[..close];
|
||||
let arg_count = if args_str.trim().is_empty() {
|
||||
0
|
||||
} else {
|
||||
args_str.split(',').count()
|
||||
};
|
||||
assert!(
|
||||
arg_count >= min_args && arg_count <= total_params,
|
||||
"test_system.cpp line {}: Tmp36Sim() called with \
|
||||
{} args, but constructor accepts {}-{} args.\n \
|
||||
Line: {}",
|
||||
line_num + 1,
|
||||
arg_count,
|
||||
min_args,
|
||||
total_params,
|
||||
trimmed
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user