Supporting multiple boards

This commit is contained in:
Eric Ratliff
2026-02-19 10:12:33 -06:00
parent 2739d83b99
commit b909da298e
16 changed files with 1554 additions and 94 deletions

View File

@@ -15,6 +15,7 @@ fn test_basic_template_extracts_all_expected_files() {
let ctx = TemplateContext {
project_name: "test_proj".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -29,6 +30,7 @@ fn test_template_creates_sketch_directory() {
let ctx = TemplateContext {
project_name: "blink".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -59,6 +61,7 @@ fn test_template_creates_hal_files() {
let ctx = TemplateContext {
project_name: "sensor".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -92,6 +95,7 @@ fn test_template_creates_app_header() {
let ctx = TemplateContext {
project_name: "my_sensor".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -118,6 +122,7 @@ fn test_template_creates_test_infrastructure() {
let ctx = TemplateContext {
project_name: "blink".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -156,6 +161,7 @@ fn test_template_test_file_references_correct_app() {
let ctx = TemplateContext {
project_name: "motor_ctrl".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -177,6 +183,7 @@ fn test_template_cmake_references_correct_project() {
let ctx = TemplateContext {
project_name: "my_bot".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -198,6 +205,7 @@ fn test_template_creates_dot_files() {
let ctx = TemplateContext {
project_name: "blink".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -228,6 +236,7 @@ fn test_template_creates_readme() {
let ctx = TemplateContext {
project_name: "blink".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -251,6 +260,7 @@ fn test_template_creates_valid_config() {
let ctx = TemplateContext {
project_name: "blink".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -260,7 +270,7 @@ fn test_template_creates_valid_config() {
// Should be loadable by ProjectConfig
let config = ProjectConfig::load(tmp.path()).unwrap();
assert_eq!(config.project.name, "blink");
assert_eq!(config.build.fqbn, "arduino:avr:uno");
assert_eq!(config.build.default, "uno");
assert_eq!(config.monitor.baud, 115200);
assert!(config.build.extra_flags.contains(&"-Werror".to_string()));
}
@@ -273,7 +283,7 @@ fn test_config_roundtrip() {
let loaded = ProjectConfig::load(tmp.path()).unwrap();
assert_eq!(loaded.project.name, "roundtrip_test");
assert_eq!(loaded.build.fqbn, config.build.fqbn);
assert_eq!(loaded.build.default, config.build.default);
assert_eq!(loaded.monitor.baud, config.monitor.baud);
assert_eq!(loaded.build.include_dirs, config.build.include_dirs);
}
@@ -332,6 +342,7 @@ fn test_full_project_structure() {
let ctx = TemplateContext {
project_name: "full_test".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -383,6 +394,7 @@ fn test_no_unicode_in_template_output() {
let ctx = TemplateContext {
project_name: "ascii_test".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -432,6 +444,7 @@ fn test_unknown_template_fails() {
let ctx = TemplateContext {
project_name: "test".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -457,6 +470,7 @@ fn test_template_creates_self_contained_scripts() {
let ctx = TemplateContext {
project_name: "standalone".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -481,6 +495,7 @@ fn test_build_sh_reads_anvil_toml() {
let ctx = TemplateContext {
project_name: "toml_reader".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -508,6 +523,7 @@ fn test_upload_sh_reads_anvil_toml() {
let ctx = TemplateContext {
project_name: "uploader".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -543,6 +559,7 @@ fn test_monitor_sh_reads_anvil_toml() {
let ctx = TemplateContext {
project_name: "serial_mon".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -570,6 +587,7 @@ fn test_scripts_have_shebangs() {
let ctx = TemplateContext {
project_name: "shebangs".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -594,6 +612,7 @@ fn test_scripts_no_anvil_binary_dependency() {
let ctx = TemplateContext {
project_name: "no_anvil_dep".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -643,6 +662,7 @@ fn test_gitignore_excludes_build_cache() {
let ctx = TemplateContext {
project_name: "gitcheck".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -670,6 +690,7 @@ fn test_readme_documents_self_contained_workflow() {
let ctx = TemplateContext {
project_name: "docs_check".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -705,6 +726,7 @@ fn test_scripts_tolerate_missing_toml_keys() {
let ctx = TemplateContext {
project_name: "grep_safe".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -742,6 +764,7 @@ fn test_bat_scripts_no_unescaped_parens_in_echo() {
let ctx = TemplateContext {
project_name: "parens_test".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -803,6 +826,7 @@ fn test_scripts_read_anvil_local_for_port() {
let ctx = TemplateContext {
project_name: "local_test".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -825,6 +849,7 @@ fn test_anvil_toml_template_has_no_port() {
let ctx = TemplateContext {
project_name: "no_port".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -856,6 +881,7 @@ fn test_bat_scripts_call_detect_port_ps1() {
let ctx = TemplateContext {
project_name: "ps1_test".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -878,6 +904,7 @@ fn test_detect_port_ps1_is_valid() {
let ctx = TemplateContext {
project_name: "ps1_valid".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -910,6 +937,7 @@ fn test_refresh_freshly_extracted_is_up_to_date() {
let ctx = TemplateContext {
project_name: "fresh_proj".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -940,6 +968,7 @@ fn test_refresh_detects_modified_script() {
let ctx = TemplateContext {
project_name: "mod_proj".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -1009,6 +1038,7 @@ fn test_scripts_read_vid_pid_from_anvil_local() {
let ctx = TemplateContext {
project_name: "vidpid_test".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
@@ -1035,6 +1065,7 @@ fn test_board_preset_fqbn_in_config() {
let ctx = TemplateContext {
project_name: "mega_test".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "mega".to_string(),
fqbn: "arduino:avr:mega:cpu=atmega2560".to_string(),
baud: 115200,
};
@@ -1042,7 +1073,7 @@ fn test_board_preset_fqbn_in_config() {
let config = ProjectConfig::load(tmp.path()).unwrap();
assert_eq!(
config.build.fqbn, "arduino:avr:mega:cpu=atmega2560",
config.build.default, "mega",
".anvil.toml should contain mega FQBN"
);
}
@@ -1054,12 +1085,113 @@ fn test_board_preset_custom_fqbn_in_config() {
let ctx = TemplateContext {
project_name: "custom_board".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "esp".to_string(),
fqbn: "esp32:esp32:esp32".to_string(),
baud: 9600,
};
TemplateManager::extract("basic", tmp.path(), &ctx).unwrap();
let config = ProjectConfig::load(tmp.path()).unwrap();
assert_eq!(config.build.fqbn, "esp32:esp32:esp32");
assert_eq!(config.build.default, "esp");
assert_eq!(config.monitor.baud, 9600);
}
// ==========================================================================
// Multi-board profile tests
// ==========================================================================
#[test]
fn test_scripts_accept_board_flag() {
// All build/upload/monitor scripts should accept --board
let tmp = TempDir::new().unwrap();
let ctx = TemplateContext {
project_name: "multi_test".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
TemplateManager::extract("basic", tmp.path(), &ctx).unwrap();
let scripts = vec![
"build.sh", "build.bat",
"upload.sh", "upload.bat",
"monitor.sh", "monitor.bat",
];
for script in &scripts {
let content = fs::read_to_string(tmp.path().join(script)).unwrap();
assert!(
content.contains("--board"),
"{} should accept --board flag",
script
);
}
}
#[test]
fn test_sh_scripts_have_toml_section_get() {
// Shell scripts need section-aware TOML parsing for board profiles
let tmp = TempDir::new().unwrap();
let ctx = TemplateContext {
project_name: "section_test".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
TemplateManager::extract("basic", tmp.path(), &ctx).unwrap();
for script in &["build.sh", "upload.sh", "monitor.sh"] {
let content = fs::read_to_string(tmp.path().join(script)).unwrap();
assert!(
content.contains("toml_section_get"),
"{} should have toml_section_get function for board profiles",
script
);
}
}
#[test]
fn test_bat_scripts_have_section_parser() {
// Batch scripts need section-aware TOML parsing for board profiles
let tmp = TempDir::new().unwrap();
let ctx = TemplateContext {
project_name: "bat_section".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
TemplateManager::extract("basic", tmp.path(), &ctx).unwrap();
for bat in &["build.bat", "upload.bat", "monitor.bat"] {
let content = fs::read_to_string(tmp.path().join(bat)).unwrap();
assert!(
content.contains("BOARD_SECTION") || content.contains("IN_SECTION"),
"{} should have section parser for board profiles",
bat
);
}
}
#[test]
fn test_toml_template_has_board_profile_comments() {
// The generated .anvil.toml should include commented examples
// showing how to add board profiles
let tmp = TempDir::new().unwrap();
let ctx = TemplateContext {
project_name: "comment_test".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
TemplateManager::extract("basic", tmp.path(), &ctx).unwrap();
let content = fs::read_to_string(tmp.path().join(".anvil.toml")).unwrap();
assert!(
content.contains("[boards.mega]") || content.contains("boards.mega"),
".anvil.toml should show board profile examples in comments"
);
}