ic-host-process 0.4.3

Admitted host executable resolution, bounded execution and Git observations
Documentation
use super::*;

#[test]
fn observed_identity_is_retained_and_drift_is_rejected_before_execution() {
    let fixture = Fixture::new();
    let candidate = fixture.root.join("installed tool");
    fs::copy(tool_path(), &candidate).unwrap();
    let marker = fixture.root.join("invocations");
    let environment = [(
        OsString::from("FIXTURE_MARKER"),
        marker.as_os_str().to_owned(),
    )];
    let tool = AdmittedTool::admit_version(
        &VersionSpec {
            executable: &candidate,
            executable_bytes: 1024 * 1024,
            version_arguments: &[OsString::from("--version")],
            version_identity: VERSION,
        },
        &fixture.context(&environment),
        LIMITS,
    )
    .unwrap();
    assert_eq!(tool.path(), candidate);
    assert_eq!(tool.version_identity(), VERSION);
    assert_eq!(tool.identity(), hash_file(&candidate, 1024 * 1024).unwrap());
    assert_eq!(fs::read(&marker).unwrap(), b"invoked\n");
    let output = tool
        .run(
            &[OsString::from("--arguments"), OsString::from("a b")],
            &fixture.context(&environment),
            LIMITS,
        )
        .unwrap();
    assert_eq!(output.stdout, b"[a b]");
    let original = fs::read(&candidate).unwrap();
    let mut changed = original.clone();
    changed.extend_from_slice(b"\n# changed bytes, same version\n");
    fs::write(&candidate, changed).unwrap();
    assert!(matches!(
        tool.run(&[], &fixture.context(&environment), LIMITS),
        Err(ToolError::Artifact(ArtifactError::DigestMismatch { .. }))
    ));
    fs::write(&candidate, original).unwrap();
    fs::set_permissions(&candidate, fs::Permissions::from_mode(0o600)).unwrap();
    assert!(matches!(
        tool.run(&[], &fixture.context(&environment), LIMITS),
        Err(ToolError::NotExecutable)
    ));
    assert_eq!(fs::read(marker).unwrap(), b"invoked\ninvoked\n");
}

#[test]
fn version_admission_retains_exact_match_and_failure_evidence() {
    let fixture = Fixture::new();
    let path = tool_path();
    let spec = VersionSpec {
        executable: &path,
        executable_bytes: 1024 * 1024,
        version_arguments: &[OsString::from("--version")],
        version_identity: VERSION,
    };
    let environment = [(
        OsString::from("FIXTURE_VERSION"),
        OsString::from(format!("  {VERSION}\t")),
    )];
    assert!(AdmittedTool::admit_version(&spec, &fixture.context(&environment), LIMITS).is_ok());
    let environment = [(OsString::from("FIXTURE_VERSION"), OsString::from("wrong"))];
    let Err(ToolError::VersionMismatch { evidence }) =
        AdmittedTool::admit_version(&spec, &fixture.context(&environment), LIMITS)
    else {
        panic!("version must match exactly");
    };
    assert_eq!(evidence.stdout, b"wrong\n");
    assert!(evidence.status.unwrap().success());
    let environment = [(
        OsString::from("FIXTURE_VERSION_MODE"),
        OsString::from("invalid-utf8"),
    )];
    assert!(matches!(
        AdmittedTool::admit_version(&spec, &fixture.context(&environment), LIMITS),
        Err(ToolError::VersionUtf8 { evidence, .. }) if evidence.stdout == [0xff]
    ));
    let environment = [(
        OsString::from("FIXTURE_VERSION_MODE"),
        OsString::from("fail"),
    )];
    let Err(ToolError::Execution(error)) =
        AdmittedTool::admit_version(&spec, &fixture.context(&environment), LIMITS)
    else {
        panic!("version command must succeed");
    };
    assert!(matches!(error.failure, ExecutionFailure::ExitStatus));
    assert_eq!(error.evidence.status.unwrap().code(), Some(23));
    assert_eq!(error.evidence.stderr, b"version failed");
}

#[test]
fn installed_file_and_spec_are_admitted_before_version_dispatch() {
    let fixture = Fixture::new();
    let candidate = fixture.root.join("tool");
    fs::copy(tool_path(), &candidate).unwrap();
    let marker = fixture.root.join("invocations");
    let environment = [(
        OsString::from("FIXTURE_MARKER"),
        marker.as_os_str().to_owned(),
    )];
    let mut spec = VersionSpec {
        executable: &candidate,
        executable_bytes: 1,
        version_arguments: &[OsString::from("--version")],
        version_identity: VERSION,
    };
    assert!(matches!(
        AdmittedTool::admit_version(&spec, &fixture.context(&environment), LIMITS),
        Err(ToolError::Artifact(ArtifactError::LimitExceeded { .. }))
    ));
    spec.executable_bytes = 1024 * 1024;
    for invalid in ["", " leading", "trailing "] {
        spec.version_identity = invalid;
        assert!(matches!(
            AdmittedTool::admit_version(&spec, &fixture.context(&environment), LIMITS),
            Err(ToolError::InvalidInvocation(
                InvalidInvocation::VersionIdentity
            ))
        ));
    }
    spec.version_identity = VERSION;
    spec.executable = Path::new("relative");
    assert!(matches!(
        AdmittedTool::admit_version(&spec, &fixture.context(&environment), LIMITS),
        Err(ToolError::InvalidInvocation(
            InvalidInvocation::ExecutablePath
        ))
    ));
    spec.executable = &candidate;
    fs::set_permissions(&candidate, fs::Permissions::from_mode(0o600)).unwrap();
    assert!(matches!(
        AdmittedTool::admit_version(&spec, &fixture.context(&environment), LIMITS),
        Err(ToolError::NotExecutable)
    ));
    assert!(!marker.exists());
}

#[test]
fn version_capture_keeps_output_and_deadline_limits() {
    let fixture = Fixture::new();
    let path = tool_path();
    for argument in ["--flood-stdout", "--wait"] {
        let Err(ToolError::Execution(error)) = AdmittedTool::admit_version(
            &VersionSpec {
                executable: &path,
                executable_bytes: 1024 * 1024,
                version_arguments: &[OsString::from(argument)],
                version_identity: VERSION,
            },
            &fixture.context(&[]),
            OutputLimits {
                stdout_bytes: 17,
                timeout: Duration::from_millis(100),
                ..LIMITS
            },
        ) else {
            panic!("version capture must remain bounded");
        };
        if argument == "--flood-stdout" {
            assert!(matches!(
                error.failure,
                ExecutionFailure::OutputLimit {
                    stream: OutputStream::Stdout
                }
            ));
            assert_eq!(error.evidence.stdout.len(), 17);
            assert!(error.evidence.stdout_truncated);
        } else {
            assert!(matches!(error.failure, ExecutionFailure::TimedOut));
        }
        assert!(error.evidence.status.is_some());
        assert!(error.kill_error.is_none() && error.wait_error.is_none());
    }
}