Add more tests for io::Error packing, and fix some comments that weren't quite accurate anymore
This commit is contained in:
parent
a17a896d09
commit
9cbe99488b
3 changed files with 103 additions and 18 deletions
|
@ -76,6 +76,9 @@ impl fmt::Debug for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only derive debug in tests, to make sure it
|
||||||
|
// doesn't accidentally get printed.
|
||||||
|
#[cfg_attr(test, derive(Debug))]
|
||||||
enum ErrorData<C> {
|
enum ErrorData<C> {
|
||||||
Os(i32),
|
Os(i32),
|
||||||
Simple(ErrorKind),
|
Simple(ErrorKind),
|
||||||
|
@ -98,6 +101,7 @@ enum ErrorData<C> {
|
||||||
// if `error/repr_bitpacked.rs` is in use — for the unpacked repr it doesn't
|
// if `error/repr_bitpacked.rs` is in use — for the unpacked repr it doesn't
|
||||||
// matter at all)
|
// matter at all)
|
||||||
#[repr(align(4))]
|
#[repr(align(4))]
|
||||||
|
#[derive(Debug)]
|
||||||
pub(crate) struct SimpleMessage {
|
pub(crate) struct SimpleMessage {
|
||||||
kind: ErrorKind,
|
kind: ErrorKind,
|
||||||
message: &'static str,
|
message: &'static str,
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
//! a more clever manner than `rustc`'s default layout algorithm would).
|
//! a more clever manner than `rustc`'s default layout algorithm would).
|
||||||
//!
|
//!
|
||||||
//! Conceptually, it stores the same data as the "unpacked" equivalent we use on
|
//! Conceptually, it stores the same data as the "unpacked" equivalent we use on
|
||||||
//! other targets. Specifically, you can imagine it as an optimized following
|
//! other targets. Specifically, you can imagine it as an optimized version of
|
||||||
//! data (which is equivalent to what's stored by `repr_unpacked::Repr`, e.g.
|
//! the following enum (which is roughly equivalent to what's stored by
|
||||||
//! `super::ErrorData<Box<Custom>>`):
|
//! `repr_unpacked::Repr`, e.g. `super::ErrorData<Box<Custom>>`):
|
||||||
//!
|
//!
|
||||||
//! ```ignore (exposition-only)
|
//! ```ignore (exposition-only)
|
||||||
//! enum ErrorData {
|
//! enum ErrorData {
|
||||||
|
@ -135,7 +135,16 @@ impl Repr {
|
||||||
// (rather than `ptr::wrapping_add`), but it's unclear this would give
|
// (rather than `ptr::wrapping_add`), but it's unclear this would give
|
||||||
// any benefit, so we just use `wrapping_add` instead.
|
// any benefit, so we just use `wrapping_add` instead.
|
||||||
let tagged = p.wrapping_add(TAG_CUSTOM).cast::<()>();
|
let tagged = p.wrapping_add(TAG_CUSTOM).cast::<()>();
|
||||||
// Safety: the above safety comment also means the result can't be null.
|
// Safety: `TAG_CUSTOM + p` is the same as `TAG_CUSTOM | p`,
|
||||||
|
// because `p`'s alignment means it isn't allowed to have any of the
|
||||||
|
// `TAG_BITS` set (you can verify that addition and bitwise-or are the
|
||||||
|
// same when the operands have no bits in common using a truth table).
|
||||||
|
//
|
||||||
|
// Then, `TAG_CUSTOM | p` is not zero, as that would require
|
||||||
|
// `TAG_CUSTOM` and `p` both be zero, and neither is (as `p` came from a
|
||||||
|
// box, and `TAG_CUSTOM` just... isn't zero -- it's `0b01`). Therefore,
|
||||||
|
// `TAG_CUSTOM + p` isn't zero and so `tagged` can't be, and the
|
||||||
|
// `new_unchecked` is safe.
|
||||||
let res = Self(unsafe { NonNull::new_unchecked(tagged) });
|
let res = Self(unsafe { NonNull::new_unchecked(tagged) });
|
||||||
// quickly smoke-check we encoded the right thing (This generally will
|
// quickly smoke-check we encoded the right thing (This generally will
|
||||||
// only run in libstd's tests, unless the user uses -Zbuild-std)
|
// only run in libstd's tests, unless the user uses -Zbuild-std)
|
||||||
|
@ -342,12 +351,25 @@ static_assert!(@usize_eq: size_of::<NonNull<()>>(), size_of::<usize>());
|
||||||
static_assert!(@usize_eq: size_of::<&'static SimpleMessage>(), 8);
|
static_assert!(@usize_eq: size_of::<&'static SimpleMessage>(), 8);
|
||||||
static_assert!(@usize_eq: size_of::<Box<Custom>>(), 8);
|
static_assert!(@usize_eq: size_of::<Box<Custom>>(), 8);
|
||||||
|
|
||||||
// And they must have >= 4 byte alignment.
|
static_assert!((TAG_MASK + 1).is_power_of_two());
|
||||||
static_assert!(align_of::<SimpleMessage>() >= 4);
|
// And they must have sufficient alignment.
|
||||||
static_assert!(align_of::<Custom>() >= 4);
|
static_assert!(align_of::<SimpleMessage>() >= TAG_MASK + 1);
|
||||||
|
static_assert!(align_of::<Custom>() >= TAG_MASK + 1);
|
||||||
|
|
||||||
// This is obviously true (`TAG_CUSTOM` is `0b01`), but our implementation of
|
static_assert!(@usize_eq: (TAG_MASK & TAG_SIMPLE_MESSAGE), TAG_SIMPLE_MESSAGE);
|
||||||
// `Repr::new_custom` and such would be wrong if it were not, so we check.
|
static_assert!(@usize_eq: (TAG_MASK & TAG_CUSTOM), TAG_CUSTOM);
|
||||||
|
static_assert!(@usize_eq: (TAG_MASK & TAG_OS), TAG_OS);
|
||||||
|
static_assert!(@usize_eq: (TAG_MASK & TAG_SIMPLE), TAG_SIMPLE);
|
||||||
|
|
||||||
|
// This is obviously true (`TAG_CUSTOM` is `0b01`), but in `Repr::new_custom` we
|
||||||
|
// offset a pointer by this value, and expect it to both be within the same
|
||||||
|
// object, and to not wrap around the address space. See the comment in that
|
||||||
|
// function for further details.
|
||||||
|
//
|
||||||
|
// Actually, at the moment we use `ptr::wrapping_add`, not `ptr::add`, so this
|
||||||
|
// check isn't needed for that one, although the assertion that we don't
|
||||||
|
// actually wrap around in that wrapping_add does simplify the safety reasoning
|
||||||
|
// elsewhere considerably.
|
||||||
static_assert!(size_of::<Custom>() >= TAG_CUSTOM);
|
static_assert!(size_of::<Custom>() >= TAG_CUSTOM);
|
||||||
|
|
||||||
// These two store a payload which is allowed to be zero, so they must be
|
// These two store a payload which is allowed to be zero, so they must be
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::{const_io_error, Custom, Error, ErrorKind, Repr};
|
use super::{const_io_error, Custom, Error, ErrorData, ErrorKind, Repr};
|
||||||
|
use crate::assert_matches::assert_matches;
|
||||||
use crate::error;
|
use crate::error;
|
||||||
use crate::fmt;
|
use crate::fmt;
|
||||||
use crate::mem::size_of;
|
use crate::mem::size_of;
|
||||||
|
@ -69,16 +70,74 @@ fn test_const() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_error_packing() {
|
fn test_os_packing() {
|
||||||
for code in -20i32..20i32 {
|
for code in -20i32..20i32 {
|
||||||
let e = Error::from_raw_os_error(code);
|
let e = Error::from_raw_os_error(code);
|
||||||
assert_eq!(e.raw_os_error(), Some(code));
|
assert_eq!(e.raw_os_error(), Some(code));
|
||||||
|
assert_matches!(
|
||||||
|
e.repr.data(),
|
||||||
|
ErrorData::Os(c) if c == code,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
assert_eq!(Error::from(ErrorKind::NotFound).kind(), ErrorKind::NotFound);
|
}
|
||||||
assert_eq!(Error::from(ErrorKind::Uncategorized).kind(), ErrorKind::Uncategorized);
|
|
||||||
assert_eq!(Error::from(ErrorKind::NotFound).kind(), ErrorKind::NotFound);
|
#[test]
|
||||||
assert_eq!(Error::from(ErrorKind::Uncategorized).kind(), ErrorKind::Uncategorized);
|
fn test_errorkind_packing() {
|
||||||
let dunno = const_io_error!(ErrorKind::Uncategorized, "dunno");
|
assert_eq!(Error::from(ErrorKind::NotFound).kind(), ErrorKind::NotFound);
|
||||||
assert_eq!(dunno.kind(), ErrorKind::Uncategorized);
|
assert_eq!(Error::from(ErrorKind::PermissionDenied).kind(), ErrorKind::PermissionDenied);
|
||||||
assert!(format!("{:?}", dunno).contains("dunno"))
|
assert_eq!(Error::from(ErrorKind::Uncategorized).kind(), ErrorKind::Uncategorized);
|
||||||
|
// Check that the innards look like like what we want.
|
||||||
|
assert_matches!(
|
||||||
|
Error::from(ErrorKind::OutOfMemory).repr.data(),
|
||||||
|
ErrorData::Simple(ErrorKind::OutOfMemory),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple_message_packing() {
|
||||||
|
use super::{ErrorKind::*, SimpleMessage};
|
||||||
|
macro_rules! check_simple_msg {
|
||||||
|
($err:expr, $kind:ident, $msg:literal) => {{
|
||||||
|
let e = &$err;
|
||||||
|
// Check that the public api is right.
|
||||||
|
assert_eq!(e.kind(), $kind);
|
||||||
|
assert!(format!("{:?}", e).contains($msg));
|
||||||
|
// and we got what we expected
|
||||||
|
assert_matches!(
|
||||||
|
e.repr.data(),
|
||||||
|
ErrorData::SimpleMessage(SimpleMessage { kind: $kind, message: $msg })
|
||||||
|
);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
let not_static = const_io_error!(Uncategorized, "not a constant!");
|
||||||
|
check_simple_msg!(not_static, Uncategorized, "not a constant!");
|
||||||
|
|
||||||
|
const CONST: Error = const_io_error!(NotFound, "definitely a constant!");
|
||||||
|
check_simple_msg!(CONST, NotFound, "definitely a constant!");
|
||||||
|
|
||||||
|
static STATIC: Error = const_io_error!(BrokenPipe, "a constant, sort of!");
|
||||||
|
check_simple_msg!(STATIC, BrokenPipe, "a constant, sort of!");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
struct Bojji(bool);
|
||||||
|
impl error::Error for Bojji {}
|
||||||
|
impl fmt::Display for Bojji {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "ah! {:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_custom_error_packing() {
|
||||||
|
use super::Custom;
|
||||||
|
let test = Error::new(ErrorKind::Uncategorized, Bojji(true));
|
||||||
|
assert_matches!(
|
||||||
|
test.repr.data(),
|
||||||
|
ErrorData::Custom(Custom {
|
||||||
|
kind: ErrorKind::Uncategorized,
|
||||||
|
error,
|
||||||
|
}) if error.downcast_ref::<Bojji>().as_deref() == Some(&Bojji(true)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue