Test Template Guide¶
Detailed walkthrough of using the BIT SDK test template to create custom tests.
The test template (test_template/) provides a complete starting point for creating new BIT tests. It includes all necessary boilerplate, configuration structures, and build setup.
Template Overview¶
The test template is a fully functional example test that demonstrates: - Plugin trait implementation - Configuration loading - Test execution patterns - Learn function integration - Build configuration
Location: /home/zen/local/bit/test_template/
Template Structure¶
test_template/
├── Cargo.toml # Build configuration
├── README.md # Template documentation
└── src/
└── lib.rs # Test implementation
Cargo.toml¶
[package]
name = "test_template"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"] # Builds as dynamic library (.so)
[dependencies]
bit_plugin = { path = "../bit_plugin" }
serde = { version = "1.0", features = ["derive"] }
toml = "0.8"
log = "0.4"
Key elements:
- crate-type = ["cdylib"] - Essential for plugin loading
- bit_plugin dependency - Core trait definitions
- serde + toml - Configuration parsing
- log - Logging infrastructure
src/lib.rs¶
Complete implementation showing: - Configuration structure with serde - Test trait implementations - Plugin entry point macro - Error handling patterns
Creating a New Test¶
Step 1: Copy Template¶
Choose a descriptive name following the pattern:
- pbit_* - Power-On BIT tests
- cbit_* - Continuous BIT tests
- fbit_* - Factory BIT tests
Step 2: Update Package Name¶
Edit Cargo.toml:
[package]
name = "my_new_test" # Change this
version = "0.1.0"
edition = "2021"
# Rest remains the same
Step 3: Customize Configuration¶
Edit src/lib.rs configuration structure:
Simple configuration:
#[derive(Debug, Deserialize)]
pub struct MyNewTestConfig {
enabled: bool,
threshold: i32,
interval_secs: u64,
}
Configuration with devices:
#[derive(Debug, Deserialize)]
pub struct DeviceConfig {
name: String,
address: String,
timeout_ms: u64,
}
#[derive(Debug, Deserialize)]
pub struct MyNewTestConfig {
enabled: bool,
device: Vec<DeviceConfig>,
}
Nested configuration:
#[derive(Debug, Deserialize)]
pub struct Limits {
min: f64,
max: f64,
warning: f64,
}
#[derive(Debug, Deserialize)]
pub struct MyNewTestConfig {
enabled: bool,
limits: Limits,
check_interval_secs: u64,
}
Step 4: Update Test Metadata¶
impl Test for MyNewTest {
fn name(&self) -> &str {
"my_new_test" // Unique identifier
}
fn enabled(&self) -> bool {
self.config.enabled
}
fn description(&self) -> &str {
"Tests my specific hardware functionality" // Update this
}
fn version(&self) -> &str {
env!("CARGO_PKG_VERSION")
}
}
Step 5: Implement Test Logic¶
impl TestRun for MyNewTest {
fn run(&self) -> Result<(), Box<dyn std::error::Error>> {
log::info!("Starting {}", self.name());
// 1. Check preconditions
if !self.config.enabled {
return Ok(());
}
// 2. Your test implementation here
let result = self.perform_my_test()?;
// 3. Validate results
if result < self.config.threshold {
return Err(format!(
"Test failed: result {} below threshold {}",
result, self.config.threshold
).into());
}
log::info!("Test passed: result = {}", result);
Ok(())
}
}
// Add your helper methods
impl MyNewTest {
fn perform_my_test(&self) -> Result<i32, Box<dyn std::error::Error>> {
// Your test logic
Ok(42)
}
}
Step 6: Set Test Type¶
impl TestDetails for MyNewTest {
fn test_type(&self) -> TestType {
TestType::Pbit // Choose: Pbit, Cbit, or Fbit
}
}
Test type guidelines: - Pbit - Quick validation, run at startup - Cbit - Ongoing monitoring, run periodically - Fbit - Comprehensive testing, may be destructive
Step 7: Build and Test¶
# Build the plugin
cargo build
# Output location
ls -l ../target/debug/libmy_new_test.so
# Create test configuration
mkdir -p ../bit_manager/config
cat > ../bit_manager/config/my_new_test.toml << EOF
[my_new_test]
enabled = true
threshold = 42
EOF
# Run test
cd ..
BIT_TEST_PATH="./target/debug" \
BIT_CONFIG_PATH="./bit_manager/config" \
./target/debug/bit-manager -t my_new_test -o
Template Walkthrough¶
Configuration Loading¶
The template shows standard TOML loading:
impl MyNewTest {
pub fn new(config_path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
// Read config file
let config_content = std::fs::read_to_string(config_path)?;
// Parse TOML
let config: MyNewTestConfig = toml::from_str(&config_content)?;
// Create instance
Ok(Self { config })
}
}
Error handling:
- File not found → std::fs::read_to_string returns error
- Invalid TOML → toml::from_str returns error
- Both propagate to caller via ? operator
Plugin Entry Point¶
The create_plugin! macro generates the C-compatible entry point:
Expands to:
#[no_mangle]
pub extern "C" fn create_test(
config_path: *const std::os::raw::c_char
) -> *mut dyn Test {
// Convert C string to Rust Path
// Call MyNewTest::new(path)
// Return boxed trait object
}
This allows BIT Manager to load the plugin dynamically.
Test Execution Flow¶
impl TestRun for MyNewTest {
fn run(&self) -> Result<(), Box<dyn std::error::Error>> {
// 1. Log start
log::info!("Starting {}", self.name());
// 2. Validate enabled
if !self.enabled() {
log::debug!("Test disabled, skipping");
return Ok(());
}
// 3. Run test logic
match self.execute_test() {
Ok(()) => {
log::info!("Test passed");
Ok(())
}
Err(e) => {
log::error!("Test failed: {}", e);
Err(e)
}
}
}
}
Advanced Patterns¶
Multi-Device Testing¶
#[derive(Debug, Deserialize)]
pub struct DeviceConfig {
name: String,
enabled: bool,
}
#[derive(Debug, Deserialize)]
pub struct MyTestConfig {
enabled: bool,
device: Vec<DeviceConfig>,
}
impl TestRun for MyTest {
fn run(&self) -> Result<(), Box<dyn std::error::Error>> {
// Test each device
for device in &self.config.device {
if !device.enabled {
log::debug!("Skipping disabled device: {}", device.name);
continue;
}
log::info!("Testing device: {}", device.name);
self.test_device(device)?;
}
Ok(())
}
}
Threshold-Based Validation¶
#[derive(Debug, Deserialize)]
pub struct Thresholds {
warning: i32,
critical: i32,
}
#[derive(Debug, Deserialize)]
pub struct MyTestConfig {
enabled: bool,
thresholds: Thresholds,
}
impl TestRun for MyTest {
fn run(&self) -> Result<(), Box<dyn std::error::Error>> {
let value = self.read_sensor()?;
if value >= self.config.thresholds.critical {
return Err(format!(
"Critical: {} >= {}",
value, self.config.thresholds.critical
).into());
}
if value >= self.config.thresholds.warning {
log::warn!(
"Warning: {} >= {}",
value, self.config.thresholds.warning
);
}
log::info!("Value {} within limits", value);
Ok(())
}
}
Continuous Monitoring¶
For CBIT tests, add interval support:
#[derive(Debug, Deserialize)]
pub struct MyCbitConfig {
enabled: bool,
interval_secs: u64,
threshold: i32,
}
impl TestRun for MyCbitTest {
fn run(&self) -> Result<(), Box<dyn std::error::Error>> {
log::info!("CBIT monitoring started (interval: {}s)",
self.config.interval_secs);
loop {
// Perform periodic check
match self.check_status() {
Ok(status) => {
log::debug!("Status check: {:?}", status);
if status > self.config.threshold {
log::warn!("Threshold exceeded: {}", status);
}
}
Err(e) => {
log::error!("Status check failed: {}", e);
}
}
// Sleep until next check
std::thread::sleep(
std::time::Duration::from_secs(self.config.interval_secs)
);
}
}
}
Adding Learn Function¶
Create interactive configuration generator in tools/src/learn.rs:
pub fn my_new_test_learn() -> Result<(), Box<dyn std::error::Error>> {
use crate::common::*;
println!("\n=== My New Test Configuration ===");
// 1. Basic settings
let enabled = prompt_bool("Enable my_new_test?", true)?;
let threshold = prompt_number("Threshold value:", 42)?;
// 2. Detect hardware
println!("Detecting devices...");
let devices = detect_my_devices()?;
if devices.is_empty() {
println!("⚠ No devices detected");
println!("Test will be enabled but may not function");
} else {
println!("✓ Found {} device(s)", devices.len());
}
// 3. Generate config
let mut config = format!(
"[my_new_test]\nenabled = {}\nthreshold = {}\n",
enabled, threshold
);
// 4. Add device entries
for device in devices {
config.push_str(&format!(
"\n[[device]]\nname = \"{}\"\nenabled = true\n",
device
));
}
// 5. Write to file
let config_path = get_config_path("my_new_test.toml")?;
std::fs::write(&config_path, config)?;
println!("\n✓ Configuration saved to {}", config_path.display());
Ok(())
}
// Helper: Detect hardware
fn detect_my_devices() -> Result<Vec<String>, Box<dyn std::error::Error>> {
// Example: scan /sys/class/my_device/
let sys_path = "/sys/class/my_device";
if !Path::new(sys_path).exists() {
return Ok(Vec::new());
}
let mut devices = Vec::new();
for entry in std::fs::read_dir(sys_path)? {
let entry = entry?;
devices.push(entry.file_name().to_string_lossy().to_string());
}
Ok(devices)
}
Register learn function:
// In tools/src/learn.rs run_learn()
pub fn run_learn(test_name: &str) -> Result<(), Box<dyn std::error::Error>> {
match test_name {
"my_new_test" => my_new_test_learn(),
// ... other tests ...
_ => Err(format!("Unknown test: {}", test_name).into()),
}
}
Workspace Integration¶
Add to Workspace Cargo.toml¶
Edit /home/zen/local/bit/Cargo.toml:
Build with Workspace¶
# Build all tests including new one
cargo build
# Build only new test
cargo build -p my_new_test
# Release build
cargo build --release -p my_new_test
Testing Checklist¶
- [ ] Configuration structure defined
- [ ] TOML deserialization working
- [ ] Test traits implemented
- [ ] Plugin entry point macro used
- [ ] Test compiles without warnings
- [ ] Configuration file created
- [ ] Test runs successfully
- [ ] Test produces correct pass/fail results
- [ ] Logging statements added
- [ ] Learn function created (optional)
- [ ] Learn function registered
- [ ] Documentation added
Common Issues¶
Plugin Not Loading¶
Symptom: bit-manager doesn't find your test
Solutions:
1. Check filename: must be lib<name>.so
2. Verify BIT_TEST_PATH points to correct directory
3. Check crate-type = ["cdylib"] in Cargo.toml
4. Ensure create_plugin! macro is present
Configuration Not Found¶
Symptom: Error loading config file
Solutions:
1. Verify file named <test_name>.toml
2. Check BIT_CONFIG_PATH environment variable
3. Ensure config file in correct directory
4. Test TOML syntax with toml::from_str
Test Always Passes/Fails¶
Symptom: Unexpected test results
Solutions:
1. Check enabled() returns correct value
2. Verify error return in run() method
3. Add debug logging to trace execution
4. Test with RUST_LOG=debug
Deserialization Errors¶
Symptom: TOML parsing fails
Solutions:
1. Verify struct field names match TOML keys
2. Check for typos in configuration
3. Ensure all required fields present
4. Use #[serde(default)] for optional fields
Example: Complete Custom Test¶
Here's a complete example testing GPIO pins:
use bit_plugin::{create_plugin, Test, TestDetails, TestRun, TestType};
use serde::Deserialize;
use std::path::Path;
#[derive(Debug, Deserialize)]
pub struct GpioPin {
pin: u32,
expected_state: String,
}
#[derive(Debug, Deserialize)]
pub struct MyGpioTestConfig {
enabled: bool,
gpio: Vec<GpioPin>,
}
pub struct MyGpioTest {
config: MyGpioTestConfig,
}
impl MyGpioTest {
pub fn new(config_path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(config_path)?;
let config = toml::from_str(&content)?;
Ok(Self { config })
}
fn test_gpio(&self, gpio: &GpioPin) -> Result<(), Box<dyn std::error::Error>> {
let path = format!("/sys/class/gpio/gpio{}/value", gpio.pin);
let value = std::fs::read_to_string(&path)?
.trim()
.to_string();
let expected = match gpio.expected_state.as_str() {
"high" => "1",
"low" => "0",
_ => return Err("Invalid state".into()),
};
if value != expected {
return Err(format!(
"GPIO {} state mismatch: got {}, expected {}",
gpio.pin, value, expected
).into());
}
Ok(())
}
}
impl Test for MyGpioTest {
fn name(&self) -> &str { "my_gpio_test" }
fn enabled(&self) -> bool { self.config.enabled }
fn description(&self) -> &str { "Custom GPIO pin test" }
}
impl TestRun for MyGpioTest {
fn run(&self) -> Result<(), Box<dyn std::error::Error>> {
log::info!("Testing {} GPIO pins", self.config.gpio.len());
for gpio in &self.config.gpio {
log::debug!("Testing GPIO {}", gpio.pin);
self.test_gpio(gpio)?;
}
log::info!("All GPIO tests passed");
Ok(())
}
}
impl TestDetails for MyGpioTest {
fn test_type(&self) -> TestType { TestType::Pbit }
}
create_plugin!(MyGpioTest, MyGpioTest::new);
Configuration:
[my_gpio_test]
enabled = true
[[gpio]]
pin = 17
expected_state = "high"
[[gpio]]
pin = 27
expected_state = "low"
Next Steps¶
- Creating Tests - Detailed development guide
- Test Reference - Example implementations
- Running Tests - Test execution
- Configuration - Config file format