Skip to content

Conversation

@Coldaine
Copy link
Owner

@Coldaine Coldaine commented Dec 23, 2025

User description

Summary

Rebased and integrated from Jules draft PR #268.

Changes

  • Add unit tests for EnigoInjector with test harness
  • Add unit tests for KdotoolInjector with test harness
  • Tests cover injector initialization, text injection, and error handling

Testing

cargo test -p coldvox-text-injection

Closes #268


PR Type

Tests, Enhancement


Description

  • Refactored EnigoInjector to extract testable logic into generic functions

    • Created type_text_logic() and trigger_paste_logic() methods accepting Keyboard trait
  • Added comprehensive unit tests for EnigoInjector with MockEnigo implementation

    • Tests cover text typing, special characters, and error handling
  • Added comprehensive unit tests for KdotoolInjector with mock script approach

    • Tests cover injector initialization, window operations, and command failures
  • Implemented PathGuard RAII pattern for safe temporary PATH manipulation in tests


Diagram Walkthrough

flowchart LR
  EnigoInjector["EnigoInjector<br/>Refactored Logic"]
  TypeTextLogic["type_text_logic<br/>Generic over Keyboard"]
  TriggerPasteLogic["trigger_paste_logic<br/>Generic over Keyboard"]
  MockEnigo["MockEnigo<br/>Test Implementation"]
  EnigoTests["EnigoInjector Tests<br/>Unit & Integration"]
  
  KdotoolInjector["KdotoolInjector<br/>Unchanged Logic"]
  MockKdotool["Mock kdotool<br/>Script in TempDir"]
  KdotoolTests["KdotoolInjector Tests<br/>Window & Command Tests"]
  
  EnigoInjector -- "extracts" --> TypeTextLogic
  EnigoInjector -- "extracts" --> TriggerPasteLogic
  TypeTextLogic -- "tested by" --> MockEnigo
  TriggerPasteLogic -- "tested by" --> MockEnigo
  MockEnigo -- "implements" --> EnigoTests
  
  KdotoolInjector -- "tested with" --> MockKdotool
  MockKdotool -- "enables" --> KdotoolTests
Loading

File Walkthrough

Relevant files
Tests
enigo_injector.rs
Refactor enigo logic and add comprehensive unit tests       

crates/coldvox-text-injection/src/enigo_injector.rs

  • Extracted keyboard manipulation logic into two generic functions:
    type_text_logic() and trigger_paste_logic() that accept any type
    implementing the Keyboard trait
  • Refactored type_text() and trigger_paste() async methods to call the
    new generic logic functions
  • Added comprehensive test module with MockEnigo struct implementing the
    Keyboard trait for isolated testing
  • Tests cover simple text typing, special character handling (space,
    newline, tab), error scenarios, and platform-specific paste operations
    (macOS vs non-macOS)
+203/-61
kdotool_injector.rs
Add comprehensive unit tests for kdotool injector               

crates/coldvox-text-injection/src/kdotool_injector.rs

  • Added comprehensive test module with helper functions for creating
    mock kdotool scripts
  • Implemented PathGuard RAII struct to safely manage temporary PATH
    environment variable modifications during tests
  • Added tests covering injector initialization with
    available/unavailable kdotool, window retrieval, window activation,
    window focusing, and ensure_focus operations
  • Tests use temporary directories and mock shell scripts to simulate
    kdotool behavior without requiring the actual binary
+152/-0 

Copilot AI review requested due to automatic review settings December 23, 2025 22:12
@qodo-code-review
Copy link

qodo-code-review bot commented Dec 23, 2025

ⓘ Your approaching your monthly quota for Qodo. Upgrade your plan

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Detailed error strings: New error mapping includes underlying backend error details in
InjectionError::MethodFailed(format!(...{}...)), which may be user-visible depending on
how InjectionError is surfaced.

