diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 531ddd1..0ef6eed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,26 +8,28 @@ jobs: strategy: fail-fast: false matrix: - rust-toolchain: [nightly] targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat] steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - with: - toolchain: ${{ matrix.rust-toolchain }} - components: rust-src, clippy, rustfmt - targets: ${{ matrix.targets }} - - name: Check rust version - run: rustc --version --verbose - - name: Check code format - run: cargo fmt --all -- --check - - name: Clippy - run: cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default - - name: Build - run: cargo build --target ${{ matrix.targets }} --all-features - - name: Unit test - if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }} - run: cargo test --target ${{ matrix.targets }} -- --nocapture + - uses: actions/checkout@v4 + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + profile: minimal + components: rust-src, rustfmt, clippy + targets: ${{ matrix.targets }} + override: true + - name: Check rust version + run: rustc --version --verbose + - name: Check code format + run: cargo fmt --all -- --check + - name: Clippy + run: cargo clippy -Z build-std=core,alloc --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default + - name: Build + run: cargo build -Z build-std=core,alloc --target ${{ matrix.targets }} --all-features + - name: Unit test + if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }} + run: cargo test --target ${{ matrix.targets }} -- --nocapture doc: runs-on: ubuntu-latest @@ -39,17 +41,17 @@ jobs: default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }} RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - - name: Build docs - continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }} - run: | - cargo doc --no-deps --all-features - printf '' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html - - name: Deploy to Github Pages - if: ${{ github.ref == env.default-branch }} - uses: JamesIves/github-pages-deploy-action@v4 - with: - single-commit: true - branch: gh-pages - folder: target/doc + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - name: Build docs + continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }} + run: | + cargo doc --no-deps --all-features + printf '' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html + - name: Deploy to Github Pages + if: ${{ github.ref == env.default-branch }} + uses: JamesIves/github-pages-deploy-action@v4 + with: + single-commit: true + branch: gh-pages + folder: target/doc diff --git a/.gitignore b/.gitignore index ff78c42..abe6900 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ /target -/.vscode +.vscode .DS_Store Cargo.lock +.idea +*.json +*.xml diff --git a/Cargo.toml b/Cargo.toml index 8049ae9..775a3f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "axio" -version = "0.1.1" +version = "0.1.2" edition = "2021" -authors = ["Yuekai Jia "] +authors = [ + "Yuekai Jia ", + "Leo Cheng ", +] description = "`std::io`-like I/O traits for `no_std` environment" license = "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0" homepage = "https://github.com/arceos-org/arceos" @@ -16,4 +19,4 @@ alloc = [] default = [] [dependencies] -axerrno = "0.1" +axerrno = { version = "0.1" } diff --git a/README.md b/README.md index 0ee809b..642b090 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,52 @@ -# axio +

axio

