-
-
Notifications
You must be signed in to change notification settings - Fork 249
/
proptests.rs
109 lines (94 loc) · 3.87 KB
/
proptests.rs
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
use crate::proptest::PgTestRunner;
use core::ffi;
use paste::paste;
use pgrx::prelude::*;
use proptest::prelude::*;
use TimeWithTimeZone as TimeTz;
/// Generate the roundtrip property tests for a datetime type
///
/// This macro relies on the fact Postgres has a very regular naming format
/// for serialization/deserialization functions for most types, always using
/// - `type_in`
/// - `type_out`
macro_rules! pg_proptest_datetime_roundtrip_tests {
($datetime_ty:ty, $nop_fn:ident, $prop_strat:expr) => {
paste! {
// A property test consists of
// 1. Posing a hypothesis
#[doc = concat!("A value of ", stringify!($datetime_ty), "should be able to be passed to Postgres and back.")]
#[pg_test]
pub fn [<$datetime_ty:lower _spi_roundtrip>] () {
// 2. Constructing the Postgres-adapted test runner
let mut proptest = PgTestRunner::default();
// 3. A strategy to create and refine values, which is a somewhat aggrandized function.
// In some cases it actually can be replaced directly by a closure, or, in this case,
// it involves using a closure to `prop_map` an existing Strategy for producing
// "any kind of i32" into "any kind of in-range value for a Date".
let strat = $prop_strat;
// 4. The runner invocation
proptest
.run(&strat, |datetime| {
let query = concat!("SELECT ", stringify!($nop_fn), "($1)");
let builtin_oid = PgOid::BuiltIn(pg_sys::BuiltinOid::from_u32(<$datetime_ty as IntoDatum>::type_oid().as_u32()).unwrap());
let args = vec![(builtin_oid, datetime.into_datum())];
let spi_ret: $datetime_ty = Spi::get_one_with_args(query, args).unwrap().unwrap();
// 5. A condition on which the test is accepted or rejected:
// this is easily done via `prop_assert!` and its friends,
// which just early-returns a TestCaseError on failure
prop_assert_eq!(datetime, spi_ret);
Ok(())
})
.unwrap();
}
#[doc = concat!("A value of ", stringify!($datetime_ty), "should be able to be serialized to text, passed to Postgres, and recovered.")]
#[pg_test]
pub fn [<$datetime_ty:lower _literal_spi_roundtrip>] () {
let mut proptest = PgTestRunner::default();
let strat = $prop_strat;
proptest
.run(&strat, |datetime| {
let datum = datetime.into_datum();
let datetime_cstr: &ffi::CStr =
unsafe { pgrx::direct_function_call(pg_sys:: [<$datetime_ty:lower _out>] , &[datum]).unwrap() };
let datetime_text = datetime_cstr.to_str().unwrap().to_owned();
let spi_select_command = format!(concat!("SELECT ", stringify!($nop_fn), "('{}')"), datetime_text);
let spi_ret: Option<$datetime_ty> = Spi::get_one(&spi_select_command).unwrap();
prop_assert_eq!(datetime, spi_ret.unwrap());
Ok(())
})
.unwrap();
}
}
}
}
macro_rules! pg_proptest_datetime_types {
($($datetime_ty:ty = $prop_strat:expr;)*) => {
paste! {
$(
#[pg_extern]
pub fn [<nop_ $datetime_ty:lower>](datetime: $datetime_ty) -> $datetime_ty {
datetime
}
)*
#[cfg(any(test, feature = "pg_test"))]
#[pgrx::pg_schema]
mod tests {
use super::*;
#[allow(unused)] // I can never tell when this is actually needed.
use crate as pgrx_tests;
$(
pg_proptest_datetime_roundtrip_tests! {
$datetime_ty, [<nop_ $datetime_ty:lower>], $prop_strat
}
)*
}
}
}
}
pg_proptest_datetime_types! {
Date = prop::num::i32::ANY.prop_map(Date::saturating_from_raw);
Time = prop::num::i64::ANY.prop_map(Time::modular_from_raw);
Timestamp = prop::num::i64::ANY.prop_map(Timestamp::saturating_from_raw);
// TimestampTz = prop::num::i64::ANY.prop_map(TimestampTz::from); // This doesn't exist, and that's a good thing.
TimeTz = (prop::num::i64::ANY, prop::num::i32::ANY).prop_map(TimeTz::modular_from_raw);
}