// Copyright 2016 Kitware, Inc.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

extern crate itertools;
use self::itertools::Itertools;

extern crate log;
use self::log::{Log, LogLevel, LogLevelFilter, LogMetadata, LogRecord};

extern crate serde_json;
use self::serde_json::Value;

extern crate tempdir;
use self::tempdir::TempDir;

use super::{Director, ErrorKind, Handler, HandlerResult, Result, RunResult};

use std::cell::RefCell;
use std::env;
use std::fs::{self, File, Permissions};
use std::io::{self, Read, Write};
use std::os::unix::fs::PermissionsExt;
use std::path::Path;

fn setup_logging() {
    struct SimpleLogger;

    impl Log for SimpleLogger {
        fn enabled(&self, metadata: &LogMetadata) -> bool {
            metadata.level() <= LogLevel::Debug
        }

        fn log(&self, record: &LogRecord) {
            if self.enabled(record.metadata()) {
                println!("[{}] {}", record.level(), record.args());
            }
        }
    }

    // Since the tests run in parallel, this may get called multiple times. Just ignore errors.
    let _ = log::set_logger(|max_level| {
        max_level.set(LogLevelFilter::Debug);
        Box::new(SimpleLogger)
    });
}

fn test_workspace_dir(test_name: &str) -> TempDir {
    setup_logging();

    let mut working_dir = env::current_exe().unwrap();
    working_dir.pop();

    TempDir::new_in(working_dir, test_name).unwrap()
}

fn files_in_path(path: &Path) -> Vec<String> {
    path.read_dir()
        .unwrap()
        .filter_map(|e| e.ok())
        .filter(|e| !e.path().is_dir())
        .map(|e| e.path().file_name().unwrap().to_string_lossy().into_owned())
        .sorted()
}

fn drop_job(path: &Path, kind: &str) {
    let object_str = format!(r#"{{"kind":"{}","data":{{}}}}"#,
                             kind);
    let object = serde_json::from_str::<Value>(&object_str).unwrap();
    let nfiles = files_in_path(path).len();
    let mut fout = File::create(path.join(format!("{}-{}.json", nfiles, kind))).unwrap();
    serde_json::to_writer(&mut fout, &object).unwrap();
}

fn check_queues(path: &Path, accept: &[&str], reject: &[&str], reject_reasons: &[&str],
                fail: &[&str], fail_reasons: &[&str], ignored: &[&str]) {
    let accept_files = files_in_path(&path.join("accept"));
    let reject_files = files_in_path(&path.join("reject"));
    let fail_files = files_in_path(&path.join("fail"));
    let ignored_files = files_in_path(path);

    assert_eq!(accept_files, accept);
    assert_eq!(reject_files, reject);
    assert_eq!(fail_files, fail);
    assert_eq!(ignored_files, ignored);

    let reasons = reject_files.into_iter()
        .filter(|reject| reject.ends_with(".reason"))
        .map(|file_name| {
            let reason_fname = path.join("reject").join(file_name);
            let mut reason_file = File::open(&reason_fname).unwrap();
            let mut reason = String::new();
            reason_file.read_to_string(&mut reason).unwrap();
            reason
        })
        .collect::<Vec<_>>();
    assert_eq!(reasons, reject_reasons);

    let reasons = fail_files.into_iter()
        .filter(|fail| fail.ends_with(".reason"))
        .map(|file_name| {
            let reason_fname = path.join("fail").join(file_name);
            let mut reason_file = File::open(&reason_fname).unwrap();
            let mut reason = String::new();
            reason_file.read_to_string(&mut reason).unwrap();
            reason
        })
        .collect::<Vec<_>>();
    assert_eq!(reasons, fail_reasons);
}

#[derive(Default)]
struct TestHandler {
    jobs: RefCell<Vec<String>>,
}

impl TestHandler {
    fn jobs(self) -> Vec<String> {
        self.jobs.into_inner()
    }
}

impl Handler for TestHandler {
    fn add_to_director<'a>(&'a self, director: &mut Director<'a>) -> Result<()> {
        try!(director.add_handler("accept", self));
        try!(director.add_handler("defer", self));
        try!(director.add_handler("reject", self));
        try!(director.add_handler("fail", self));
        try!(director.add_handler("restart", self));
        try!(director.add_handler("done", self));
        try!(director.add_handler("error", self));