Referred Code
/// Inner logic for typing text, generic over the Keyboard trait for testing.
fn type_text_logic<K: Keyboard>(enigo: &mut K, text: &str) -> Result<(), InjectionError> {
    // Type each character with a small delay
    for c in text.chars() {
        match c {
            ' ' => enigo
                .key(Key::Space, Direction::Click)
                .map_err(|e| InjectionError::MethodFailed(format!("Failed to type space: {}", e)))?,
            '\n' => enigo
                .key(Key::Return, Direction::Click)
                .map_err(|e| InjectionError::MethodFailed(format!("Failed to type enter: {}", e)))?,
            '\t' => enigo
                .key(Key::Tab, Direction::Click)
                .map_err(|e| InjectionError::MethodFailed(format!("Failed to type tab: {}", e)))?,
            _ => {
                // Use text method for all other characters
                enigo
                    .text(&c.to_string())
                    .map_err(|e| InjectionError::MethodFailed(format!("Failed to type text: {}", e)))?;
            }
        }


 ... (clipped 52 lines)

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-free-for-open-source-projects

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🔴
Concurrent PATH manipulation

Description: The PathGuard struct modifies the global PATH environment variable during tests, which can
cause race conditions and unpredictable behavior when tests run concurrently, potentially
affecting other tests or system operations.
kdotool_injector.rs [153-170]

Referred Code
struct PathGuard {
    original_path: String,
}

impl PathGuard {
    fn new(temp_dir: &PathBuf) -> Self {
        let original_path = env::var("PATH").unwrap_or_default();
        let new_path = format!("{}:{}", temp_dir.to_str().unwrap(), original_path);
        env::set_var("PATH", new_path);
        Self { original_path }
    }
}

impl Drop for PathGuard {
    fn drop(&mut self) {
        env::set_var("PATH", &self.original_path);
    }
}
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-free-for-open-source-projects
Copy link

qodo-free-for-open-source-projects bot commented Dec 23, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
kdotool tests will be flaky due to parallel PATH modification

The KdotoolInjector tests modify the PATH environment variable, which is unsafe
when tests run in parallel. This can cause race conditions and flaky tests. Use
a crate like serial_test to serialize these tests and prevent them from
interfering with each other.

Examples:

crates/coldvox-text-injection/src/kdotool_injector.rs [153-170]
    struct PathGuard {
        original_path: String,
    }

    impl PathGuard {
        fn new(temp_dir: &PathBuf) -> Self {
            let original_path = env::var("PATH").unwrap_or_default();
            let new_path = format!("{}:{}", temp_dir.to_str().unwrap(), original_path);
            env::set_var("PATH", new_path);
            Self { original_path }

 ... (clipped 8 lines)
crates/coldvox-text-injection/src/kdotool_injector.rs [172-180]
    #[tokio::test]
    async fn test_kdotool_injector_new_available() {
        let (_script, dir) = create_mock_kdotool("exit 0");
        let _guard = PathGuard::new(&dir.path().to_path_buf());

        let config = InjectionConfig::default();
        let injector = KdotoolInjector::new(config);
        assert!(injector.is_available);
    }

Solution Walkthrough:

Before:

// In kdotool_injector.rs

struct PathGuard {
    original_path: String,
}

impl PathGuard {
    fn new(temp_dir: &PathBuf) -> Self {
        let original_path = env::var("PATH").unwrap_or_default();
        let new_path = format!("{}:{}", temp_dir.to_str().unwrap(), original_path);
        env::set_var("PATH", new_path); // This is not safe for parallel tests
        Self { original_path }
    }
}

#[tokio::test]
async fn test_get_active_window_success() {
    let (_script, dir) = create_mock_kdotool("echo '12345'");
    let _guard = PathGuard::new(&dir.path().to_path_buf());
    // ... test logic that depends on the modified PATH
}

After:

// In kdotool_injector.rs
use serial_test::serial;

struct PathGuard {
    original_path: String,
}

impl PathGuard {
    fn new(temp_dir: &PathBuf) -> Self {
        let original_path = env::var("PATH").unwrap_or_default();
        let new_path = format!("{}:{}", temp_dir.to_str().unwrap(), original_path);
        env::set_var("PATH", new_path);
        Self { original_path }
    }
}

#[tokio::test]
#[serial] // Ensures this test does not run in parallel with other `#[serial]` tests
async fn test_get_active_window_success() {
    let (_script, dir) = create_mock_kdotool("echo '12345'");
    let _guard = PathGuard::new(&dir.path().to_path_buf());
    // ... test logic
}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical race condition in the kdotool tests due to parallel modification of the shared PATH environment variable, which will cause flaky and unreliable test results.

High
Possible issue
Ensure modifier key is always released

Refactor trigger_paste_logic to ensure the modifier key (Cmd/Ctrl) is always
released, even if the paste action fails, by separating the key press, click,
and release operations and handling their results individually.

crates/coldvox-text-injection/src/enigo_injector.rs [80-107]

 fn trigger_paste_logic<K: Keyboard>(enigo: &mut K) -> Result<(), InjectionError> {
     // Press platform-appropriate paste shortcut
     #[cfg(target_os = "macos")]
-    {
-        enigo
-            .key(Key::Meta, Direction::Press)
-            .map_err(|e| InjectionError::MethodFailed(format!("Failed to press Cmd: {}", e)))?;
-        enigo
-            .key(Key::Unicode('v'), Direction::Click)
-            .map_err(|e| InjectionError::MethodFailed(format!("Failed to type 'v': {}", e)))?;
-        enigo
-            .key(Key::Meta, Direction::Release)
-            .map_err(|e| InjectionError::MethodFailed(format!("Failed to release Cmd: {}", e)))?;
-    }
+    let (modifier, mod_name) = (Key::Meta, "Cmd");
     #[cfg(not(target_os = "macos"))]
-    {
-        enigo
-            .key(Key::Control, Direction::Press)
-            .map_err(|e| InjectionError::MethodFailed(format!("Failed to press Ctrl: {}", e)))?;
-        enigo
-            .key(Key::Unicode('v'), Direction::Click)
-            .map_err(|e| InjectionError::MethodFailed(format!("Failed to type 'v': {}", e)))?;
-        enigo
-            .key(Key::Control, Direction::Release)
-            .map_err(|e| InjectionError::MethodFailed(format!("Failed to release Ctrl: {}", e)))?;
-    }
-    Ok(())
+    let (modifier, mod_name) = (Key::Control, "Ctrl");
+
+    enigo
+        .key(modifier, Direction::Press)
+        .map_err(|e| InjectionError::MethodFailed(format!("Failed to press {}: {}", mod_name, e)))?;
+
+    let click_result = enigo
+        .key(Key::Unicode('v'), Direction::Click)
+        .map_err(|e| InjectionError::MethodFailed(format!("Failed to type 'v': {}", e)));
+
+    // Always attempt to release the modifier key.
+    let release_result = enigo
+        .key(modifier, Direction::Release)
+        .map_err(|e| InjectionError::MethodFailed(format!("Failed to release {}: {}", mod_name, e)));
+
+    // Return the first error encountered, prioritizing the click error.
+    click_result.and(release_result)
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a bug where a modifier key could be left pressed if an intermediate operation fails, and provides a robust solution that guarantees the key is released.

Medium
General
Use RAII guard for environment variables

In the test_kdotool_injector_new_not_available test, replace the manual
modification of the PATH environment variable with the PathGuard RAII helper to
ensure it is always restored, even on panic.

crates/coldvox-text-injection/src/kdotool_injector.rs [182-192]

 #[tokio::test]
 async fn test_kdotool_injector_new_not_available() {
-    let original_path = env::var("PATH").unwrap_or_default();
-    env::set_var("PATH", "/tmp/non-existent-dir");
+    // Create a dummy directory that we know doesn't contain kdotool.
+    let dir = tempfile::tempdir().unwrap();
+    let _guard = PathGuard::new(&dir.path().to_path_buf());
 
     let config = InjectionConfig::default();
     let injector = KdotoolInjector::new(config);
     assert!(!injector.is_available);
-
-    env::set_var("PATH", original_path);
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that the test is not panic-safe and should use the PathGuard RAII helper introduced in the same PR, which improves test robustness and consistency.

Medium
  • Update

@qodo-code-review
Copy link

ⓘ Your approaching your monthly quota for Qodo. Upgrade your plan

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Ensure modifier key is always released

In trigger_paste_logic, ensure the modifier key is always released, even if the
paste action fails, to prevent it from getting stuck in a pressed state.

crates/coldvox-text-injection/src/enigo_injector.rs [82-105]

 #[cfg(target_os = "macos")]
 {
     enigo
         .key(Key::Meta, Direction::Press)
         .map_err(|e| InjectionError::MethodFailed(format!("Failed to press Cmd: {}", e)))?;
-    enigo
+    let result = enigo
         .key(Key::Unicode('v'), Direction::Click)
-        .map_err(|e| InjectionError::MethodFailed(format!("Failed to type 'v': {}", e)))?;
-    enigo
+        .map_err(|e| InjectionError::MethodFailed(format!("Failed to type 'v': {}", e)));
+    let release_result = enigo
         .key(Key::Meta, Direction::Release)
-        .map_err(|e| InjectionError::MethodFailed(format!("Failed to release Cmd: {}", e)))?;
+        .map_err(|e| InjectionError::MethodFailed(format!("Failed to release Cmd: {}", e)));
+    result.and(release_result)?;
 }
 #[cfg(not(target_os = "macos"))]
 {
     enigo
         .key(Key::Control, Direction::Press)
         .map_err(|e| InjectionError::MethodFailed(format!("Failed to press Ctrl: {}", e)))?;
-    enigo
+    let result = enigo
         .key(Key::Unicode('v'), Direction::Click)
-        .map_err(|e| InjectionError::MethodFailed(format!("Failed to type 'v': {}", e)))?;
-    enigo
+        .map_err(|e| InjectionError::MethodFailed(format!("Failed to type 'v': {}", e)));
+    let release_result = enigo
         .key(Key::Control, Direction::Release)
-        .map_err(|e| InjectionError::MethodFailed(format!("Failed to release Ctrl: {}", e)))?;
+        .map_err(|e| InjectionError::MethodFailed(format!("Failed to release Ctrl: {}", e)));
+    result.and(release_result)?;
 }
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical bug where a failure during a paste operation could leave a modifier key pressed system-wide, and provides a correct solution.

High
Map spawn_blocking errors properly

When a spawn_blocking task fails, map the JoinError to a more descriptive
InjectionError::Other instead of returning a generic timeout error.

crates/coldvox-text-injection/src/enigo_injector.rs [75]

-Err(_) => Err(InjectionError::Timeout(0)), // Spawn failed
+Err(e) => Err(InjectionError::Other(format!("Task join error: {}", e))), // Spawn failed

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion improves error handling by providing a more descriptive error message when a spawn_blocking task fails, which aids in debugging.

Low
High-level
Abstract command execution for kdotool tests

Instead of using mock shell scripts and modifying the PATH for KdotoolInjector
tests, abstract the command execution into a trait. This allows for a mock
implementation, leading to more robust and isolated unit tests.

Examples:

crates/coldvox-text-injection/src/kdotool_injector.rs [153-170]
    struct PathGuard {
        original_path: String,
    }

    impl PathGuard {
        fn new(temp_dir: &PathBuf) -> Self {
            let original_path = env::var("PATH").unwrap_or_default();
            let new_path = format!("{}:{}", temp_dir.to_str().unwrap(), original_path);
            env::set_var("PATH", new_path);
            Self { original_path }

 ... (clipped 8 lines)
crates/coldvox-text-injection/src/kdotool_injector.rs [194-203]
    #[tokio::test]
    async fn test_get_active_window_success() {
        let (_script, dir) = create_mock_kdotool("echo '12345'");
        let _guard = PathGuard::new(&dir.path().to_path_buf());

        let config = InjectionConfig::default();
        let injector = KdotoolInjector::new(config);
        let window_id = injector.get_active_window().await.unwrap();
        assert_eq!(window_id, "12345");
    }

Solution Walkthrough:

Before:

// In kdotool_injector.rs tests

// RAII guard to modify PATH for the duration of a test
struct PathGuard {
    original_path: String,
}
impl PathGuard {
    fn new(temp_dir: &PathBuf) -> Self {
        let original_path = env::var("PATH").unwrap_or_default();
        let new_path = format!("{}:{}", temp_dir.to_str().unwrap(), original_path);
        env::set_var("PATH", new_path); // Modifies global state
        Self { original_path }
    }
}

#[tokio::test]
async fn test_get_active_window_success() {
    let (_script, dir) = create_mock_kdotool("echo '12345'"); // Creates script on disk
    let _guard = PathGuard::new(&dir.path().to_path_buf()); // Modifies PATH
    let injector = KdotoolInjector::new(config);
    let window_id = injector.get_active_window().await.unwrap(); // Spawns real process
    assert_eq!(window_id, "12345");
}

After:

// In kdotool_injector.rs

// Abstract command execution
#[async_trait]
trait CommandRunner {
    async fn run(&self, args: &[&str]) -> Result<Output, Error>;
}

// Inject the dependency
pub struct KdotoolInjector<R: CommandRunner> {
    runner: R,
    // ...
}

// In tests
struct MockRunner { /* ... */ }
// ... implement CommandRunner for MockRunner

#[tokio::test]
async fn test_get_active_window_success() {
    let runner = MockRunner::new_with_stdout("12345");
    let injector = KdotoolInjector::new(config, runner);
    let window_id = injector.get_active_window().await.unwrap(); // Uses mock runner
    assert_eq!(window_id, "12345");
}
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that manipulating the PATH environment variable can cause flaky parallel tests and proposes a robust abstraction, mirroring the testable design already implemented for EnigoInjector in this PR.

Medium
General
Use RAII guard for test cleanup

Refactor the test_kdotool_injector_new_not_available test to use the PathGuard
RAII helper for managing the PATH environment variable, ensuring cleanup occurs
even if the test panics.

crates/coldvox-text-injection/src/kdotool_injector.rs [182-192]

 #[tokio::test]
 async fn test_kdotool_injector_new_not_available() {
-    let original_path = env::var("PATH").unwrap_or_default();
-    env::set_var("PATH", "/tmp/non-existent-dir");
+    // Create a dummy temp dir that won't contain kdotool
+    let dir = tempfile::tempdir().unwrap();
+    let _guard = PathGuard::new(&dir.path().to_path_buf());
 
     let config = InjectionConfig::default();
     let injector = KdotoolInjector::new(config);
     assert!(!injector.is_available);
-
-    env::set_var("PATH", original_path);
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out a test fragility issue and proposes using an existing RAII guard (PathGuard) from the PR to make the test panic-safe, improving test suite robustness and consistency.

Medium
Make PATH modification portable

Use env::split_paths and env::join_paths for modifying the PATH environment
variable to ensure cross-platform compatibility.

crates/coldvox-text-injection/src/kdotool_injector.rs [160-161]

-let new_path = format!("{}:{}", temp_dir.to_str().unwrap(), original_path);
-env::set_var("PATH", new_path);
+let original_path_os = env::var_os("PATH").unwrap_or_default();
+let mut paths = env::split_paths(&original_path_os).collect::<Vec<_>>();
+paths.insert(0, temp_dir.clone());
+let new_path = env::join_paths(paths).unwrap();
+env::set_var("PATH", &new_path);
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly recommends using standard library functions for PATH manipulation, which is more robust and platform-agnostic than manual string concatenation.

Low
  • More

Comment on lines +153 to +163
struct PathGuard {
original_path: String,
}

impl PathGuard {
fn new(temp_dir: &PathBuf) -> Self {
let original_path = env::var("PATH").unwrap_or_default();
let new_path = format!("{}:{}", temp_dir.to_str().unwrap(), original_path);
env::set_var("PATH", new_path);
Self { original_path }
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 CRITICAL: Concurrent PATH manipulation

The PathGuard struct modifies the global PATH environment variable during tests, which can cause race conditions and unpredictable behavior when tests run concurrently, potentially affecting other tests or system operations.

Consider using a more isolated approach for testing, such as:

  1. Using a separate test harness that doesn't modify global state
  2. Using a mock filesystem or containerization for tests
  3. Running these tests sequentially with proper synchronization

This issue was also flagged by the Qodo compliance check.

Comment on lines +35 to +42
// Type each character with a small delay
for c in text.chars() {
match c {
' ' => enigo
.key(Key::Space, Direction::Click)
.map_err(|e| InjectionError::MethodFailed(format!("Failed to type space: {}", e)))?,
'\n' => enigo
.key(Key::Return, Direction::Click)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ WARNING: Detailed error strings

New error mapping includes underlying backend error details in InjectionError::MethodFailed(format!(...{}...)), which may be user-visible depending on how InjectionError is surfaced.

Consider whether exposing detailed system error messages to end users is appropriate from a security standpoint. You might want to log the detailed error internally and return a more generic message to users.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds comprehensive unit tests for the EnigoInjector and KdotoolInjector text injection backends, closing the work started in draft PR #268.

Key Changes:

  • Refactored EnigoInjector to extract testable logic into separate functions (type_text_logic and trigger_paste_logic)
  • Added mock-based unit tests for EnigoInjector with a custom MockEnigo test harness
  • Added integration-style tests for KdotoolInjector using mock shell scripts and PATH manipulation

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
crates/coldvox-text-injection/src/enigo_injector.rs Refactored typing and paste logic into testable functions; added comprehensive unit tests with platform-specific testing for macOS and non-macOS paste shortcuts
crates/coldvox-text-injection/src/kdotool_injector.rs Added integration tests using mock kdotool scripts with PATH manipulation to test window management functionality

Comment on lines +312 to +313
let injector = EnigoInjector::new(config);
assert_eq!(injector.config, config);
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test compares injector.config with config, but InjectionConfig does not derive PartialEq. This comparison will fail to compile. Consider removing this assertion or deriving PartialEq for InjectionConfig.

Suggested change
let injector = EnigoInjector::new(config);
assert_eq!(injector.config, config);
let expected_allow_enigo = config.allow_enigo;
let injector = EnigoInjector::new(config);
assert_eq!(injector.config.allow_enigo, expected_allow_enigo);

Copilot uses AI. Check for mistakes.
Comment on lines +184 to +191
let original_path = env::var("PATH").unwrap_or_default();
env::set_var("PATH", "/tmp/non-existent-dir");

let config = InjectionConfig::default();
let injector = KdotoolInjector::new(config);
assert!(!injector.is_available);

env::set_var("PATH", original_path);
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test manually restores PATH in the cleanup code. However, tests can run concurrently in Rust by default, which means modifying the global PATH environment variable can cause race conditions between tests. Consider using a mutex or running these tests serially with #[serial] (from the serial_test crate), or structuring tests to avoid PATH modification entirely.

Copilot uses AI. Check for mistakes.
Comment on lines +152 to +170
// RAII guard to modify PATH for the duration of a test
struct PathGuard {
original_path: String,
}

impl PathGuard {
fn new(temp_dir: &PathBuf) -> Self {
let original_path = env::var("PATH").unwrap_or_default();
let new_path = format!("{}:{}", temp_dir.to_str().unwrap(), original_path);
env::set_var("PATH", new_path);
Self { original_path }
}
}

impl Drop for PathGuard {
fn drop(&mut self) {
env::set_var("PATH", &self.original_path);
}
}
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PathGuard RAII pattern is used to manage PATH modifications, but tests can run concurrently which means multiple tests could be modifying PATH simultaneously. Consider using a mutex or running these tests serially with #[serial] (from the serial_test crate) to prevent race conditions from concurrent PATH modifications.

Copilot uses AI. Check for mistakes.
Comment on lines +172 to +180
#[tokio::test]
async fn test_kdotool_injector_new_available() {
let (_script, dir) = create_mock_kdotool("exit 0");
let _guard = PathGuard::new(&dir.path().to_path_buf());

let config = InjectionConfig::default();
let injector = KdotoolInjector::new(config);
assert!(injector.is_available);
}
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test creates shell scripts but only sets execute permissions on Unix systems. However, the tests themselves don't have platform-specific guards and will fail on non-Unix platforms (e.g., Windows) because shell scripts cannot be executed. Consider adding #[cfg(unix)] to these tests or using a cross-platform mock approach.

Copilot uses AI. Check for mistakes.
impl PathGuard {
fn new(temp_dir: &PathBuf) -> Self {
let original_path = env::var("PATH").unwrap_or_default();
let new_path = format!("{}:{}", temp_dir.to_str().unwrap(), original_path);
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PATH separator is hardcoded as ":" which is Unix-specific. On Windows, the PATH separator is ";". Since kdotool is likely Unix-only, consider adding #[cfg(unix)] to all these tests or using std::env::consts::PATH_SEPARATOR for cross-platform compatibility.

Suggested change
let new_path = format!("{}:{}", temp_dir.to_str().unwrap(), original_path);
let temp_str = temp_dir.to_str().unwrap();
let new_path = if original_path.is_empty() {
temp_str.to_string()
} else {
format!(
"{}{}{}",
temp_str,
std::env::consts::PATH_SEPARATOR,
original_path
)
};

Copilot uses AI. Check for mistakes.
@qodo-code-review
Copy link

qodo-code-review bot commented Dec 23, 2025

CI Feedback 🧐

(Feedback updated until commit a1353d3)

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: Unit Tests & Golden Master (Hosted) (stable)

Failed stage: Run Moonshine E2E Tests [❌]

Failure summary:

The action failed because the system ran out of disk space while installing Python packages.
Specifically:
- The error occurred during the installation of Python dependencies for the Moonshine
E2E tests
- The error message at line 3686 states: "ERROR: Could not install packages due to an
OSError: [Errno 28] No space left on device"
- This happened while attempting to install large
packages including PyTorch, CUDA libraries, and other ML dependencies
- The installation was trying
to uninstall numpy-2.4.0 and huggingface_hub-1.2.3 and install new versions along with many NVIDIA
CUDA packages when it ran out of space

Note: There was also a non-fatal formatting warning earlier (line 2021) indicating that cargo fmt
detected formatting differences in crates/coldvox-text-injection/src/enigo_injector.rs, but this did
not cause the failure as the script continued with exit 0.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

1893:  �[36;1m  echo "::warning::cargo fmt detected formatting differences. Please run 'cargo fmt --all' locally before committing."�[0m
1894:  �[36;1mfi�[0m
1895:  �[36;1mexit 0�[0m
1896:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
1897:  env:
1898:  CARGO_TERM_COLOR: always
1899:  WHISPER_MODEL_SIZE: tiny
1900:  MIN_FREE_DISK_GB: 10
1901:  MAX_LOAD_AVERAGE: 5
1902:  CARGO_INCREMENTAL: 0
1903:  CARGO_PROFILE_DEV_DEBUG: 0
1904:  RUST_BACKTRACE: short
1905:  RUSTFLAGS: -D warnings
1906:  CARGO_UNSTABLE_SPARSE_REGISTRY: true
1907:  CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
1908:  CACHE_ON_FAILURE: true
1909:  ##[endgroup]
1910:  ##[warning]Diff in /home/runner/work/ColdVox/ColdVox/crates/coldvox-text-injection/src/enigo_injector.rs:35:
1911:  // Type each character with a small delay
1912:  for c in text.chars() {
1913:  match c {
1914:  -                ' ' => enigo
1915:  -                    .key(Key::Space, Direction::Click)
1916:  -                    .map_err(|e| InjectionError::MethodFailed(format!("Failed to type space: {}", e)))?,
1917:  -                '\n' => enigo
1918:  -                    .key(Key::Return, Direction::Click)
1919:  -                    .map_err(|e| InjectionError::MethodFailed(format!("Failed to type enter: {}", e)))?,
1920:  -                '\t' => enigo
1921:  -                    .key(Key::Tab, Direction::Click)
1922:  -                    .map_err(|e| InjectionError::MethodFailed(format!("Failed to type tab: {}", e)))?,
1923:  +                ' ' => enigo.key(Key::Space, Direction::Click).map_err(|e| {
1924:  +                    InjectionError::MethodFailed(format!("Failed to type space: {}", e))
1925:  +                })?,
1926:  +                '\n' => enigo.key(Key::Return, Direction::Click).map_err(|e| {
1927:  +                    InjectionError::MethodFailed(format!("Failed to type enter: {}", e))
1928:  +                })?,
1929:  +                '\t' => enigo.key(Key::Tab, Direction::Click).map_err(|e| {
1930:  +                    InjectionError::MethodFailed(format!("Failed to type tab: {}", e))
1931:  +                })?,
1932:  _ => {
1933:  // Use text method for all other characters
1934:  -                    enigo
1935:  -                        .text(&c.to_string())
1936:  -                        .map_err(|e| InjectionError::MethodFailed(format!("Failed to type text: {}", e)))?;
1937:  +                    enigo.text(&c.to_string()).map_err(|e| {
1938:  +                        InjectionError::MethodFailed(format!("Failed to type text: {}", e))
1939:  +                    })?;
1940:  }
1941:  }
1942:  }
1943:  ##[warning]Diff in /home/runner/work/ColdVox/ColdVox/crates/coldvox-text-injection/src/enigo_injector.rs:60:
1944:  let text_clone = text.to_string();
1945:  let result = tokio::task::spawn_blocking(move || {
1946:  -            let mut enigo = Enigo::new(&Settings::default())
1947:  -                .map_err(|e| InjectionError::MethodFailed(format!("Failed to create Enigo: {}", e)))?;
1948:  +            let mut enigo = Enigo::new(&Settings::default()).map_err(|e| {
1949:  +                InjectionError::MethodFailed(format!("Failed to create Enigo: {}", e))
1950:  +            })?;
1951:  Self::type_text_logic(&mut enigo, &text_clone)
1952:  })
1953:  .await;
1954:  ##[warning]Diff in /home/runner/work/ColdVox/ColdVox/crates/coldvox-text-injection/src/enigo_injector.rs:87:
1955:  enigo
1956:  .key(Key::Unicode('v'), Direction::Click)
1957:  .map_err(|e| InjectionError::MethodFailed(format!("Failed to type 'v': {}", e)))?;
1958:  -            enigo
1959:  -                .key(Key::Meta, Direction::Release)
1960:  -                .map_err(|e| InjectionError::MethodFailed(format!("Failed to release Cmd: {}", e)))?;
1961:  +            enigo.key(Key::Meta, Direction::Release).map_err(|e| {
1962:  +                InjectionError::MethodFailed(format!("Failed to release Cmd: {}", e))
1963:  +            })?;
1964:  }
1965:  #[cfg(not(target_os = "macos"))]
1966:  {
1967:  ##[warning]Diff in /home/runner/work/ColdVox/ColdVox/crates/coldvox-text-injection/src/enigo_injector.rs:96:
1968:  +            enigo.key(Key::Control, Direction::Press).map_err(|e| {
1969:  +                InjectionError::MethodFailed(format!("Failed to press Ctrl: {}", e))
1970:  +            })?;
1971:  enigo
1972:  -                .key(Key::Control, Direction::Press)
1973:  -                .map_err(|e| InjectionError::MethodFailed(format!("Failed to press Ctrl: {}", e)))?;
1974:  -            enigo
1975:  .key(Key::Unicode('v'), Direction::Click)
1976:  .map_err(|e| InjectionError::MethodFailed(format!("Failed to type 'v': {}", e)))?;
1977:  -            enigo
1978:  -                .key(Key::Control, Direction::Release)
1979:  -                .map_err(|e| InjectionError::MethodFailed(format!("Failed to release Ctrl: {}", e)))?;
1980:  +            enigo.key(Key::Control, Direction::Release).map_err(|e| {
1981:  +                InjectionError::MethodFailed(format!("Failed to release Ctrl: {}", e))
1982:  +            })?;
1983:  }
1984:  Ok(())
1985:  }
1986:  ##[warning]Diff in /home/runner/work/ColdVox/ColdVox/crates/coldvox-text-injection/src/enigo_injector.rs:109:
1987:  /// Trigger paste action using enigo (Ctrl+V)
1988:  async fn trigger_paste(&self) -> Result<(), InjectionError> {
1989:  let result = tokio::task::spawn_blocking(|| {
1990:  -            let mut enigo = Enigo::new(&Settings::default())
1991:  -                .map_err(|e| InjectionError::MethodFailed(format!("Failed to create Enigo: {}", e)))?;
1992:  +            let mut enigo = Enigo::new(&Settings::default()).map_err(|e| {
1993:  +                InjectionError::MethodFailed(format!("Failed to create Enigo: {}", e))
1994:  +            })?;
1995:  Self::trigger_paste_logic(&mut enigo)
1996:  })
1997:  .await;
1998:  ##[warning]Diff in /home/runner/work/ColdVox/ColdVox/crates/coldvox-text-injection/src/enigo_injector.rs:226:
1999:  if self.should_fail() {
2000:  return Err(enigo::Error::InvalidText);
2001:  }
...

2021:  ##[warning]cargo fmt detected formatting differences. Please run 'cargo fmt --all' locally before committing.
2022:  ##[group]Run cargo clippy --all-targets --locked
2023:  �[36;1mcargo clippy --all-targets --locked�[0m
2024:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
2025:  env:
2026:  CARGO_TERM_COLOR: always
2027:  WHISPER_MODEL_SIZE: tiny
2028:  MIN_FREE_DISK_GB: 10
2029:  MAX_LOAD_AVERAGE: 5
2030:  CARGO_INCREMENTAL: 0
2031:  CARGO_PROFILE_DEV_DEBUG: 0
2032:  RUST_BACKTRACE: short
2033:  RUSTFLAGS: -D warnings
2034:  CARGO_UNSTABLE_SPARSE_REGISTRY: true
2035:  CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
2036:  CACHE_ON_FAILURE: true
2037:  ##[endgroup]
...

2119:  �[1m�[92m    Finished�[0m `dev` profile [unoptimized] target(s) in 28.48s
2120:  ##[group]Run cargo check --workspace --all-targets --locked
2121:  �[36;1mcargo check --workspace --all-targets --locked�[0m
2122:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
2123:  env:
2124:  CARGO_TERM_COLOR: always
2125:  WHISPER_MODEL_SIZE: tiny
2126:  MIN_FREE_DISK_GB: 10
2127:  MAX_LOAD_AVERAGE: 5
2128:  CARGO_INCREMENTAL: 0
2129:  CARGO_PROFILE_DEV_DEBUG: 0
2130:  RUST_BACKTRACE: short
2131:  RUSTFLAGS: -D warnings
2132:  CARGO_UNSTABLE_SPARSE_REGISTRY: true
2133:  CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
2134:  CACHE_ON_FAILURE: true
2135:  ##[endgroup]
...

2145:  �[1m�[92m    Finished�[0m `dev` profile [unoptimized] target(s) in 5.76s
2146:  ##[group]Run cargo build --workspace --locked
2147:  �[36;1mcargo build --workspace --locked�[0m
2148:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
2149:  env:
2150:  CARGO_TERM_COLOR: always
2151:  WHISPER_MODEL_SIZE: tiny
2152:  MIN_FREE_DISK_GB: 10
2153:  MAX_LOAD_AVERAGE: 5
2154:  CARGO_INCREMENTAL: 0
2155:  CARGO_PROFILE_DEV_DEBUG: 0
2156:  RUST_BACKTRACE: short
2157:  RUSTFLAGS: -D warnings
2158:  CARGO_UNSTABLE_SPARSE_REGISTRY: true
2159:  CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
2160:  CACHE_ON_FAILURE: true
2161:  ##[endgroup]
...

2207:  �[1m�[92m    Finished�[0m `dev` profile [unoptimized] target(s) in 22.87s
2208:  ##[group]Run cargo doc --workspace --no-deps --locked
2209:  �[36;1mcargo doc --workspace --no-deps --locked�[0m
2210:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
2211:  env:
2212:  CARGO_TERM_COLOR: always
2213:  WHISPER_MODEL_SIZE: tiny
2214:  MIN_FREE_DISK_GB: 10
2215:  MAX_LOAD_AVERAGE: 5
2216:  CARGO_INCREMENTAL: 0
2217:  CARGO_PROFILE_DEV_DEBUG: 0
2218:  RUST_BACKTRACE: short
2219:  RUSTFLAGS: -D warnings
2220:  CARGO_UNSTABLE_SPARSE_REGISTRY: true
2221:  CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
2222:  CACHE_ON_FAILURE: true
2223:  ##[endgroup]
...

2257:  �[36;1mls -la "$WHISPER_MODEL_PATH" || echo "Model directory not accessible"�[0m
2258:  �[36;1mecho "=== Running Tests ==="�[0m
2259:  �[36;1mcargo test --workspace --locked --�[0m
2260:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
2261:  env:
2262:  CARGO_TERM_COLOR: always
2263:  WHISPER_MODEL_SIZE: tiny
2264:  MIN_FREE_DISK_GB: 10
2265:  MAX_LOAD_AVERAGE: 5
2266:  CARGO_INCREMENTAL: 0
2267:  CARGO_PROFILE_DEV_DEBUG: 0
2268:  RUST_BACKTRACE: short
2269:  RUSTFLAGS: -D warnings
2270:  CARGO_UNSTABLE_SPARSE_REGISTRY: true
2271:  CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
2272:  CACHE_ON_FAILURE: true
2273:  WHISPER_MODEL_PATH: /home/coldaine/actions-runner/_work/ColdVox/ColdVox/vendor/whisper/model/tiny
...

2293:  �[1m�[92m   Compiling�[0m coldvox-vad v0.1.0 (/home/runner/work/ColdVox/ColdVox/crates/coldvox-vad)
2294:  �[1m�[92m   Compiling�[0m coldvox-telemetry v0.1.0 (/home/runner/work/ColdVox/ColdVox/crates/coldvox-telemetry)
2295:  �[1m�[92m   Compiling�[0m wayland-protocols-wlr v0.3.9
2296:  �[1m�[92m   Compiling�[0m atspi-common v0.13.0
2297:  �[1m�[92m   Compiling�[0m wl-clipboard-rs v0.9.3
2298:  �[1m�[92m   Compiling�[0m atspi-proxies v0.13.0
2299:  �[1m�[92m   Compiling�[0m atspi-connection v0.13.0
2300:  �[1m�[92m   Compiling�[0m atspi v0.29.0
2301:  �[1m�[92m   Compiling�[0m coldvox-text-injection v0.1.0 (/home/runner/work/ColdVox/ColdVox/crates/coldvox-text-injection)
2302:  �[1m�[92m   Compiling�[0m coldvox-app v0.1.0 (/home/runner/work/ColdVox/ColdVox/crates/app)
2303:  �[1m�[92m    Finished�[0m `test` profile [unoptimized] target(s) in 23.42s
2304:  �[1m�[92m     Running�[0m unittests src/lib.rs (target/debug/deps/coldvox_app-7f8a5a0173ba9e3f)
2305:  running 17 tests
2306:  test audio::vad_adapter::tests::resampler_frame_aggregation_same_rate_diff_size ... ok
2307:  test audio::vad_adapter::tests::resampler_pass_through_same_rate_same_size ... ok
2308:  test stt::plugin_manager::tests::test_fallback_failure_when_no_plugins_available ... ok
2309:  test stt::plugin_manager::tests::test_plugin_manager_initialization ... ok
2310:  test stt::plugin_manager::tests::test_plugin_switching ... ok
2311:  test stt::plugin_manager::tests::test_switch_plugin_unload_metrics ... ok
2312:  test stt::plugin_manager::tests::test_unload_all_idempotency ... ok
2313:  test stt::plugin_manager::tests::test_unload_all_plugins ... ok
2314:  test stt::plugin_manager::tests::test_unload_error_metrics ... ok
2315:  test stt::plugin_manager::tests::test_unload_idempotency ... ok
2316:  test stt::plugin_manager::tests::test_unload_metrics ... ok
2317:  test stt::plugin_manager::tests::test_unload_nonexistent_plugin ... ok
2318:  test stt::tests::wer_utils::tests::test_calculate_wer_basic ... ok
2319:  test stt::plugin_manager::tests::test_unload_plugin ... ok
2320:  test stt::tests::wer_utils::tests::test_format_wer_percentage ... ok
2321:  test audio::vad_adapter::tests::resampler_downsample_48k_to_16k_produces_full_frames ... ok
2322:  test stt::plugin_manager::tests::test_concurrent_process_audio_and_gc_no_double_borrow ... ok
2323:  test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.67s
2324:  �[1m�[92m     Running�[0m unittests src/main.rs (target/debug/deps/coldvox-5af6ec839cc969ac)
2325:  running 9 tests
2326:  test tests::test_settings_new_default ... ok
2327:  test tests::test_settings_new_invalid_env_var_deserial ... ok
2328:  test tests::test_settings_new_validation_err ... ok
2329:  test tests::test_settings_new_with_env_override ... ok
2330:  test tests::test_settings_validate_zero_timeout ... ok
2331:  test tests::test_settings_validate_zero_validation ... ok
2332:  test tests::test_settings_validate_invalid_mode ... ok
2333:  test tests::test_settings_validate_invalid_rate ... ok
2334:  test tests::test_settings_validate_success_rate ... ok
2335:  test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2336:  �[1m�[92m     Running�[0m unittests src/bin/mic_probe.rs (target/debug/deps/mic_probe-133fa5cbc30c4d97)
2337:  running 0 tests
2338:  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2339:  �[1m�[92m     Running�[0m unittests src/bin/tui_dashboard.rs (target/debug/deps/tui_dashboard-94b8fb9c9fae46d9)
2340:  running 0 tests
2341:  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2342:  �[1m�[92m     Running�[0m tests/chunker_timing_tests.rs (target/debug/deps/chunker_timing_tests-ff05eb7ea6ad053e)
2343:  running 18 tests
2344:  test common::logging::tests::test_log_path_generation ... ok
2345:  test common::timeout::tests::test_timeout_config_defaults ... ok
2346:  test common::timeout::tests::test_injection_timeout_wrapper ... ok
2347:  test common::timeout::tests::test_stt_timeout_wrapper ... ok
2348:  test common::timeout::tests::test_timeout_macro ... ok
2349:  test common::timeout::tests::test_timeout_success ... ok
2350:  test common::wer::tests::test_assert_wer_below_threshold_pass ... ok
2351:  test common::wer::tests::test_calculate_wer_complete_mismatch ... ok
2352:  test common::wer::tests::test_calculate_wer_partial_errors ... ok
2353:  test common::wer::tests::test_calculate_wer_perfect_match ... ok
2354:  test common::wer::tests::test_format_wer_percentage ... ok
2355:  test common::wer::tests::test_wer_metrics_basic ... ok
2356:  test common::wer::tests::test_wer_metrics_deletion ... ok
2357:  test common::wer::tests::test_wer_metrics_display ... ok
2358:  test common::wer::tests::test_assert_wer_below_threshold_fail - should panic ... ok
2359:  test common::wer::tests::test_wer_metrics_insertion ... ok
2360:  test chunker_timestamps_are_32ms_apart_at_16k ... ok
2361:  test common::timeout::tests::test_timeout_failure ... ok
2362:  test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s
2363:  �[1m�[92m     Running�[0m tests/golden_master.rs (target/debug/deps/golden_master-f557692d363ceb9e)
2364:  running 18 tests
2365:  test common::logging::tests::test_log_path_generation ... ok
2366:  test common::timeout::tests::test_timeout_config_defaults ... ok
2367:  test common::timeout::tests::test_injection_timeout_wrapper ... ok
2368:  test common::timeout::tests::test_stt_timeout_wrapper ... ok
2369:  test common::timeout::tests::test_timeout_macro ... ok
2370:  test common::timeout::tests::test_timeout_success ... ok
2371:  test common::wer::tests::test_assert_wer_below_threshold_pass ... ok
2372:  test common::wer::tests::test_calculate_wer_complete_mismatch ... ok
2373:  test common::wer::tests::test_calculate_wer_perfect_match ... ok
2374:  test common::wer::tests::test_calculate_wer_partial_errors ... ok
2375:  test common::wer::tests::test_format_wer_percentage ... ok
2376:  test common::wer::tests::test_wer_metrics_basic ... ok
2377:  test common::wer::tests::test_wer_metrics_deletion ... ok
2378:  test common::wer::tests::test_wer_metrics_display ... ok
2379:  test common::wer::tests::test_wer_metrics_insertion ... ok
2380:  test common::wer::tests::test_assert_wer_below_threshold_fail - should panic ... ok
2381:  test common::timeout::tests::test_timeout_failure ... ok
2382:  test tests::test_short_phrase_pipeline ... ok
2383:  test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.58s
2384:  �[1m�[92m     Running�[0m tests/hardware_check.rs (target/debug/deps/hardware_check-565882a7fa49bd43)
2385:  running 19 tests
2386:  test common::logging::tests::test_log_path_generation ... ok
2387:  test common::timeout::tests::test_timeout_config_defaults ... ok
2388:  test common::timeout::tests::test_timeout_macro ... ok
2389:  test common::timeout::tests::test_stt_timeout_wrapper ... ok
2390:  test common::timeout::tests::test_injection_timeout_wrapper ... ok
2391:  test common::timeout::tests::test_timeout_success ... ok
2392:  test common::wer::tests::test_assert_wer_below_threshold_pass ... ok
2393:  test common::wer::tests::test_calculate_wer_complete_mismatch ... ok
2394:  test common::wer::tests::test_calculate_wer_partial_errors ... ok
2395:  test common::wer::tests::test_calculate_wer_perfect_match ... ok
2396:  test common::wer::tests::test_format_wer_percentage ... ok
2397:  test common::wer::tests::test_wer_metrics_basic ... ok
2398:  test common::wer::tests::test_wer_metrics_deletion ... ok
2399:  test common::wer::tests::test_wer_metrics_display ... ok
2400:  test hardware_tests::test_audio_capture_device_open ... ignored, Requires real audio hardware
2401:  test hardware_tests::test_text_injector_availability ... ignored, Requires display server
2402:  test common::wer::tests::test_wer_metrics_insertion ... ok
2403:  test common::wer::tests::test_assert_wer_below_threshold_fail - should panic ... ok
2404:  test common::timeout::tests::test_timeout_failure ... ok
2405:  test result: ok. 17 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 0.05s
2406:  �[1m�[92m     Running�[0m tests/pipeline_integration.rs (target/debug/deps/pipeline_integration-90874f6617f78160)
2407:  running 0 tests
2408:  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2409:  �[1m�[92m     Running�[0m tests/settings_test.rs (target/debug/deps/settings_test-8df733b9e615c069)
2410:  running 9 tests
2411:  test test_settings_new_invalid_env_var_deserial ... ignored
2412:  test test_settings_new_validation_err ... ignored
2413:  test test_settings_new_with_env_override ... ignored
2414:  test test_settings_new_default ... ok
2415:  test test_settings_validate_invalid_rate ... ok
2416:  test test_settings_validate_invalid_mode ... ok
2417:  test test_settings_validate_zero_timeout ... ok
2418:  test test_settings_validate_zero_validation ... ok
2419:  test test_settings_validate_success_rate ... ok
2420:  test result: ok. 6 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 0.00s
2421:  �[1m�[92m     Running�[0m tests/verify_mock_injection_fix.rs (target/debug/deps/verify_mock_injection_fix-965c079c0736475a)
2422:  running 2 tests
2423:  test test_mock_injection_sink_option_available ... ok
2424:  test test_mock_injector_captures_text ... ok
2425:  test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2426:  �[1m�[92m     Running�[0m unittests src/lib.rs (target/debug/deps/coldvox_audio-6e93d6069b91cbfb)
2427:  running 37 tests
2428:  test capture::convert_tests::f32_to_i16_basic ... ok
2429:  test capture::convert_tests::u16_to_i16_centering ... ok
2430:  test capture::convert_tests::f64_to_i16_basic ... ok
2431:  test capture::convert_tests::u32_to_i16_scaling ... ok
2432:  test chunker::tests::stereo_to_mono_averaging ... ok
2433:  test device::tests::test_alsa_only ... ok
2434:  test chunker::tests::reconfigure_resampler_on_rate_change ... ok
2435:  test device::tests::test_command_fail ... ok
2436:  test device::tests::test_malformed_pactl_output ... ok
2437:  test device::tests::test_native_pulse ... ok
2438:  test device::tests::test_candidate_duplicates_prevented ... ok
2439:  test device::tests::test_candidate_no_pipewire ... ok
2440:  test device::tests::test_candidate_order_default_first ... ok
2441:  test device::tests::test_open_device_fallback ... ok
2442:  test device::tests::test_partial_pipewire_install ... ok
2443:  test device::tests::test_permission_error ... ok
2444:  test device::tests::test_pipewire_full_shims ... ok
...

2450:  test device::tests::test_open_device_hardware_fallback ... ok
2451:  test monitor::tests::test_device_status_tracking ... ok
2452:  test monitor::tests::test_device_switch_requested_event ... ok
2453:  test monitor::tests::test_manual_device_switch_request ... ok
2454:  test device::tests::test_open_device_no_name_priority ... ok
2455:  test monitor::tests::test_device_preferences ... ok
2456:  test resampler::tests::passthrough_same_rate ... ok
2457:  test resampler::tests::downsample_48k_to_16k_ramp ... ok
2458:  test ring_buffer::tests::test_basic_write_read ... ok
2459:  test ring_buffer::tests::test_overflow ... ok
2460:  test stderr_suppressor::tests::test_suppressor_basic ... ok
2461:  test stderr_suppressor::tests::test_with_suppressed ... ok
2462:  test resampler::tests::upsample_16k_to_48k_constant ... ok
2463:  test resampler::quality_tests::process_with_all_quality_presets ... ok
2464:  test monitor::tests::test_device_monitor_integration ... ok
2465:  test result: ok. 37 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.58s
2466:  �[1m�[92m     Running�[0m tests/default_mic_detection_it.rs (target/debug/deps/default_mic_detection_it-35345f2641b15adb)
2467:  running 1 test
2468:  test default_mic_is_detected_and_used_via_pulseaudio ... ok
2469:  test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2470:  �[1m�[92m     Running�[0m tests/device_hotplug_tests.rs (target/debug/deps/device_hotplug_tests-991d857363c5f237)
2471:  running 5 tests
2472:  test device_hotplug_tests::test_device_event_types ... ok
2473:  test device_hotplug_tests::test_device_status_management ... ok
2474:  test device_hotplug_tests::test_recovery_strategy_for_device_errors ... ok
2475:  test device_hotplug_tests::test_device_monitor_basic_functionality ... ok
2476:  test device_hotplug_tests::test_audio_capture_thread_with_device_events ... ok
2477:  test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.01s
2478:  �[1m�[92m     Running�[0m unittests src/lib.rs (target/debug/deps/coldvox_foundation-0b55b23b375b4ac7)
...

2495:  test env::tests::test_display_protocol_is_x11 ... ok
2496:  test env::tests::test_display_protocol_is_xwayland ... ok
2497:  test env::tests::test_environment_helpers ... ok
2498:  test env::tests::test_environment_info_display ... ok
2499:  test env::tests::test_environment_info_helper_methods ... ok
2500:  test env::tests::test_environment_info_new ... ok
2501:  test env::tests::test_environment_info_summary ... ok
2502:  test env::tests::test_environment_info_xwayland_detection ... ok
2503:  test env::tests::test_get_environment_timeout ... ok
2504:  test env::tests::test_has_display ... ok
2505:  test env::tests::test_is_ci_environment ... ok
2506:  test env::tests::test_is_development_environment ... ok
2507:  test test_env::tests::test_requirements_builder ... ok
2508:  test test_env::tests::test_environment_detection ... ok
2509:  test test_env::tests::test_requirements_check ... ok
2510:  test result: ok. 30 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
2511:  �[1m�[92m     Running�[0m unittests src/main.rs (target/debug/deps/coldvox_gui-4cb2c8c5b3ba4164)
2512:  running 0 tests
2513:  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2514:  �[1m�[92m     Running�[0m unittests src/lib.rs (target/debug/deps/coldvox_stt-eb96c6d9c5515297)
2515:  running 0 tests
2516:  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2517:  �[1m�[92m     Running�[0m tests/moonshine_e2e.rs (target/debug/deps/moonshine_e2e-2569760fe30d8926)
2518:  running 0 tests
2519:  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2520:  �[1m�[92m     Running�[0m unittests src/lib.rs (target/debug/deps/coldvox_telemetry-194a4dccc02414c0)
2521:  running 11 tests
2522:  test integration::tests::test_metrics_builder ... ok
2523:  test integration::tests::test_metrics_report_formatting ... ok
2524:  test integration::tests::test_performance_summary ... ok
2525:  test stt_metrics::tests::test_confidence_tracking ... ok
2526:  test integration::tests::test_preset_configurations ... ok
2527:  test stt_metrics::tests::test_latency_recording ... ok
2528:  test stt_metrics::tests::test_memory_usage_tracking ... ok
2529:  test stt_metrics::tests::test_performance_alerts ... ok
2530:  test stt_metrics::tests::test_stt_performance_metrics_creation ... ok
2531:  test stt_metrics::tests::test_success_rate_calculation ... ok
2532:  test stt_metrics::tests::test_timing_guard ... ok
2533:  test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
2534:  �[1m�[92m     Running�[0m unittests src/lib.rs (target/debug/deps/coldvox_text_injection-0860f8b98b65ea04)
...

2549:  test detection::tests::test_display_protocol_is_xwayland ... ok
2550:  test injectors::atspi::tests::test_atspi_injector_creation ... ok
2551:  test injectors::atspi::tests::test_context_default ... ok
2552:  test injectors::atspi::tests::test_empty_text_handling ... ok
2553:  test injectors::atspi::tests::test_legacy_inject_text ... ok
2554:  test injectors::clipboard::tests::test_backend_detection ... ok
2555:  test injectors::clipboard::tests::test_clipboard_backup_creation ... ok
2556:  test injectors::clipboard::tests::test_clipboard_injector_creation ... ok
2557:  test injectors::clipboard::tests::test_context_default ... ok
2558:  test injectors::clipboard::tests::test_empty_text_handling ... ok
2559:  test injectors::clipboard::tests::test_execute_command_with_stdin_nonexistent ... ok
2560:  test injectors::clipboard::tests::test_execute_command_with_stdin_success ... ok
2561:  test injectors::clipboard::tests::test_fallback_function_extensibility ... ok
2562:  test injectors::clipboard::tests::test_helper_functions_are_generic ... ok
2563:  test injectors::clipboard::tests::test_legacy_inject_text ... ok
2564:  test injectors::clipboard::tests::test_native_attempt_with_fallback_both_fail ... ok
2565:  test injectors::clipboard::tests::test_native_attempt_with_fallback_fallback ... ok
...

2572:  test injectors::unified_clipboard::tests::test_unified_clipboard_injector_creation ... ok
2573:  test log_throttle::tests::test_atspi_unknown_method_suppression ... ok
2574:  test injectors::unified_clipboard::tests::test_unified_clipboard_injector_strict_mode ... ok
2575:  test log_throttle::tests::test_log_throttle_allows_after_duration ... ok
2576:  test log_throttle::tests::test_log_throttle_allows_first_message ... ok
2577:  test log_throttle::tests::test_log_throttle_different_keys ... ok
2578:  test logging::tests::test_injection_event_logging ... ok
2579:  test logging::tests::test_log_injection_attempt ... ok
2580:  test logging::tests::test_logging_config_default ... ok
2581:  test injectors::atspi::tests::test_atspi_injector_availability ... ok
2582:  test manager::tests::test_budget_checking ... ok
2583:  test manager::tests::test_cooldown_update ... ok
2584:  test log_throttle::tests::test_cleanup_old_entries ... ok
2585:  test manager::tests::test_inject_success ... ok
2586:  test manager::tests::test_empty_text ... ok
2587:  test manager::tests::test_inject_failure ... ok
2588:  test manager::tests::test_strategy_manager_creation ... ok
...

2596:  test prewarm::tests::test_cached_data_ttl ... ok
2597:  test prewarm::tests::test_mime_to_string ... ok
2598:  test orchestrator::tests::test_orchestrator_creation ... ok
2599:  test prewarm::tests::test_prewarm_controller_creation ... ok
2600:  test manager::tests::test_method_ordering ... ok
2601:  test processor::tests::test_metrics_update ... ok
2602:  test processor::tests::test_partial_transcription_handling ... ok
2603:  test session::tests::test_buffer_size_limit ... ok
2604:  test session::tests::test_empty_transcription_filtering ... ok
2605:  test prewarm::tests::test_run_function ... ok
2606:  test injectors::clipboard::tests::test_execute_command_with_stdin_timeout ... ok
2607:  test tests::wl_copy_basic_test::test_wl_copy_stdin_piping_basic ... ignored
2608:  test tests::wl_copy_simple_test::test_wl_copy_stdin_piping_simple ... ignored
2609:  test tests::wl_copy_stdin_test::test_wl_copy_clipboard_backup_restore ... ignored
2610:  test tests::wl_copy_stdin_test::test_wl_copy_edge_cases ... ignored
2611:  test tests::wl_copy_stdin_test::test_wl_copy_error_handling ... ignored
2612:  test tests::wl_copy_stdin_test::test_wl_copy_stdin_piping ... ignored
2613:  test tests::wl_copy_stdin_test::test_wl_copy_timeout_handling ... ignored
2614:  test window_manager::tests::test_window_detection ... ok
2615:  test window_manager::tests::test_window_info ... ok
2616:  test session::tests::test_session_state_transitions ... ok
2617:  test session::tests::test_silence_detection ... ok
2618:  test processor::tests::test_injection_processor_basic_flow ... ok
2619:  test result: ok. 76 passed; 0 failed; 7 ignored; 0 measured; 0 filtered out; finished in 0.36s
2620:  �[1m�[92m     Running�[0m unittests src/lib.rs (target/debug/deps/coldvox_vad-7ac6d81fd75a1a60)
2621:  running 11 tests
2622:  test energy::tests::test_full_scale_returns_zero_dbfs ... ok
2623:  test energy::tests::test_rms_calculation ... ok
2624:  test state::tests::test_initial_state ... ok
2625:  test state::tests::test_speech_continuation ... ok
2626:  test energy::tests::test_silence_returns_low_dbfs ... ok
2627:  test state::tests::test_speech_offset_debouncing ... ok
2628:  test state::tests::test_speech_onset_debouncing ... ok
2629:  test threshold::tests::test_activation_deactivation ... ok
2630:  test threshold::tests::test_no_update_during_speech ... ok
2631:  test threshold::tests::test_noise_floor_adaptation ... ok
2632:  test threshold::tests::test_threshold_initialization ... ok
2633:  test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2634:  �[1m�[92m     Running�[0m unittests src/lib.rs (target/debug/deps/coldvox_vad_silero-7173d1c945814c89)
2635:  running 3 tests
2636:  test silero_wrapper::tests::silero_engine_rejects_incorrect_frame_sizes ... ok
2637:  test silero_wrapper::tests::silero_engine_creates_and_reports_requirements ... ok
2638:  test silero_wrapper::tests::silero_engine_processes_silence_without_event ... ok
2639:  test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s
2640:  �[1m�[92m   Doc-tests�[0m coldvox_app
2641:  running 0 tests
2642:  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2643:  �[1m�[92m   Doc-tests�[0m coldvox_audio
2644:  running 0 tests
2645:  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2646:  �[1m�[92m   Doc-tests�[0m coldvox_foundation
2647:  running 4 tests
2648:  test crates/coldvox-foundation/src/env.rs - env::env (line 112) ... ignored
2649:  test crates/coldvox-foundation/src/test_env.rs - test_env (line 14) ... ignored
2650:  test crates/coldvox-foundation/src/test_env.rs - test_env::skip_test_unless (line 493) ... ok
2651:  test crates/coldvox-foundation/src/env.rs - env::detect (line 345) ... ok
2652:  test result: ok. 2 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 0.15s
2653:  �[1m�[92m   Doc-tests�[0m coldvox_stt
2654:  running 0 tests
2655:  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2656:  �[1m�[92m   Doc-tests�[0m coldvox_telemetry
2657:  running 0 tests
2658:  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2659:  �[1m�[92m   Doc-tests�[0m coldvox_text_injection
2660:  running 0 tests
2661:  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2662:  �[1m�[92m   Doc-tests�[0m coldvox_vad
2663:  running 0 tests
2664:  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2665:  �[1m�[92m   Doc-tests�[0m coldvox_vad_silero
2666:  running 0 tests
2667:  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2668:  ##[group]Run echo "=== Running Golden Master Test ==="
...

2671:  �[36;1mpip install faster-whisper�[0m
2672:  �[36;1mexport PYTHONPATH=$(python3 -c "import site; print(site.getsitepackages()[0])")�[0m
2673:  �[36;1mcargo test -p coldvox-app --test golden_master -- --nocapture�[0m
2674:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
2675:  env:
2676:  CARGO_TERM_COLOR: always
2677:  WHISPER_MODEL_SIZE: tiny
2678:  MIN_FREE_DISK_GB: 10
2679:  MAX_LOAD_AVERAGE: 5
2680:  CARGO_INCREMENTAL: 0
2681:  CARGO_PROFILE_DEV_DEBUG: 0
2682:  RUST_BACKTRACE: short
2683:  RUSTFLAGS: -D warnings
2684:  CARGO_UNSTABLE_SPARSE_REGISTRY: true
2685:  CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
2686:  CACHE_ON_FAILURE: true
2687:  WHISPER_MODEL_PATH: /home/coldaine/actions-runner/_work/ColdVox/ColdVox/vendor/whisper/model/tiny
...

2819:  �[1m�[92m   Compiling�[0m ratatui v0.29.0
2820:  �[1m�[92m   Compiling�[0m coldvox-app v0.1.0 (/home/runner/work/ColdVox/ColdVox/crates/app)
2821:  �[1m�[92m   Compiling�[0m tracing-appender v0.2.4
2822:  �[1m�[92m   Compiling�[0m atspi-common v0.13.0
2823:  �[1m�[92m   Compiling�[0m atspi-proxies v0.13.0
2824:  �[1m�[92m   Compiling�[0m atspi-connection v0.13.0
2825:  �[1m�[92m   Compiling�[0m atspi v0.29.0
2826:  �[1m�[92m    Finished�[0m `test` profile [unoptimized] target(s) in 26.29s
2827:  �[1m�[92m     Running�[0m tests/golden_master.rs (target/debug/deps/golden_master-ea42592bf089c89b)
2828:  running 18 tests
2829:  test common::logging::tests::test_log_path_generation ... ok
2830:  test common::timeout::tests::test_timeout_config_defaults ... ok
2831:  test common::timeout::tests::test_injection_timeout_wrapper ... ok
2832:  test common::timeout::tests::test_stt_timeout_wrapper ... ok
2833:  test common::timeout::tests::test_timeout_macro ... ok
2834:  thread 'common::wer::tests::test_assert_wer_below_threshold_fail' (10792) panicked at crates/app/tests/common/wer.rs:129:9:
2835:  WER 50.0% exceeds threshold 40.0%
2836:  Reference:  'hello world'
2837:  Hypothesis: 'hello there'
2838:  Reference words: 2, Hypothesis words: 2
2839:  stack backtrace:
2840:  test common::timeout::tests::test_timeout_success ... ok
2841:  test common::wer::tests::test_assert_wer_below_threshold_pass ... ok
2842:  test common::wer::tests::test_calculate_wer_partial_errors ... ok
2843:  test common::wer::tests::test_calculate_wer_complete_mismatch ... ok
2844:  test common::wer::tests::test_calculate_wer_perfect_match ... ok
2845:  test common::wer::tests::test_format_wer_percentage ... ok
2846:  test common::wer::tests::test_wer_metrics_basic ... ok
2847:  test common::wer::tests::test_wer_metrics_deletion ... ok
2848:  test common::wer::tests::test_wer_metrics_display ... ok
2849:  test common::wer::tests::test_wer_metrics_insertion ... ok
2850:  0: __rustc::rust_begin_unwind
2851:  1: core::panicking::panic_fmt
2852:  2: golden_master::common::wer::assert_wer_below_threshold
2853:  3: golden_master::common::wer::tests::test_assert_wer_below_threshold_fail
2854:  4: golden_master::common::wer::tests::test_assert_wer_below_threshold_fail::{{closure}}
2855:  5: core::ops::function::FnOnce::call_once
2856:  note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
2857:  �[2m2025-12-23T22:32:15.673260Z�[0m �[32m INFO�[0m ThreadId(19) �[2mgolden_master::common::logging�[0m�[2m:�[0m ========================================
2858:  �[2m2025-12-23T22:32:15.673297Z�[0m �[32m INFO�[0m ThreadId(19) �[2mgolden_master::common::logging�[0m�[2m:�[0m TEST START: golden_master_short_phrase
2859:  �[2m2025-12-23T22:32:15.673316Z�[0m �[32m INFO�[0m ThreadId(19) �[2mgolden_master::common::logging�[0m�[2m:�[0m Log file: /home/runner/work/ColdVox/ColdVox/target/test-logs/golden_master_short_phrase.log
2860:  �[2m2025-12-23T22:32:15.673331Z�[0m �[32m INFO�[0m ThreadId(19) �[2mgolden_master::common::logging�[0m�[2m:�[0m ========================================
2861:  �[2m2025-12-23T22:32:15.673347Z�[0m �[32m INFO�[0m ThreadId(19) �[2mgolden_master::tests�[0m�[2m:�[0m Starting golden master test for: short_phrase
2862:  �[2m2025-12-23T22:32:15.673412Z�[0m �[32m INFO�[0m ThreadId(19) �[2mcoldvox_app::audio::wav_file_loader�[0m�[2m:�[0m Loading WAV: 16000 Hz, 1 channels, 16 bits
2863:  test common::wer::tests::test_assert_wer_below_threshold_fail - should panic ... ok
2864:  �[2m2025-12-23T22:32:15.682209Z�[0m �[32m INFO�[0m ThreadId(19) �[2mcoldvox_app::audio::wav_file_loader�[0m�[2m:�[0m WAV loaded: 55840 samples (interleaved) at 16000 Hz, 1 channels
2865:  �[2m2025-12-23T22:32:15.682262Z�[0m �[32m INFO�[0m ThreadId(19) �[2mgolden_master::tests�[0m�[2m:�[0m Loaded WAV file: 16000 Hz, 1 channels
2866:  �[2m2025-12-23T22:32:15.682309Z�[0m �[32m INFO�[0m ThreadId(19) �[2mgolden_master::tests�[0m�[2m:�[0m Starting application runtime...
2867:  �[2m2025-12-23T22:32:15.682356Z�[0m �[32m INFO�[0m ThreadId(19) �[2mcoldvox_app::runtime�[0m�[2m:�[0m Starting ColdVox runtime with unified STT architecture
2868:  �[2m2025-12-23T22:32:15.682526Z�[0m �[32m INFO�[0m ThreadId(19) �[2mcoldvox_app::audio::vad_processor�[0m�[2m:�[0m VAD processor task spawning for mode: Silero
2869:  �[2m2025-12-23T22:32:15.682621Z�[0m �[34mDEBUG�[0m ThreadId(19) �[2mort::environment�[0m�[2m:�[0m Environment not yet initialized, creating a new one
2870:  �[2m2025-12-23T22:32:15.697021Z�[0m �[34mDEBUG�[0m ThreadId(19) �[2mort::environment�[0m�[2m:�[0m Environment created �[3menv_ptr�[0m�[2m=�[0m"0x7f4b4847cc60"
2871:  �[2m2025-12-23T22:32:15.697251Z�[0m �[32m INFO�[0m ThreadId(19) �[2mort::logging�[0m�[2m:�[0m Session Options {  execution_mode:0 execution_order:DEFAULT enable_profiling:0 optimized_model_filepath:"" enable_mem_pattern:1 enable_mem_reuse:1 enable_cpu_mem_arena:1 profile_file_prefix:onnxruntime_profile_ session_logid: session_log_severity_level:-1 session_log_verbosity_level:0 max_num_graph_transformation_steps:10 graph_optimization_level:3 intra_op_param:OrtThreadPoolParams { thread_pool_size: 1 auto_set_affinity: 0 allow_spinning: 1 dynamic_block_base_: 0 stack_size: 0 affinity_str:  set_denormal_as_zero: 0 } inter_op_param:OrtThreadPoolParams { thread_pool_size: 1 auto_set_affinity: 0 allow_spinning: 1 dynamic_block_base_: 0 stack_size: 0 affinity_str:  set_denormal_as_zero: 0 } use_per_session_threads:1 thread_pool_allow_spinning:1 use_deterministic_compute:0 ep_selection_policy:0 config_options: {  } }
2872:  �[2m2025-12-23T22:32:15.697343Z�[0m �[32m INFO�[0m ThreadId(19) �[2mort::logging�[0m�[2m:�[0m Flush-to-zero and denormal-as-zero are off
2873:  �[2m2025-12-23T22:32:15.697375Z�[0m �[32m INFO�[0m ThreadId(19) �[2mort::logging�[0m�[2m:�[0m Creating and using per session threadpools since use_per_session_threads_ is true
2874:  �[2m2025-12-23T22:32:15.697395Z�[0m �[32m INFO�[0m ThreadId(19) �[2mort::logging�[0m�[2m:�[0m Dynamic block base set to 0
2875:  �[2m2025-12-23T22:32:15.716199Z�[0m �[32m INFO�[0m ThreadId(19) �[2mort::logging�[0m�[2m:�[0m Initializing session.
2876:  �[2m2025-12-23T22:32:15.716228Z�[0m �[32m INFO�[0m ThreadId(19) �[2mort::logging�[0m�[2m:�[0m Adding default CPU execution provider.
2877:  �[2m2025-12-23T22:32:15.716270Z�[0m �[32m INFO�[0m ThreadId(19) �[2mort::logging�[0m�[2m:�[0m Creating BFCArena for Cpu with following configs: initial_chunk_size_bytes: 1048576 max_dead_bytes_per_chunk: 134217728 initial_growth_chunk_size_bytes: 2097152 max_power_of_two_extend_bytes: 1073741824 memory limit: 18446744073709551615 arena_extend_strategy: 0
2878:  test common::timeout::tests::test_timeout_failure ... ok
2879:  �[2m2025-12-23T22:32:15.718149Z�[0m �[32m INFO�[0m ThreadId(19) �[2mort::logging�[0m�[2m:�[0m This model does not have any local functions defined. AOT Inlining is not performed
...

3459:  �[2m2025-12-23T22:32:16.248655Z�[0m �[32m INFO�[0m ThreadId(19) �[2mgolden_master::tests�[0m�[2m:�[0m VAD event captured: SerializableVadEvent { kind: "SpeechStart", duration_ms: None }
3460:  �[2m2025-12-23T22:32:19.081887Z�[0m �[34mDEBUG�[0m ThreadId(19) �[2mcoldvox_app::audio::vad_processor�[0m�[2m:�[0m VAD: Received 100 frames, processing active
3461:  �[2m2025-12-23T22:32:19.391757Z�[0m �[32m INFO�[0m ThreadId(19) �[2mcoldvox_app::audio::wav_file_loader�[0m�[2m:�[0m WAV streaming completed (55840 total samples processed), feeding silence to flush VAD.
3462:  �[2m2025-12-23T22:32:19.710145Z�[0m �[34mDEBUG�[0m ThreadId(19) �[2mcoldvox_app::audio::vad_processor�[0m�[2m:�[0m VAD: Speech ended at 3808ms (duration: 3424ms, energy: -25.35 dB)
3463:  �[2m2025-12-23T22:32:19.710189Z�[0m �[34mDEBUG�[0m ThreadId(19) �[2mcoldvox_app::audio::vad_processor�[0m�[2m:�[0m VAD event: SpeechEnd { timestamp_ms: 3808, duration_ms: 3424, energy_db: -25.350218 } @ 3808ms
3464:  �[2m2025-12-23T22:32:19.710305Z�[0m �[32m INFO�[0m ThreadId(19) �[2mgolden_master::tests�[0m�[2m:�[0m VAD event captured: SerializableVadEvent { kind: "SpeechEnd", duration_ms: Some(3456) }
3465:  �[2m2025-12-23T22:32:19.710347Z�[0m �[32m INFO�[0m ThreadId(19) �[2mgolden_master::tests�[0m�[2m:�[0m Pipeline completion detected!
3466:  �[2m2025-12-23T22:32:19.886864Z�[0m �[32m INFO�[0m ThreadId(19) �[2mgolden_master::tests�[0m�[2m:�[0m WAV streaming completed successfully
3467:  �[2m2025-12-23T22:32:20.212228Z�[0m �[32m INFO�[0m ThreadId(19) �[2mgolden_master::tests�[0m�[2m:�[0m Shutting down pipeline...
3468:  �[2m2025-12-23T22:32:20.212270Z�[0m �[34mDEBUG�[0m ThreadId(19) �[2mcoldvox_app::runtime�[0m�[2m:�[0m Shutting down ColdVox runtime...
3469:  �[2m2025-12-23T22:32:20.241614Z�[0m �[34mDEBUG�[0m ThreadId(19) �[2mcoldvox_app::runtime�[0m�[2m:�[0m ColdVox runtime shutdown complete
3470:  �[2m2025-12-23T22:32:20.241744Z�[0m �[32m INFO�[0m ThreadId(19) �[2mgolden_master::tests�[0m�[2m:�[0m Pipeline shutdown complete
3471:  �[2m2025-12-23T22:32:20.241796Z�[0m �[32m INFO�[0m ThreadId(19) �[2mgolden_master::tests�[0m�[2m:�[0m Final VAD events: [SerializableVadEvent { kind: "SpeechStart", duration_ms: None }, SerializableVadEvent { kind: "SpeechEnd", duration_ms: Some(3456) }]
3472:  �[2m2025-12-23T22:32:20.241830Z�[0m �[32m INFO�[0m ThreadId(19) �[2mgolden_master::tests�[0m�[2m:�[0m Final injected text: []
3473:  test tests::test_short_phrase_pipeline ... ok
3474:  test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.58s
3475:  ##[group]Run echo "=== Running Moonshine E2E Tests ==="
...

3479:  �[36;1mexport PYTHONPATH=$(python3 -c "import site; print(site.getsitepackages()[0])")�[0m
3480:  �[36;1m# Run the specific E2E test with the moonshine feature enabled�[0m
3481:  �[36;1mcargo test -p coldvox-stt --features moonshine --test moonshine_e2e -- --nocapture�[0m
3482:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
3483:  env:
3484:  CARGO_TERM_COLOR: always
3485:  WHISPER_MODEL_SIZE: tiny
3486:  MIN_FREE_DISK_GB: 10
3487:  MAX_LOAD_AVERAGE: 5
3488:  CARGO_INCREMENTAL: 0
3489:  CARGO_PROFILE_DEV_DEBUG: 0
3490:  RUST_BACKTRACE: short
3491:  RUSTFLAGS: -D warnings
3492:  CARGO_UNSTABLE_SPARSE_REGISTRY: true
3493:  CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
3494:  CACHE_ON_FAILURE: true
3495:  ##[endgroup]
...

3671:  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 68.5 MB/s eta 0:00:00
3672:  Downloading llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (56.3 MB)
3673:  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 56.3/56.3 MB 71.0 MB/s eta 0:00:00
3674:  Downloading threadpoolctl-3.6.0-py3-none-any.whl (18 kB)
3675:  Downloading pycparser-2.23-py3-none-any.whl (118 kB)
3676:  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 45.4 MB/s eta 0:00:00
3677:  Installing collected packages: nvidia-cusparselt-cu12, triton, threadpoolctl, safetensors, regex, pycparser, psutil, pooch, nvidia-nvtx-cu12, nvidia-nvshmem-cu12, nvidia-nvjitlink-cu12, nvidia-nccl-cu12, nvidia-curand-cu12, nvidia-cufile-cu12, nvidia-cuda-runtime-cu12, nvidia-cuda-nvrtc-cu12, nvidia-cuda-cupti-cu12, nvidia-cublas-cu12, numpy, networkx, msgpack, llvmlite, lazy_loader, joblib, huggingface-hub, decorator, audioread, soxr, scipy, nvidia-cusparse-cu12, nvidia-cufft-cu12, nvidia-cudnn-cu12, numba, cffi, transformers, soundfile, scikit-learn, nvidia-cusolver-cu12, torch, librosa, accelerate
3678:  Attempting uninstall: numpy
3679:  Found existing installation: numpy 2.4.0
3680:  Uninstalling numpy-2.4.0:
3681:  Successfully uninstalled numpy-2.4.0
3682:  Attempting uninstall: huggingface-hub
3683:  Found existing installation: huggingface_hub 1.2.3
3684:  Uninstalling huggingface_hub-1.2.3:
3685:  Successfully uninstalled huggingface_hub-1.2.3
3686:  ERROR: Could not install packages due to an OSError: [Errno 28] No space left on device
3687:  ##[error]Process completed with exit code 1.
3688:  ##[group]Run actions/upload-artifact@v6
...

3695:  if-no-files-found: warn
3696:  compression-level: 6
3697:  overwrite: false
3698:  include-hidden-files: false
3699:  env:
3700:  CARGO_TERM_COLOR: always
3701:  WHISPER_MODEL_SIZE: tiny
3702:  MIN_FREE_DISK_GB: 10
3703:  MAX_LOAD_AVERAGE: 5
3704:  CARGO_INCREMENTAL: 0
3705:  CARGO_PROFILE_DEV_DEBUG: 0
3706:  RUST_BACKTRACE: short
3707:  RUSTFLAGS: -D warnings
3708:  CARGO_UNSTABLE_SPARSE_REGISTRY: true
3709:  CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
3710:  CACHE_ON_FAILURE: true
3711:  ##[endgroup]

Adds comprehensive unit tests for the `enigo_injector` and `kdotool_injector` modules.

For the `enigo_injector`, the code was refactored to extract the core keyboard manipulation logic into synchronous, generic functions. This allows for the use of a mock `Enigo` object, enabling isolated unit tests without side effects.

For the `kdotool_injector`, the tests mock the `kdotool` command-line tool by creating a temporary script and manipulating the `PATH` environment variable. This ensures that the tests are reliable and do not depend on the actual `kdotool` binary being installed.
auto-merge was automatically disabled December 25, 2025 00:00

Head branch was modified

@kiloconnect
Copy link

kiloconnect bot commented Dec 25, 2025

⚠️ 5 Issues Found

Severity Issue Location
CRITICAL Concurrent PATH manipulation crates/coldvox-text-injection/src/kdotool_injector.rs:153-170
WARNING Modifier key not released on error crates/coldvox-text-injection/src/enigo_injector.rs:82-105
WARNING Unix-only PATH separator crates/coldvox-text-injection/src/kdotool_injector.rs:160-161
WARNING Manual PATH cleanup crates/coldvox-text-injection/src/kdotool_injector.rs:182-192
WARNING Shell scripts Unix-only crates/coldvox-text-injection/src/kdotool_injector.rs:180

Recommendation: Address critical issues before merge

Review Details (2 files)

Files: enigo_injector.rs (2 issues), kdotool_injector.rs (3 issues)

Summary: This PR adds comprehensive unit tests for text injectors. The tests are well-structured and use good mocking patterns. However, there are several issues with test isolation and cross-platform compatibility that need to be addressed.

Critical Issues:

  • The kdotool tests modify the global PATH environment variable without proper synchronization, which will cause race conditions when tests run in parallel.

Warning Issues:

  • Modifier keys (Ctrl/Cmd) may not be released if paste operations fail in the middle
  • PATH separator is hardcoded as ":" which doesn't work on Windows
  • One test manually restores PATH instead of using the PathGuard RAII helper
  • Shell scripts are only made executable on Unix systems, causing Windows failures

Fix these issues in Kilo Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants