Skip to content

Commit

Permalink
Add more tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
TheVeryDarkness committed Aug 11, 2024
1 parent 3a109f9 commit 45fc5ec
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 64 deletions.
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! A utility library for reading integers, floating numbers and strings from input/output.
pub use {
formatted::*,
formatted::SepBy,
mat::Mat,
read_into::{
read, read_array, read_m_n, read_n, read_tuple, try_read, try_read_array, try_read_m_n,
Expand Down
98 changes: 74 additions & 24 deletions src/mat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,34 +173,84 @@ impl<T, const M: usize, const N: usize> From<[[T; N]; M]> for Mat<T> {
Self { inner, n, m }
}
}
impl<T: PartialEq> PartialEq<Vec<Vec<T>>> for Mat<T> {
fn eq(&self, other: &Vec<Vec<T>>) -> bool {
if self.len_rows() != other.len() {
return false;
}
for (row, other_row) in self.iter().zip(other.iter()) {
if row != other_row.as_slice() {
return false;
}
}
true

trait AsSlice {
type Item;
fn as_slice(&self) -> &[Self::Item];
fn len(&self) -> usize {
self.as_slice().len()
}
}
impl<T: PartialEq, const M: usize, const N: usize> PartialEq<[[T; N]; M]> for Mat<T> {
fn eq(&self, other: &[[T; N]; M]) -> bool {
if self.len_rows() != M || self.len_columns() != N {
return false;
}
for i in 0..M {
for j in 0..N {
if self[i][j] != other[i][j] {
return false;
}
}
}
true
impl<T> AsSlice for [T] {
type Item = T;
fn as_slice(&self) -> &[T] {
self
}
}
impl<T> AsSlice for Vec<T> {
type Item = T;
fn as_slice(&self) -> &[T] {
self
}
}
impl<T, const N: usize> AsSlice for [T; N] {
type Item = T;
fn as_slice(&self) -> &[T] {
self
}
#[inline]
fn len(&self) -> usize {
N
}
}

impl<T: PartialEq, U2: AsSlice<Item = U1>, U1: AsSlice<Item = T>> PartialEq<U2> for Mat<T> {
fn eq(&self, other: &U2) -> bool {
self.iter()
.eq(other.as_slice().iter().map(|row| row.as_slice()))
}
}

// impl<T: PartialEq> PartialEq<Vec<Vec<T>>> for Mat<T> {
// fn eq(&self, other: &Vec<Vec<T>>) -> bool {
// self.iter().eq(other.iter().map(|row| row.as_slice()))
// }
// }
// impl<T: PartialEq> PartialEq<Mat<T>> for Vec<Vec<T>> {
// fn eq(&self, other: &Mat<T>) -> bool {
// other.eq(self)
// }
// }
// impl<T: PartialEq, const N: usize> PartialEq<Vec<[T; N]>> for Mat<T> {
// fn eq(&self, other: &Vec<[T; N]>) -> bool {
// self.iter().eq(other.iter().map(|row| row.as_ref()))
// }
// }
// impl<T: PartialEq, const N: usize> PartialEq<Mat<T>> for Vec<[T; N]> {
// fn eq(&self, other: &Mat<T>) -> bool {
// other.eq(self)
// }
// }
// impl<T: PartialEq, const N: usize> PartialEq<[[T; N]]> for Mat<T> {
// fn eq(&self, other: &[[T; N]]) -> bool {
// self.iter().eq(other.iter().map(|row| row.as_ref()))
// }
// }
// impl<T: PartialEq, const N: usize> PartialEq<Mat<T>> for [[T; N]] {
// fn eq(&self, other: &Mat<T>) -> bool {
// other.eq(self)
// }
// }
// impl<T: PartialEq, const M: usize, const N: usize> PartialEq<[[T; N]; M]> for Mat<T> {
// fn eq(&self, other: &[[T; N]; M]) -> bool {
// self.iter().eq(other.iter().map(|row| row.as_ref()))
// }
// }
// impl<T: PartialEq, const M: usize, const N: usize> PartialEq<Mat<T>> for [[T; N]; M] {
// fn eq(&self, other: &Mat<T>) -> bool {
// other.eq(self)
// }
// }

impl<T, const N: usize> FromIterator<[T; N]> for Mat<T> {
fn from_iter<I: IntoIterator<Item = [T; N]>>(iter: I) -> Self {
Expand Down
48 changes: 48 additions & 0 deletions src/mat/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,52 @@ fn repeating_rows() {
for row in &mat {
assert_eq!(row, [1, 2].as_slice());
}
assert_eq!(mat, Mat::from_iter([[1, 2], [1, 2], [1, 2]]));
assert_eq!(mat, Mat::from_iter([&[1, 2], &[1, 2], &[1, 2]]));
assert_eq!(mat, Mat::from_iter(&[[1, 2], [1, 2], [1, 2]]));
}

#[test]
fn unit() {
let a = Mat::diagonal_from_fn(5, |_| 1);
let b = Mat::scalar(5, 1);
assert_eq!(a, b);
let arr_arr = [
[1, 0, 0, 0, 0],
[0, 1, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 1, 0],
[0, 0, 0, 0, 1],
];
let arr_vec = [
vec![1, 0, 0, 0, 0],
vec![0, 1, 0, 0, 0],
vec![0, 0, 1, 0, 0],
vec![0, 0, 0, 1, 0],
vec![0, 0, 0, 0, 1],
];
let vec_arr = vec![
[1, 0, 0, 0, 0],
[0, 1, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 1, 0],
[0, 0, 0, 0, 1],
];
let vec_vec = vec![
vec![1, 0, 0, 0, 0],
vec![0, 1, 0, 0, 0],
vec![0, 0, 1, 0, 0],
vec![0, 0, 0, 1, 0],
vec![0, 0, 0, 0, 1],
];
assert_eq!(a, arr_arr);
assert_eq!(b, arr_arr);
assert_eq!(a, arr_vec);
assert_eq!(b, arr_vec);
assert_eq!(a, vec_arr);
assert_eq!(b, vec_arr);
assert_eq!(a, vec_vec);
assert_eq!(b, vec_vec);
assert_eq!(Mat::from(arr_arr), a);
assert_eq!(Mat::from(arr_arr), arr_arr);
}
2 changes: 1 addition & 1 deletion src/read_into.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ pub trait ReadInto<T> {
fn read_remained_line(&mut self) -> T {
unwrap!(self.try_read_remained_line())
}
/// Read an element in a single trimmed line from `self`, parse into `T`.
/// Read an element in a single trimmed line that is not empty from `self`, parse into `T`.
fn try_read_line(&mut self) -> Result<T, Self::Error>;
/// Unwrapping version of [ReadInto::try_read_line].
fn read_line(&mut self) -> T {
Expand Down
12 changes: 0 additions & 12 deletions src/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,3 @@ use std::{cell::RefCell, io::StdinLock};
thread_local! {
pub static STDIN: RefCell<InputStream<StdinLock<'static>>> = RefCell::new(InputStream::new(std::io::stdin().lock()));
}

#[cfg(test)]
mod tests {
use super::*;
use crate::read_into::ReadInto;

#[test]
fn read_remained() {
let s: String = STDIN.with(|lock| lock.borrow_mut().read_remained_line());
assert_eq!(s, "");
}
}
72 changes: 49 additions & 23 deletions src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ pub struct InputStream<B> {
}

const WHITE: [char; 4] = [' ', '\t', '\n', '\r'];
const EOL: [char; 2] = ['\n', '\r'];

impl<B> InputStream<B> {
fn err_eof(msg: &'static str) -> Error {
Error::new(ErrorKind::UnexpectedEof, msg)
}

impl<B: BufRead> InputStream<B> {
/// Create an input stream from a buffer that implements [BufRead].
pub fn new(buffer: B) -> Self {
let line_buf = String::new();
Expand All @@ -26,25 +31,36 @@ impl<B> InputStream<B> {
}
}
}

#[inline]
fn as_slice_from(s: &str, i: usize) -> &str {
// Assume we get correct encoding.
debug_assert!(s.is_char_boundary(i));
unsafe { s.get_unchecked(i..) }
}
#[inline]
fn as_slice_to(s: &str, i: usize) -> &str {
// Assume we get correct encoding.
debug_assert!(s.is_char_boundary(i));
unsafe { s.get_unchecked(..i) }
}

impl<B: BufRead> InputStream<B> {
#[inline]
fn fill_buf(&mut self, msg: &'static str) -> Result<(), Error> {
self.line_buf.clear();
self.cursor = 0;
let i = self.buffer.read_line(&mut self.line_buf)?;
if i == 0 {
return Err(err_eof(msg));
}
Ok(())
}
fn remove_white(&mut self) -> Result<&str, Error> {
while self.cursor == self.line_buf.len() {
self.line_buf.clear();
self.cursor = 0;
let i = self.buffer.read_line(&mut self.line_buf)?;
if i == 0 {
return Err(Error::new(
ErrorKind::UnexpectedEof,
"failed to read a non-whitespace character before EOF",
));
}
self.fill_buf("failed to read a non-whitespace character before EOF")?;
}
// Assume we get correct encoding.
let remaining = if cfg!(debug_assertions) {
&self.line_buf[self.cursor..]
} else {
unsafe { self.line_buf.get_unchecked(self.cursor..) }
};
let remaining = as_slice_from(&self.line_buf, self.cursor);
let remaining = remaining.trim_start_matches(WHITE);
self.cursor = self.line_buf.len() - remaining.len();
debug_assert!(self.line_buf.is_char_boundary(self.cursor));
Expand All @@ -68,11 +84,7 @@ impl<B: BufRead> InputStream<B> {
continue;
} else {
let i = remaining.find(WHITE).unwrap_or(remaining.len());
let frag = if cfg!(debug_assertions) {
&remaining[..i]
} else {
unsafe { remaining.get_unchecked(..i) }
};
let frag = as_slice_to(remaining, i);
let res = f(frag);
self.cursor += i;
return Ok(res);
Expand All @@ -92,11 +104,25 @@ impl<B: BufRead> InputStream<B> {
}
}
}
/// Consume the remained line.
#[inline]
fn read_buf(&mut self) -> Result<(), Error> {
self.line_buf.clear();
self.cursor = 0;
self.buffer.read_line(&mut self.line_buf)?;
Ok(())
}
/// Consume the remained line without trailing CR or LF.
///
/// Similar to `std::getline` in C++.
pub fn consume_remained_line<T>(&mut self, f: impl FnOnce(&str) -> T) -> Result<T, Error> {
if self.cursor == self.line_buf.len() {
self.read_buf()?;
}
let line = &self.line_buf[self.cursor..];
let result = f(line.trim_end_matches(EOL));
self.cursor = self.line_buf.len();
Ok(f(line))
self.read_buf()?;
Ok(result)
}
}

Expand Down
9 changes: 9 additions & 0 deletions tests/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,12 @@ fn read_array_4() {

assert!(iof::ReadInto::<u32>::try_read_n(&mut reader, 1).is_err());
}

#[test]
#[should_panic = "failed to read a non-whitespace character before EOF"]
fn read_array_insuffcient() {
let reader = Cursor::new("-1 -2".as_bytes());
let mut reader = InputStream::new(reader);

let _: [i32; 4] = *reader.read_array();
}
29 changes: 28 additions & 1 deletion tests/integers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,38 @@ fn read_3() {
assert!(iof::ReadInto::<u32>::try_read(&mut reader).is_err());
}

#[test]
#[should_panic = "invalid digit found in string"]
fn read_sign_error() {
let reader = Cursor::new("-1".as_bytes());
let mut reader = InputStream::new(reader);

let _: u32 = reader.read();
}

#[test]
#[should_panic = "called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }"]
fn try_read_sign_error() {
let reader = Cursor::new("-1".as_bytes());
let mut reader = InputStream::new(reader);

let _: u32 = reader.try_read().unwrap();
}

#[test]
#[should_panic = "failed to read a non-whitespace character before EOF"]
fn read_panic() {
fn read_empty() {
let reader = Cursor::new("".as_bytes());
let mut reader = InputStream::new(reader);

let _: u32 = reader.read();
}

#[test]
#[should_panic = "failed to read a non-whitespace character before EOF"]
fn try_read_empty() {
let reader = Cursor::new("".as_bytes());
let mut reader = InputStream::new(reader);

let _: u32 = reader.try_read().unwrap();
}
11 changes: 10 additions & 1 deletion tests/mat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@ use iof::*;
use std::{io::Cursor, vec};

#[test]
fn read_m_n_1() {
#[should_panic = "failed to read a non-whitespace character before EOF"]
fn read_m_n_insufficient() {
let reader = Cursor::new("1 2\n3".as_bytes());
let mut reader = InputStream::new(reader);

let _: Mat<u32> = reader.read_m_n(2, 2);
}

#[test]
fn read_m_n() {
let reader = Cursor::new("1 2 3\n4 5 6".as_bytes());
let mut reader = InputStream::new(reader);

Expand Down
11 changes: 11 additions & 0 deletions tests/stream.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include <iostream>
#include <string>

int main() {
std::string s;
std::getline(std::cin, s);
std::cout << '{' << s << '}';
std::getline(std::cin, s);
std::cout << '{' << s << '}';
return 0;
}
Loading

0 comments on commit 45fc5ec

Please sign in to comment.