io: Introduce api test file

Introduce an `io/tests/api.rs` file to test the API surface of the `io`
crate. In doing so fix C-DEBUG and prove C-DEBUG-NONEMPTY.

C-DEBUG: https://rust-lang.github.io/api-guidelines/debuggability.html#c-debug
This commit is contained in:
Tobin C. Harding 2024-12-30 12:51:58 +11:00
parent 01f3eefc12
commit 1175faca32
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
3 changed files with 145 additions and 0 deletions

View File

@ -5,6 +5,7 @@ use internals::rust_version;
/// A bridging wrapper providing the IO traits for types that already implement `std` IO traits.
#[repr(transparent)]
#[derive(Debug)]
pub struct FromStd<T>(T);
impl<T> FromStd<T> {
@ -116,6 +117,7 @@ impl<T: std::io::Write> std::io::Write for FromStd<T> {
/// A bridging wrapper providing the std traits for types that already implement our traits.
#[repr(transparent)]
#[derive(Debug)]
pub struct ToStd<T>(T);
impl<T> ToStd<T> {

View File

@ -94,6 +94,7 @@ pub trait BufRead: Read {
/// Reader adapter which limits the bytes read from an underlying reader.
///
/// Created by calling `[Read::take]`.
#[derive(Debug)]
pub struct Take<'a, R: Read + ?Sized> {
reader: &'a mut R,
remaining: u64,
@ -191,6 +192,7 @@ impl BufRead for &[u8] {
}
/// Wraps an in memory reader providing the `position` function.
#[derive(Debug)]
pub struct Cursor<T> {
inner: T,
pos: u64,
@ -329,6 +331,7 @@ impl Write for &mut [u8] {
/// A sink to which all writes succeed. See [`std::io::Sink`] for more info.
///
/// Created using `io::sink()`.
#[derive(Debug)]
pub struct Sink;
impl Write for Sink {

140
io/tests/api.rs Normal file
View File

@ -0,0 +1,140 @@
//! Test the API surface of `io`.
//!
//! The point of these tests are to check the API surface as opposed to test the API functionality.
//!
//! ref: <https://rust-lang.github.io/api-guidelines/about.html>
#![allow(dead_code)]
#![allow(unused_imports)]
use core::cell::Cell;
use core::convert::Infallible;
// These imports test "typical" usage by user code.
use bitcoin_io::{self as io, BufRead, Cursor, ErrorKind, Read, Sink, Take, Write};
#[cfg(feature = "std")]
use bitcoin_io::{FromStd, ToStd};
/// An arbitrary error kind.
const ERROR_KIND: ErrorKind = ErrorKind::TimedOut;
/// A struct that includes all public non-error enums.
#[derive(Debug)] // All public types implement Debug (C-DEBUG).
struct Enums {
a: ErrorKind,
}
impl Enums {
/// Creates an arbitrary `Enums` instance.
fn new() -> Self { Self { a: ERROR_KIND } }
}
/// A struct that includes all public non-error structs except `Take`.
#[derive(Debug)] // All public types implement Debug (C-DEBUG).
struct Structs {
#[cfg(feature = "std")]
a: FromStd<u32>,
#[cfg(feature = "std")]
b: ToStd<Dummy>,
c: Cursor<Dummy>,
d: Sink,
}
impl Structs {
fn new() -> Self {
Self {
#[cfg(feature = "std")]
a: FromStd::new(0),
#[cfg(feature = "std")]
b: ToStd::new(DUMMY),
c: Cursor::new(DUMMY),
d: Sink,
}
}
}
#[derive(Debug)] // `Take` implements Debug (C-DEBUG).
struct Taker<'a> {
a: Take<'a, Dummy>,
}
/// An arbitrary `Dummy` instance.
static DUMMY: Dummy = Dummy(0);
/// Dummy struct to implement all the traits we provide.
#[derive(Debug, Copy, Clone)]
struct Dummy(u64);
impl Read for Dummy {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
if buf.is_empty() {
Ok(0)
} else {
buf[0] = (self.0 & 0xFF) as u8;
Ok(1)
}
}
}
impl BufRead for Dummy {
fn fill_buf(&mut self) -> Result<&[u8], io::Error> { Ok(&[]) }
fn consume(&mut self, _: usize) {}
}
impl Write for Dummy {
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> { Ok(buf.len()) }
fn write_all(&mut self, _: &[u8]) -> Result<(), io::Error> { Ok(()) }
fn flush(&mut self) -> Result<(), io::Error> { Ok(()) }
}
impl AsRef<[u8]> for Dummy {
fn as_ref(&self) -> &[u8] { &[] }
}
/// A struct that includes all public non-error types.
#[derive(Debug)] // All public types implement Debug (C-DEBUG).
struct Types {
a: Enums,
b: Structs,
}
impl Types {
fn new() -> Self { Self { a: Enums::new(), b: Structs::new() } }
}
/// A struct that includes all public error types.
#[derive(Debug)] // `io::Error` only implements `Debug`.
struct Errors {
a: io::Error,
}
// `Debug` representation is never empty (C-DEBUG-NONEMPTY).
#[test]
fn api_all_non_error_types_have_non_empty_debug() {
let t = Types::new();
let debug = format!("{:?}", t.a.a);
assert!(!debug.is_empty());
#[cfg(feature = "std")]
{
let debug = format!("{:?}", t.b.a);
assert!(!debug.is_empty());
let debug = format!("{:?}", t.b.b);
assert!(!debug.is_empty());
}
let debug = format!("{:?}", t.b.c);
assert!(!debug.is_empty());
let debug = format!("{:?}", t.b.d);
assert!(!debug.is_empty());
}
// Types are `Send` and `Sync` where possible (C-SEND-SYNC).
#[test]
fn all_non_error_tyes_implement_send_sync() {
fn assert_send<T: Send>() {}
assert_send::<Types>();
fn assert_sync<T: Sync>() {}
assert_sync::<Types>();
}