// vim: tw=80
//! A generic struct with a generic method on a different parameter
#![deny(warnings)]

use mockall::*;
use std::sync::Mutex;

mock! {
    Foo<T: 'static> {
        fn foo<Q: 'static>(t: T, q: Q) -> u64;
        // We must use a different method for every should_panic test, so the
        // shared mutex doesn't get poisoned.
        fn foo2<Q: 'static>(t: T, q: Q) -> u64;
        fn foo3<Q: 'static>(t: T, q: Q) -> u64;
    }
}

lazy_static! {
    static ref FOO_MTX: Mutex<()> = Mutex::new(());
}

// Checkpointing the mock object should not checkpoint static methods too
#[test]
fn checkpoint() {
    let _m = FOO_MTX.lock().unwrap();

    let mut mock = MockFoo::<u32>::new();
    let ctx = MockFoo::<u32>::foo_context();
    ctx.expect::<i16>()
        .returning(|_, _| 0)
        .times(1..3);
    mock.checkpoint();
    MockFoo::foo(42u32, 69i16);
}

// It should also be possible to checkpoint just the context object
#[test]
#[should_panic(expected =
    "MockFoo::foo2: Expectation(<anything>) called fewer than 1 times")]
fn ctx_checkpoint() {
    let ctx = MockFoo::<u32>::foo2_context();
    ctx.expect::<i16>()
        .returning(|_, _| 0)
        .times(1..3);
    ctx.checkpoint();
    panic!("Shouldn't get here!");
}

// Expectations should be cleared when a context object drops
#[test]
#[cfg_attr(feature = "nightly", should_panic(
        expected = "MockFoo::foo3(42, 69): No matching expectation found"
))]
#[cfg_attr(not(feature = "nightly"), should_panic(
        expected = "MockFoo::foo3(?, ?): No matching expectation found"
))]
fn ctx_hygiene() {
    {
        let ctx0 = MockFoo::<u32>::foo3_context();
        ctx0.expect::<i16>()
            .returning(|_, _| 0);
    }
    MockFoo::foo3(42, 69);
}

#[cfg_attr(not(feature = "nightly"), ignore)]
#[cfg_attr(not(feature = "nightly"), allow(unused_must_use))]
#[test]
fn return_default() {
    let _m = FOO_MTX.lock().unwrap();

    let ctx = MockFoo::<u32>::foo_context();
    ctx.expect::<i16>();
    MockFoo::foo(5u32, 6i16);
}

#[test]
fn returning() {
    let _m = FOO_MTX.lock().unwrap();

    let ctx = MockFoo::<u32>::foo_context();
    ctx.expect::<i16>()
        .returning(|_, _| 0);
    MockFoo::foo(41u32, 42i16);
}

#[test]
fn two_matches() {
    let _m = FOO_MTX.lock().unwrap();

    let ctx = MockFoo::<u32>::foo_context();
    ctx.expect::<i16>()
        .with(predicate::eq(42u32), predicate::eq(0i16))
        .return_const(99u64);
    ctx.expect::<i16>()
        .with(predicate::eq(69u32), predicate::eq(0i16))
        .return_const(101u64);
    assert_eq!(101, MockFoo::foo(69u32, 0i16));
    assert_eq!(99, MockFoo::foo(42u32, 0i16));
}

#[test]
fn with() {
    let _m = FOO_MTX.lock().unwrap();

    let ctx = MockFoo::<u32>::foo_context();
    ctx.expect::<i16>()
        .with(predicate::eq(42u32), predicate::eq(99i16))
        .returning(|_, _| 0);
    MockFoo::foo(42u32, 99i16);
}