        Ok(())
    }

    fn handle(&self, kind: &str, _: &Value) -> Result<HandlerResult> {
        self.jobs.borrow_mut().push(kind.to_string());

        match kind {
            "accept" => Ok(HandlerResult::Accept),
            "defer" => Ok(HandlerResult::Defer("deferring".to_string())),
            "reject" => Ok(HandlerResult::Reject("rejecting".to_string())),
            "restart" => Ok(HandlerResult::Restart),
            "fail" => {
                let err = io::Error::new(io::ErrorKind::Other, "fail");
                Ok(HandlerResult::Fail(Box::new(err)))
            },
            "done" => Ok(HandlerResult::Done),
            "error" => bail!(ErrorKind::HandlerError),
            _ => unreachable!(),
        }
    }

    fn retry_limit(&self, _: &str) -> usize {
        0
    }

    fn handle_retry(&self, kind: &str, object: &Value, reasons: Vec<String>)
                    -> Result<HandlerResult> {
        if reasons.len() > self.retry_limit(kind) {
            self.handle("done", object)
        } else {
            self.handle(kind, object)
        }
    }
}

#[test]
fn test_not_a_directory() {
    let path = env::current_exe().unwrap();
    let director = Director::new(&path);
    assert!(director.is_err());

    let err = director.unwrap_err();

    match *err.kind() {
        ErrorKind::NotADirectory(ref err_path) => {
            assert_eq!(*err_path, path);
        },
        _ => panic!("unexpected error: {:?}", err),
    }
}

#[test]
fn test_duplicate_handler() {
    let tempdir = test_workspace_dir("test_duplicate_handler");
    let handler1 = TestHandler::default();
    let handler2 = TestHandler::default();
    let mut director = Director::new(tempdir.path()).unwrap();

    handler1.add_to_director(&mut director).unwrap();
    let res = handler2.add_to_director(&mut director);
    assert!(res.is_err());

    let err = res.unwrap_err();

    match *err.kind() {
        ErrorKind::DuplicateHandler(ref kind) => {
            assert_eq!(kind, "accept");
        },
        _ => panic!("unexpected error: {:?}", err),
    }
}

#[test]
fn test_no_permissions() {
    let tempdir = test_workspace_dir("test_no_permissions");
    fs::set_permissions(tempdir.path(), Permissions::from_mode(0o555)).unwrap();
    let director = Director::new(tempdir.path());
    assert!(director.is_err());

    let err = director.unwrap_err();

    match *err.kind() {
        ErrorKind::Msg(ref msg) => {
            assert_eq!(msg, "failed to create the 'accept' directory");
        },
        _ => panic!("unexpected error: {:?}", err),
    }
}

#[test]
fn test_ignore_directories() {
    let tempdir = test_workspace_dir("test_ignore_directories");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        drop_job(&tempdir.path(), "done");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["done"]);
    check_queues(tempdir.path(),
                 &["0-done.json", "0-done.stamp"],
                 &[],
                 &[],
                 &[],
                 &[],
                 &[]);
}

#[test]
fn test_ignore_wrong_extension() {
    let tempdir = test_workspace_dir("test_ignore_wrong_extension");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        File::create(&tempdir.path().join("0-ignored.txt")).unwrap();
        drop_job(&tempdir.path(), "done");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["done"]);
    check_queues(tempdir.path(),
                 &["1-done.json", "1-done.stamp"],
                 &[],
                 &[],
                 &[],
                 &[],
                 &["0-ignored.txt"]);
}

#[test]
fn test_ignore_invalid_json() {
    let tempdir = test_workspace_dir("test_ignore_invalid_json");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        {
            let mut fout = File::create(&tempdir.path().join("0-invalid.json")).unwrap();
            write!(&mut fout, "invalid json").unwrap();
        }
        drop_job(&tempdir.path(), "done");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["done"]);
    check_queues(tempdir.path(),
                 &["1-done.json", "1-done.stamp"],
                 &[],
                 &[],
                 &[],
                 &[],
                 &["0-invalid.json"]);
}

