feat: Weevil v1.0.0-beta1 - FTC Project Generator

Cross-platform tool for generating clean, testable FTC robot projects
without editing the SDK installation.

Features:
- Standalone project generation with proper separation from SDK
- Per-project SDK configuration via .weevil.toml
- Local unit testing support (no robot required)
- Cross-platform build/deploy scripts (Linux/macOS/Windows)
- Project upgrade system preserving user code
- Configuration management commands
- Comprehensive test suite (11 passing tests)
- Zero-warning builds

Architecture:
- Pure Rust implementation with embedded Gradle wrapper
- Projects use deployToSDK task to copy code to FTC SDK TeamCode
- Git-ready projects with automatic initialization
- USB and WiFi deployment with auto-detection

Commands:
- weevil new <name> - Create new project
- weevil upgrade <path> - Update project infrastructure
- weevil config <path> - View/modify project configuration
- weevil sdk status/install/update - Manage SDKs

Addresses the core problem: FTC's SDK structure forces students to
edit framework internals instead of separating concerns like industry
standard practices. Weevil enables proper software engineering workflows
for robotics education.
This commit is contained in:
Eric Ratliff
2026-01-24 15:20:18 -06:00
commit 70a1acc2a1
35 changed files with 3558 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
// File: tests/config_tests.rs
// Unit tests for project configuration
use weevil::project::ProjectConfig;
use std::path::PathBuf;
use tempfile::TempDir;
use std::fs;
#[test]
fn test_config_create_and_save() {
let temp_dir = TempDir::new().unwrap();
let sdk_path = PathBuf::from("/mock/sdk/path");
let config = ProjectConfig::new("test-robot", sdk_path.clone()).unwrap();
assert_eq!(config.project_name, "test-robot");
assert_eq!(config.ftc_sdk_path, sdk_path);
assert_eq!(config.weevil_version, "1.0.0");
// Save and reload
config.save(temp_dir.path()).unwrap();
let loaded = ProjectConfig::load(temp_dir.path()).unwrap();
assert_eq!(loaded.project_name, config.project_name);
assert_eq!(loaded.ftc_sdk_path, config.ftc_sdk_path);
}
#[test]
fn test_config_load_missing_file() {
let temp_dir = TempDir::new().unwrap();
let result = ProjectConfig::load(temp_dir.path());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("missing .weevil.toml"));
}
#[test]
fn test_config_toml_format() {
let temp_dir = TempDir::new().unwrap();
let sdk_path = PathBuf::from("/test/sdk");
let config = ProjectConfig::new("my-robot", sdk_path).unwrap();
config.save(temp_dir.path()).unwrap();
let content = fs::read_to_string(temp_dir.path().join(".weevil.toml")).unwrap();
assert!(content.contains("project_name = \"my-robot\""));
assert!(content.contains("weevil_version = \"1.0.0\""));
assert!(content.contains("ftc_sdk_path"));
}
#[test]
fn test_config_update_sdk_path() {
let temp_dir = TempDir::new().unwrap();
let old_sdk = PathBuf::from("/old/sdk");
let new_sdk = PathBuf::from("/new/sdk");
let mut config = ProjectConfig::new("test", old_sdk).unwrap();
// Note: This will fail in tests because SDK doesn't exist
// In real usage, the SDK path is validated
// For now, just test the struct update
config.ftc_sdk_path = new_sdk.clone();
assert_eq!(config.ftc_sdk_path, new_sdk);
}

5
tests/unit/mod.rs Normal file
View File

@@ -0,0 +1,5 @@
// File: tests/unit/mod.rs
// Unit tests module declarations
mod config_tests;
mod sdk_tests;

53
tests/unit/sdk_tests.rs Normal file
View File

@@ -0,0 +1,53 @@
// File: tests/sdk_tests.rs
// Unit tests for SDK detection and verification
use weevil::sdk::ftc;
use std::path::PathBuf;
use tempfile::TempDir;
use std::fs;
#[test]
fn test_ftc_sdk_verification_missing() {
let temp_dir = TempDir::new().unwrap();
let result = ftc::verify(temp_dir.path());
assert!(result.is_err());
}
#[test]
fn test_ftc_sdk_verification_with_structure() {
let temp_dir = TempDir::new().unwrap();
// Create minimal FTC SDK structure
fs::create_dir_all(temp_dir.path().join("TeamCode/src/main/java")).unwrap();
fs::create_dir_all(temp_dir.path().join("FtcRobotController")).unwrap();
fs::write(temp_dir.path().join("build.gradle"), "// test").unwrap();
let result = ftc::verify(temp_dir.path());
assert!(result.is_ok());
}
#[test]
fn test_get_version_from_file() {
let temp_dir = TempDir::new().unwrap();
// Create version file
fs::write(temp_dir.path().join(".version"), "v10.1.1\n").unwrap();
let version = ftc::get_version(temp_dir.path()).unwrap();
assert_eq!(version, "v10.1.1");
}
#[test]
fn test_get_version_from_git_tag() {
// This test requires a real git repo, so we'll skip it in unit tests
// It's covered in integration tests instead
}
#[test]
fn test_get_version_missing() {
let temp_dir = TempDir::new().unwrap();
let result = ftc::get_version(temp_dir.path());
assert!(result.is_err());
}