Compare commits
2 Commits
master
...
5f68bde615
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f68bde615 | ||
|
|
38f1e0f3ed |
@@ -133,11 +133,6 @@ deploy.bat
|
||||
java
|
||||
}}
|
||||
|
||||
repositories {{
|
||||
mavenCentral()
|
||||
google()
|
||||
}}
|
||||
|
||||
dependencies {{
|
||||
// Testing (runs on PC without SDK)
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
|
||||
@@ -200,8 +195,18 @@ tasks.register<Exec>("buildApk") {{
|
||||
"#, sdk_path, sdk_path);
|
||||
fs::write(project_path.join("build.gradle.kts"), build_gradle)?;
|
||||
|
||||
// settings.gradle.kts
|
||||
let settings_gradle = format!("rootProject.name = \"{}\"\n", self.name);
|
||||
// settings.gradle.kts - Repositories go here in Gradle 8+
|
||||
let settings_gradle = format!(r#"rootProject.name = "{}"
|
||||
|
||||
// Repository configuration (Gradle 8+ prefers repositories in settings)
|
||||
dependencyResolutionManagement {{
|
||||
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
|
||||
repositories {{
|
||||
mavenCentral()
|
||||
google()
|
||||
}}
|
||||
}}
|
||||
"#, self.name);
|
||||
fs::write(project_path.join("settings.gradle.kts"), settings_gradle)?;
|
||||
|
||||
// build.sh (Linux/Mac)
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
[project]
|
||||
name = "{{PROJECT_NAME}}"
|
||||
created = "{{CREATION_DATE}}"
|
||||
weevil_version = "{{WEEVIL_VERSION}}"
|
||||
template = "{{TEMPLATE_NAME}}"
|
||||
|
||||
[ftc]
|
||||
sdk_version = "10.1.1"
|
||||
|
||||
[build]
|
||||
gradle_version = "8.5"
|
||||
@@ -1,86 +1,27 @@
|
||||
// Generated by Weevil {{WEEVIL_VERSION}} on {{CREATION_DATE}}
|
||||
package robot.{{PACKAGE_NAME}};
|
||||
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
package robot.opmodes;
|
||||
|
||||
/**
|
||||
* Basic OpMode template for {{PROJECT_NAME}}
|
||||
* Basic OpMode for {{PROJECT_NAME}}
|
||||
*
|
||||
* This is a minimal starting point for your robot code.
|
||||
* Add your hardware and control logic here.
|
||||
* This is a placeholder to demonstrate project structure.
|
||||
* To use this with FTC SDK:
|
||||
* 1. Run: weevil deploy {{PROJECT_NAME}}
|
||||
* 2. Add FTC SDK imports (OpMode, TeleOp, etc.)
|
||||
* 3. Extend OpMode and implement methods
|
||||
*
|
||||
* For local testing (without robot), write unit tests in src/test/java/robot/
|
||||
* Run tests with: ./gradlew test
|
||||
*
|
||||
* Created by Weevil {{WEEVIL_VERSION}}
|
||||
* Template: {{TEMPLATE_NAME}}
|
||||
*/
|
||||
@TeleOp(name = "{{PROJECT_NAME}}: Basic", group = "TeleOp")
|
||||
public class BasicOpMode extends OpMode {
|
||||
public class BasicOpMode {
|
||||
|
||||
// Declare your hardware here
|
||||
// private DcMotor leftMotor;
|
||||
// private DcMotor rightMotor;
|
||||
// This placeholder compiles without FTC SDK dependencies
|
||||
// Replace with actual OpMode code when deploying to robot
|
||||
|
||||
/**
|
||||
* Initialize hardware and setup
|
||||
*/
|
||||
@Override
|
||||
public void init() {
|
||||
// Initialize your hardware
|
||||
// leftMotor = hardwareMap.get(DcMotor.class, "left_motor");
|
||||
// rightMotor = hardwareMap.get(DcMotor.class, "right_motor");
|
||||
|
||||
telemetry.addData("Status", "{{PROJECT_NAME}} initialized");
|
||||
telemetry.addData("Created", "Weevil {{WEEVIL_VERSION}}");
|
||||
telemetry.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs repeatedly after init, before play
|
||||
*/
|
||||
@Override
|
||||
public void init_loop() {
|
||||
telemetry.addData("Status", "Waiting for start...");
|
||||
telemetry.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs once when play is pressed
|
||||
*/
|
||||
@Override
|
||||
public void start() {
|
||||
telemetry.addData("Status", "Running!");
|
||||
telemetry.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Main control loop - runs repeatedly during play
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
// Add your control code here
|
||||
|
||||
// Example: Read gamepad and control motors
|
||||
// double leftPower = -gamepad1.left_stick_y;
|
||||
// double rightPower = -gamepad1.right_stick_y;
|
||||
// leftMotor.setPower(leftPower);
|
||||
// rightMotor.setPower(rightPower);
|
||||
|
||||
// Update telemetry
|
||||
telemetry.addData("Status", "Running");
|
||||
telemetry.addData("Project", "{{PROJECT_NAME}}");
|
||||
// telemetry.addData("Left Power", leftPower);
|
||||
// telemetry.addData("Right Power", rightPower);
|
||||
telemetry.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs once when stop is pressed
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
// Stop all motors
|
||||
// leftMotor.setPower(0);
|
||||
// rightMotor.setPower(0);
|
||||
|
||||
telemetry.addData("Status", "Stopped");
|
||||
telemetry.update();
|
||||
public static void main(String[] args) {
|
||||
System.out.println("{{PROJECT_NAME}} - Ready for deployment");
|
||||
System.out.println("Run: weevil deploy {{PROJECT_NAME}}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
project_name = "my-robot"
|
||||
weevil_version = "1.1.0-beta.2"
|
||||
ftc_sdk_path = 'C:\Users\Eric\.weevil\ftc-sdk'
|
||||
ftc_sdk_version = "v10.1.1"
|
||||
android_sdk_path = 'C:\Users\Eric\.weevil\android-sdk'
|
||||
@@ -1,7 +1,18 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
// Build configuration for {{PROJECT_NAME}}
|
||||
// This file is managed by the FTC SDK
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.1.0'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
namespace 'org.firstinspires.ftc.{{PACKAGE_NAME}}'
|
||||
compileSdk 34
|
||||
@@ -9,18 +20,11 @@ android {
|
||||
defaultConfig {
|
||||
applicationId 'org.firstinspires.ftc.{{PACKAGE_NAME}}'
|
||||
minSdk 24
|
||||
targetSdk 34
|
||||
//noinspection ExpiredTargetSdkVersion
|
||||
targetSdk 28
|
||||
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -31,12 +35,12 @@ android {
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ['src/main/java']
|
||||
srcDir 'src/main/java'
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ['src/test/java']
|
||||
srcDir 'src/test/java'
|
||||
}
|
||||
}
|
||||
}
|
||||
305
tests/template_tests.rs
Normal file
305
tests/template_tests.rs
Normal file
@@ -0,0 +1,305 @@
|
||||
use anyhow::Result;
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use tempfile::TempDir;
|
||||
|
||||
// Import the template system
|
||||
use weevil::templates::{TemplateManager, TemplateContext};
|
||||
|
||||
/// Helper to create a test template context
|
||||
fn test_context(project_name: &str) -> TemplateContext {
|
||||
TemplateContext {
|
||||
project_name: project_name.to_string(),
|
||||
package_name: project_name.to_lowercase().replace("-", ""),
|
||||
creation_date: "2026-02-02T12:00:00Z".to_string(),
|
||||
weevil_version: "1.1.0-test".to_string(),
|
||||
template_name: "basic".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_manager_creation() {
|
||||
let mgr = TemplateManager::new();
|
||||
assert!(mgr.is_ok(), "TemplateManager should be created successfully");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_exists() {
|
||||
let mgr = TemplateManager::new().unwrap();
|
||||
|
||||
assert!(mgr.template_exists("basic"), "basic template should exist");
|
||||
assert!(mgr.template_exists("testing"), "testing template should exist");
|
||||
assert!(!mgr.template_exists("nonexistent"), "nonexistent template should not exist");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_templates() {
|
||||
let mgr = TemplateManager::new().unwrap();
|
||||
let templates = mgr.list_templates();
|
||||
|
||||
assert_eq!(templates.len(), 2, "Should have exactly 2 templates");
|
||||
assert!(templates.iter().any(|t| t.contains("basic")), "Should list basic template");
|
||||
assert!(templates.iter().any(|t| t.contains("testing")), "Should list testing template");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_template_extraction() -> Result<()> {
|
||||
let mgr = TemplateManager::new()?;
|
||||
let temp_dir = TempDir::new()?;
|
||||
let project_dir = temp_dir.path().join("test-robot");
|
||||
fs::create_dir(&project_dir)?;
|
||||
|
||||
let context = test_context("test-robot");
|
||||
let file_count = mgr.extract_template("basic", &project_dir, &context)?;
|
||||
|
||||
assert!(file_count > 0, "Should extract at least one file from basic template");
|
||||
|
||||
// Verify key files exist
|
||||
assert!(project_dir.join(".gitignore").exists(), ".gitignore should exist");
|
||||
assert!(project_dir.join("README.md").exists(), "README.md should exist (processed from .template)");
|
||||
assert!(project_dir.join(".weevil.toml").exists(), ".weevil.toml should exist (processed from .template)");
|
||||
assert!(project_dir.join("build.gradle").exists(), "build.gradle should exist (processed from .template)");
|
||||
assert!(project_dir.join("settings.gradle").exists(), "settings.gradle should exist");
|
||||
|
||||
// Verify OpMode exists
|
||||
let opmode_path = project_dir.join("src/main/java/robot/opmodes/BasicOpMode.java");
|
||||
assert!(opmode_path.exists(), "BasicOpMode.java should exist");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_testing_template_extraction() -> Result<()> {
|
||||
let mgr = TemplateManager::new()?;
|
||||
let temp_dir = TempDir::new()?;
|
||||
let project_dir = temp_dir.path().join("test-showcase");
|
||||
fs::create_dir(&project_dir)?;
|
||||
|
||||
let mut context = test_context("test-showcase");
|
||||
context.template_name = "testing".to_string();
|
||||
|
||||
let file_count = mgr.extract_template("testing", &project_dir, &context)?;
|
||||
|
||||
assert!(file_count > 20, "Testing template should have 20+ files, got {}", file_count);
|
||||
|
||||
// Verify documentation files
|
||||
assert!(project_dir.join("README.md").exists(), "README.md should exist");
|
||||
assert!(project_dir.join("DESIGN_AND_TEST_PLAN.md").exists(), "DESIGN_AND_TEST_PLAN.md should exist");
|
||||
assert!(project_dir.join("TESTING_GUIDE.md").exists(), "TESTING_GUIDE.md should exist");
|
||||
|
||||
// Verify subsystems
|
||||
assert!(project_dir.join("src/main/java/robot/subsystems/MotorCycler.java").exists(), "MotorCycler.java should exist");
|
||||
assert!(project_dir.join("src/main/java/robot/subsystems/WallApproach.java").exists(), "WallApproach.java should exist");
|
||||
assert!(project_dir.join("src/main/java/robot/subsystems/TurnController.java").exists(), "TurnController.java should exist");
|
||||
|
||||
// Verify hardware interfaces and implementations
|
||||
assert!(project_dir.join("src/main/java/robot/hardware/MotorController.java").exists(), "MotorController interface should exist");
|
||||
assert!(project_dir.join("src/main/java/robot/hardware/FtcMotorController.java").exists(), "FtcMotorController should exist");
|
||||
assert!(project_dir.join("src/main/java/robot/hardware/DistanceSensor.java").exists(), "DistanceSensor interface should exist");
|
||||
assert!(project_dir.join("src/main/java/robot/hardware/FtcDistanceSensor.java").exists(), "FtcDistanceSensor should exist");
|
||||
|
||||
// Verify test files
|
||||
assert!(project_dir.join("src/test/java/robot/subsystems/MotorCyclerTest.java").exists(), "MotorCyclerTest.java should exist");
|
||||
assert!(project_dir.join("src/test/java/robot/subsystems/WallApproachTest.java").exists(), "WallApproachTest.java should exist");
|
||||
assert!(project_dir.join("src/test/java/robot/subsystems/TurnControllerTest.java").exists(), "TurnControllerTest.java should exist");
|
||||
|
||||
// Verify mock implementations
|
||||
assert!(project_dir.join("src/test/java/robot/hardware/MockMotorController.java").exists(), "MockMotorController should exist");
|
||||
assert!(project_dir.join("src/test/java/robot/hardware/MockDistanceSensor.java").exists(), "MockDistanceSensor should exist");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_variable_substitution() -> Result<()> {
|
||||
let mgr = TemplateManager::new()?;
|
||||
let temp_dir = TempDir::new()?;
|
||||
let project_dir = temp_dir.path().join("my-test-robot");
|
||||
fs::create_dir(&project_dir)?;
|
||||
|
||||
let context = test_context("my-test-robot");
|
||||
mgr.extract_template("basic", &project_dir, &context)?;
|
||||
|
||||
// Check README.md for variable substitution
|
||||
let readme_path = project_dir.join("README.md");
|
||||
let readme_content = fs::read_to_string(readme_path)?;
|
||||
|
||||
assert!(readme_content.contains("my-test-robot"), "README should contain project name");
|
||||
assert!(readme_content.contains("1.1.0-test"), "README should contain weevil version");
|
||||
assert!(!readme_content.contains("{{PROJECT_NAME}}"), "README should not contain template variable");
|
||||
assert!(!readme_content.contains("{{WEEVIL_VERSION}}"), "README should not contain template variable");
|
||||
|
||||
// Check .weevil.toml for variable substitution
|
||||
let weevil_toml = project_dir.join(".weevil.toml");
|
||||
let toml_content = fs::read_to_string(weevil_toml)?;
|
||||
|
||||
assert!(toml_content.contains("my-test-robot"), ".weevil.toml should contain project name");
|
||||
assert!(!toml_content.contains("{{PROJECT_NAME}}"), ".weevil.toml should not contain template variable");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_template_extraction() {
|
||||
let mgr = TemplateManager::new().unwrap();
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let project_dir = temp_dir.path().join("test-robot");
|
||||
fs::create_dir(&project_dir).unwrap();
|
||||
|
||||
let context = test_context("test-robot");
|
||||
let result = mgr.extract_template("nonexistent", &project_dir, &context);
|
||||
|
||||
assert!(result.is_err(), "Should fail for nonexistent template");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_package_name_sanitization() {
|
||||
let context1 = test_context("my-robot");
|
||||
assert_eq!(context1.package_name, "myrobot", "Hyphens should be removed");
|
||||
|
||||
let context2 = test_context("team_1234_bot");
|
||||
assert_eq!(context2.package_name, "team1234bot", "Underscores should be removed");
|
||||
}
|
||||
|
||||
/// Integration test: Create a project with testing template and run gradle tests
|
||||
/// This is marked with #[ignore] by default since it requires:
|
||||
/// - Java installed
|
||||
/// - Network access (first time to download gradle wrapper)
|
||||
/// - Takes ~1-2 minutes to run
|
||||
///
|
||||
/// Run with: cargo test test_testing_template_gradle_build -- --ignored --nocapture
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_testing_template_gradle_build() -> Result<()> {
|
||||
println!("Testing complete gradle build and test execution...");
|
||||
|
||||
let mgr = TemplateManager::new()?;
|
||||
let temp_dir = TempDir::new()?;
|
||||
let project_dir = temp_dir.path().join("gradle-test-robot");
|
||||
fs::create_dir(&project_dir)?;
|
||||
|
||||
// Extract testing template
|
||||
let mut context = test_context("gradle-test-robot");
|
||||
context.template_name = "testing".to_string();
|
||||
|
||||
let file_count = mgr.extract_template("testing", &project_dir, &context)?;
|
||||
println!("Extracted {} files from testing template", file_count);
|
||||
|
||||
// Check if gradlew exists (should be in testing template)
|
||||
let gradlew = if cfg!(windows) {
|
||||
project_dir.join("gradlew.bat")
|
||||
} else {
|
||||
project_dir.join("gradlew")
|
||||
};
|
||||
|
||||
if !gradlew.exists() {
|
||||
println!("WARNING: gradlew not found in template, skipping gradle test");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Make gradlew executable on Unix
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(&gradlew)?.permissions();
|
||||
perms.set_mode(0o755);
|
||||
fs::set_permissions(&gradlew, perms)?;
|
||||
}
|
||||
|
||||
println!("Running gradle test...");
|
||||
|
||||
// Run gradlew test
|
||||
let output = Command::new(&gradlew)
|
||||
.arg("test")
|
||||
.current_dir(&project_dir)
|
||||
.output()?;
|
||||
|
||||
println!("=== Gradle Output ===");
|
||||
println!("{}", String::from_utf8_lossy(&output.stdout));
|
||||
|
||||
if !output.status.success() {
|
||||
println!("=== Gradle Errors ===");
|
||||
println!("{}", String::from_utf8_lossy(&output.stderr));
|
||||
panic!("Gradle tests failed with status: {}", output.status);
|
||||
}
|
||||
|
||||
// Verify test output mentions 45 tests
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
// Look for test success indicators
|
||||
let has_success = stdout.contains("BUILD SUCCESSFUL") ||
|
||||
stdout.contains("45 tests") ||
|
||||
stdout.to_lowercase().contains("tests passed");
|
||||
|
||||
assert!(has_success, "Gradle test output should indicate success");
|
||||
|
||||
println!("✓ All 45 tests passed!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that basic template creates a valid directory structure
|
||||
#[test]
|
||||
fn test_basic_template_directory_structure() -> Result<()> {
|
||||
let mgr = TemplateManager::new()?;
|
||||
let temp_dir = TempDir::new()?;
|
||||
let project_dir = temp_dir.path().join("structure-test");
|
||||
fs::create_dir(&project_dir)?;
|
||||
|
||||
let context = test_context("structure-test");
|
||||
mgr.extract_template("basic", &project_dir, &context)?;
|
||||
|
||||
// Verify directory structure
|
||||
assert!(project_dir.join("src").is_dir(), "src directory should exist");
|
||||
assert!(project_dir.join("src/main").is_dir(), "src/main directory should exist");
|
||||
assert!(project_dir.join("src/main/java").is_dir(), "src/main/java directory should exist");
|
||||
assert!(project_dir.join("src/main/java/robot").is_dir(), "src/main/java/robot directory should exist");
|
||||
assert!(project_dir.join("src/main/java/robot/opmodes").is_dir(), "opmodes directory should exist");
|
||||
assert!(project_dir.join("src/test/java/robot").is_dir(), "test directory should exist");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that .gitignore is not named ".gitignore.template"
|
||||
#[test]
|
||||
fn test_gitignore_naming() -> Result<()> {
|
||||
let mgr = TemplateManager::new()?;
|
||||
let temp_dir = TempDir::new()?;
|
||||
let project_dir = temp_dir.path().join("gitignore-test");
|
||||
fs::create_dir(&project_dir)?;
|
||||
|
||||
let context = test_context("gitignore-test");
|
||||
mgr.extract_template("basic", &project_dir, &context)?;
|
||||
|
||||
assert!(project_dir.join(".gitignore").exists(), ".gitignore should exist");
|
||||
assert!(!project_dir.join(".gitignore.template").exists(), ".gitignore.template should NOT exist");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that template extraction doesn't fail with unusual project names
|
||||
#[test]
|
||||
fn test_unusual_project_names() -> Result<()> {
|
||||
let mgr = TemplateManager::new()?;
|
||||
|
||||
let test_names = vec![
|
||||
"robot-2024",
|
||||
"team_1234",
|
||||
"FTC_Bot",
|
||||
"my-awesome-bot",
|
||||
];
|
||||
|
||||
for name in test_names {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let project_dir = temp_dir.path().join(name);
|
||||
fs::create_dir(&project_dir)?;
|
||||
|
||||
let context = test_context(name);
|
||||
let result = mgr.extract_template("basic", &project_dir, &context);
|
||||
|
||||
assert!(result.is_ok(), "Should handle project name: {}", name);
|
||||
assert!(project_dir.join("README.md").exists(), "README should exist for {}", name);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user