#[test]
fn test_ignore_wrong_json_type() {
    let tempdir = test_workspace_dir("test_ignore_wrong_json_type");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        {
            let mut fout = File::create(&tempdir.path().join("0-array.json")).unwrap();
            write!(&mut fout, "[]").unwrap();
            let mut fout = File::create(&tempdir.path().join("1-bool.json")).unwrap();
            write!(&mut fout, "true").unwrap();
            let mut fout = File::create(&tempdir.path().join("2-null.json")).unwrap();
            write!(&mut fout, "null").unwrap();
            let mut fout = File::create(&tempdir.path().join("3-number.json")).unwrap();
            write!(&mut fout, "0").unwrap();
            let mut fout = File::create(&tempdir.path().join("4-string.json")).unwrap();
            write!(&mut fout, r#""""#).unwrap();
        }
        drop_job(&tempdir.path(), "done");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["done"]);
    check_queues(tempdir.path(),
                 &["5-done.json", "5-done.stamp"],
                 &[],
                 &[],
                 &[],
                 &[],
                 &["0-array.json", "1-bool.json", "2-null.json", "3-number.json", "4-string.json"]);
}

#[test]
fn test_reject_missing_kind() {
    let tempdir = test_workspace_dir("test_reject_missing_kind");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        {
            let mut fout = File::create(&tempdir.path().join("0-missing-kind.json")).unwrap();
            fout.write_all(r#"{"data":{}}"#.as_bytes()).unwrap();
        }
        drop_job(&tempdir.path(), "done");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["done"]);
    check_queues(tempdir.path(),
                 &["1-done.json", "1-done.stamp"],
                 &["0-missing-kind.json", "0-missing-kind.reason", "0-missing-kind.stamp"],
                 &["no 'kind'\n"],
                 &[],
                 &[],
                 &[]);
}

