#![allow(dead_code)]

#[macro_use]
extern crate error_chain;

#[test]
fn smoke_test_1() {
    error_chain! {
        types {
            Error, ErrorKind, ResultExt, Result;
        }

        links { }

        foreign_links { }

        errors { }
    };
}

#[test]
fn smoke_test_2() {
    error_chain! {
        types { }

        links { }

        foreign_links { }

        errors { }
    };
}

#[test]
fn smoke_test_3() {
    error_chain! {
        links { }

        foreign_links { }

        errors { }
    };
}

#[test]
fn smoke_test_4() {
    error_chain! {
        links { }

        foreign_links { }

        errors {
            HttpStatus(e: u32) {
                description("http request returned an unsuccessful status code")
                display("http request returned an unsuccessful status code: {}", e)
            }
        }
    };
}

#[test]
fn smoke_test_5() {
    error_chain! {
        types { }

        links { }

        foreign_links { }

        errors {
            HttpStatus(e: u32) {
                description("http request returned an unsuccessful status code")
                display("http request returned an unsuccessful status code: {}", e)
            }
        }
    };
}

#[test]
fn smoke_test_6() {
    error_chain! {
        errors {
            HttpStatus(e: u32) {
                description("http request returned an unsuccessful status code")
                display("http request returned an unsuccessful status code: {}", e)
            }
        }
    };
}

#[test]
fn smoke_test_7() {
    error_chain! {
        types { }

        foreign_links { }

        errors {
            HttpStatus(e: u32) {
                description("http request returned an unsuccessful status code")
                display("http request returned an unsuccessful status code: {}", e)
            }
        }
    };
}

#[test]
fn smoke_test_8() {
    error_chain! {
        types { }

        links { }
        links { }

        foreign_links { }
        foreign_links { }

        errors {
            FileNotFound
            AccessDenied
        }
    };
}

#[test]
fn order_test_1() {
    error_chain! { types { } links { } foreign_links { } errors { } };
}

#[test]
fn order_test_2() {
    error_chain! { links { } types { } foreign_links { } errors { } };
}

#[test]
fn order_test_3() {
    error_chain! { foreign_links { }  links { }  errors { } types { } };
}

#[test]
fn order_test_4() {
    error_chain! { errors { } types { } foreign_links { } };
}

#[test]
fn order_test_5() {
    error_chain! { foreign_links { } types { }  };
}

#[test]
fn order_test_6() {
    error_chain! {
        links { }

        errors {
            HttpStatus(e: u32) {
                description("http request returned an unsuccessful status code")
                display("http request returned an unsuccessful status code: {}", e)
            }
        }


        foreign_links { }
    };
}

#[test]
fn order_test_7() {
    error_chain! {
        links { }

        foreign_links { }

        types {
            Error, ErrorKind, ResultExt, Result;
        }
    };
}


#[test]
fn order_test_8() {
    error_chain! {
        links { }

        foreign_links { }
        foreign_links { }

        types {
            Error, ErrorKind, ResultExt, Result;
        }
    };
}

#[test]
fn empty() {
    error_chain!{};
}

#[test]
#[cfg(feature = "backtrace")]
fn has_backtrace_depending_on_env() {
    use std::process::Command;
    use std::path::Path;

    let cmd_path = if cfg!(windows) {
        Path::new("./target/debug/has_backtrace.exe")
    } else {
        Path::new("./target/debug/has_backtrace")
    };
    let mut cmd = Command::new(cmd_path);

    // missing RUST_BACKTRACE and RUST_BACKTRACE=0
    cmd.env_remove("RUST_BACKTRACE");
    assert_eq!(cmd.status().unwrap().code().unwrap(), 0);

    cmd.env("RUST_BACKTRACE", "0");
    assert_eq!(cmd.status().unwrap().code().unwrap(), 0);

    // RUST_BACKTRACE set to anything but 0
    cmd.env("RUST_BACKTRACE", "yes");
    assert_eq!(cmd.status().unwrap().code().unwrap(), 1);

    cmd.env("RUST_BACKTRACE", "1");
    assert_eq!(cmd.status().unwrap().code().unwrap(), 1);
}

#[test]
fn chain_err() {
    use std::fmt;

    error_chain! {
        foreign_links {
            Fmt(fmt::Error);
        }
        errors {
            Test
        }
    }

    let _: Result<()> = Err(fmt::Error).chain_err(|| "");
    let _: Result<()> = Err(Error::from_kind(ErrorKind::Test)).chain_err(|| "");
}

