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.

pub trait TestDetails {
    /// Returns the test type classification
    fn test_type(&self) -> TestType;
}

Usage:

impl TestDetails for MyTest {
    fn test_type(&self) -> TestType {
        TestType::Pbit
    }
}


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.

create_plugin!(TestStruct, TestStruct::constructor);

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:

[my_test]
enabled = true
threshold = 42

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:

[my_test]
enabled = true

[my_test.thresholds]
min = 0.0
max = 100.0

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:

# Generate docs
cargo doc --open

# Document private items
cargo doc --document-private-items --open

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

External Resources