API Reference¶
Rust API documentation for the BIT SDK.
The BIT SDK provides a plugin-based architecture for creating hardware tests. This page documents the core APIs and traits used to develop custom tests.
Core Crate: bit_plugin¶
The bit_plugin crate defines the fundamental traits and types that all BIT tests must implement.
Traits¶
Test¶
Provides test metadata and lifecycle control.
pub trait Test {
/// Returns the unique name/identifier for this test
fn name(&self) -> &str;
/// Returns whether this test is enabled
fn enabled(&self) -> bool;
/// Returns a human-readable description (optional)
fn description(&self) -> &str {
"No description provided"
}
/// Returns the test version string (optional)
fn version(&self) -> &str {
"unknown"
}
}
Usage:
impl Test for MyTest {
fn name(&self) -> &str {
"my_test"
}
fn enabled(&self) -> bool {
self.config.enabled
}
fn description(&self) -> &str {
"Tests my hardware functionality"
}
fn version(&self) -> &str {
env!("CARGO_PKG_VERSION")
}
}
TestRun¶
Defines test execution logic.
pub trait TestRun {
/// Execute the test
/// Returns Ok(()) on success, Err on failure
fn run(&self) -> Result<(), Box<dyn std::error::Error>>;
}
Usage:
impl TestRun for MyTest {
fn run(&self) -> Result<(), Box<dyn std::error::Error>> {
log::info!("Running test");
// Test logic here
let result = self.perform_test()?;
// Validation
if result < self.config.threshold {
return Err("Test failed".into());
}
Ok(())
}
}
TestDetails¶
Classifies test type and characteristics.
Usage:
Types¶
TestType¶
Enumeration of test classifications.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TestType {
/// Power-On Built-In Test
/// Run once at system startup
Pbit,
/// Continuous Built-In Test
/// Run periodically during operation
Cbit,
/// Factory Built-In Test
/// Comprehensive testing for factory/depot
Fbit,
}
Usage:
let test_type = TestType::Pbit;
match test_type {
TestType::Pbit => println!("Power-on test"),
TestType::Cbit => println!("Continuous test"),
TestType::Fbit => println!("Factory test"),
}
Macros¶
create_plugin!¶
Generates the C-compatible plugin entry point.
Parameters:
- TestStruct: Your test struct type
- TestStruct::constructor: Function with signature fn(&Path) -> Result<TestStruct, Error>
Expansion:
The macro expands to create an extern "C" function that:
1. Converts C string to Rust Path
2. Calls your constructor function
3. Returns a boxed trait object pointer
Usage:
pub struct MyTest {
config: MyTestConfig,
}
impl MyTest {
pub fn new(config_path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
// Load and parse config
let content = std::fs::read_to_string(config_path)?;
let config = toml::from_str(&content)?;
Ok(Self { config })
}
}
// Generate plugin entry point
create_plugin!(MyTest, MyTest::new);
Configuration API¶
Tests use TOML for configuration with serde deserialization.
Basic Configuration¶
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct MyTestConfig {
enabled: bool,
threshold: i32,
}
TOML:
Array Configuration¶
#[derive(Debug, Deserialize)]
pub struct Device {
name: String,
address: String,
}
#[derive(Debug, Deserialize)]
pub struct MyTestConfig {
enabled: bool,
device: Vec<Device>,
}
TOML:
[my_test]
enabled = true
[[device]]
name = "dev0"
address = "0x1234"
[[device]]
name = "dev1"
address = "0x5678"
Nested Configuration¶
#[derive(Debug, Deserialize)]
pub struct Thresholds {
min: f64,
max: f64,
}
#[derive(Debug, Deserialize)]
pub struct MyTestConfig {
enabled: bool,
thresholds: Thresholds,
}
TOML:
Optional Fields¶
#[derive(Debug, Deserialize)]
pub struct MyTestConfig {
enabled: bool,
#[serde(default = "default_threshold")]
threshold: i32,
#[serde(default)]
optional_field: Option<String>,
}
fn default_threshold() -> i32 {
42
}
Logging API¶
BIT uses the log crate for structured logging, backed by tracing.
Log Levels¶
use log::{trace, debug, info, warn, error};
impl TestRun for MyTest {
fn run(&self) -> Result<(), Box<dyn std::error::Error>> {
trace!("Entering run()");
debug!("Config: {:?}", self.config);
info!("Starting test");
warn!("Value near threshold");
error!("Test failed: {}", error);
Ok(())
}
}
Environment Control¶
# Set log level
RUST_LOG=debug ./bit-manager -t my_test -o
# Per-module logging
RUST_LOG=my_test=trace ./bit-manager -t my_test -o
# Multiple modules
RUST_LOG=my_test=trace,bit_manager=info ./bit-manager -t my_test -o
Error Handling¶
Error Types¶
Use Box<dyn std::error::Error> for flexibility:
impl TestRun for MyTest {
fn run(&self) -> Result<(), Box<dyn std::error::Error>> {
// IO errors
let content = std::fs::read_to_string(path)?;
// Parse errors
let value: i32 = content.trim().parse()?;
// Custom errors
if value < 0 {
return Err("Value cannot be negative".into());
}
Ok(())
}
}
Error Context¶
Add context to errors:
use std::fs;
impl TestRun for MyTest {
fn run(&self) -> Result<(), Box<dyn std::error::Error>> {
let path = "/sys/class/device/value";
let content = fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path, e))?;
let value: i32 = content.trim().parse()
.map_err(|e| format!("Invalid value in {}: {}", path, e))?;
Ok(())
}
}
Common Patterns¶
Sysfs Access¶
fn read_sysfs_value(&self, path: &str) -> Result<String, Box<dyn std::error::Error>> {
std::fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path, e))?
.trim()
.to_string()
}
fn write_sysfs_value(&self, path: &str, value: &str) -> Result<(), Box<dyn std::error::Error>> {
std::fs::write(path, value)
.map_err(|e| format!("Failed to write {}: {}", path, e))
}
Command Execution¶
fn run_command(&self, cmd: &str, args: &[&str]) -> Result<String, Box<dyn std::error::Error>> {
let output = std::process::Command::new(cmd)
.args(args)
.output()
.map_err(|e| format!("Failed to execute {}: {}", cmd, e))?;
if !output.status.success() {
return Err(format!(
"Command '{}' failed with exit code {}",
cmd, output.status
).into());
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
Device Enumeration¶
fn enumerate_devices(&self, sys_path: &str) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let path = Path::new(sys_path);
if !path.exists() {
return Ok(Vec::new());
}
let mut devices = Vec::new();
for entry in std::fs::read_dir(path)? {
let entry = entry?;
if entry.file_type()?.is_dir() {
devices.push(entry.file_name().to_string_lossy().to_string());
}
}
Ok(devices)
}
File Checksum¶
use std::fs::File;
use std::io::{BufReader, Read};
fn calculate_checksum(&self, path: &str) -> Result<u64, Box<dyn std::error::Error>> {
let file = File::open(path)?;
let mut reader = BufReader::new(file);
let mut buffer = [0u8; 8192];
let mut hash: u64 = 0;
loop {
let bytes_read = reader.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
for &byte in &buffer[..bytes_read] {
hash = hash.wrapping_mul(31).wrapping_add(byte as u64);
}
}
Ok(hash)
}
Plugin Loading (Internal)¶
Note: This section documents BIT Manager internals for reference. Plugin authors don't need to implement this.
Discovery¶
// Scan plugin directory
let plugin_dir = env::var("BIT_TEST_PATH")?;
for entry in fs::read_dir(plugin_dir)? {
let path = entry?.path();
if path.extension() == Some("so") {
load_plugin(&path)?;
}
}
Loading¶
// Load shared library
let lib = unsafe { libloading::Library::new(plugin_path)? };
// Get entry point
let create_test: Symbol<unsafe extern "C" fn(*const c_char) -> *mut dyn Test> =
unsafe { lib.get(b"create_test")? };
// Call constructor
let config_path_cstr = CString::new(config_path.to_str().unwrap())?;
let test = unsafe { create_test(config_path_cstr.as_ptr()) };
Execution¶
// Run test
match test.run() {
Ok(()) => println!("✓ Test passed: {}", test.name()),
Err(e) => println!("✗ Test failed: {}: {}", test.name(), e),
}
Testing Best Practices¶
Unit Testing¶
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_threshold_validation() {
let config = MyTestConfig {
enabled: true,
threshold: 100,
};
let test = MyTest { config };
// Test logic
assert_eq!(test.validate_value(50), Ok(()));
assert!(test.validate_value(150).is_err());
}
}
Integration Testing¶
#[cfg(test)]
mod integration_tests {
#[test]
fn test_config_loading() {
let config_path = Path::new("test_config.toml");
std::fs::write(config_path, "[my_test]\nenabled = true\n").unwrap();
let test = MyTest::new(config_path).unwrap();
assert!(test.enabled());
std::fs::remove_file(config_path).unwrap();
}
}
Generated Documentation¶
Rustdoc¶
Generate API documentation for your test:
Doc Comments¶
/// Custom test for hardware validation
///
/// This test performs comprehensive checks on:
/// - Device presence
/// - Configuration validity
/// - Functional operation
///
/// # Examples
///
/// ```rust
/// let test = MyTest::new(config_path)?;
/// test.run()?;
/// ```
pub struct MyTest {
/// Test configuration loaded from TOML
config: MyTestConfig,
}
impl MyTest {
/// Creates a new test instance
///
/// # Arguments
///
/// * `config_path` - Path to TOML configuration file
///
/// # Errors
///
/// Returns error if:
/// - Config file not found
/// - TOML parsing fails
/// - Invalid configuration values
pub fn new(config_path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
// Implementation
}
}
Dependencies¶
Common crates used in BIT tests:
[dependencies]
# Core
bit_plugin = { path = "../bit_plugin" }
# Serialization
serde = { version = "1.0", features = ["derive"] }
toml = "0.8"
# Logging
log = "0.4"
# Error handling
anyhow = "1.0" # Convenient error handling
thiserror = "1.0" # Custom error types
# Async (if needed)
tokio = { version = "1.0", features = ["full"] }
# System utilities
nix = "0.27" # Unix system calls
libc = "0.2" # C library bindings
# Hardware access
rppal = "0.14" # Raspberry Pi GPIO
Next Steps¶
- Creating Tests - Development guide
- Test Template - Template walkthrough
- Test Reference - Example implementations
- Configuration - Config format