diff --git a/CHANGELOG b/CHANGELOG index a5d36e4d..c08ef207 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added fuzzing and miri code safety analysis to our CI pipelines. - Removed requirement of `alloc` in `no_std` ennvironments without the `write` feature. - Make multi-digit optimizations in integer parsing optional. +- Much higher miri coverage including for proptests. ### Changed @@ -29,6 +30,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Writing `-0.0` with a leading `-`. - Reduced binary siezes when the compact feature was enabled. - Improved performance of integer and float parsing, particularly with small integers. +- Removed almost all unsafety in `lexical-util` and clearly documented the preconditions to use safely. +- Removed almost all unsafety in `lexical-write-integer` and clearly documented the preconditions to use safely. ### Removed diff --git a/lexical-parse-float/tests/api_tests.rs b/lexical-parse-float/tests/api_tests.rs index 3c402bd7..17cbad64 100644 --- a/lexical-parse-float/tests/api_tests.rs +++ b/lexical-parse-float/tests/api_tests.rs @@ -1,3 +1,6 @@ +mod util; + +use crate::util::default_proptest_config; #[cfg(feature = "format")] use core::num; use lexical_parse_float::{FromLexical, FromLexicalWithOptions, Options}; @@ -13,7 +16,6 @@ use lexical_util::format::NumberFormatBuilder; use lexical_util::format::STANDARD; use lexical_util::num::Float; use proptest::prelude::*; -use quickcheck::quickcheck; #[test] fn special_bytes_test() { @@ -179,7 +181,6 @@ fn f32_radix_test() { } #[test] -#[cfg_attr(miri, ignore)] fn parse_f32_test() { let parse = move |x| f32::from_lexical_partial(x); @@ -242,7 +243,6 @@ fn parse_f32_test() { } #[test] -#[cfg_attr(miri, ignore)] fn parse_f64_test() { let parse = move |x| f64::from_lexical_partial(x); @@ -1250,15 +1250,13 @@ fn float_equal(x: F, y: F) -> bool { } } -quickcheck! { - #[cfg_attr(miri, ignore)] +default_quickcheck! { fn f32_roundtrip_quickcheck(x: f32) -> bool { let string = x.to_string(); let result = f32::from_lexical(string.as_bytes()); result.map_or(false, |y| float_equal(x, y)) } - #[cfg_attr(miri, ignore)] fn f32_short_decimal_quickcheck(x: f32) -> bool { let string = format!("{:.4}", x); let actual = f32::from_lexical(string.as_bytes()); @@ -1266,7 +1264,6 @@ quickcheck! { actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) } - #[cfg_attr(miri, ignore)] fn f32_long_decimal_quickcheck(x: f32) -> bool { let string = format!("{:.100}", x); let actual = f32::from_lexical(string.as_bytes()); @@ -1274,7 +1271,6 @@ quickcheck! { actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) } - #[cfg_attr(miri, ignore)] fn f32_short_exponent_quickcheck(x: f32) -> bool { let string = format!("{:.4e}", x); let actual = f32::from_lexical(string.as_bytes()); @@ -1282,7 +1278,6 @@ quickcheck! { actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) } - #[cfg_attr(miri, ignore)] fn f32_long_exponent_quickcheck(x: f32) -> bool { let string = format!("{:.100e}", x); let actual = f32::from_lexical(string.as_bytes()); @@ -1290,14 +1285,12 @@ quickcheck! { actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) } - #[cfg_attr(miri, ignore)] fn f64_roundtrip_quickcheck(x: f64) -> bool { let string = x.to_string(); let result = f64::from_lexical(string.as_bytes()); result.map_or(false, |y| float_equal(x, y)) } - #[cfg_attr(miri, ignore)] fn f64_short_decimal_quickcheck(x: f64) -> bool { let string = format!("{:.4}", x); let actual = f64::from_lexical(string.as_bytes()); @@ -1305,7 +1298,6 @@ quickcheck! { actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) } - #[cfg_attr(miri, ignore)] fn f64_long_decimal_quickcheck(x: f64) -> bool { let string = format!("{:.100}", x); let actual = f64::from_lexical(string.as_bytes()); @@ -1313,7 +1305,6 @@ quickcheck! { actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) } - #[cfg_attr(miri, ignore)] fn f64_short_exponent_quickcheck(x: f64) -> bool { let string = format!("{:.4e}", x); let actual = f64::from_lexical(string.as_bytes()); @@ -1321,7 +1312,6 @@ quickcheck! { actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) } - #[cfg_attr(miri, ignore)] fn f64_long_exponent_quickcheck(x: f64) -> bool { let string = format!("{:.100e}", x); let actual = f64::from_lexical(string.as_bytes()); @@ -1330,7 +1320,6 @@ quickcheck! { } #[cfg(feature = "f16")] - #[cfg_attr(miri, ignore)] fn f16_roundtrip_quickcheck(bits: u16) -> bool { let x = f16::from_bits(bits); let string = x.as_f32().to_string(); @@ -1339,7 +1328,6 @@ quickcheck! { } #[cfg(feature = "f16")] - #[cfg_attr(miri, ignore)] fn bf16_roundtrip_quickcheck(bits: u16) -> bool { let x = bf16::from_bits(bits); let string = x.as_f32().to_string(); @@ -1349,8 +1337,9 @@ quickcheck! { } proptest! { + #![proptest_config(default_proptest_config())] + #[test] - #[cfg_attr(miri, ignore)] fn f32_invalid_proptest(i in r"[+-]?[0-9]{2}[^\deE]?\.[^\deE]?[0-9]{2}[^\deE]?e[+-]?[0-9]+[^\deE]") { let res = f32::from_lexical(i.as_bytes()); prop_assert!(res.is_err()); @@ -1358,7 +1347,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_double_sign_proptest(i in r"[+-]{2}[0-9]{2}\.[0-9]{2}e[+-]?[0-9]+") { let res = f32::from_lexical(i.as_bytes()); prop_assert!(res.is_err()); @@ -1369,7 +1357,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_sign_or_dot_only_proptest(i in r"[+-]?\.?") { let res = f32::from_lexical(i.as_bytes()); prop_assert!(res.is_err()); @@ -1380,7 +1367,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_double_exponent_sign_proptest(i in r"[+-]?[0-9]{2}\.[0-9]{2}e[+-]{2}[0-9]+") { let res = f32::from_lexical(i.as_bytes()); prop_assert!(res.is_err()); @@ -1388,7 +1374,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_missing_exponent_proptest(i in r"[+-]?[0-9]{2}\.[0-9]{2}e[+-]?") { let res = f32::from_lexical(i.as_bytes()); prop_assert!(res.is_err()); @@ -1396,28 +1381,24 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_roundtrip_display_proptest(i in f32::MIN..f32::MAX) { let input: String = format!("{}", i); prop_assert_eq!(i, f32::from_lexical(input.as_bytes()).unwrap()); } #[test] - #[cfg_attr(miri, ignore)] fn f32_roundtrip_debug_proptest(i in f32::MIN..f32::MAX) { let input: String = format!("{:?}", i); prop_assert_eq!(i, f32::from_lexical(input.as_bytes()).unwrap()); } #[test] - #[cfg_attr(miri, ignore)] fn f32_roundtrip_scientific_proptest(i in f32::MIN..f32::MAX) { let input: String = format!("{:e}", i); prop_assert_eq!(i, f32::from_lexical(input.as_bytes()).unwrap()); } #[test] - #[cfg_attr(miri, ignore)] fn f64_invalid_proptest(i in r"[+-]?[0-9]{2}[^\deE]?\.[^\deE]?[0-9]{2}[^\deE]?e[+-]?[0-9]+[^\deE]") { let res = f64::from_lexical(i.as_bytes()); prop_assert!(res.is_err()); @@ -1425,7 +1406,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_double_sign_proptest(i in r"[+-]{2}[0-9]{2}\.[0-9]{2}e[+-]?[0-9]+") { let res = f64::from_lexical(i.as_bytes()); prop_assert!(res.is_err()); @@ -1436,7 +1416,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_sign_or_dot_only_proptest(i in r"[+-]?\.?") { let res = f64::from_lexical(i.as_bytes()); prop_assert!(res.is_err()); @@ -1447,7 +1426,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_double_exponent_sign_proptest(i in r"[+-]?[0-9]{2}\.[0-9]{2}e[+-]{2}[0-9]+") { let res = f64::from_lexical(i.as_bytes()); prop_assert!(res.is_err()); @@ -1455,7 +1433,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_missing_exponent_proptest(i in r"[+-]?[0-9]{2}\.[0-9]{2}e[+-]?") { let res = f64::from_lexical(i.as_bytes()); prop_assert!(res.is_err()); @@ -1463,21 +1440,18 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_roundtrip_display_proptest(i in f64::MIN..f64::MAX) { let input: String = format!("{}", i); prop_assert_eq!(i, f64::from_lexical(input.as_bytes()).unwrap()); } #[test] - #[cfg_attr(miri, ignore)] fn f64_roundtrip_debug_proptest(i in f64::MIN..f64::MAX) { let input: String = format!("{:?}", i); prop_assert_eq!(i, f64::from_lexical(input.as_bytes()).unwrap()); } #[test] - #[cfg_attr(miri, ignore)] fn f64_roundtrip_scientific_proptest(i in f64::MIN..f64::MAX) { let input: String = format!("{:e}", i); prop_assert_eq!(i, f64::from_lexical(input.as_bytes()).unwrap()); diff --git a/lexical-parse-float/tests/util.rs b/lexical-parse-float/tests/util.rs new file mode 100644 index 00000000..e87a0836 --- /dev/null +++ b/lexical-parse-float/tests/util.rs @@ -0,0 +1,57 @@ +#![allow(dead_code, unused_imports)] + +use proptest::prelude::*; +pub(crate) use quickcheck::QuickCheck; + +pub fn default_proptest_config() -> ProptestConfig { + ProptestConfig { + cases: if cfg!(miri) { + 10 + } else { + ProptestConfig::default().cases + }, + max_shrink_iters: if cfg!(miri) { + 10 + } else { + ProptestConfig::default().max_shrink_iters + }, + failure_persistence: if cfg!(miri) { + None + } else { + ProptestConfig::default().failure_persistence + }, + ..ProptestConfig::default() + } +} + +// This is almost identical to quickcheck's itself, just to add default arguments +// https://docs.rs/quickcheck/1.0.3/src/quickcheck/lib.rs.html#43-67 +// The code is unlicensed. +#[macro_export] +macro_rules! default_quickcheck { + (@as_items $($i:item)*) => ($($i)*); + { + $( + $(#[$m:meta])* + fn $fn_name:ident($($arg_name:ident : $arg_ty:ty),*) -> $ret:ty { + $($code:tt)* + } + )* + } => ( + $crate::default_quickcheck! { + @as_items + $( + #[test] + $(#[$m])* + fn $fn_name() { + fn prop($($arg_name: $arg_ty),*) -> $ret { + $($code)* + } + $crate::util::QuickCheck::new() + .max_tests(if cfg!(miri) { 10 } else { 10_000 }) + .quickcheck(prop as fn($($arg_ty),*) -> $ret); + } + )* + } + ) +} diff --git a/lexical-parse-integer/tests/algorithm_tests.rs b/lexical-parse-integer/tests/algorithm_tests.rs index 14f24a32..35d9ba24 100644 --- a/lexical-parse-integer/tests/algorithm_tests.rs +++ b/lexical-parse-integer/tests/algorithm_tests.rs @@ -1,8 +1,8 @@ #![cfg(not(feature = "compact"))] -#[cfg(feature = "power-of-two")] mod util; +use crate::util::default_proptest_config; use lexical_parse_integer::algorithm; use lexical_parse_integer::options::SMALL_NUMBERS; use lexical_util::format::STANDARD; @@ -180,8 +180,9 @@ fn algorithm_128_test() { } proptest! { + #![proptest_config(default_proptest_config())] + #[test] - #[cfg_attr(miri, ignore)] fn parse_4digits_proptest( a in 0x30u32..0x39, b in 0x30u32..0x39, @@ -196,7 +197,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn parse_8digits_proptest( a in 0x30u64..0x39, b in 0x30u64..0x39, diff --git a/lexical-parse-integer/tests/api_tests.rs b/lexical-parse-integer/tests/api_tests.rs index 93863e8a..e3a1b4be 100644 --- a/lexical-parse-integer/tests/api_tests.rs +++ b/lexical-parse-integer/tests/api_tests.rs @@ -1,6 +1,6 @@ -#[cfg(feature = "power-of-two")] mod util; +use crate::util::default_proptest_config; use lexical_parse_integer::{FromLexical, FromLexicalWithOptions, Options}; use lexical_util::error::Error; #[cfg(feature = "format")] @@ -402,8 +402,9 @@ macro_rules! is_invalid_digit_match { } proptest! { + #![proptest_config(default_proptest_config())] + #[test] - #[cfg_attr(miri, ignore)] #[cfg(feature = "power-of-two")] fn i32_binary_roundtrip_display_proptest(i in i32::MIN..i32::MAX) { let options = Options::new(); @@ -418,361 +419,301 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn u8_invalid_proptest(i in r"[+]?[0-9]{2}\D") { is_invalid_digit_match!(u8::from_lexical(i.as_bytes()), 2 | 3); } #[test] - #[cfg_attr(miri, ignore)] fn u8_overflow_proptest(i in r"[+]?[1-9][0-9]{3}") { is_overflow!(u8::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn u8_negative_proptest(i in r"[-][1-9][0-9]{2}") { is_invalid_digit!(u8::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn u8_double_sign_proptest(i in r"[+]{2}[0-9]{2}") { is_invalid_digit_match!(u8::from_lexical(i.as_bytes()), 1); } #[test] - #[cfg_attr(miri, ignore)] fn u8_sign_only_proptest(i in r"[+]") { is_empty!(u8::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn u8_trailing_digits_proptest(i in r"[+]?[0-9]{2}\D[0-9]{2}") { is_invalid_digit_match!(u8::from_lexical(i.as_bytes()), 2 | 3); } #[test] - #[cfg_attr(miri, ignore)] fn i8_invalid_proptest(i in r"[+-]?[0-9]{2}\D") { is_invalid_digit_match!(i8::from_lexical(i.as_bytes()), 2 | 3); } #[test] - #[cfg_attr(miri, ignore)] fn i8_overflow_proptest(i in r"[+]?[1-9][0-9]{3}") { is_overflow!(i8::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn i8_underflow_proptest(i in r"[-][1-9][0-9]{3}") { is_underflow!(i8::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn i8_double_sign_proptest(i in r"[+-]{2}[0-9]{2}") { is_invalid_digit_match!(i8::from_lexical(i.as_bytes()), 1); } #[test] - #[cfg_attr(miri, ignore)] fn i8_sign_only_proptest(i in r"[+-]") { is_empty!(i8::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn i8_trailing_digits_proptest(i in r"[+-]?[0-9]{2}\D[0-9]{2}") { is_invalid_digit_match!(i8::from_lexical(i.as_bytes()), 2 | 3); } #[test] - #[cfg_attr(miri, ignore)] fn u16_invalid_proptest(i in r"[+]?[0-9]{4}\D") { is_invalid_digit_match!(u16::from_lexical(i.as_bytes()), 4 | 5); } #[test] - #[cfg_attr(miri, ignore)] fn u16_overflow_proptest(i in r"[+]?[1-9][0-9]{5}") { is_overflow!(u16::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn u16_negative_proptest(i in r"[-][1-9][0-9]{4}") { is_invalid_digit!(u16::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn u16_double_sign_proptest(i in r"[+]{2}[0-9]{4}") { is_invalid_digit_match!(u16::from_lexical(i.as_bytes()), 1); } #[test] - #[cfg_attr(miri, ignore)] fn u16_sign_only_proptest(i in r"[+]") { is_empty!(u16::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn u16_trailing_digits_proptest(i in r"[+]?[0-9]{4}\D[0-9]{2}") { is_invalid_digit_match!(u16::from_lexical(i.as_bytes()), 4 | 5); } #[test] - #[cfg_attr(miri, ignore)] fn i16_invalid_proptest(i in r"[+-]?[0-9]{4}\D") { is_invalid_digit_match!(i16::from_lexical(i.as_bytes()), 4 | 5); } #[test] - #[cfg_attr(miri, ignore)] fn i16_overflow_proptest(i in r"[+]?[1-9][0-9]{5}") { is_overflow!(i16::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn i16_underflow_proptest(i in r"[-][1-9][0-9]{5}") { is_underflow!(i16::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn i16_double_sign_proptest(i in r"[+-]{2}[0-9]{4}") { is_invalid_digit_match!(i16::from_lexical(i.as_bytes()), 1); } #[test] - #[cfg_attr(miri, ignore)] fn i16_sign_only_proptest(i in r"[+-]") { is_empty!(i16::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn i16_trailing_digits_proptest(i in r"[+-]?[0-9]{4}\D[0-9]{2}") { is_invalid_digit_match!(i16::from_lexical(i.as_bytes()), 4 | 5); } #[test] - #[cfg_attr(miri, ignore)] fn u32_invalid_proptest(i in r"[+]?[0-9]{9}\D") { is_invalid_digit_match!(u32::from_lexical(i.as_bytes()), 9 | 10); } #[test] - #[cfg_attr(miri, ignore)] fn u32_overflow_proptest(i in r"[+]?[1-9][0-9]{10}") { is_overflow!(u32::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn u32_negative_proptest(i in r"[-][1-9][0-9]{9}") { is_invalid_digit!(u32::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn u32_double_sign_proptest(i in r"[+]{2}[0-9]{9}") { is_invalid_digit_match!(u32::from_lexical(i.as_bytes()), 1); } #[test] - #[cfg_attr(miri, ignore)] fn u32_sign_only_proptest(i in r"[+]") { is_empty!(u32::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn u32_trailing_digits_proptest(i in r"[+]?[0-9]{9}\D[0-9]{2}") { is_invalid_digit_match!(u32::from_lexical(i.as_bytes()), 9 | 10); } #[test] - #[cfg_attr(miri, ignore)] fn i32_invalid_proptest(i in r"[+-]?[0-9]{9}\D") { is_invalid_digit_match!(i32::from_lexical(i.as_bytes()), 9 | 10); } #[test] - #[cfg_attr(miri, ignore)] fn i32_overflow_proptest(i in r"[+]?[1-9][0-9]{10}") { is_overflow!(i32::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn i32_underflow_proptest(i in r"-[1-9][0-9]{10}") { is_underflow!(i32::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn i32_double_sign_proptest(i in r"[+-]{2}[0-9]{9}") { is_invalid_digit_match!(i32::from_lexical(i.as_bytes()), 1); } #[test] - #[cfg_attr(miri, ignore)] fn i32_sign_only_proptest(i in r"[+-]") { is_empty!(i32::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn i32_trailing_digits_proptest(i in r"[+-]?[0-9]{9}\D[0-9]{2}") { is_invalid_digit_match!(i32::from_lexical(i.as_bytes()), 9 | 10); } #[test] - #[cfg_attr(miri, ignore)] fn u64_invalid_proptest(i in r"[+]?[0-9]{19}\D") { is_invalid_digit_match!(u64::from_lexical(i.as_bytes()), 19 | 20); } #[test] - #[cfg_attr(miri, ignore)] fn u64_overflow_proptest(i in r"[+]?[1-9][0-9]{21}") { is_overflow!(u64::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn u64_negative_proptest(i in r"[-][1-9][0-9]{21}") { is_invalid_digit!(u64::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn u64_double_sign_proptest(i in r"[+]{2}[0-9]{19}") { is_invalid_digit_match!(u64::from_lexical(i.as_bytes()), 1); } #[test] - #[cfg_attr(miri, ignore)] fn u64_sign_only_proptest(i in r"[+]") { is_empty!(u64::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn u64_trailing_digits_proptest(i in r"[+]?[0-9]{19}\D[0-9]{2}") { is_invalid_digit_match!(u64::from_lexical(i.as_bytes()), 19 | 20); } #[test] - #[cfg_attr(miri, ignore)] fn i64_invalid_proptest(i in r"[+-]?[0-9]{18}\D") { is_invalid_digit_match!(i64::from_lexical(i.as_bytes()), 18 | 19); } #[test] - #[cfg_attr(miri, ignore)] fn i64_overflow_proptest(i in r"[+]?[1-9][0-9]{19}") { is_overflow!(i64::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn i64_underflow_proptest(i in r"-[1-9][0-9]{19}") { is_underflow!(i64::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn i64_double_sign_proptest(i in r"[+-]{2}[0-9]{18}") { is_invalid_digit_match!(i64::from_lexical(i.as_bytes()), 1); } #[test] - #[cfg_attr(miri, ignore)] fn i64_sign_only_proptest(i in r"[+-]") { is_empty!(i64::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn i64_trailing_digits_proptest(i in r"[+-]?[0-9]{18}\D[0-9]{2}") { is_invalid_digit_match!(i64::from_lexical(i.as_bytes()), 18 | 19); } #[test] - #[cfg_attr(miri, ignore)] fn u128_invalid_proptest(i in r"[+]?[0-9]{38}\D") { is_invalid_digit_match!(u128::from_lexical(i.as_bytes()), 38 | 39); } #[test] - #[cfg_attr(miri, ignore)] fn u128_overflow_proptest(i in r"[+]?[1-9][0-9]{39}") { is_overflow!(u128::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn u128_negative_proptest(i in r"[-][1-9][0-9]{39}") { is_invalid_digit!(u128::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn u128_double_sign_proptest(i in r"[+]{2}[0-9]{38}") { is_invalid_digit_match!(u128::from_lexical(i.as_bytes()), 1); } #[test] - #[cfg_attr(miri, ignore)] fn u128_sign_only_proptest(i in r"[+]") { is_empty!(u128::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn u128_trailing_digits_proptest(i in r"[+]?[0-9]{38}\D[0-9]{2}") { is_invalid_digit_match!(u128::from_lexical(i.as_bytes()), 38 | 39); } #[test] - #[cfg_attr(miri, ignore)] fn i128_invalid_proptest(i in r"[+-]?[0-9]{38}\D") { is_invalid_digit_match!(i128::from_lexical(i.as_bytes()), 38 | 39); } #[test] - #[cfg_attr(miri, ignore)] fn i128_overflow_proptest(i in r"[+]?[1-9][0-9]{39}") { is_overflow!(i128::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn i128_underflow_proptest(i in r"-[1-9][0-9]{39}") { is_underflow!(i128::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn i128_double_sign_proptest(i in r"[+-]{2}[0-9]{38}") { is_invalid_digit_match!(i128::from_lexical(i.as_bytes()), 1); } #[test] - #[cfg_attr(miri, ignore)] fn i128_sign_only_proptest(i in r"[+-]") { is_empty!(i128::from_lexical(i.as_bytes())); } #[test] - #[cfg_attr(miri, ignore)] fn i128_trailing_digits_proptest(i in r"[+-]?[0-9]{38}\D[0-9]{2}") { is_invalid_digit_match!(i128::from_lexical(i.as_bytes()), 38 | 39); } diff --git a/lexical-parse-integer/tests/util.rs b/lexical-parse-integer/tests/util.rs index f4210baa..8d71bdfb 100644 --- a/lexical-parse-integer/tests/util.rs +++ b/lexical-parse-integer/tests/util.rs @@ -1,7 +1,64 @@ +#![allow(dead_code, unused_imports)] + #[cfg(feature = "power-of-two")] use lexical_util::format::NumberFormatBuilder; +use proptest::prelude::*; +pub(crate) use quickcheck::QuickCheck; #[cfg(feature = "power-of-two")] pub const fn from_radix(radix: u8) -> u128 { NumberFormatBuilder::from_radix(radix) } + +pub fn default_proptest_config() -> ProptestConfig { + ProptestConfig { + cases: if cfg!(miri) { + 10 + } else { + ProptestConfig::default().cases + }, + max_shrink_iters: if cfg!(miri) { + 10 + } else { + ProptestConfig::default().max_shrink_iters + }, + failure_persistence: if cfg!(miri) { + None + } else { + ProptestConfig::default().failure_persistence + }, + ..ProptestConfig::default() + } +} + +// This is almost identical to quickcheck's itself, just to add default arguments +// https://docs.rs/quickcheck/1.0.3/src/quickcheck/lib.rs.html#43-67 +// The code is unlicensed. +#[macro_export] +macro_rules! default_quickcheck { + (@as_items $($i:item)*) => ($($i)*); + { + $( + $(#[$m:meta])* + fn $fn_name:ident($($arg_name:ident : $arg_ty:ty),*) -> $ret:ty { + $($code:tt)* + } + )* + } => ( + $crate::default_quickcheck! { + @as_items + $( + #[test] + $(#[$m])* + fn $fn_name() { + fn prop($($arg_name: $arg_ty),*) -> $ret { + $($code)* + } + $crate::util::QuickCheck::new() + .max_tests(if cfg!(miri) { 10 } else { 10_000 }) + .quickcheck(prop as fn($($arg_ty),*) -> $ret); + } + )* + } + ) +} diff --git a/lexical-size/bin/write.rs b/lexical-size/bin/write.rs index 1d4cfe08..1db50518 100644 --- a/lexical-size/bin/write.rs +++ b/lexical-size/bin/write.rs @@ -26,6 +26,8 @@ macro_rules! integer_module { #[cfg(feature = "lexical")] { + // FIXME: This is UB but it doesn't affect code integrity here + // Undo when we implement #92. let buffer: mem::MaybeUninit<[u8; 128]> = mem::MaybeUninit::uninit(); let mut buffer = unsafe { buffer.assume_init() }; println!("{}", value.to_lexical(&mut buffer).len()); @@ -69,6 +71,8 @@ macro_rules! float_module { #[cfg(feature = "lexical")] { + // FIXME: This is UB but it doesn't affect code integrity here + // Undo when we implement #92. let buffer: mem::MaybeUninit<[u8; 128]> = mem::MaybeUninit::uninit(); let mut buffer = unsafe { buffer.assume_init() }; println!("{}", value.to_lexical(&mut buffer).len()); diff --git a/lexical-util/src/buffer.rs b/lexical-util/src/buffer.rs index 4f9cd46a..bbfeea8a 100644 --- a/lexical-util/src/buffer.rs +++ b/lexical-util/src/buffer.rs @@ -4,8 +4,6 @@ //! which then can be used for contiguous or non-contiguous iterables, //! including containers or iterators of any kind. -use core::mem; - /// A trait for working with iterables of bytes. /// /// These buffers can either be contiguous or not contiguous and provide @@ -96,44 +94,3 @@ pub unsafe trait Buffer<'a> { } } } - -// NOTE: These two functions are taken directly from the Rust corelib. -// https://doc.rust-lang.org/1.81.0/src/core/mem/maybe_uninit.rs.html#964 - -/// Assuming all the elements are initialized, get a slice to them. -/// -/// # Safety -/// -/// It is up to the caller to guarantee that the `MaybeUninit` elements -/// really are in an initialized state. Calling this when the content is not -/// yet fully initialized causes undefined behavior. -/// -/// See [`assume_init_ref`] for more details and examples. -/// -/// [`assume_init_ref`]: mem::MaybeUninit::assume_init_ref -#[inline(always)] -pub const unsafe fn slice_assume_init(slice: &[mem::MaybeUninit]) -> &[T] { - // SAFETY: casting `slice` to a `*const [T]` is safe since the caller guarantees that - // `slice` is initialized, and `MaybeUninit` is guaranteed to have the same layout as `T`. - // The pointer obtained is valid since it refers to memory owned by `slice` which is a - // reference and thus guaranteed to be valid for reads. - unsafe { &*(slice as *const [mem::MaybeUninit] as *const [T]) } -} - -// Assuming all the elements are initialized, get a mutable slice to them. -/// -/// # Safety -/// -/// It is up to the caller to guarantee that the `MaybeUninit` elements -/// really are in an initialized state. Calling this when the content is -/// not yet fully initialized causes undefined behavior. -/// -/// See [`assume_init_mut`] for more details and examples. -/// -/// [`assume_init_mut`]: mem::MaybeUninit::assume_init_mut -#[inline(always)] -pub unsafe fn slice_assume_init_mut(slice: &mut [mem::MaybeUninit]) -> &mut [T] { - // SAFETY: similar to safety notes for `slice_get_ref`, but we have a - // mutable reference which is also guaranteed to be valid for writes. - unsafe { &mut *(slice as *mut [mem::MaybeUninit] as *mut [T]) } -} diff --git a/lexical-util/tests/bf16_tests.rs b/lexical-util/tests/bf16_tests.rs index 9e54c0c9..45c88a97 100644 --- a/lexical-util/tests/bf16_tests.rs +++ b/lexical-util/tests/bf16_tests.rs @@ -1,9 +1,11 @@ #![cfg(feature = "f16")] +mod util; + +use crate::util::default_proptest_config; use lexical_util::bf16::bf16; use lexical_util::num::Float; use proptest::prelude::*; -use quickcheck::quickcheck; #[test] fn as_f32_test() { @@ -45,8 +47,7 @@ fn math_tests() { assert_eq!(bf16::ONE % bf16::ONE, bf16::ZERO); } -quickcheck! { - #[cfg_attr(miri, ignore)] +default_quickcheck! { fn f32_roundtrip_quickcheck(x: u16) -> bool { let f = bf16::from_bits(x).as_f32(); bf16::from_f32(f).to_bits() == x @@ -54,8 +55,9 @@ quickcheck! { } proptest! { + #![proptest_config(default_proptest_config())] + #[test] - #[cfg_attr(miri, ignore)] fn f32_roundtrip_proptest(x in u16::MIN..u16::MAX) { let f = bf16::from_bits(x).as_f32(); prop_assert_eq!(bf16::from_f32(f).to_bits(), x); diff --git a/lexical-util/tests/div128_tests.rs b/lexical-util/tests/div128_tests.rs index 9d6c5bf8..9ec71e98 100644 --- a/lexical-util/tests/div128_tests.rs +++ b/lexical-util/tests/div128_tests.rs @@ -1,13 +1,17 @@ #![cfg(not(feature = "compact"))] #![cfg(feature = "write")] +mod util; + +use crate::util::default_proptest_config; use lexical_util::div128::u128_divrem; use lexical_util::step::u64_step; use proptest::{prop_assert_eq, proptest}; proptest! { + #![proptest_config(default_proptest_config())] + #[test] - #[cfg_attr(miri, ignore)] fn u128_divrem_proptest(i in u128::MIN..u128::MAX) { let (hi, lo) = u128_divrem(i, 10); let step = u64_step(10); @@ -17,7 +21,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] #[cfg(feature = "radix")] fn u128_divrem_radix_proptest(i in u128::MIN..u128::MAX, radix in 2u32..=36) { // Simulate a const expr. diff --git a/lexical-util/tests/f16_tests.rs b/lexical-util/tests/f16_tests.rs index 728d5e9e..3e6dbcb4 100644 --- a/lexical-util/tests/f16_tests.rs +++ b/lexical-util/tests/f16_tests.rs @@ -1,9 +1,11 @@ #![cfg(feature = "f16")] +mod util; + +use crate::util::default_proptest_config; use lexical_util::f16::f16; use lexical_util::num::Float; use proptest::prelude::*; -use quickcheck::quickcheck; #[test] fn as_f32_test() { @@ -42,8 +44,7 @@ fn math_tests() { assert_eq!(f16::ONE % f16::ONE, f16::ZERO); } -quickcheck! { - #[cfg_attr(miri, ignore)] +default_quickcheck! { fn f32_roundtrip_quickcheck(x: u16) -> bool { let f = f16::from_bits(x).as_f32(); if f.is_nan() { @@ -55,8 +56,9 @@ quickcheck! { } proptest! { + #![proptest_config(default_proptest_config())] + #[test] - #[cfg_attr(miri, ignore)] fn f32_roundtrip_proptest(x in u16::MIN..u16::MAX) { let f = f16::from_bits(x).as_f32(); if f.is_nan() { diff --git a/lexical-util/tests/mul_tests.rs b/lexical-util/tests/mul_tests.rs index cb87fe7e..38792048 100644 --- a/lexical-util/tests/mul_tests.rs +++ b/lexical-util/tests/mul_tests.rs @@ -1,8 +1,8 @@ +mod util; + use lexical_util::mul::{mul, mulhi}; -use quickcheck::quickcheck; -quickcheck! { - #[cfg_attr(miri, ignore)] +default_quickcheck! { fn mul_u16_quickcheck(x: u16, y: u16) -> bool { let (hi, lo) = mul::(x, y); let hi = hi as u32; @@ -11,7 +11,6 @@ quickcheck! { ((hi << 16) | lo) == expected } - #[cfg_attr(miri, ignore)] fn mul_u32_quickcheck(x: u32, y: u32) -> bool { let (hi, lo) = mul::(x, y); let hi = hi as u64; @@ -20,7 +19,6 @@ quickcheck! { ((hi << 32) | lo) == expected } - #[cfg_attr(miri, ignore)] fn mul_u64_quickcheck(x: u64, y: u64) -> bool { let (hi, lo) = mul::(x, y); let hi = hi as u128; @@ -29,21 +27,18 @@ quickcheck! { ((hi << 64) | lo) == expected } - #[cfg_attr(miri, ignore)] fn mulhi_u16_quickcheck(x: u16, y: u16) -> bool { let actual = mulhi::(x, y); let expected = (x as u32 * y as u32) >> 16; actual == expected as u16 } - #[cfg_attr(miri, ignore)] fn mulhi_u32_quickcheck(x: u32, y: u32) -> bool { let actual = mulhi::(x, y); let expected = (x as u64 * y as u64) >> 32; actual == expected as u32 } - #[cfg_attr(miri, ignore)] fn mulhi_u64_quickcheck(x: u64, y: u64) -> bool { let actual = mulhi::(x, y); let expected = (x as u128 * y as u128) >> 64; diff --git a/lexical-util/tests/util.rs b/lexical-util/tests/util.rs new file mode 100644 index 00000000..e87a0836 --- /dev/null +++ b/lexical-util/tests/util.rs @@ -0,0 +1,57 @@ +#![allow(dead_code, unused_imports)] + +use proptest::prelude::*; +pub(crate) use quickcheck::QuickCheck; + +pub fn default_proptest_config() -> ProptestConfig { + ProptestConfig { + cases: if cfg!(miri) { + 10 + } else { + ProptestConfig::default().cases + }, + max_shrink_iters: if cfg!(miri) { + 10 + } else { + ProptestConfig::default().max_shrink_iters + }, + failure_persistence: if cfg!(miri) { + None + } else { + ProptestConfig::default().failure_persistence + }, + ..ProptestConfig::default() + } +} + +// This is almost identical to quickcheck's itself, just to add default arguments +// https://docs.rs/quickcheck/1.0.3/src/quickcheck/lib.rs.html#43-67 +// The code is unlicensed. +#[macro_export] +macro_rules! default_quickcheck { + (@as_items $($i:item)*) => ($($i)*); + { + $( + $(#[$m:meta])* + fn $fn_name:ident($($arg_name:ident : $arg_ty:ty),*) -> $ret:ty { + $($code:tt)* + } + )* + } => ( + $crate::default_quickcheck! { + @as_items + $( + #[test] + $(#[$m])* + fn $fn_name() { + fn prop($($arg_name: $arg_ty),*) -> $ret { + $($code)* + } + $crate::util::QuickCheck::new() + .max_tests(if cfg!(miri) { 10 } else { 10_000 }) + .quickcheck(prop as fn($($arg_ty),*) -> $ret); + } + )* + } + ) +} diff --git a/lexical-write-float/tests/algorithm_tests.rs b/lexical-write-float/tests/algorithm_tests.rs index 5c47f499..ec8fe631 100644 --- a/lexical-write-float/tests/algorithm_tests.rs +++ b/lexical-write-float/tests/algorithm_tests.rs @@ -1,5 +1,8 @@ #![cfg(not(feature = "compact"))] +mod util; + +use crate::util::default_proptest_config; use core::num; use lexical_util::constants::BUFFER_SIZE; use lexical_util::format::NumberFormatBuilder; @@ -8,7 +11,6 @@ use lexical_write_float::algorithm::DragonboxFloat; use lexical_write_float::float::{ExtendedFloat80, RawFloat}; use lexical_write_float::{algorithm, Options, RoundMode}; use proptest::prelude::*; -use quickcheck::quickcheck; const DECIMAL: u128 = NumberFormatBuilder::decimal(); @@ -748,8 +750,7 @@ fn is_left_endpoint_test() { assert_eq!(algorithm::is_left_endpoint::(4), false); } -quickcheck! { - #[cfg_attr(miri, ignore)] +default_quickcheck! { fn f32_quickcheck(f: f32) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -764,7 +765,6 @@ quickcheck! { } } - #[cfg_attr(miri, ignore)] fn f64_quickcheck(f: f64) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -781,8 +781,9 @@ quickcheck! { } proptest! { + #![proptest_config(default_proptest_config())] + #[test] - #[cfg_attr(miri, ignore)] fn f32_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -796,7 +797,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); diff --git a/lexical-write-float/tests/api_tests.rs b/lexical-write-float/tests/api_tests.rs index 7bd7cf9c..78b3aa1f 100644 --- a/lexical-write-float/tests/api_tests.rs +++ b/lexical-write-float/tests/api_tests.rs @@ -1,3 +1,6 @@ +mod util; + +use crate::util::default_proptest_config; #[cfg(feature = "f16")] use lexical_util::bf16::bf16; use lexical_util::constants::BUFFER_SIZE; @@ -6,7 +9,6 @@ use lexical_util::f16::f16; use lexical_util::format::STANDARD; use lexical_write_float::{Options, ToLexical, ToLexicalWithOptions}; use proptest::prelude::*; -use quickcheck::quickcheck; #[test] fn error_tests() { @@ -84,8 +86,7 @@ fn hex_test() { assert_eq!(result, b"3.039^12"); } -quickcheck! { - #[cfg_attr(miri, ignore)] +default_quickcheck! { fn f32_quickcheck(f: f32) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; @@ -97,7 +98,6 @@ quickcheck! { } } - #[cfg_attr(miri, ignore)] fn f64_quickcheck(f: f64) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; @@ -111,8 +111,9 @@ quickcheck! { } proptest! { + #![proptest_config(default_proptest_config())] + #[test] - #[cfg_attr(miri, ignore)] fn f32_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; @@ -125,7 +126,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; @@ -139,7 +139,6 @@ proptest! { #[test] #[cfg(feature = "f16")] - #[cfg_attr(miri, ignore)] fn f16_proptest(bits in u16::MIN..u16::MAX) { use lexical_util::num::Float; @@ -156,7 +155,6 @@ proptest! { #[test] #[cfg(feature = "f16")] - #[cfg_attr(miri, ignore)] fn bf16_proptest(bits in u16::MIN..u16::MAX) { use lexical_util::num::Float; diff --git a/lexical-write-float/tests/binary_tests.rs b/lexical-write-float/tests/binary_tests.rs index e4881821..edc9094e 100644 --- a/lexical-write-float/tests/binary_tests.rs +++ b/lexical-write-float/tests/binary_tests.rs @@ -1,7 +1,9 @@ #![cfg(feature = "power-of-two")] mod parse_radix; +mod util; +use crate::util::default_proptest_config; use core::num; use lexical_util::constants::{FormattedSize, BUFFER_SIZE}; use lexical_util::format::NumberFormatBuilder; @@ -11,7 +13,6 @@ use lexical_write_float::{binary, Options}; use lexical_write_integer::write::WriteInteger; use parse_radix::{parse_f32, parse_f64}; use proptest::prelude::*; -use quickcheck::quickcheck; const BINARY: u128 = NumberFormatBuilder::binary(); const BASE4: u128 = NumberFormatBuilder::from_radix(4); @@ -1089,8 +1090,7 @@ fn write_float_test() { write_float::<_, BINARY>(1.2345678901234567890e2f64, &round, "1111011.1"); } -quickcheck! { - #[cfg_attr(miri, ignore)] +default_quickcheck! { fn f32_binary_quickcheck(f: f32) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -1104,7 +1104,6 @@ quickcheck! { } } - #[cfg_attr(miri, ignore)] fn f32_octal_quickcheck(f: f32) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -1118,7 +1117,6 @@ quickcheck! { } } - #[cfg_attr(miri, ignore)] fn f64_binary_quickcheck(f: f64) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -1132,7 +1130,6 @@ quickcheck! { } } - #[cfg_attr(miri, ignore)] fn f64_octal_quickcheck(f: f64) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -1148,8 +1145,9 @@ quickcheck! { } proptest! { + #![proptest_config(default_proptest_config())] + #[test] - #[cfg_attr(miri, ignore)] fn f32_binary_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -1162,7 +1160,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_octal_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -1175,7 +1172,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_binary_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -1188,7 +1184,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_octal_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); diff --git a/lexical-write-float/tests/compact_tests.rs b/lexical-write-float/tests/compact_tests.rs index ecf6bb7b..84ad117a 100644 --- a/lexical-write-float/tests/compact_tests.rs +++ b/lexical-write-float/tests/compact_tests.rs @@ -1,5 +1,8 @@ #![cfg(feature = "compact")] +mod util; + +use crate::util::default_proptest_config; use core::num; use lexical_util::constants::BUFFER_SIZE; use lexical_util::format::NumberFormatBuilder; @@ -7,7 +10,6 @@ use lexical_util::num::Float; use lexical_write_float::float::{ExtendedFloat80, RawFloat}; use lexical_write_float::{compact, Options, RoundMode}; use proptest::prelude::*; -use quickcheck::quickcheck; const DECIMAL: u128 = NumberFormatBuilder::decimal(); @@ -759,8 +761,7 @@ fn f64_roundtrip_test() { } } -quickcheck! { - #[cfg_attr(miri, ignore)] +default_quickcheck! { fn f32_quickcheck(f: f32) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -775,7 +776,6 @@ quickcheck! { } } - #[cfg_attr(miri, ignore)] fn f64_quickcheck(f: f64) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -792,8 +792,9 @@ quickcheck! { } proptest! { + #![proptest_config(default_proptest_config())] + #[test] - #[cfg_attr(miri, ignore)] fn f32_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -807,7 +808,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); diff --git a/lexical-write-float/tests/radix_tests.rs b/lexical-write-float/tests/radix_tests.rs index a404633d..27babc88 100644 --- a/lexical-write-float/tests/radix_tests.rs +++ b/lexical-write-float/tests/radix_tests.rs @@ -1,7 +1,9 @@ #![cfg(feature = "radix")] mod parse_radix; +mod util; +use crate::util::default_proptest_config; use approx::{assert_relative_eq, relative_eq}; use core::num; use lexical_util::constants::{FormattedSize, BUFFER_SIZE}; @@ -12,7 +14,6 @@ use lexical_write_float::{radix, Options}; use lexical_write_integer::write::WriteInteger; use parse_radix::{parse_f32, parse_f64}; use proptest::prelude::*; -use quickcheck::quickcheck; const BASE3: u128 = NumberFormatBuilder::from_radix(3); const BASE5: u128 = NumberFormatBuilder::from_radix(5); @@ -281,7 +282,6 @@ macro_rules! test_all { } #[test] -#[cfg_attr(miri, ignore)] fn f32_radix_roundtrip_test() { let mut buffer = [b'\x00'; 1200]; let options = Options::new(); @@ -291,7 +291,6 @@ fn f32_radix_roundtrip_test() { } #[test] -#[cfg_attr(miri, ignore)] fn f64_radix_roundtrip_test() { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::new(); @@ -361,8 +360,7 @@ macro_rules! is_overflow { }; } -quickcheck! { - #[cfg_attr(miri, ignore)] +default_quickcheck! { fn f32_base3_quickcheck(f: f32) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -376,7 +374,6 @@ quickcheck! { } } - #[cfg_attr(miri, ignore)] fn f32_base5_quickcheck(f: f32) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -390,7 +387,6 @@ quickcheck! { } } - #[cfg_attr(miri, ignore)] fn f32_base21_quickcheck(f: f32) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().exponent(b'^').build().unwrap(); @@ -404,7 +400,6 @@ quickcheck! { } } - #[cfg_attr(miri, ignore)] fn f64_base3_quickcheck(f: f64) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -418,7 +413,6 @@ quickcheck! { } } - #[cfg_attr(miri, ignore)] fn f64_base5_quickcheck(f: f64) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -432,7 +426,6 @@ quickcheck! { } } - #[cfg_attr(miri, ignore)] fn f64_base21_quickcheck(f: f64) -> bool { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().exponent(b'^').build().unwrap(); @@ -448,8 +441,9 @@ quickcheck! { } proptest! { + #![proptest_config(default_proptest_config())] + #[test] - #[cfg_attr(miri, ignore)] fn f32_base3_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -463,7 +457,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_base5_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -477,7 +470,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_base21_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().exponent(b'^').build().unwrap(); @@ -491,7 +483,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_base3_short_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -508,7 +499,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_base5_short_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -525,7 +515,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_base21_short_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -543,7 +532,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_base3_long_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -560,7 +548,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_base5_long_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -577,7 +564,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_base21_long_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -595,7 +581,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_base3_short_exponent_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -614,7 +599,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_base5_short_exponent_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -633,7 +617,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_base21_short_exponent_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -653,7 +636,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_base3_long_exponent_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -672,7 +654,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_base5_long_exponent_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -691,7 +672,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f32_base21_long_exponent_proptest(f in f32::MIN..f32::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -711,7 +691,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_base3_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -725,7 +704,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_base5_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().build().unwrap(); @@ -739,7 +717,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_base21_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; BUFFER_SIZE]; let options = Options::builder().exponent(b'^').build().unwrap(); @@ -753,7 +730,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_base3_short_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -770,7 +746,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_base5_short_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -787,7 +762,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_base21_short_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -805,7 +779,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_base3_long_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -822,7 +795,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_base5_long_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -839,7 +811,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_base21_long_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -857,7 +828,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_base3_short_exponent_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -876,7 +846,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_base5_short_exponent_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -895,7 +864,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_base21_short_exponent_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -915,7 +883,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_base3_long_exponent_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -934,7 +901,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_base5_long_exponent_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() @@ -953,7 +919,6 @@ proptest! { } #[test] - #[cfg_attr(miri, ignore)] fn f64_base21_long_exponent_proptest(f in f64::MIN..f64::MAX) { let mut buffer = [b'\x00'; 512]; let options = Options::builder() diff --git a/lexical-write-float/tests/util.rs b/lexical-write-float/tests/util.rs new file mode 100644 index 00000000..e87a0836 --- /dev/null +++ b/lexical-write-float/tests/util.rs @@ -0,0 +1,57 @@ +#![allow(dead_code, unused_imports)] + +use proptest::prelude::*; +pub(crate) use quickcheck::QuickCheck; + +pub fn default_proptest_config() -> ProptestConfig { + ProptestConfig { + cases: if cfg!(miri) { + 10 + } else { + ProptestConfig::default().cases + }, + max_shrink_iters: if cfg!(miri) { + 10 + } else { + ProptestConfig::default().max_shrink_iters + }, + failure_persistence: if cfg!(miri) { + None + } else { + ProptestConfig::default().failure_persistence + }, + ..ProptestConfig::default() + } +} + +// This is almost identical to quickcheck's itself, just to add default arguments +// https://docs.rs/quickcheck/1.0.3/src/quickcheck/lib.rs.html#43-67 +// The code is unlicensed. +#[macro_export] +macro_rules! default_quickcheck { + (@as_items $($i:item)*) => ($($i)*); + { + $( + $(#[$m:meta])* + fn $fn_name:ident($($arg_name:ident : $arg_ty:ty),*) -> $ret:ty { + $($code:tt)* + } + )* + } => ( + $crate::default_quickcheck! { + @as_items + $( + #[test] + $(#[$m])* + fn $fn_name() { + fn prop($($arg_name: $arg_ty),*) -> $ret { + $($code)* + } + $crate::util::QuickCheck::new() + .max_tests(if cfg!(miri) { 10 } else { 10_000 }) + .quickcheck(prop as fn($($arg_ty),*) -> $ret); + } + )* + } + ) +} diff --git a/lexical-write-integer/src/algorithm.rs b/lexical-write-integer/src/algorithm.rs index 3c9dd9dc..bf4d07aa 100644 --- a/lexical-write-integer/src/algorithm.rs +++ b/lexical-write-integer/src/algorithm.rs @@ -69,7 +69,14 @@ macro_rules! write_digit { /// small changes here can destroy performance, so it's crucial we do this /// correctly. /// -/// See [algorithm] for more detailed information on the safety considerations. +/// If `buffer.len() >= T::DIGITS` and `index >= T::DIGITS`, then this is +/// safe. We first carve off 4 digits off the end, similar to the algorithm +/// in compact, then 2 at a time, then 1, index will never wrap under 0. +/// Since we validate the table size and radix inside, this is the only +/// safety precondition that must be held up. +/// +/// See [algorithm] and the [crate] documentation for more detailed +/// information on the safety considerations. #[inline(always)] unsafe fn write_digits( mut value: T, @@ -175,6 +182,8 @@ unsafe fn write_step_digits( /// /// See the crate [crate] documentation for more security considerations. /// +/// `write_digits` internally +/// /// [digit_count]: crate::decimal::DigitCount #[inline(always)] pub unsafe fn algorithm(value: T, radix: u32, table: &[u8], buffer: &mut [u8]) -> usize diff --git a/lexical-write-integer/src/compact.rs b/lexical-write-integer/src/compact.rs index 4019f902..a9f9bd5a 100644 --- a/lexical-write-integer/src/compact.rs +++ b/lexical-write-integer/src/compact.rs @@ -6,10 +6,7 @@ #![cfg(feature = "compact")] #![doc(hidden)] -use core::mem; - use lexical_util::algorithm::copy_to_dst; -use lexical_util::buffer::slice_assume_init_mut; use lexical_util::constants::FormattedSize; use lexical_util::digit::digit_to_char; use lexical_util::num::{AsCast, UnsignedInteger}; @@ -26,14 +23,18 @@ pub trait Compact: UnsignedInteger + FormattedSize { fn compact(self, radix: u32, buffer: &mut [u8]) -> usize { // NOTE: We do not have to validate the buffer length because `copy_to_dst` is safe. assert!(Self::BITS <= 128); - let mut digits: [mem::MaybeUninit; 128] = [mem::MaybeUninit::uninit(); 128]; + let mut digits: [u8; 128] = [0u8; 128]; let mut index = digits.len(); // SAFETY: safe as long as buffer is large enough to hold the max value. // We never read unwritten values, and we never assume the data is initialized. // Need at least 128-bits, at least as many as the bits in the current type. // Since we make our safety variants inside, this is always safe. - let inititalized = unsafe { + // + // The logic is this: each iteration we remove a digit from the end, decrement + // the index, and assign it to the buffer. Since the longest digits possible + // would be radix 2, log2(128) == 128, so at most 128 digits. + let slc = unsafe { // Decode all but the last digit. let radix = Self::from_u32(radix); let mut value = self; @@ -41,19 +42,16 @@ pub trait Compact: UnsignedInteger + FormattedSize { let r = value % radix; value /= radix; index -= 1; - digits.get_unchecked_mut(index).write(digit_to_char(u32::as_cast(r))); + index_unchecked_mut!(digits[index]) = digit_to_char(u32::as_cast(r)); } // Decode last digit. let r = value % radix; index -= 1; - digits.get_unchecked_mut(index).write(digit_to_char(u32::as_cast(r))); - let uninit = digits.get_unchecked_mut(index..); - // SAFETY: All these values have been initialized, since we know - // we've only written from `index..len()` - slice_assume_init_mut(uninit) + index_unchecked_mut!(digits[index]) = digit_to_char(u32::as_cast(r)); + digits.get_unchecked_mut(index..) }; - copy_to_dst(buffer, inititalized) + copy_to_dst(buffer, slc) } } diff --git a/lexical-write-integer/src/lib.rs b/lexical-write-integer/src/lib.rs index 5c4bd444..3ab8d436 100644 --- a/lexical-write-integer/src/lib.rs +++ b/lexical-write-integer/src/lib.rs @@ -44,10 +44,10 @@ //! //! # Safety //! -//! This module uses a some more unsafe code for moderately acceptable performance. The compact -//! decimal serializer has no non-local safety invariants, which since it's focused on code size -//! rather than performance, this tradeoff is acceptable and it uses a temporary, over-allocated -//! buffer as an intermediate. +//! This module uses a some more unsafe code for moderately acceptable performance. The +//! compact decimal serializer has no non-local safety invariants, which since it's +//! focused on code size rather than performance, this tradeoff is acceptable and it +//! uses a temporary, over-allocated buffer as an intermediate. //! //! The decimal writer relies on pre-computed tables and an exact calculation //! of the digit count ([digit_count]) to avoid any overhead. Avoid intermediary @@ -59,7 +59,8 @@ //! and factoring of the code, it's fairly easy to demonstrate the safety as long //! as the caller enusres at least the required number of digits are provided. //! -//! Our algorithms work like this: +//! Our algorithms work like this, carving off the lower digits and writing them +//! to the back of the buffer. //! //! ```rust,ignore //! let mut value = 12345u32; diff --git a/lexical-write-integer/tests/api_tests.rs b/lexical-write-integer/tests/api_tests.rs index 4d3b0476..fa04f932 100644 --- a/lexical-write-integer/tests/api_tests.rs +++ b/lexical-write-integer/tests/api_tests.rs @@ -1,6 +1,6 @@ -#[cfg(feature = "radix")] mod util; +use crate::util::default_proptest_config; use core::fmt::Debug; use core::str::{from_utf8_unchecked, FromStr}; #[cfg(feature = "radix")] @@ -10,7 +10,6 @@ use lexical_util::format::NumberFormatBuilder; use lexical_util::format::STANDARD; use lexical_write_integer::{Options, ToLexical, ToLexicalWithOptions}; use proptest::prelude::*; -use quickcheck::quickcheck; #[cfg(feature = "radix")] use util::from_radix; @@ -1186,220 +1185,186 @@ fn u128_pow10_test() { } } -quickcheck! { - #[cfg_attr(miri, ignore)] +default_quickcheck! { fn u8_quickcheck(i: u8) -> bool { i == roundtrip(i) } - #[cfg_attr(miri, ignore)] fn u16_quickcheck(i: u16) -> bool { i == roundtrip(i) } - #[cfg_attr(miri, ignore)] fn u32_quickcheck(i: u32) -> bool { i == roundtrip(i) } - #[cfg_attr(miri, ignore)] fn u64_quickcheck(i: u64) -> bool { i == roundtrip(i) } - #[cfg_attr(miri, ignore)] fn u128_quickcheck(i: u128) -> bool { i == roundtrip(i) } - #[cfg_attr(miri, ignore)] fn usize_quickcheck(i: usize) -> bool { i == roundtrip(i) } - #[cfg_attr(miri, ignore)] fn i8_quickcheck(i: i8) -> bool { i == roundtrip(i) } - #[cfg_attr(miri, ignore)] fn i16_quickcheck(i: i16) -> bool { i == roundtrip(i) } - #[cfg_attr(miri, ignore)] fn i32_quickcheck(i: i32) -> bool { i == roundtrip(i) } - #[cfg_attr(miri, ignore)] fn i64_quickcheck(i: i64) -> bool { i == roundtrip(i) } - #[cfg_attr(miri, ignore)] fn i128_quickcheck(i: i128) -> bool { i == roundtrip(i) } - #[cfg_attr(miri, ignore)] fn isize_quickcheck(i: isize) -> bool { i == roundtrip(i) } } proptest! { + #![proptest_config(default_proptest_config())] + #[test] - #[cfg_attr(miri, ignore)] fn u8_proptest(i in u8::MIN..u8::MAX) { prop_assert_eq!(i, roundtrip(i)); } #[test] - #[cfg_attr(miri, ignore)] fn i8_proptest(i in i8::MIN..i8::MAX) { prop_assert_eq!(i, roundtrip(i)); } #[test] - #[cfg_attr(miri, ignore)] fn u16_proptest(i in u16::MIN..u16::MAX) { prop_assert_eq!(i, roundtrip(i)); } #[test] - #[cfg_attr(miri, ignore)] fn i16_proptest(i in i16::MIN..i16::MAX) { prop_assert_eq!(i, roundtrip(i)); } #[test] - #[cfg_attr(miri, ignore)] fn u32_proptest(i in u32::MIN..u32::MAX) { prop_assert_eq!(i, roundtrip(i)); } #[test] - #[cfg_attr(miri, ignore)] fn i32_proptest(i in i32::MIN..i32::MAX) { prop_assert_eq!(i, roundtrip(i)); } #[test] - #[cfg_attr(miri, ignore)] fn u64_proptest(i in u64::MIN..u64::MAX) { prop_assert_eq!(i, roundtrip(i)); } #[test] - #[cfg_attr(miri, ignore)] fn i64_proptest(i in i64::MIN..i64::MAX) { prop_assert_eq!(i, roundtrip(i)); } #[test] - #[cfg_attr(miri, ignore)] fn u128_proptest(i in u128::MIN..u128::MAX) { prop_assert_eq!(i, roundtrip(i)); } #[test] - #[cfg_attr(miri, ignore)] fn i128_proptest(i in i128::MIN..i128::MAX) { prop_assert_eq!(i, roundtrip(i)); } #[test] - #[cfg_attr(miri, ignore)] fn usize_proptest(i in usize::MIN..usize::MAX) { prop_assert_eq!(i, roundtrip(i)); } #[test] - #[cfg_attr(miri, ignore)] fn isize_proptest(i in isize::MIN..isize::MAX) { prop_assert_eq!(i, roundtrip(i)); } #[test] - #[cfg_attr(miri, ignore)] #[cfg(feature = "radix")] fn u8_proptest_radix(i in u8::MIN..u8::MAX, radix in 2u32..=36) { prop_assert_eq!(i, roundtrip_radix(i, radix)); } #[test] - #[cfg_attr(miri, ignore)] #[cfg(feature = "radix")] fn i8_proptest_radix(i in i8::MIN..i8::MAX, radix in 2u32..=36) { prop_assert_eq!(i, roundtrip_radix(i, radix)); } #[test] - #[cfg_attr(miri, ignore)] #[cfg(feature = "radix")] fn u16_proptest_radix(i in u16::MIN..u16::MAX, radix in 2u32..=36) { prop_assert_eq!(i, roundtrip_radix(i, radix)); } #[test] - #[cfg_attr(miri, ignore)] #[cfg(feature = "radix")] fn i16_proptest_radix(i in i16::MIN..i16::MAX, radix in 2u32..=36) { prop_assert_eq!(i, roundtrip_radix(i, radix)); } #[test] - #[cfg_attr(miri, ignore)] #[cfg(feature = "radix")] fn u32_proptest_radix(i in u32::MIN..u32::MAX, radix in 2u32..=36) { prop_assert_eq!(i, roundtrip_radix(i, radix)); } #[test] - #[cfg_attr(miri, ignore)] #[cfg(feature = "radix")] fn i32_proptest_radix(i in i32::MIN..i32::MAX, radix in 2u32..=36) { prop_assert_eq!(i, roundtrip_radix(i, radix)); } #[test] - #[cfg_attr(miri, ignore)] #[cfg(feature = "radix")] fn u64_proptest_radix(i in u64::MIN..u64::MAX, radix in 2u32..=36) { prop_assert_eq!(i, roundtrip_radix(i, radix)); } #[test] - #[cfg_attr(miri, ignore)] #[cfg(feature = "radix")] fn i64_proptest_radix(i in i64::MIN..i64::MAX, radix in 2u32..=36) { prop_assert_eq!(i, roundtrip_radix(i, radix)); } #[test] - #[cfg_attr(miri, ignore)] #[cfg(feature = "radix")] fn u128_proptest_radix(i in u128::MIN..u128::MAX, radix in 2u32..=36) { prop_assert_eq!(i, roundtrip_radix(i, radix)); } #[test] - #[cfg_attr(miri, ignore)] #[cfg(feature = "radix")] fn i128_proptest_radix(i in i128::MIN..i128::MAX, radix in 2u32..=36) { prop_assert_eq!(i, roundtrip_radix(i, radix)); } #[test] - #[cfg_attr(miri, ignore)] #[cfg(feature = "radix")] fn usize_proptest_radix(i in usize::MIN..usize::MAX, radix in 2u32..=36) { prop_assert_eq!(i, roundtrip_radix(i, radix)); } #[test] - #[cfg_attr(miri, ignore)] #[cfg(feature = "radix")] fn isize_proptest_radix(i in isize::MIN..isize::MAX, radix in 2u32..=36) { prop_assert_eq!(i, roundtrip_radix(i, radix)); diff --git a/lexical-write-integer/tests/decimal_tests.rs b/lexical-write-integer/tests/decimal_tests.rs index 4b83ecc2..6c345d9e 100644 --- a/lexical-write-integer/tests/decimal_tests.rs +++ b/lexical-write-integer/tests/decimal_tests.rs @@ -1,8 +1,9 @@ #![cfg(not(feature = "compact"))] +mod util; + use lexical_util::num::UnsignedInteger; use lexical_write_integer::decimal::{self, Decimal, DigitCount}; -use quickcheck::quickcheck; #[test] fn fast_log2_test() { @@ -412,28 +413,23 @@ fn slow_digit_count(x: T) -> usize { x.to_string().len() } -quickcheck! { - #[cfg_attr(miri, ignore)] +default_quickcheck! { fn fast_log2_quickcheck(x: u32) -> bool { slow_log2(x) == decimal::fast_log2(x) } - #[cfg_attr(miri, ignore)] fn u32_digit_count_quickcheck(x: u32) -> bool { slow_digit_count(x) == x.digit_count() } - #[cfg_attr(miri, ignore)] fn u64_digit_count_quickcheck(x: u64) -> bool { slow_digit_count(x) == x.digit_count() } - #[cfg_attr(miri, ignore)] fn u128_digit_count_quickcheck(x: u128) -> bool { slow_digit_count(x) == x.digit_count() } - #[cfg_attr(miri, ignore)] fn u32toa_quickcheck(x: u32) -> bool { let actual = x.to_string(); let mut buffer = [b'\x00'; 16]; @@ -441,7 +437,6 @@ quickcheck! { &buffer[..actual.len()] == actual.as_bytes() } - #[cfg_attr(miri, ignore)] fn u64toa_quickcheck(x: u64) -> bool { let actual = x.to_string(); let mut buffer = [b'\x00'; 32]; @@ -449,7 +444,6 @@ quickcheck! { &buffer[..actual.len()] == actual.as_bytes() } - #[cfg_attr(miri, ignore)] fn u128toa_quickcheck(x: u128) -> bool { let actual = x.to_string(); let mut buffer = [b'\x00'; 48]; diff --git a/lexical-write-integer/tests/radix_tests.rs b/lexical-write-integer/tests/radix_tests.rs index 5bf19671..5ec8bc7d 100644 --- a/lexical-write-integer/tests/radix_tests.rs +++ b/lexical-write-integer/tests/radix_tests.rs @@ -3,11 +3,11 @@ mod util; +use crate::util::{default_proptest_config, from_radix}; use core::str::from_utf8_unchecked; use lexical_util::constants::BUFFER_SIZE; use lexical_write_integer::write::WriteInteger; use proptest::prelude::*; -use util::from_radix; #[test] #[cfg(feature = "radix")] @@ -167,6 +167,8 @@ fn u128toa_mockup(x: u128, radix: u32) -> Result<(), TestCaseError> { } proptest! { + #![proptest_config(default_proptest_config())] + #[test] #[cfg_attr(miri, ignore)] #[cfg(feature = "radix")] diff --git a/lexical-write-integer/tests/util.rs b/lexical-write-integer/tests/util.rs index f4210baa..8d71bdfb 100644 --- a/lexical-write-integer/tests/util.rs +++ b/lexical-write-integer/tests/util.rs @@ -1,7 +1,64 @@ +#![allow(dead_code, unused_imports)] + #[cfg(feature = "power-of-two")] use lexical_util::format::NumberFormatBuilder; +use proptest::prelude::*; +pub(crate) use quickcheck::QuickCheck; #[cfg(feature = "power-of-two")] pub const fn from_radix(radix: u8) -> u128 { NumberFormatBuilder::from_radix(radix) } + +pub fn default_proptest_config() -> ProptestConfig { + ProptestConfig { + cases: if cfg!(miri) { + 10 + } else { + ProptestConfig::default().cases + }, + max_shrink_iters: if cfg!(miri) { + 10 + } else { + ProptestConfig::default().max_shrink_iters + }, + failure_persistence: if cfg!(miri) { + None + } else { + ProptestConfig::default().failure_persistence + }, + ..ProptestConfig::default() + } +} + +// This is almost identical to quickcheck's itself, just to add default arguments +// https://docs.rs/quickcheck/1.0.3/src/quickcheck/lib.rs.html#43-67 +// The code is unlicensed. +#[macro_export] +macro_rules! default_quickcheck { + (@as_items $($i:item)*) => ($($i)*); + { + $( + $(#[$m:meta])* + fn $fn_name:ident($($arg_name:ident : $arg_ty:ty),*) -> $ret:ty { + $($code:tt)* + } + )* + } => ( + $crate::default_quickcheck! { + @as_items + $( + #[test] + $(#[$m])* + fn $fn_name() { + fn prop($($arg_name: $arg_ty),*) -> $ret { + $($code)* + } + $crate::util::QuickCheck::new() + .max_tests(if cfg!(miri) { 10 } else { 10_000 }) + .quickcheck(prop as fn($($arg_ty),*) -> $ret); + } + )* + } + ) +}