How to Use openlogi-agent-mock for Scripted Inventory in Headless Development

The openlogi-agent-mock binary implements the same Tarpc Agent IPC contract as the production openlogi-agent, serving a fully scripted, in-memory device inventory that enables CI pipelines and automated UI testing without physical Logitech hardware.

Developers working on the AprilNEA/OpenLogi ecosystem can leverage openlogi-agent-mock to simulate complete device inventories in headless environments. This specialized binary, located in crates/openlogi-agent/src/bin/mock_agent.rs, mirrors the production agent's exact API surface while maintaining all device state in memory according to the patterns documented in the repository's Development guide (lines 9‑12).

Architecture and Startup Sequence

The mock agent replicates the production startup sequence while substituting physical HID access with pure Rust data structures. This ensures the desktop GUI connects transparently without code changes.

Single-Instance Locking

To prevent race conditions with a real agent, the mock acquires exclusive ownership of the agent socket by locking the agent.lock file. In lines 29‑33 of mock_agent.rs, the code calls single_instance::acquire("agent.lock") to guarantee that only one agent—mock or real—runs at a time. This forces the GUI to connect to the mock during development sessions.

Scripted Inventory Initialization

The in-memory inventory is instantiated via State::new() (lines 21‑28), which populates a DeviceInventory containing:

  • A Bolt receiver
  • A directly-attached mouse
  • A standalone Litra light
  • Several paired peripheral devices

The MockAgent struct holds this state within an Arc<Mutex<State>>, allowing concurrent access by the Tokio runtime while preserving thread safety.

IPC Transport Binding

Lines 56‑62 invoke transport::listen() to bind the same local-socket address used by the production agent. This exposes the full Agent API—including pairing flows, DPI changes, SmartShift torque adjustments, and ring observations—over the identical Tarpc service definition. The GUI handshakes the PROTOCOL_VERSION and receives the scripted state exactly as it would from physical hardware.

Running the Mock Agent

By default, the binary launches with the dev profile (OPENLOGI_PROFILE=dev) as defined in lines 13‑17, ensuring it binds to the development socket without interfering with production installations.

Execute the mock from the repository root:

cargo run -p openlogi-agent --bin openlogi-agent-mock

To test production socket behavior instead, set the environment variable before launching:

OPENLOGI_PROFILE=prod cargo run -p openlogi-agent --bin openlogi-agent-mock

Configuring the Scripted Inventory

The mock simulates dynamic device behaviors through constants defined at the top of mock_agent.rs. Developers can tune these values to test specific scenarios without hardware modifications:

  • CAMERA_TOGGLE_PERIOD – Controls Litra light state change intervals
  • FOREGROUND_SWITCH_PERIOD – Simulates application switching for foreground-aware features
  • SCRIPTED_APPS – Defines the set of applications cycled through during the mock's simulated foreground-app rotation

These parameters drive the State machine, allowing you to simulate varying battery drain rates, pairing timings, or DPI switching behaviors purely in software.

CI/CD Integration for Headless Testing

The mock enables fully automated UI validation in headless pipelines. The following GitHub Actions pattern starts the mock as a background service before executing GUI tests:

- name: Start mock agent
  run: |
    cargo run -p openlogi-agent --bin openlogi-agent-mock &
    echo $! > mock.pid

- name: Run headless UI tests
  env:
    OPENLOGI_DEV_AGENT: 0   # Force GUI to use dev socket

  run: cargo test -p openlogi-desktop --test ui

- name: Stop mock agent
  run: kill $(cat mock.pid)

Setting OPENLOGI_DEV_AGENT=0 directs the desktop GUI to connect to the development socket where the mock listens, ensuring all device interactions occur against the scripted inventory rather than physical USB devices.

Embedded Testing with Custom Devices

For Rust unit tests requiring specific hardware configurations, instantiate State and MockAgent directly in your test code:

use openlogi_agent_mock::{MockAgent, State};
use tokio::runtime::Builder;

let mut state = State::new().expect("initial inventory");
state.inventory.devices.push(
    openlogi_core::device::DeviceKind::Mouse {
        model: "TestMouse".into(),
        dpi: vec![800, 1600, 3200],
        ..Default::default()
    }
);

let rt = Builder::new_multi_thread().enable_all().build().unwrap();
rt.block_on(async {
    MockAgent::new(state).serve().await.expect("serve mock");
});

This pattern allows validation of edge-case device capabilities—such as unusual DPI ranges or lighting modes—without maintaining physical hardware labs.

Summary

  • The openlogi-agent-mock binary in crates/openlogi-agent/src/bin/mock_agent.rs implements the production Tarpc Agent service contract (lines 56‑62), providing complete API compatibility.
  • Single-instance locking via single_instance::acquire("agent.lock") (lines 29‑33) prevents conflicts with real agents.
  • The scripted inventory initializes through State::new() (lines 21‑28) and persists in Arc<Mutex<State>> for the process lifetime.
  • Headless CI pipelines launch the mock with cargo run -p openlogi-agent --bin openlogi-agent-mock and connect the GUI using OPENLOGI_DEV_AGENT=0.
  • Constants like CAMERA_TOGGLE_PERIOD and SCRIPTED_APPS enable fine-grained simulation of hardware behaviors without physical devices.

Frequently Asked Questions

How does the mock prevent conflicts with the production agent?

The mock calls single_instance::acquire("agent.lock") during startup (lines 29‑33), creating an exclusive filesystem lock that prevents the real openlogi-agent from starting simultaneously. This guarantees the desktop GUI connects to the mock instance rather than failing to attach to occupied hardware resources.

Can I simulate custom device types not in the default inventory?

Yes. Import State from the mock crate and manually append devices to state.inventory.devices before calling MockAgent::new(state). You can define any DeviceKind variant supported by openlogi_core, including custom DPI ranges or battery levels, to test specific UI code paths without physical hardware.

Does the mock support testing production socket bindings?

Set OPENLOGI_PROFILE=prod before launching the mock to override the default dev profile (lines 13‑17). This configures transport::listen() to bind the production socket address instead of the development endpoint, allowing validation of production-specific IPC behavior in controlled environments.

What Tokio runtime configuration does the mock use?

The mock initializes a multi-threaded Tokio runtime via Builder::new_multi_thread() that drives the Tarpc service asynchronously (as implemented in the serve() method). This matches the production agent's async architecture, ensuring that timing-sensitive operations like pairing handshakes and DPI changes behave identically to real hardware interactions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →