cueward-adapter-macos 0.5.0

macOS adapter for Cueward with Safari, Notes, Messages, Reminders, Calendar, Screenshot, Clipboard, and OCR integrations.
//! The same lifeline must stop a batch after native progress, not just before dispatch.
use super::*;
use std::os::unix::fs::PermissionsExt;
use std::process::{Command, Stdio};

pub(super) fn wait_for(mut predicate: impl FnMut() -> bool) {
    let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
    while !predicate() {
        assert!(
            std::time::Instant::now() < deadline,
            "owned helper did not reach expected checkpoint"
        );
        std::thread::sleep(std::time::Duration::from_millis(10));
    }
}
fn config() -> Option<(std::path::PathBuf, serde_json::Value)> {
    let path = std::path::PathBuf::from(std::env::var_os("CUEWARD_TEST_BATCH_PARTIAL_CONFIG")?);
    let value = serde_json::from_slice(&fs::read(&path).unwrap()).unwrap();
    Some((path, value))
}
#[test]
fn partial_parent_helper() {
    let Some((_, value)) = config() else {
        return;
    };
    let request: BatchRenameRequest = serde_json::from_value(value["request"].clone()).unwrap();
    let _ = run(
        Path::new(value["worker"].as_str().unwrap()),
        &request,
        30000,
    );
}
#[test]
fn partial_worker_helper() {
    let Some((path, value)) = config() else {
        return;
    };
    let input = read_supervised_request().unwrap();
    let platform = Racing {
        root: value["request"]["root"].as_str().unwrap().into(),
        calls: Cell::new(0),
        change: Change::Pause(path.parent().unwrap().into()),
    };
    execute_prepared(&platform, &input).unwrap();
}
struct Processes {
    parent: Option<std::process::Child>,
    group: Option<String>,
}
impl Drop for Processes {
    fn drop(&mut self) {
        if let Some(parent) = &mut self.parent {
            let _ = parent.kill();
            let _ = parent.wait();
        }
        if let Some(group) = &self.group {
            let _ = Command::new("/bin/kill")
                .args(["-KILL", &format!("-{group}")])
                .status();
        }
    }
}
fn launch(directory: &Path, request: &BatchRenameRequest) -> Processes {
    let worker = directory.join("worker");
    let exe = std::env::current_exe().unwrap();
    fs::write(&worker,format!("#!/bin/sh\nprintf '%s' \"$$\" > '{0}/group'\n'{1}' --exact files::batch_execution::tests::races::lifetime::partial_worker_helper --nocapture\nprintf '%s' \"$?\" > '{0}/finished'\n",directory.display(),exe.display())).unwrap();
    fs::set_permissions(&worker, fs::Permissions::from_mode(0o700)).unwrap();
    let config = directory.join("config.json");
    fs::write(
        &config,
        serde_json::to_vec(&serde_json::json!({"request":request,"worker":worker})).unwrap(),
    )
    .unwrap();
    let parent = Command::new(exe)
        .args([
            "--exact",
            "files::batch_execution::tests::races::lifetime::partial_parent_helper",
            "--nocapture",
        ])
        .env("CUEWARD_TEST_BATCH_PARTIAL_CONFIG", config)
        .stdout(Stdio::null())
        .stderr(Stdio::from(
            File::create(directory.join("progress")).unwrap(),
        ))
        .spawn()
        .unwrap();
    Processes {
        parent: Some(parent),
        group: None,
    }
}
fn interrupt(signal: &str, links: bool) {
    let root = fixture();
    let transport = tempfile::tempdir().unwrap();
    let outside = tempfile::tempdir().unwrap();
    let target = outside.path().join("PRIVATE");
    fs::write(&target, b"OWNED outside").unwrap();
    let target_before = MacFiles.stamp(&fs::metadata(&target).unwrap());
    if links {
        fs::remove_file(root.path().join("a")).unwrap();
        fs::remove_file(root.path().join("b")).unwrap();
        symlink(&target, root.path().join("a")).unwrap();
        symlink("missing", root.path().join("b")).unwrap();
    }
    let inode = fs::symlink_metadata(root.path().join("a")).unwrap().ino();
    let mut request = request(root.path(), &[("a", "new"), ("b", "other")]);
    for entry in &mut request.entries {
        entry.link_itself = links;
    }
    let mut processes = launch(transport.path(), &request);
    wait_for(|| transport.path().join("ready").exists());
    processes.group = Some(fs::read_to_string(transport.path().join("group")).unwrap());
    let parent = processes.parent.as_mut().unwrap();
    assert!(
        Command::new("/bin/kill")
            .args([signal, &parent.id().to_string()])
            .status()
            .unwrap()
            .success()
    );
    assert!(!parent.wait().unwrap().success());
    processes.parent = None;
    fs::write(transport.path().join("release"), b"").unwrap();
    wait_for(|| fs::metadata(transport.path().join("finished")).is_ok_and(|m| m.len() > 0));
    processes.group = None;
    assert_ne!(
        fs::read_to_string(transport.path().join("finished")).unwrap(),
        "0"
    );
    let progress = fs::read_to_string(transport.path().join("progress")).unwrap();
    let id = progress
        .split("operation_id=")
        .nth(1)
        .unwrap()
        .split(';')
        .next()
        .unwrap();
    let receipt = read_receipt(id).unwrap();
    assert_eq!(receipt.status, RelocationStatus::Uncertain);
    assert!(!receipt.completion_verified && receipt.items[0].mutation_attempted);
    assert_eq!(receipt.items[0].renamed, None);
    assert!(receipt.items[1].operation_id.is_none());
    assert_eq!(
        fs::symlink_metadata(root.path().join("new")).unwrap().ino(),
        inode
    );
    if links {
        assert_eq!(fs::read_link(root.path().join("new")).unwrap(), target);
        assert_eq!(
            fs::read_link(root.path().join("b")).unwrap(),
            Path::new("missing")
        );
        assert_eq!(
            MacFiles.stamp(&fs::metadata(&target).unwrap()),
            target_before
        );
        assert_eq!(fs::read(&target).unwrap(), b"OWNED outside");
    } else {
        assert_eq!(fs::read(root.path().join("new")).unwrap(), b"OWNED a\0");
        assert_eq!(fs::read(root.path().join("b")).unwrap(), b"OWNED b\0");
    }
    assert!(fs::symlink_metadata(root.path().join("a")).is_err());
    assert!(fs::symlink_metadata(root.path().join("other")).is_err());
    cleanup(&receipt);
}
#[test]
fn batch_execute_parent_death_after_first_native_rename_preserves_partial_evidence() {
    for signal in ["-INT", "-TERM", "-KILL"] {
        interrupt(signal, false);
    }
}

#[test]
fn batch_link_parent_death_after_first_native_rename_preserves_partial_evidence_and_targets() {
    for signal in ["-INT", "-TERM", "-KILL"] {
        interrupt(signal, true);
    }
}