use super::*;
use std::io::Cursor;
use tempfile::NamedTempFile;
fn six_lines() -> NamedTempFile {
let f = NamedTempFile::new().unwrap();
std::fs::write(f.path(), b"l1\nl2\nl3\nl4\nl5\nl6\n").unwrap();
f
}
fn read(path: &std::path::Path, input: serde_json::Value) -> (String, String) {
let mut object = input;
object["path"] = serde_json::json!(path);
let mut stdin = Cursor::new(object.to_string().into_bytes());
let mut stdout = Vec::new();
let mut stderr = Vec::new();
run(&mut stdin, &mut stdout, &mut stderr).unwrap();
(
String::from_utf8(stdout).unwrap(),
String::from_utf8(stderr).unwrap(),
)
}
#[test]
fn a_window_returns_its_lines_and_says_where_to_continue() {
let f = six_lines();
let (out, note) = read(f.path(), serde_json::json!({ "offset": 2, "limit": 3 }));
assert_eq!(out, "l2\nl3\nl4\n");
assert_eq!(
note,
"read_file: 3 of 6 lines from offset 2; continue with offset 5\n"
);
}
#[test]
fn the_last_window_names_no_continuation() {
let f = six_lines();
let (out, note) = read(f.path(), serde_json::json!({ "offset": 5 }));
assert_eq!(out, "l5\nl6\n");
assert_eq!(note, "read_file: 2 of 6 lines from offset 5\n");
}
#[test]
fn a_limit_that_overruns_the_file_stops_at_the_end() {
let f = six_lines();
let (out, note) = read(f.path(), serde_json::json!({ "offset": 5, "limit": 999 }));
assert_eq!(out, "l5\nl6\n");
assert_eq!(note, "read_file: 2 of 6 lines from offset 5\n");
}
#[test]
fn a_whole_file_read_is_the_window_with_empty_inputs() {
let f = six_lines();
let (out, note) = read(f.path(), serde_json::json!({}));
assert_eq!(out, "l1\nl2\nl3\nl4\nl5\nl6\n");
assert_eq!(note, "read_file: 6 of 6 lines from offset 1\n");
}
#[test]
fn a_limit_alone_reads_from_the_first_line() {
let f = six_lines();
let (out, note) = read(f.path(), serde_json::json!({ "limit": 2 }));
assert_eq!(out, "l1\nl2\n");
assert_eq!(
note,
"read_file: 2 of 6 lines from offset 1; continue with offset 3\n"
);
}
#[test]
fn an_offset_past_the_end_is_an_answer_not_a_failure() {
let f = six_lines();
let (out, note) = read(f.path(), serde_json::json!({ "offset": 40 }));
assert!(out.is_empty(), "{out:?}");
assert_eq!(note, "read_file: 0 of 6 lines from offset 40\n");
}
#[test]
fn a_final_line_without_a_newline_is_still_a_line() {
let f = NamedTempFile::new().unwrap();
std::fs::write(f.path(), b"a\nb").unwrap();
let (out, note) = read(f.path(), serde_json::json!({ "offset": 2 }));
assert_eq!(out, "b");
assert_eq!(note, "read_file: 1 of 2 lines from offset 2\n");
}
#[test]
fn a_trailing_newline_does_not_add_an_empty_line() {
let f = NamedTempFile::new().unwrap();
std::fs::write(f.path(), b"a\nb\nc\n").unwrap();
let (_, note) = read(f.path(), serde_json::json!({}));
assert_eq!(note, "read_file: 3 of 3 lines from offset 1\n");
}
#[test]
fn a_zero_offset_is_declined_by_name() {
let f = six_lines();
let mut stdin = Cursor::new(
serde_json::json!({ "path": f.path(), "offset": 0 })
.to_string()
.into_bytes(),
);
let err = run(&mut stdin, &mut Vec::new(), &mut Vec::new()).unwrap_err();
let msg = err.to_string();
assert!(matches!(err, Error::Range { field: "offset" }), "{msg}");
assert!(msg.contains("lines count from 1"), "{msg}");
}
#[test]
fn a_zero_limit_is_declined_by_name() {
let f = six_lines();
let mut stdin = Cursor::new(
serde_json::json!({ "path": f.path(), "limit": 0 })
.to_string()
.into_bytes(),
);
let err = run(&mut stdin, &mut Vec::new(), &mut Vec::new()).unwrap_err();
let msg = err.to_string();
assert!(matches!(err, Error::Range { field: "limit" }), "{msg}");
assert!(msg.contains("limit"), "{msg}");
}
#[test]
fn a_broken_stderr_surfaces_write() {
struct BrokenWriter;
impl Write for BrokenWriter {
fn write(&mut self, _: &[u8]) -> io::Result<usize> {
Err(io::Error::other("stderr pipe closed"))
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
let f = six_lines();
let mut stdin = Cursor::new(
serde_json::json!({ "path": f.path() })
.to_string()
.into_bytes(),
);
let err = run(&mut stdin, &mut Vec::new(), &mut BrokenWriter).unwrap_err();
assert!(matches!(err, Error::Write(_)), "{err}");
}
#[test]
fn the_saturating_limit_cannot_overflow_the_last_line() {
let f = six_lines();
let (out, note) = read(
f.path(),
serde_json::json!({ "offset": 1, "limit": u64::MAX }),
);
assert_eq!(out, "l1\nl2\nl3\nl4\nl5\nl6\n");
assert_eq!(note, "read_file: 6 of 6 lines from offset 1\n");
}