/// Verify that an error chain is extended one by `Error::chain_err`, with
/// the new error added to the end.
#[test]
fn error_chain_err() {
    error_chain! {
        errors {
            Test
        }
    }

    let base = Error::from(ErrorKind::Test);
    let ext = base.chain_err(|| "Test passes");

    if let Error(ErrorKind::Msg(_), _) = ext {
        // pass
    } else {
        panic!("The error should be wrapped. {:?}", ext);
    }
}

#[test]
fn links() {
    mod test {
        error_chain!{}
    }

    error_chain! {
        links {
            Test(test::Error, test::ErrorKind);
        }
    }
}

#[cfg(test)]
mod foreign_link_test {

    use std::fmt;

    // Note: foreign errors must be `pub` because they appear in the
    // signature of the public foreign_link_error_path
    #[derive(Debug)]
    pub struct ForeignError {
        cause: ForeignErrorCause,
    }

    impl ::std::error::Error for ForeignError {
        fn description(&self) -> &'static str {
            "Foreign error description"
        }

        fn cause(&self) -> Option<&::std::error::Error> {
            Some(&self.cause)
        }
    }

    impl fmt::Display for ForeignError {
        fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            write!(formatter, "Foreign error display")
        }
    }

    #[derive(Debug)]
    pub struct ForeignErrorCause {}

    impl ::std::error::Error for ForeignErrorCause {
        fn description(&self) -> &'static str {
            "Foreign error cause description"
        }

        fn cause(&self) -> Option<&::std::error::Error> {
            None
        }
    }

    impl fmt::Display for ForeignErrorCause {
        fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            write!(formatter, "Foreign error cause display")
        }
    }

    error_chain! {
        types{
            Error, ErrorKind, ResultExt, Result;
        }
        links {}
        foreign_links {
            Foreign(ForeignError);
            Io(::std::io::Error);
        }
        errors {}
    }

    #[test]
    fn display_underlying_error() {
        let chained_error = try_foreign_error().err().unwrap();
        assert_eq!(format!("{}", ForeignError { cause: ForeignErrorCause {} }),
                   format!("{}", chained_error));
    }

    #[test]
    fn finds_cause() {
        let chained_error = try_foreign_error().err().unwrap();
        assert_eq!(format!("{}", ForeignErrorCause {}),
                   format!("{}", ::std::error::Error::cause(&chained_error).unwrap()));
    }

    #[test]
    fn iterates() {
        let chained_error = try_foreign_error().err().unwrap();
        let mut error_iter = chained_error.iter();
        assert!(!format!("{:?}", error_iter).is_empty());
        assert_eq!(format!("{}", ForeignError { cause: ForeignErrorCause {} }),
                   format!("{}", error_iter.next().unwrap()));
        assert_eq!(format!("{}", ForeignErrorCause {}),
                   format!("{}", error_iter.next().unwrap()));
        assert_eq!(format!("{:?}", None as Option<&::std::error::Error>),
                   format!("{:?}", error_iter.next()));
    }

    fn try_foreign_error() -> Result<()> {
        Err(ForeignError { cause: ForeignErrorCause {} })?;
        Ok(())
    }
}

#[cfg(test)]
mod attributes_test {
    #[allow(unused_imports)]
    use std::io;

    #[cfg(not(test))]
    mod inner {
        error_chain!{}
    }

    error_chain! {
        types {
            Error, ErrorKind, ResultExt, Result;
        }

        links {
            Inner(inner::Error, inner::ErrorKind) #[cfg(not(test))];
        }

        foreign_links {
            Io(io::Error) #[cfg(not(test))];
        }

        errors {
            #[cfg(not(test))]
            AnError {

            }
        }
    }
}

#[test]
fn with_result() {
    error_chain! {
        types {
            Error, ErrorKind, ResultExt, Result;
        }
    }
    let _: Result<()> = Ok(());
}

#[test]
fn without_result() {
    error_chain! {
        types {
            Error, ErrorKind, ResultExt;
        }
    }
    let _: Result<(), ()> = Ok(());
}

