wait-file 0.1.0

cli utility to monitor for changes in one or multiple files
use std::sync::{Arc, Once};

use super::*;

#[derive(Clone)]
pub struct Exit {
    flag:	Arc<()>,
}

impl Exit {
    pub fn new() -> Self {
        Self {
            flag: Arc::new(())
        }
    }

    pub fn is_running(&self) -> bool {
        println!("XXX {}", Arc::strong_count(&self.flag));
        Arc::strong_count(&self.flag) > 1
    }

    pub fn do_exit(self) {
    }
}

struct TestEnvironment {
    dir:	tempfile::TempDir,
    bindir:	PathBuf,
    tmpdir:	PathBuf,
    rundir:	PathBuf,
}

impl TestEnvironment {
    pub fn new() -> Self {
        let d = tempfile::TempDir::new().unwrap();

        Self {
            bindir:	d.path().join("bin"),
            tmpdir:	d.path().join("tmp"),
            rundir:	d.path().join("run"),
            dir:	d,
        }.init()
    }

    fn init(self) -> Self {
        std::fs::create_dir(&self.bindir).unwrap();
        std::fs::create_dir(&self.tmpdir).unwrap();
        std::fs::create_dir(&self.rundir).unwrap();

        self.mkdir("a");
        self.mkdir("b/c/d");

        self.touch("a/pre-a");
        self.touch("b/pre-b");
        self.touch("b/c/pre-c");
        self.touch("b/c/d/pre-d");

        self
    }

    pub fn mkdir<P: AsRef<Path>>(&self, p: P) {
        let p = p.as_ref();

        std::fs::create_dir_all(self.tmpdir.join(p)).unwrap();
    }

    pub fn touch<P: AsRef<Path>>(&self, p: P) {
        let p = p.as_ref();

        std::fs::File::options()
            .create(true)
            .append(true)
            .open(self.tmpdir.join(p))
            .unwrap();
    }

    #[allow(unused)]
    pub fn touch_run<P: AsRef<Path>>(&self, p: P) {
        let p = p.as_ref();

        std::fs::File::options()
            .create(true)
            .append(true)
            .open(self.rundir.join(p))
            .unwrap();
    }

    pub fn unlink_run<P: AsRef<Path>>(&self, p: P) {
        let _ = std::fs::remove_file(self.rundir.join(p));
    }

    pub fn unlink<P: AsRef<Path>>(&self, p: P) {
        let _ = std::fs::remove_file(self.tmpdir.join(p));
    }

    pub fn path<P: AsRef<Path>>(&self, p: P) -> PathBuf {
        self.tmpdir.join(p)
    }

    pub fn script_dir() -> PathBuf {
        let p: &Path = env!("CARGO_MANIFEST_DIR").as_ref();
        p.join("tests")
    }
}

struct Cli {
    args: Vec<OsString>,
}

impl Cli {
    pub fn new() -> Self {
        Self { args: Vec::new() }
    }

    pub fn arg<S: Into<OsString>>(mut self, s: S) -> Self {
        self.args.push(s.into());
        self
    }
}

fn delay(cnt: u64) {
    std::thread::sleep(Duration::from_millis(100 * cnt));
}

fn exists<P: AsRef<Path>>(p: P) -> bool {
    std::fs::exists(p.as_ref()).unwrap()
}

fn init_tests() {
    static INIT: Once = Once::new();

    INIT.call_once(|| {
        env_logger::init();
    });
}

#[test]
fn test_00() {
    init_tests();

    let env = TestEnvironment::new();
    let cli = Cli::new()
        .arg("wait-file")
        .arg("--watch")
        .arg(env.path("a/stamp-a"))
        .arg("--watch")
        .arg(env.path("b/c/d/file"))
        .arg("--build")
        .arg(format!("{:?} {:?}", TestEnvironment::script_dir().join("build"), env.dir.path()))
        .arg("--settle-time")
        .arg("0")
        .arg("--")
        .arg(TestEnvironment::script_dir().join("test"))
        .arg(env.dir.path())
        .arg(env.path("b/c/d/file"));

    println!("cli={:?}", cli.args);

    let args = Args::parse_from(cli.args);

    std::thread::scope(|s| {
        let exit = Exit::new();

        let h = s.spawn({
            let exit = exit.clone();
            move || {
                println!("running test_00");
                args.run(exit)
            }
        });

        info!("A test_00 starting");

        while !exists(env.rundir.join("build-1")) {
            delay(1);
        }

        info!("B test_00 starting");

        delay(1);

        assert!(!h.is_finished());
        assert!(!exists(env.rundir.join("test-run")));

        env.touch("b/c/d/file");

        delay(1);
        assert!(!h.is_finished());
        assert!(!exists(env.rundir.join("test-run")));

        env.touch("a/stamp-a");

        delay(5);

        assert!(!h.is_finished());
        assert!(exists(env.rundir.join("build-run")));
        assert!(exists(env.rundir.join("test-run")));
        assert!(exists(env.rundir.join("test-1")));
        assert!(exists(env.rundir.join("test-exists-1")));

        env.unlink_run("build-run");
        env.unlink_run("test-run");
        env.unlink("a/stamp-a");

        delay(5);

        assert!(!h.is_finished());
        assert!(exists(env.rundir.join("build-run")));
        assert!(!exists(env.rundir.join("test-run")));

        env.touch("a/stamp-a");
        delay(5);

        assert!(!h.is_finished());
        assert!(exists(env.rundir.join("build-run")));
        assert!(exists(env.rundir.join("test-run")));

        assert!(!h.is_finished());

        info!("test_00: waiting for exit");

        exit.do_exit();
        env.touch("a/stamp-a");
        h.join().unwrap();
    });
}

#[test]
fn test_01() {
    init_tests();

    let env = TestEnvironment::new();
    let cli = Cli::new()
        .arg("wait-file")
        .arg("--watch")
        .arg(env.path("b/c/d/file-0"))
        .arg("--watch")
        .arg(env.path("b/c/d/file-1"))
        .arg("--build")
        .arg(format!("{:?} {:?}", TestEnvironment::script_dir().join("build"), env.dir.path()))
        .arg("--settle-time")
        .arg("0")
        .arg("--")
        .arg(TestEnvironment::script_dir().join("test"))
        .arg(env.dir.path())
        .arg(env.path("b/c/d/file-0"));

    println!("cli={:?}", cli.args);

    let args = Args::parse_from(cli.args);

    std::thread::scope(|s| {
        let exit = Exit::new();

        let h = s.spawn({
            let exit = exit.clone();
            move || {
                println!("running test_01");
                args.run(exit)
            }
        });

        info!("A test_01 starting");

        while !exists(env.rundir.join("build-run")) {
            delay(1);
        }

        info!("B test_01 starting");

        delay(1);

        assert!(!h.is_finished());
        assert!(!exists(env.rundir.join("test-run")));

        env.unlink_run("build-run");

        env.touch("b/c/d/file-1");
        delay(5);

        assert!(!h.is_finished());
        assert!(exists(env.rundir.join("build-run")));
        assert!(!exists(env.rundir.join("test-run")));

        env.touch("b/c/d/file-0");
        delay(5);

        assert!(!h.is_finished());
        assert!(exists(env.rundir.join("test-run")));

        info!("test_01: waiting for exit");

        exit.do_exit();
        env.touch("b/c/d/file-0");
        h.join().unwrap();

    });
}