+ +
+ +> [`std::io`][1]-like I/O traits for `no_std` environment. + +
+ + + +--- + +
[![Crates.io](https://img.shields.io/crates/v/axio)](https://crates.io/crates/axio) [![Docs.rs](https://docs.rs/axio/badge.svg)](https://docs.rs/axio) [![CI](https://github.com/arceos-org/axio/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/arceos-org/axio/actions/workflows/ci.yml) +[![DeepWiki](https://img.shields.io/badge/DeepWiki-docs-8A2BE2)](https://deepwiki.org/arceos-org/axio) +[![Dependencies](https://img.shields.io/librariesio/release/cargo/axio)](https://libraries.io/cargo/axio) +[![Downloads](https://img.shields.io/crates/d/axio)](https://crates.io/crates/axio) +[![Code Size](https://img.shields.io/github/languages/code-size/arceos-org/axio)](https://github.com/arceos-org/axio) -[`std::io`][1]-like I/O traits for `no_std` environment. +[![Activity](https://img.shields.io/github/commit-activity/m/arceos-org/axio)](https://github.com/arceos-org/axio/pulse) +[![Toolchain](https://img.shields.io/badge/toolchain-nightly--2025--06--18-orange)](https://rust-lang.github.io/rustup/concepts/channels.html) +[![License](https://img.shields.io/crates/l/axio)](https://github.com/arceos-org/axio/blob/main/LICENSE) + +
+ +--- [1]: https://doc.rust-lang.org/std/io/index.html + + + +## Example + +```rust +fn main() { + use axio::{Read, BufReader}; + + let data = b"hello world"; + let mut reader = BufReader::new(&data[..]); + let mut buf = [0u8; 5]; + reader.read_exact(&mut buf).unwrap(); +} +``` diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..517b722 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly-2025-05-12" diff --git a/src/buffered/bufreader.rs b/src/buffered/bufreader.rs index b466c1e..c37fb91 100644 --- a/src/buffered/bufreader.rs +++ b/src/buffered/bufreader.rs @@ -3,6 +3,7 @@ use crate::{BufRead, Read, Result}; #[cfg(feature = "alloc")] use alloc::{string::String, vec::Vec}; +/// Default buffer size used by `BufReader` (1 KB) const DEFAULT_BUF_SIZE: usize = 1024; /// The `BufReader` struct adds buffering to any reader. @@ -15,6 +16,19 @@ pub struct BufReader { impl BufReader { /// Creates a new `BufReader` with a default buffer capacity (1 KB). + /// + /// # Examples + /// ``` + /// use axio::BufReader; + /// use axio::Read; + /// + /// fn example() -> impl Read { + /// "test".as_bytes() + /// } + /// + /// let reader = example(); + /// let buf_reader = BufReader::new(reader); + /// ``` pub const fn new(inner: R) -> BufReader { Self { inner, @@ -66,6 +80,8 @@ impl BufReader { } impl Read for BufReader { + /// Reads data into the provided buffer, using the internal buffer to + /// minimize direct reads from the underlying reader fn read(&mut self, buf: &mut [u8]) -> Result { // If we don't have any buffered data and we're doing a massive read // (larger than our internal buffer), bypass our internal buffer @@ -82,10 +98,11 @@ impl Read for BufReader { Ok(nread) } - // Small read_exacts from a BufReader are extremely common when used with a deserializer. - // The default implementation calls read in a loop, which results in surprisingly poor code - // generation for the common path where the buffer has enough bytes to fill the passed-in - // buffer. + /// Reads exactly enough bytes to fill the buffer, using buffered data first + /// Small read_exacts from a BufReader are extremely common when used with a deserializer. + /// The default implementation calls read in a loop, which results in surprisingly poor code + /// generation for the common path where the buffer has enough bytes to fill the passed-in + /// buffer. fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { let amt = buf.len(); if let Some(claimed) = self.buffer().get(..amt) { @@ -96,8 +113,9 @@ impl Read for BufReader { self.inner.read_exact(buf) } - // The inner reader might have an optimized `read_to_end`. Drain our buffer and then - // delegate to the inner implementation. + /// Reads all bytes until EOF, appending them to the provided vector + /// The inner reader might have an optimized `read_to_end`. Drain our buffer and then + /// delegate to the inner implementation. #[cfg(feature = "alloc")] fn read_to_end(&mut self, buf: &mut Vec) -> Result { let inner_buf = self.buffer(); @@ -107,8 +125,9 @@ impl Read for BufReader { Ok(nread + self.inner.read_to_end(buf)?) } - // The inner reader might have an optimized `read_to_end`. Drain our buffer and then - // delegate to the inner implementation. + /// Reads all bytes until EOF as UTF-8, appending them to the string + /// The inner reader might have an optimized `read_to_end`. Drain our buffer and then + /// delegate to the inner implementation. #[cfg(feature = "alloc")] fn read_to_string(&mut self, buf: &mut String) -> Result { // In the general `else` case below we must read bytes into a side buffer, check @@ -143,6 +162,7 @@ impl Read for BufReader { } impl BufRead for BufReader { + /// Fills the internal buffer if empty and returns its contents fn fill_buf(&mut self) -> Result<&[u8]> { if self.is_empty() { let read_len = self.inner.read(&mut self.buf)?; @@ -152,7 +172,190 @@ impl BufRead for BufReader { Ok(self.buffer()) } + /// Consumes the specified number of bytes from the buffer fn consume(&mut self, amt: usize) { self.pos = core::cmp::min(self.pos + amt, self.filled); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::Result; + + struct TempReader { + data: &'static [u8], + pos: usize, + } + + impl TempReader { + fn new(data: &'static [u8]) -> Self { + Self { data, pos: 0 } + } + } + + impl Read for TempReader { + fn read(&mut self, buf: &mut [u8]) -> Result { + let remaining = self.data.len() - self.pos; + if remaining == 0 { + return Ok(0); + } + let to_copy = core::cmp::min(buf.len(), remaining); + buf[..to_copy].copy_from_slice(&self.data[self.pos..self.pos + to_copy]); + self.pos += to_copy; + Ok(to_copy) + } + } + + #[test] + fn test_get_ref() { + let reader = TempReader::new(b"test"); + let buf_reader = BufReader::new(reader); + assert_eq!(buf_reader.get_ref().data, b"test"); + } + + #[test] + fn test_get_mut() { + let reader = TempReader::new(b"test"); + let mut buf_reader = BufReader::new(reader); + assert_eq!(buf_reader.get_mut().data, b"test"); + } + + #[test] + fn test_buffer_empty() { + let reader = TempReader::new(b""); + let buf_reader = BufReader::new(reader); + assert!(buf_reader.buffer().is_empty()); + } + + #[test] + fn test_into_inner() { + let reader = TempReader::new(b"test"); + let buf_reader = BufReader::new(reader); + let inner = buf_reader.into_inner(); + assert_eq!(inner.data, b"test"); + } + + #[test] + fn test_read_small() { + let reader = TempReader::new(b"hello world"); + let mut buf_reader = BufReader::new(reader); + + let mut buf = [0; 5]; + assert_eq!(buf_reader.read(&mut buf).unwrap(), 5); + assert_eq!(&buf, b"hello"); + + assert_eq!(buf_reader.read(&mut buf).unwrap(), 5); + assert_eq!(&buf, b" worl"); + + assert_eq!(buf_reader.read(&mut buf).unwrap(), 1); + assert_eq!(&buf[..1], b"d"); + + assert_eq!(buf_reader.read(&mut buf).unwrap(), 0); + } + + #[test] + fn test_read_large() { + const DATA: &'static [u8] = &[1u8; DEFAULT_BUF_SIZE * 2]; + let reader = TempReader::new(DATA); + let mut buf_reader = BufReader::new(reader); + + let mut buf = [0u8; DEFAULT_BUF_SIZE * 2]; + assert_eq!(buf_reader.read(&mut buf).unwrap(), DEFAULT_BUF_SIZE * 2); + assert_eq!(&buf, DATA); + } + + #[test] + fn test_read_exact() { + let reader = TempReader::new(b"hello world"); + let mut buf_reader = BufReader::new(reader); + + let mut buf = [0; 5]; + buf_reader.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"hello"); + + buf_reader.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b" worl"); + + let mut buf2 = [0; 1]; + buf_reader.read_exact(&mut buf2).unwrap(); + assert_eq!(&buf2, b"d"); + + let mut buf3 = [0; 1]; + assert!(buf_reader.read_exact(&mut buf3).is_err()); + } + + #[test] + #[cfg(feature = "alloc")] + fn test_read_to_end() { + let reader = TempReader::new(b"hello world"); + let mut buf_reader = BufReader::new(reader); + + let mut buf = Vec::new(); + assert_eq!(buf_reader.read_to_end(&mut buf).unwrap(), 11); + assert_eq!(buf, b"hello world"); + } + + #[test] + #[cfg(feature = "alloc")] + fn test_read_to_string() { + let reader = TempReader::new(b"hello world"); + let mut buf_reader = BufReader::new(reader); + + let mut buf = String::new(); + assert_eq!(buf_reader.read_to_string(&mut buf).unwrap(), 11); + assert_eq!(buf, "hello world"); + } + + #[test] + fn test_fill_buf() { + let reader = TempReader::new(b"hello world"); + let mut buf_reader = BufReader::new(reader); + + let buf = buf_reader.fill_buf().unwrap(); + assert_eq!(buf, b"hello world"); + + buf_reader.consume(5); + let buf = buf_reader.fill_buf().unwrap(); + assert_eq!(buf, b" world"); + } + + #[test] + fn test_consume() { + let reader = TempReader::new(b"hello world"); + let mut buf_reader = BufReader::new(reader); + + buf_reader.fill_buf().unwrap(); + assert_eq!(buf_reader.buffer(), b"hello world"); + + buf_reader.consume(5); + assert_eq!(buf_reader.buffer(), b" world"); + + buf_reader.consume(6); + assert!(buf_reader.buffer().is_empty()); + } + + #[test] + fn test_edge_cases() { + // Empty reader + let reader = TempReader::new(b""); + let mut buf_reader = BufReader::new(reader); + let mut buf = [0; 1]; + assert_eq!(buf_reader.read(&mut buf).unwrap(), 0); + + // Single byte + let reader = TempReader::new(b"x"); + let mut buf_reader = BufReader::new(reader); + let mut buf = [0; 1]; + assert_eq!(buf_reader.read(&mut buf).unwrap(), 1); + assert_eq!(buf[0], b'x'); + + // Exact buffer size + const DATA: &'static [u8] = &[1u8; DEFAULT_BUF_SIZE]; + let reader = TempReader::new(DATA); + let mut buf_reader = BufReader::new(reader); + let mut buf = [0u8; DEFAULT_BUF_SIZE]; + assert_eq!(buf_reader.read(&mut buf).unwrap(), DEFAULT_BUF_SIZE); + assert_eq!(&buf, DATA); + } +} diff --git a/src/buffered/mod.rs b/src/buffered/mod.rs index c7dfe1e..3e94d86 100644 --- a/src/buffered/mod.rs +++ b/src/buffered/mod.rs @@ -1,3 +1,11 @@ +//! Buffered I/O module. +//! +//! ``` +//! # #![allow(unused_imports)] +//! use axio::BufReader; +//! ``` + mod bufreader; +/// Re-export the `BufReader` type from the bufreader module. pub use self::bufreader::BufReader; diff --git a/src/error.rs b/src/error.rs index ee2d716..972abeb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,2 +1,5 @@ +/// Re-export of `AxError` from axerrno crate as the standard Error type pub use axerrno::AxError as Error; + +/// Re-export of `AxResult` from axerrno crate as the standard Result type pub use axerrno::AxResult as Result; diff --git a/src/impls.rs b/src/impls.rs index 96f83db..98a3cb1 100644 --- a/src/impls.rs +++ b/src/impls.rs @@ -1,7 +1,15 @@ +//! Implementation of core I/O traits for basic types + use crate::{prelude::*, Result}; use core::cmp; +/// Implementation of Read trait for byte slices (&[u8]) +/// +/// Provides efficient reading operations directly from byte slices without additional buffering. impl Read for &[u8] { + /// Reads bytes from the slice into the provided buffer + /// + /// Returns the number of bytes read. #[inline] fn read(&mut self, buf: &mut [u8]) -> Result { let amt = cmp::min(buf.len(), self.len()); @@ -21,6 +29,21 @@ impl Read for &[u8] { Ok(amt) } + /// Reads all remaining bytes until end of slice + /// + /// Need enable `alloc` feature to use this function cause its needs heap allocation. + #[inline] + #[cfg(feature = "alloc")] + fn read_to_end(&mut self, buf: &mut alloc::vec::Vec) -> Result { + buf.extend_from_slice(self); + let len = self.len(); + *self = &self[len..]; + Ok(len) + } + + /// Reads exactly the requested number of bytes + /// + /// Returns an error if not enough bytes are available. #[inline] fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { if buf.len() > self.len() { @@ -42,13 +65,112 @@ impl Read for &[u8] { *self = b; Ok(()) } +} - #[inline] +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_read() { + let data = b"hello"; + let mut slice = &data[..]; + let mut buf = [0u8; 5]; + + // test full read + let res = slice.read(&mut buf).unwrap(); + assert_eq!(res, 5); + assert_eq!(buf, *b"hello"); + assert!(slice.is_empty()); + + // test partial read + let data = b"world"; + let mut slice = &data[..]; + let mut buf = [0u8; 3]; + let res = slice.read(&mut buf).unwrap(); + assert_eq!(res, 3); + assert_eq!(buf, *b"wor"); + assert_eq!(slice, b"ld"); + + // test single-byte read optimization + let data = b"x"; + let mut slice = &data[..]; + let mut buf = [0u8; 1]; + let res = slice.read(&mut buf).unwrap(); + assert_eq!(res, 1); + assert_eq!(buf[0], b'x'); + assert!(slice.is_empty()); + + // test empty read + let mut empty_reader = &b""[..]; + assert_eq!(empty_reader.read(&mut buf).unwrap(), 0); + } + + #[test] + fn test_read_partial() { + let data = b"hello world"; + let mut reader = &data[..]; + let mut buf = [0u8; 5]; + + // test full read + assert_eq!(reader.read(&mut buf).unwrap(), 5); + assert_eq!(&buf, b"hello"); + + // test partial read + assert_eq!(reader.read(&mut buf).unwrap(), 5); + assert_eq!(&buf, b" worl"); + + // test single-byte read + let mut small_buf = [0u8; 1]; + assert_eq!(reader.read(&mut small_buf).unwrap(), 1); + assert_eq!(&small_buf, b"d"); + } + + #[test] + fn test_read_exact() { + let data = b"heke"; + let mut slice = &data[..]; + let mut buf = [0u8; 4]; + + // test exact read + slice.read_exact(&mut buf).unwrap(); + assert_eq!(buf, *b"heke"); + assert!(slice.is_empty()); + + // test insufficient data + let mut slice = &data[..]; + let mut buf = [0u8; 5]; + let res = slice.read_exact(&mut buf); + assert!(res.is_err()); + assert_eq!(slice, b"heke"); + + // test single-byte exact read + let data = b"x"; + let mut slice = &data[..]; + let mut buf = [0u8; 1]; + assert!(slice.read_exact(&mut buf).is_ok()); + assert_eq!(buf[0], b'x'); + } + + #[test] #[cfg(feature = "alloc")] - fn read_to_end(&mut self, buf: &mut alloc::vec::Vec) -> Result { - buf.extend_from_slice(self); - let len = self.len(); - *self = &self[len..]; - Ok(len) + fn test_read_to_end() { + use alloc::vec::Vec; + + let data = b"test"; + let mut slice = &data[..]; + let mut buf = Vec::new(); + + // test read all data + let res = slice.read_to_end(&mut buf).unwrap(); + assert_eq!(res, 4); + assert_eq!(buf, b"test"); + assert!(slice.is_empty()); + + // test empty read + let mut empty_reader = &b""[..]; + let mut empty_buf = Vec::new(); + assert_eq!(empty_reader.read_to_end(&mut empty_buf).unwrap(), 0); + assert!(empty_buf.is_empty()); } } diff --git a/src/lib.rs b/src/lib.rs index 4a8a9a5..a819dfc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,16 @@ //! [`std::io`]-like I/O traits for `no_std` environment. +//! +//! # Examples +//! +//! Basic usage with byte slices: +//! ``` +//! use axio::{Read, BufReader}; +//! +//! let data = b"hello world"; +//! let mut reader = BufReader::new(&data[..]); +//! let mut buf = [0u8; 5]; +//! reader.read_exact(&mut buf).unwrap(); +//! ``` #![cfg_attr(not(doc), no_std)] #![feature(doc_auto_cfg)] @@ -13,9 +25,13 @@ mod buffered; mod error; mod impls; +/// Re-export of commonly used I/O traits and types pub mod prelude; +/// A buffered reader that reads from another reader pub use self::buffered::BufReader; + +/// Standard error and result types for I/O operations pub use self::error::{Error, Result}; #[cfg(feature = "alloc")] @@ -377,3 +393,89 @@ pub struct PollState { /// Object can be writen now. pub writable: bool, } + +#[cfg(test)] +mod tests { + use super::*; + use crate::Result; + + // test Read trait + #[test] + fn test_read_trait() { + let data = b"hello"; + let mut slice = &data[..]; + let mut buf = [0u8; 5]; + + // test full read + assert_eq!(slice.read(&mut buf).unwrap(), 5); + assert_eq!(buf, *b"hello"); + assert!(slice.is_empty()); + + // test partial read + let data = b"world"; + let mut slice = &data[..]; + let mut buf = [0u8; 3]; + assert_eq!(slice.read(&mut buf).unwrap(), 3); + assert_eq!(buf, *b"wor"); + assert_eq!(slice, b"ld"); + } + + #[test] + fn test_read_exact() { + let data = b"test"; + let mut slice = &data[..]; + let mut buf = [0u8; 4]; + + // test exact read + slice.read_exact(&mut buf).unwrap(); + assert_eq!(buf, *b"test"); + assert!(slice.is_empty()); + + // test insufficient data + let mut slice = &data[..]; + let mut buf = [0u8; 5]; + assert!(slice.read_exact(&mut buf).is_err()); + } + + #[test] + fn test_write_trait() { + struct TestWriter { + buf: [u8; 10], + pos: usize, + } + + impl Write for TestWriter { + fn write(&mut self, buf: &[u8]) -> Result { + let remaining = self.buf.len() - self.pos; + let to_write = buf.len().min(remaining); + self.buf[self.pos..self.pos + to_write].copy_from_slice(&buf[..to_write]); + self.pos += to_write; + Ok(to_write) + } + + fn flush(&mut self) -> Result { + Ok(()) + } + } + + let mut writer = TestWriter { + buf: [0; 10], + pos: 0, + }; + + // test single write + assert_eq!(writer.write(b"hello").unwrap(), 5); + assert_eq!(&writer.buf[..5], b"hello"); + } + + #[test] + fn test_poll_state() { + let state = PollState { + readable: true, + writable: false, + }; + + assert!(state.readable); + assert!(!state.writable); + } +} diff --git a/src/prelude.rs b/src/prelude.rs index 9f8020c..5ce5434 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -5,7 +5,8 @@ //! //! ``` //! # #![allow(unused_imports)] -//! use std::io::prelude::*; +//! use axio::prelude::*; //! ``` +/// Re-exports commonly used I/O traits. pub use super::{BufRead, Read, Seek, Write};