#[test]
fn test_reject_invalid_kind() {
    let tempdir = test_workspace_dir("test_reject_invalid_kind");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        {
            let mut fout = File::create(&tempdir.path().join("0-invalid-kind-array.json")).unwrap();
            fout.write_all(r#"{"kind":[],"data":{}}"#.as_bytes()).unwrap();
            let mut fout = File::create(&tempdir.path().join("1-invalid-kind-bool.json")).unwrap();
            fout.write_all(r#"{"kind":true,"data":{}}"#.as_bytes()).unwrap();
            let mut fout = File::create(&tempdir.path().join("2-invalid-kind-null.json")).unwrap();
            fout.write_all(r#"{"kind":null,"data":{}}"#.as_bytes()).unwrap();
            let mut fout = File::create(&tempdir.path().join("3-invalid-kind-number.json"))
                .unwrap();
            fout.write_all(r#"{"kind":0,"data":{}}"#.as_bytes()).unwrap();
            let mut fout = File::create(&tempdir.path().join("4-invalid-kind-object.json"))
                .unwrap();
            fout.write_all(r#"{"kind":{},"data":{}}"#.as_bytes()).unwrap();
        }
        drop_job(&tempdir.path(), "done");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["done"]);
    check_queues(tempdir.path(),
                 &["5-done.json", "5-done.stamp"],
                 &["0-invalid-kind-array.json",
                   "0-invalid-kind-array.reason",
                   "0-invalid-kind-array.stamp",
                   "1-invalid-kind-bool.json",
                   "1-invalid-kind-bool.reason",
                   "1-invalid-kind-bool.stamp",
                   "2-invalid-kind-null.json",
                   "2-invalid-kind-null.reason",
                   "2-invalid-kind-null.stamp",
                   "3-invalid-kind-number.json",
                   "3-invalid-kind-number.reason",
                   "3-invalid-kind-number.stamp",
                   "4-invalid-kind-object.json",
                   "4-invalid-kind-object.reason",
                   "4-invalid-kind-object.stamp"],
                 &["'kind' is not a string\n",
                   "'kind' is not a string\n",
                   "'kind' is not a string\n",
                   "'kind' is not a string\n",
                   "'kind' is not a string\n"],
                 &[],
                 &[],
                 &[]);
}

#[test]
fn test_reject_missing_data() {
    let tempdir = test_workspace_dir("test_reject_missing_data");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        {
            let mut fout = File::create(&tempdir.path().join("0-missing-data.json")).unwrap();
            fout.write_all(r#"{"kind":""}"#.as_bytes()).unwrap();
        }
        drop_job(&tempdir.path(), "done");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["done"]);
    check_queues(tempdir.path(),
                 &["1-done.json", "1-done.stamp"],
                 &["0-missing-data.json", "0-missing-data.reason", "0-missing-data.stamp"],
                 &["no 'data'\n"],
                 &[],
                 &[],
                 &[]);
}

#[test]
fn test_reject_invalid_retry() {
    let tempdir = test_workspace_dir("test_reject_invalid_retry");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        {
            let mut fout = File::create(&tempdir.path().join("0-invalid-retry-array.json"))
                .unwrap();
            fout.write_all(r#"{"kind":"retry","data":{},"retry":[]}"#.as_bytes()).unwrap();
            let mut fout = File::create(&tempdir.path().join("1-invalid-retry-bool.json")).unwrap();
            fout.write_all(r#"{"kind":"retry","data":{},"retry":true}"#.as_bytes()).unwrap();
            let mut fout = File::create(&tempdir.path().join("2-invalid-retry-null.json")).unwrap();
            fout.write_all(r#"{"kind":"retry","data":{},"retry":null}"#.as_bytes()).unwrap();
            let mut fout = File::create(&tempdir.path().join("3-invalid-retry-number.json"))
                .unwrap();
            fout.write_all(r#"{"kind":"retry","data":{},"retry":0}"#.as_bytes()).unwrap();
            let mut fout = File::create(&tempdir.path().join("4-invalid-retry-string.json"))
                .unwrap();
            fout.write_all(r#"{"kind":"retry","data":{},"retry":""}"#.as_bytes()).unwrap();
        }
        drop_job(&tempdir.path(), "done");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["done"]);
    check_queues(tempdir.path(),
                 &["5-done.json", "5-done.stamp"],
                 &["0-invalid-retry-array.json",
                   "0-invalid-retry-array.reason",
                   "0-invalid-retry-array.stamp",
                   "1-invalid-retry-bool.json",
                   "1-invalid-retry-bool.reason",
                   "1-invalid-retry-bool.stamp",
                   "2-invalid-retry-null.json",
                   "2-invalid-retry-null.reason",
                   "2-invalid-retry-null.stamp",
                   "3-invalid-retry-number.json",
                   "3-invalid-retry-number.reason",
                   "3-invalid-retry-number.stamp",
                   "4-invalid-retry-string.json",
                   "4-invalid-retry-string.reason",
                   "4-invalid-retry-string.stamp"],
                 &["'retry' is not an object\n",
                   "'retry' is not an object\n",
                   "'retry' is not an object\n",
                   "'retry' is not an object\n",
                   "'retry' is not an object\n"],
                 &[],
                 &[],
                 &[]);
}

#[test]
fn test_reject_invalid_retry_object() {
    let tempdir = test_workspace_dir("test_reject_invalid_retry_object");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        {
            let mut fout = File::create(&tempdir.path().join("0-invalid-retry-object-array.json"))
                .unwrap();
            fout.write_all(r#"{"kind":"retry","data":{},"retry":{"reason":[]}}"#.as_bytes())
                .unwrap();
            let mut fout = File::create(&tempdir.path().join("1-invalid-retry-object-bool.json"))
                .unwrap();
            fout.write_all(r#"{"kind":"retry","data":{},"retry":{"reason":true}}"#.as_bytes())
                .unwrap();
            let mut fout = File::create(&tempdir.path().join("2-invalid-retry-object-null.json"))
                .unwrap();
            fout.write_all(r#"{"kind":"retry","data":{},"retry":{"reason":null}}"#.as_bytes())
                .unwrap();
            let mut fout = File::create(&tempdir.path().join("3-invalid-retry-object-number.json"))
                .unwrap();
            fout.write_all(r#"{"kind":"retry","data":{},"retry":{"reason":0}}"#.as_bytes())
                .unwrap();
            let mut fout = File::create(&tempdir.path().join("4-invalid-retry-object-object.json"))
                .unwrap();
            fout.write_all(r#"{"kind":"retry","data":{},"retry":{"reason":{}}}"#.as_bytes())
                .unwrap();
        }
        drop_job(&tempdir.path(), "done");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["done"]);
    check_queues(tempdir.path(),
                 &["5-done.json", "5-done.stamp"],
                 &["0-invalid-retry-object-array.json",
                   "0-invalid-retry-object-array.reason",
                   "0-invalid-retry-object-array.stamp",
                   "1-invalid-retry-object-bool.json",
                   "1-invalid-retry-object-bool.reason",
                   "1-invalid-retry-object-bool.stamp",
                   "2-invalid-retry-object-null.json",
                   "2-invalid-retry-object-null.reason",
                   "2-invalid-retry-object-null.stamp",
                   "3-invalid-retry-object-number.json",
                   "3-invalid-retry-object-number.reason",
                   "3-invalid-retry-object-number.stamp",
                   "4-invalid-retry-object-object.json",
                   "4-invalid-retry-object-object.reason",
                   "4-invalid-retry-object-object.stamp"],
                 &["retry reason is not a string\n",
                   "retry reason is not a string\n",
                   "retry reason is not a string\n",
                   "retry reason is not a string\n",
                   "retry reason is not a string\n"],
                 &[],
                 &[],
                 &[]);
}

#[test]
fn test_reject_no_handler() {
    let tempdir = test_workspace_dir("test_reject_no_handler");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        drop_job(&tempdir.path(), "no-handler");
        drop_job(&tempdir.path(), "done");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["done"]);
    check_queues(tempdir.path(),
                 &["1-done.json", "1-done.stamp"],
                 &["0-no-handler.json", "0-no-handler.reason", "0-no-handler.stamp"],
                 &["no handler for kind 'no-handler'\n"],
                 &[],
                 &[],
                 &[]);
}

#[test]
fn test_receive_handler_error() {
    let tempdir = test_workspace_dir("test_receive_handler_error");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        drop_job(&tempdir.path(), "error");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["error"]);
    check_queues(tempdir.path(), &[], &[], &[], &[], &[], &["0-error.json"]);
}

#[test]
fn test_accept() {
    let tempdir = test_workspace_dir("test_accept");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        drop_job(&tempdir.path(), "accept");
        drop_job(&tempdir.path(), "done");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["accept", "done"]);
    check_queues(tempdir.path(),
                 &["0-accept.json", "0-accept.stamp", "1-done.json", "1-done.stamp"],
                 &[],
                 &[],
                 &[],
                 &[],
                 &[]);
}

#[test]
fn test_defer() {
    let tempdir = test_workspace_dir("test_defer");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        drop_job(&tempdir.path(), "defer");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["defer", "done"]);
    let accept_files = files_in_path(&tempdir.path().join("accept"));
    assert_eq!(accept_files.len(), 2);
    let accept_files_ref = accept_files.iter()
        .map(|s| s.as_ref())
        .collect::<Vec<_>>();
    check_queues(tempdir.path(),
                 &accept_files_ref,
                 &["0-defer.json", "0-defer.reason", "0-defer.stamp"],
                 &["deferring\n"],
                 &[],
                 &[],
                 &[]);
}

#[test]
fn test_reject() {
    let tempdir = test_workspace_dir("test_reject");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        drop_job(&tempdir.path(), "reject");
        drop_job(&tempdir.path(), "done");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["reject", "done"]);
    check_queues(tempdir.path(),
                 &["1-done.json", "1-done.stamp"],
                 &["0-reject.json", "0-reject.reason", "0-reject.stamp"],
                 &["rejecting\n"],
                 &[],
                 &[],
                 &[]);
}

#[test]
fn test_fail() {
    let tempdir = test_workspace_dir("test_fail");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        drop_job(&tempdir.path(), "fail");
        drop_job(&tempdir.path(), "done");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["fail", "done"]);
    check_queues(tempdir.path(),
                 &["1-done.json", "1-done.stamp"],
                 &[],
                 &[],
                 &["0-fail.json", "0-fail.reason", "0-fail.stamp"],
                 &["Error { repr: Custom(Custom { kind: Other, error: StringError(\"fail\") }) \
                    }\n"],
                 &[]);
}

#[test]
fn test_restart() {
    let tempdir = test_workspace_dir("test_restart");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        drop_job(&tempdir.path(), "restart");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Restart);
    }

    assert_eq!(handler.jobs(), ["restart"]);
    check_queues(tempdir.path(),
                 &["0-restart.json", "0-restart.stamp"],
                 &[],
                 &[],
                 &[],
                 &[],
                 &[]);
}

#[test]
fn test_done() {
    let tempdir = test_workspace_dir("test_done");
    let handler = TestHandler::default();

    {
        let mut director = Director::new(tempdir.path()).unwrap();

        handler.add_to_director(&mut director).unwrap();
        drop_job(&tempdir.path(), "done");
        drop_job(&tempdir.path(), "after");

        let res = director.watch_directory(tempdir.path()).unwrap();
        assert_eq!(res, RunResult::Done);
    }

    assert_eq!(handler.jobs(), ["done"]);
    check_queues(tempdir.path(),
                 &["0-done.json", "0-done.stamp"],
                 &[],
                 &[],
                 &[],
                 &[],
                 &["1-after.json"]);
}
