1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//-
// Copyright 2017, 2018 The proptest developers
//
// 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.

use crate::std_facade::fmt;

#[cfg(feature = "std")]
use std::string::ToString;

use crate::test_runner::Reason;

/// Errors which can be returned from test cases to indicate non-successful
/// completion.
///
/// Note that in spite of the name, `TestCaseError` is currently *not* an
/// instance of `Error`, since otherwise `impl<E : Error> From<E>` could not be
/// provided.
///
/// Any `Error` can be converted to a `TestCaseError`, which places
/// `Error::display()` into the `Fail` case.
#[derive(Debug, Clone)]
pub enum TestCaseError {
    /// The input was not valid for the test case. This does not count as a
    /// test failure (nor a success); rather, it simply signals to generate
    /// a new input and try again.
    Reject(Reason),
    /// The code under test failed the test.
    Fail(Reason),
}

/// Indicates the type of test that ran successfully.
///
/// This is used for managing whether or not a success is counted against
/// configured `PROPTEST_CASES`; only `NewCases` shall be counted.
///
/// TODO-v2: Ideally `TestCaseResult = Result<TestCaseOk, TestCaseError>`
/// however this breaks source compatibility in version 1.x.x because
/// `TestCaseResult` is public.
#[derive(Debug, Clone)]
pub(crate) enum TestCaseOk {
    NewCaseSuccess,
    PersistedCaseSuccess,
    ReplayFromForkSuccess,
    CacheHitSuccess,
    Reject,
}

/// Convenience for the type returned by test cases.
pub type TestCaseResult = Result<(), TestCaseError>;

/// Intended to replace `TestCaseResult` in v2.
///
/// TODO-v2: Ideally `TestCaseResult = Result<TestCaseOk, TestCaseError>`
/// however this breaks source compatibility in version 1.x.x because
/// `TestCaseResult` is public.
pub(crate) type TestCaseResultV2 = Result<TestCaseOk, TestCaseError>;

impl TestCaseError {
    /// Rejects the generated test input as invalid for this test case. This
    /// does not count as a test failure (nor a success); rather, it simply
    /// signals to generate a new input and try again.
    ///
    /// The string gives the location and context of the rejection, and
    /// should be suitable for formatting like `Foo did X at {whence}`.
    pub fn reject(reason: impl Into<Reason>) -> Self {
        TestCaseError::Reject(reason.into())
    }

    /// The code under test failed the test.
    ///
    /// The string should indicate the location of the failure, but may
    /// generally be any string.
    pub fn fail(reason: impl Into<Reason>) -> Self {
        TestCaseError::Fail(reason.into())
    }
}

impl fmt::Display for TestCaseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            TestCaseError::Reject(ref whence) => {
                write!(f, "Input rejected at {}", whence)
            }
            TestCaseError::Fail(ref why) => write!(f, "Case failed: {}", why),
        }
    }
}

#[cfg(feature = "std")]
impl<E: ::std::error::Error> From<E> for TestCaseError {
    fn from(cause: E) -> Self {
        TestCaseError::fail(cause.to_string())
    }
}

/// A failure state from running test cases for a single test.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TestError<T> {
    /// The test was aborted for the given reason, for example, due to too many
    /// inputs having been rejected.
    Abort(Reason),
    /// A failing test case was found. The string indicates where and/or why
    /// the test failed. The `T` is the minimal input found to reproduce the
    /// failure.
    Fail(Reason, T),
}

impl<T: fmt::Debug> fmt::Display for TestError<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            TestError::Abort(ref why) => write!(f, "Test aborted: {}", why),
            TestError::Fail(ref why, ref what) => {
                writeln!(f, "Test failed: {}.", why)?;
                write!(f, "minimal failing input: {:#?}", what)
            }
        }
    }
}

#[cfg(feature = "std")]
#[allow(deprecated)] // description()
impl<T: fmt::Debug> ::std::error::Error for TestError<T> {
    fn description(&self) -> &str {
        match *self {
            TestError::Abort(..) => "Abort",
            TestError::Fail(..) => "Fail",
        }
    }
}