Skip to main content

molo_coding/coding/
test_runner.rs

1use crate::harness::ExecutionPolicy;
2use crate::{RunContext, RunMetadata};
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5
6use super::command::{CommandExecutor, CommandOutput, CommandRequest, CommandStatus};
7
8/// Test run request.
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct TestRunRequest {
11    /// Command to execute as a verification step.
12    pub command: CommandRequest,
13    /// Human-readable test target name.
14    pub name: String,
15}
16
17/// Structured verification result.
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct VerificationResult {
20    /// Test target name.
21    pub name: String,
22    /// Whether verification succeeded.
23    pub passed: bool,
24    /// Short model-visible summary.
25    pub summary: String,
26    /// Command output.
27    pub output: Option<CommandOutput>,
28    /// Host-owned metadata.
29    pub metadata: RunMetadata,
30}
31
32/// Convenience adapter for test commands.
33#[async_trait]
34pub trait TestRunner: Send + Sync {
35    /// Runs a test command through a command executor policy boundary.
36    async fn run(
37        &self,
38        request: TestRunRequest,
39        policy: &ExecutionPolicy,
40        context: &RunContext,
41    ) -> Result<VerificationResult, TestRunError>;
42}
43
44/// Test runner backed by a [`CommandExecutor`].
45#[derive(Debug, Clone)]
46pub struct CommandTestRunner<C> {
47    commands: C,
48}
49
50impl<C> CommandTestRunner<C> {
51    /// Constructs a command test runner.
52    pub fn new(commands: C) -> Self {
53        Self { commands }
54    }
55}
56
57#[async_trait]
58impl<C> TestRunner for CommandTestRunner<C>
59where
60    C: CommandExecutor,
61{
62    async fn run(
63        &self,
64        request: TestRunRequest,
65        policy: &ExecutionPolicy,
66        context: &RunContext,
67    ) -> Result<VerificationResult, TestRunError> {
68        let output = self
69            .commands
70            .execute(request.command, policy, context)
71            .await
72            .map_err(|error| TestRunError::Command {
73                message: error.to_string(),
74            })?;
75        let passed = matches!(output.status, CommandStatus::Exited { code: 0 });
76        let summary = if passed {
77            format!("{} passed", request.name)
78        } else {
79            format!("{} failed: {:?}", request.name, output.status)
80        };
81        Ok(VerificationResult {
82            name: request.name,
83            passed,
84            summary,
85            output: Some(output),
86            metadata: RunMetadata::new(),
87        })
88    }
89}
90
91/// Test run errors.
92#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, Serialize, Deserialize)]
93#[non_exhaustive]
94pub enum TestRunError {
95    /// Command execution failed.
96    #[error("test command error: {message}")]
97    Command {
98        /// Model-safe explanation.
99        message: String,
100    },
101}