diff --git a/crates/coldvox-text-injection/Cargo.toml b/crates/coldvox-text-injection/Cargo.toml index 9bf6c0b5..f630de52 100644 --- a/crates/coldvox-text-injection/Cargo.toml +++ b/crates/coldvox-text-injection/Cargo.toml @@ -68,4 +68,4 @@ xdg_kdotool = ["kdotool"] [build-dependencies] cc = "1.2" -pkg-config = "0.3" +pkg-config = "0.3.30" diff --git a/crates/coldvox-text-injection/TESTING.md b/crates/coldvox-text-injection/TESTING.md new file mode 100644 index 00000000..b19369b1 --- /dev/null +++ b/crates/coldvox-text-injection/TESTING.md @@ -0,0 +1,80 @@ +# Testing Text Injection Backends + +This document outlines the process for testing the text injection backends for the `coldvox-text-injection` crate. + +## Prerequisites + +To run these tests, you will need a graphical environment (X11 or Wayland) and the development packages for GTK3. The required packages for some common distributions are listed below: + +**Debian/Ubuntu:** +```bash +sudo apt-get install libgtk-3-dev +``` + +**Fedora:** +```bash +sudo dnf install gtk3-devel +``` + +**Arch Linux:** +```bash +sudo pacman -S gtk3 +``` + +You will also need to have the `at-spi2-core` package installed, which is usually included by default in most desktop environments. + +## Running the Tests + +A test runner script, `run_tests.sh`, is provided to simplify the process of running the tests. This script must be run from the root of the repository. + +To run all available backend tests, simply execute the script without any arguments: +```bash +./crates/coldvox-text-injection/run_tests.sh +``` + +You can also run tests for a specific backend by passing the backend name as an argument to the script. The available backends are: + +- `atspi` +- `ydotool` +- `kdotool` +- `clipboard` +- `enigo` + +For example, to run only the AT-SPI backend tests, you would use the following command: +```bash +./crates/coldvox-text-injection/run_tests.sh atspi +``` + +## Test Matrix + +The following matrix can be used to track the test results across different platforms and backends. Please fill in the results as you test each combination. + +| Backend | GNOME/Wayland | GNOME/X11 | KDE/Wayland | KDE/X11 | Sway | i3 | +|---|---|---|---|---|---|---| +| AT-SPI | ? | ? | ? | ? | ? | ? | +| Clipboard | ? | ? | ? | ? | ? | ? | +| ydotool | ? | N/A | ? | N/A | ? | N/A | +| kdotool | N/A | ? | N/A | ? | N/A | ? | +| Combo | ? | ? | ? | ? | ? | ? | + +## Known Issues + +* (Please add any known issues here) + +## Platform-Specific Setup Notes + +* **(GNOME/Wayland)** (Please add any platform-specific setup notes here) +* **(GNOME/X11)** (Please add any platform-specific setup notes here) +* **(KDE/Wayland)** (Please add any platform-specific setup notes here) +* **(KDE/X11)** (Please add any platform-specific setup notes here) +* **(Sway)** (Please add any platform-specific setup notes here) +* **(i3)** (Please add any platform-specific setup notes here) + +## Recommended Backends + +* **(GNOME/Wayland)** (Please add your recommended backend for this platform here) +* **(GNOME/X11)** (Please add your recommended backend for this platform here) +* **(KDE/Wayland)** (Please add your recommended backend for this platform here) +* **(KDE/X11)** (Please add your recommended backend for this platform here) +* **(Sway)** (Please add your recommended backend for this platform here) +* **(i3)** (Please add your recommended backend for this platform here) diff --git a/crates/coldvox-text-injection/build.rs b/crates/coldvox-text-injection/build.rs index 1fa1a509..040c46ea 100644 --- a/crates/coldvox-text-injection/build.rs +++ b/crates/coldvox-text-injection/build.rs @@ -3,8 +3,6 @@ use std::path::Path; use std::process::Command; fn main() { - // We only need to build the test applications if the `real-injection-tests` feature is enabled. - // This avoids adding build-time dependencies for regular users. if env::var("CARGO_FEATURE_REAL_INJECTION_TESTS").is_ok() { build_gtk_test_app(); build_terminal_test_app(); @@ -17,46 +15,37 @@ fn build_gtk_test_app() { let out_dir = env::var("OUT_DIR").unwrap(); let executable_path = Path::new(&out_dir).join("gtk_test_app"); - // Check if pkg-config and GTK3 are available before attempting to build. - let check_pkg_config = Command::new("pkg-config") - .arg("--atleast-version=3.0") - .arg("gtk+-3.0") - .status(); - - if check_pkg_config.is_err() || !check_pkg_config.unwrap().success() { - println!("cargo:warning=Skipping GTK test app build: GTK+ 3.0 not found by pkg-config."); - return; - } - - // Get compiler flags from pkg-config. - let cflags_output = Command::new("pkg-config") - .arg("--cflags") - .arg("gtk+-3.0") - .output() - .expect("Failed to run pkg-config for cflags"); - - // Get linker flags from pkg-config. - let libs_output = Command::new("pkg-config") - .arg("--libs") - .arg("gtk+-3.0") - .output() - .expect("Failed to run pkg-config for libs"); + // Get compiler and linker flags from pkg-config. + let cflags = match Command::new("pkg-config").arg("--cflags").arg("gtk+-3.0").output() { + Ok(output) if output.status.success() => String::from_utf8(output.stdout).unwrap(), + _ => { + println!("cargo:warning=Skipping GTK test app build: pkg-config could not get cflags for gtk+-3.0."); + return; + } + }; - let cflags = String::from_utf8(cflags_output.stdout).unwrap(); - let libs = String::from_utf8(libs_output.stdout).unwrap(); + let libs = match Command::new("pkg-config").arg("--libs").arg("gtk+-3.0").output() { + Ok(output) if output.status.success() => String::from_utf8(output.stdout).unwrap(), + _ => { + println!("cargo:warning=Skipping GTK test app build: pkg-config could not get libs for gtk+-3.0."); + return; + } + }; - // Compile the test app using gcc. - let status = Command::new("gcc") + // Compile the test app using gcc, capturing all output. + let gcc_output = Command::new("gcc") .arg("test-apps/gtk_test_app.c") .arg("-o") .arg(&executable_path) .args(cflags.split_whitespace()) .args(libs.split_whitespace()) - .status() + .output() // Capture output instead of just status .expect("Failed to execute gcc"); - if !status.success() { - println!("cargo:warning=Failed to compile GTK test app. Real injection tests against GTK may fail."); + if !gcc_output.status.success() { + println!("cargo:warning=Failed to compile GTK test app."); + println!("cargo:warning=GCC stdout: {}", String::from_utf8_lossy(&gcc_output.stdout)); + println!("cargo:warning=GCC stderr: {}", String::from_utf8_lossy(&gcc_output.stderr)); } } @@ -66,24 +55,22 @@ fn build_terminal_test_app() { let out_dir = env::var("OUT_DIR").unwrap(); let target_dir = Path::new(&out_dir).join("terminal-test-app-target"); + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - // Build the terminal test app using `cargo build`. - let status = Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())) + let build_output = Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())) + .current_dir(Path::new(&crate_dir).join("test-apps/terminal-test-app")) .arg("build") - .arg("--package") - .arg("terminal-test-app") - .arg("--release") // Build in release mode for faster startup. + .arg("--release") .arg("--target-dir") .arg(&target_dir) - .status() + .output() // Capture output .expect("Failed to execute cargo build for terminal-test-app"); - if !status.success() { - println!( - "cargo:warning=Failed to build the terminal test app. Real injection tests may fail." - ); + if !build_output.status.success() { + println!("cargo:warning=Failed to build the terminal test app."); + println!("cargo:warning=Cargo stdout: {}", String::from_utf8_lossy(&build_output.stdout)); + println!("cargo:warning=Cargo stderr: {}", String::from_utf8_lossy(&build_output.stderr)); } else { - // Copy the executable to a known location in OUT_DIR for the tests to find easily. let src_path = target_dir.join("release/terminal-test-app"); let dest_path = Path::new(&out_dir).join("terminal-test-app"); if let Err(e) = std::fs::copy(&src_path, &dest_path) { diff --git a/crates/coldvox-text-injection/run_tests.sh b/crates/coldvox-text-injection/run_tests.sh new file mode 100755 index 00000000..acd5849d --- /dev/null +++ b/crates/coldvox-text-injection/run_tests.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# +# Test Runner for ColdVox Text Injection Backends +# +# This script automates the process of building and running the real-world +# text injection tests for the `coldvox-text-injection` crate. It allows +# you to selectively test different backends. +# +# Usage: +# ./run_tests.sh [backend] +# +# Examples: +# ./run_tests.sh # Run all available backend tests +# ./run_tests.sh atspi # Run only the AT-SPI backend tests +# ./run_tests.sh ydotool # Run only the ydotool backend tests +# + +set -euo pipefail + +# --- Configuration --- + +# The crate to test +CRATE_NAME="coldvox-text-injection" + +# --- Helper Functions --- + +# Print a message in blue +info() { + echo -e "\033[1;34m[INFO]\033[0m $1" +} + +# Print a message in yellow +warn() { + echo -e "\033[1;33m[WARN]\033[0m $1" +} + +# Print a message in green +success() { + echo -e "\033[1;32m[SUCCESS]\033[0m $1" +} + +# Print an error message and exit +fatal() { + echo -e "\033[1;31m[FATAL]\033[0m $1" >&2 + exit 1 +} + +# --- Main Logic --- + +# Ensure the script is run from the repository root +if [ ! -f "Cargo.toml" ]; then + fatal "This script must be run from the repository root." +fi + +# Determine which tests to run +TEST_TARGET="${1:-all}" + +# Build the tests with the required features +info "Building tests for '$CRATE_NAME' with 'real-injection-tests' feature..." +cargo build --package "$CRATE_NAME" --features real-injection-tests --tests + +# Run the tests +case "$TEST_TARGET" in + all) + info "Running all backend tests..." + cargo test --package "$CRATE_NAME" --features real-injection-tests -- --nocapture + ;; + atspi) + info "Running AT-SPI backend tests..." + cargo test --package "$CRATE_NAME" --features real-injection-tests -- test_atspi --nocapture + ;; + ydotool) + info "Running ydotool backend tests..." + cargo test --package "$CRATE_NAME" --features real-injection-tests -- test_ydotool --nocapture + ;; + kdotool) + info "Running kdotool backend tests..." + cargo test --package "$CRATE_NAME" --features real-injection-tests -- test_kdotool --nocapture + ;; + clipboard) + info "Running clipboard backend tests..." + cargo test --package "$CRATE_NAME" --features real-injection-tests -- test_clipboard --nocapture + ;; + enigo) + info "Running enigo backend tests..." + cargo test --package "$CRATE_NAME" --features real-injection-tests -- test_enigo --nocapture + ;; + *) + fatal "Unknown test target: '$TEST_TARGET'. Available targets: all, atspi, ydotool, kdotool, clipboard, enigo" + ;; +esac + +success "Test run completed for '$TEST_TARGET'." diff --git a/crates/coldvox-text-injection/src/confirm.rs b/crates/coldvox-text-injection/src/confirm.rs index cacff8a6..a3e21365 100644 --- a/crates/coldvox-text-injection/src/confirm.rs +++ b/crates/coldvox-text-injection/src/confirm.rs @@ -57,8 +57,7 @@ use crate::types::{InjectionConfig, InjectionResult}; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::Mutex; -use tracing::{info, warn, debug, trace, error}; -use coldvox_foundation::error::InjectionError; +use tracing::{info, warn}; use unicode_segmentation::UnicodeSegmentation; /// Confirmation result for text injection diff --git a/crates/coldvox-text-injection/src/injectors/atspi.rs b/crates/coldvox-text-injection/src/injectors/atspi.rs index cf85d11b..cdfbfdb0 100644 --- a/crates/coldvox-text-injection/src/injectors/atspi.rs +++ b/crates/coldvox-text-injection/src/injectors/atspi.rs @@ -13,8 +13,6 @@ use async_trait::async_trait; use coldvox_foundation::error::InjectionError; use std::time::Instant; use tracing::{debug, trace, warn}; -use crate::log_throttle::log_atspi_connection_failure; -use crate::logging::utils; // Re-export the old Context type for backwards compatibility #[deprecated( diff --git a/crates/coldvox-text-injection/src/prewarm.rs b/crates/coldvox-text-injection/src/prewarm.rs index 8065bbb8..eb9402ba 100644 --- a/crates/coldvox-text-injection/src/prewarm.rs +++ b/crates/coldvox-text-injection/src/prewarm.rs @@ -9,7 +9,7 @@ use crate::types::{InjectionConfig, InjectionMethod, InjectionResult}; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::{Mutex, RwLock}; -use tracing::{debug, info, warn, trace}; +use tracing::{debug, info, warn}; /// TTL for cached pre-warmed data (3 seconds) const CACHE_TTL: Duration = Duration::from_secs(3); diff --git a/crates/coldvox-text-injection/src/tests/real_injection.rs.disabled b/crates/coldvox-text-injection/src/tests/real_injection.rs similarity index 88% rename from crates/coldvox-text-injection/src/tests/real_injection.rs.disabled rename to crates/coldvox-text-injection/src/tests/real_injection.rs index 82d5b5c1..dc61d812 100644 --- a/crates/coldvox-text-injection/src/tests/real_injection.rs.disabled +++ b/crates/coldvox-text-injection/src/tests/real_injection.rs @@ -14,6 +14,8 @@ use crate::clipboard_paste_injector::ClipboardPasteInjector; use crate::enigo_injector::EnigoInjector; #[cfg(feature = "atspi")] use crate::injectors::atspi::AtspiInjector; +#[cfg(feature = "kdotool")] +use crate::kdotool_injector::KdotoolInjector; #[cfg(feature = "ydotool")] use crate::ydotool_injector::YdotoolInjector; // Bring trait into scope so async trait methods (inject_text, is_available) resolve. @@ -353,4 +355,49 @@ async fn test_enigo_typing_special_chars() { run_enigo_typing_test("Enigo types\nnew lines and\ttabs.").await; } -// TODO(#40): Add tests for kdotool, combo injectors etc. +//--- Kdotool Tests --- +#[cfg(feature = "kdotool")] +/// Helper function to run a complete injection and verification test for the kdotool backend. +async fn run_kdotool_test(test_text: &str) { + let env = TestEnvironment::current(); + if !env.can_run_real_tests() { + eprintln!("Skipping kdotool test: no display server found."); + return; + } + + let injector = KdotoolInjector::new(Default::default()); + if !injector.is_available().await { + println!("Skipping kdotool test: backend is not available (is kdotool running?)."); + return; + } + + let app = TestAppManager::launch_gtk_app().expect("Failed to launch GTK app."); + tokio::time::sleep(Duration::from_millis(500)).await; + wait_for_app_ready(&app).await; + + injector + .inject_text(test_text) + .await + .unwrap_or_else(|e| panic!("kdotool injection failed for text '{}': {:?}", test_text, e)); + + verify_injection(&app.output_file, test_text) + .await + .unwrap_or_else(|e| { + panic!( + "Verification failed for kdotool with text '{}': {}", + test_text, e + ) + }); +} + +#[tokio::test] +#[cfg(feature = "kdotool")] +async fn test_kdotool_simple_text() { + run_kdotool_test("Hello from kdotool!").await; +} + +#[tokio::test] +#[cfg(feature = "kdotool")] +async fn test_kdotool_unicode_text() { + run_kdotool_test("Hello ColdVox 🎤 测试 (via kdotool)").await; +} diff --git a/crates/coldvox-text-injection/test-apps/terminal-test-app/Cargo.lock b/crates/coldvox-text-injection/test-apps/terminal-test-app/Cargo.lock new file mode 100644 index 00000000..8c75eb7a --- /dev/null +++ b/crates/coldvox-text-injection/test-apps/terminal-test-app/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "terminal-test-app" +version = "0.1.0"