#[test]
fn documentation() {
    mod inner {
        error_chain!{}
    }

    error_chain! {
        links {
            Inner(inner::Error, inner::ErrorKind) #[doc = "Doc"];
        }
        foreign_links {
            Io(::std::io::Error) #[doc = "Doc"];
        }
        errors {
            /// Doc
            Variant
        }
    }
}

#[cfg(test)]
mod multiple_error_same_mod {
    error_chain! {
        types {
            MyError, MyErrorKind, MyResultExt, MyResult;
        }
    }
    error_chain!{}
}

#[doc(test)]
#[deny(dead_code)]
mod allow_dead_code {
    error_chain!{}
}

// Make sure links actually work!
#[test]
fn rustup_regression() {
    error_chain! {
        links {
            Download(error_chain::mock::Error, error_chain::mock::ErrorKind);
        }

        foreign_links { }

        errors {
            LocatingWorkingDir {
                description("could not locate working directory")
            }
        }
    }
}

#[test]
fn error_patterns() {
    error_chain! {
        links { }

        foreign_links { }

        errors { }
    }

    // Tuples look nice when matching errors
    match Error::from("Test") {
        Error(ErrorKind::Msg(_), _) => {},
        _ => {},
    }
}

#[test]
fn error_first() {
    error_chain! {
        errors {
            LocatingWorkingDir {
                description("could not locate working directory")
            }
        }

        links {
            Download(error_chain::mock::Error, error_chain::mock::ErrorKind);
        }

        foreign_links { }
    }
}

#[test]
fn bail() {
    error_chain! {
        errors { Foo }
    }

    fn foo() -> Result<()> {
        bail!(ErrorKind::Foo)
    }

    fn bar() -> Result<()> {
        bail!("bar")
    }

    fn baz() -> Result<()> {
        bail!("{}", "baz")
    }
}

#[test]
fn ensure() {
    error_chain! {
        errors { Bar }
    }

    fn foo(x: u8) -> Result<()> {
        ensure!(x == 42, ErrorKind::Bar);
        Ok(())
    }

    assert!(foo(42).is_ok());
    assert!(foo(0).is_err());
}

/// Since the `types` declaration is a list of symbols, check if we
/// don't change their meaning or order.
#[test]
fn types_declarations() {
    error_chain! {
        types {
            MyError, MyErrorKind, MyResultExt, MyResult;
        }
    }

    MyError::from_kind(MyErrorKind::Msg("".into()));

    let err: Result<(), ::std::io::Error> = Ok(());
    MyResultExt::chain_err(err, || "").unwrap();

    let _: MyResult<()> = Ok(());
}

#[test]
/// Calling chain_err over a `Result` containing an error to get a chained error
/// and constructing a MyError directly, passing it an error should be equivalent.
fn rewrapping() {

    use std::env::VarError::{self, NotPresent, NotUnicode};

    error_chain! {
        foreign_links {
            VarErr(VarError);
        }

        types {
            MyError, MyErrorKind, MyResultExt, MyResult;
        }
    }

    let result_a_from_func: Result<String, _> = Err(VarError::NotPresent);
    let result_b_from_func: Result<String, _> = Err(VarError::NotPresent);

    let our_error_a = result_a_from_func.map_err(|e| match e {
        NotPresent => MyError::with_chain(e, "env var wasn't provided"),
        NotUnicode(_) => MyError::with_chain(e, "env var was borkæ–‡å­—åŒ–ã"),
    });

    let our_error_b = result_b_from_func.or_else(|e| match e {
        NotPresent => Err(e).chain_err(|| "env var wasn't provided"),
        NotUnicode(_) => Err(e).chain_err(|| "env var was borkæ–‡å­—åŒ–ã"),
    });

    assert_eq!(format!("{}", our_error_a.unwrap_err()),
               format!("{}", our_error_b.unwrap_err()));

}

#[test]
fn comma_in_errors_impl() {
    error_chain! {
        links { }

        foreign_links { }

        errors {
            HttpStatus(e: u32) {
                description("http request returned an unsuccessful status code"),
                display("http request returned an unsuccessful status code: {}", e)
            }
        }
    };
}


#[test]
fn trailing_comma_in_errors_impl() {
    error_chain! {
        links { }

        foreign_links { }

        errors {
            HttpStatus(e: u32) {
                description("http request returned an unsuccessful status code"),
                display("http request returned an unsuccessful status code: {}", e),
            }
        }
    };
}
