mod common;
use std::io;
use common::{golden_digits, one_million_path, two_million_path, TempYcd};
use ycd_reader::{compute_seek_params, YcdFileUtil};
fn make_corrupt_block_ycd(blocksize: usize) -> Vec<u8> {
let header = format!(
"FileVersion: 1.1.0\n\
Base: 10\n\
FirstDigits: 3.14159265358979323846\n\
TotalDigits: 0\n\
Blocksize: {blocksize}\n\
BlockID: 0\n\
EndHeader\n\n\0"
);
let mut bytes = header.into_bytes();
bytes.extend(u64::MAX.to_le_bytes());
bytes
}
fn make_truncated_payload_ycd(blocksize: usize) -> Vec<u8> {
let header = format!(
"FileVersion: 1.1.0\n\
Base: 10\n\
FirstDigits: 3.14159265358979323846\n\
TotalDigits: 0\n\
Blocksize: {blocksize}\n\
BlockID: 0\n\
EndHeader\n\n\0"
);
let mut bytes = header.into_bytes();
bytes.extend(123_u64.to_le_bytes());
bytes
}
#[test]
fn read_digits_from_position_one() {
let golden = golden_digits();
let files = [one_million_path(0)];
assert_eq!(YcdFileUtil::read_digits(&files, 1, 1).unwrap(), "1");
assert_eq!(
YcdFileUtil::read_digits(&files, 1, 19).unwrap(),
"1415926535897932384"
);
assert_eq!(
YcdFileUtil::read_digits(&files, 1, 20).unwrap(),
"14159265358979323846"
);
assert_eq!(
YcdFileUtil::read_digits(&files, 1, 1).unwrap(),
&golden[..1]
);
assert_eq!(
YcdFileUtil::read_digits(&files, 1, 100).unwrap(),
&golden[..100]
);
}
#[test]
fn read_digits_arbitrary_positions() {
let golden = golden_digits();
let files = [one_million_path(0)];
assert_eq!(
YcdFileUtil::read_digits(&files, 500, 1).unwrap(),
&golden[499..500]
);
assert_eq!(
YcdFileUtil::read_digits(&files, 12345, 50).unwrap(),
&golden[12344..12394]
);
assert_eq!(
YcdFileUtil::read_digits(&files, 500_000, 100).unwrap(),
&golden[499_999..500_099]
);
}
#[test]
fn read_digits_nineteen_block_boundaries() {
let golden = golden_digits();
let files = [one_million_path(0)];
assert_eq!(YcdFileUtil::read_digits(&files, 19, 1).unwrap(), "4");
assert_eq!(
YcdFileUtil::read_digits(&files, 19, 1).unwrap(),
&golden[18..19]
);
assert_eq!(YcdFileUtil::read_digits(&files, 20, 1).unwrap(), "6");
assert_eq!(
YcdFileUtil::read_digits(&files, 20, 1).unwrap(),
&golden[19..20]
);
assert_eq!(
YcdFileUtil::read_digits(&files, 15, 10).unwrap(),
"3238462643"
);
assert_eq!(
YcdFileUtil::read_digits(&files, 15, 10).unwrap(),
&golden[14..24]
);
assert_eq!(YcdFileUtil::read_digits(&files, 19, 2).unwrap(), "46");
assert_eq!(
YcdFileUtil::read_digits(&files, 19, 2).unwrap(),
&golden[18..20]
);
assert_eq!(
YcdFileUtil::read_digits(&files, 1, 19).unwrap(),
&golden[..19]
);
assert_eq!(
YcdFileUtil::read_digits(&files, 20, 19).unwrap(),
&golden[19..38]
);
assert_eq!(
YcdFileUtil::read_digits(&files, 10, 57).unwrap(),
&golden[9..66]
);
}
#[test]
fn read_digits_one_million_file_boundaries() {
let golden = golden_digits();
let files_0_1 = [one_million_path(0), one_million_path(1)];
let files_0_1_2 = [
one_million_path(0),
one_million_path(1),
one_million_path(2),
];
assert_eq!(
YcdFileUtil::read_digits(&files_0_1, 1_000_000, 1).unwrap(),
"1"
);
assert_eq!(
YcdFileUtil::read_digits(&files_0_1, 1_000_000, 1).unwrap(),
&golden[999_999..1_000_000]
);
assert_eq!(
YcdFileUtil::read_digits(&files_0_1, 1_000_001, 1).unwrap(),
"3"
);
assert_eq!(
YcdFileUtil::read_digits(&files_0_1, 1_000_001, 1).unwrap(),
&golden[1_000_000..1_000_001]
);
assert_eq!(
YcdFileUtil::read_digits(&files_0_1, 999_996, 10).unwrap(),
"5815130927"
);
assert_eq!(
YcdFileUtil::read_digits(&files_0_1, 999_996, 10).unwrap(),
&golden[999_995..1_000_005]
);
assert_eq!(
YcdFileUtil::read_digits(&files_0_1, 1_000_000, 2).unwrap(),
"13"
);
assert_eq!(
YcdFileUtil::read_digits(&files_0_1, 1_000_000, 2).unwrap(),
&golden[999_999..1_000_001]
);
assert_eq!(
YcdFileUtil::read_digits(&files_0_1, 999_995, 20).unwrap(),
"45815130927562832084"
);
assert_eq!(
YcdFileUtil::read_digits(&files_0_1_2, 2_000_000, 1).unwrap(),
&golden[1_999_999..2_000_000]
);
assert_eq!(
YcdFileUtil::read_digits(&files_0_1_2, 2_000_001, 1).unwrap(),
&golden[2_000_000..2_000_001]
);
assert_eq!(
YcdFileUtil::read_digits(&files_0_1_2, 1_999_996, 10).unwrap(),
&golden[1_999_995..2_000_005]
);
assert_eq!(
YcdFileUtil::read_digits(&files_0_1_2, 2_000_000, 2).unwrap(),
&golden[1_999_999..2_000_001]
);
}
#[test]
fn read_digits_two_million_file_boundary() {
let golden = golden_digits();
let files = [two_million_path(0), one_million_path(2)];
assert_eq!(YcdFileUtil::read_digits(&files, 2_000_000, 1).unwrap(), "9");
assert_eq!(
YcdFileUtil::read_digits(&files, 2_000_000, 1).unwrap(),
&golden[1_999_999..2_000_000]
);
assert_eq!(YcdFileUtil::read_digits(&files, 2_000_001, 1).unwrap(), "6");
assert_eq!(
YcdFileUtil::read_digits(&files, 2_000_001, 1).unwrap(),
&golden[2_000_000..2_000_001]
);
assert_eq!(
YcdFileUtil::read_digits(&files, 1_999_996, 10).unwrap(),
"9790961217"
);
assert_eq!(
YcdFileUtil::read_digits(&files, 1_999_996, 10).unwrap(),
&golden[1_999_995..2_000_005]
);
assert_eq!(
YcdFileUtil::read_digits(&files, 2_000_000, 2).unwrap(),
"96"
);
assert_eq!(
YcdFileUtil::read_digits(&files, 2_000_000, 2).unwrap(),
&golden[1_999_999..2_000_001]
);
}
#[test]
fn read_digits_same_range_both_layouts() {
let golden = golden_digits();
let layout_1m = [
one_million_path(0),
one_million_path(1),
one_million_path(2),
];
let layout_2m_1m = [two_million_path(0), one_million_path(2)];
let test_ranges = [
(1_usize, 100_usize),
(999_990, 20),
(1_000_000, 5),
(1_000_001, 5),
(1_999_990, 20),
(2_000_000, 5),
(2_000_001, 5),
(2_999_990, 10),
(3_000_000, 1),
];
for (start, len) in test_ranges {
let from_1m = YcdFileUtil::read_digits(&layout_1m, start, len).unwrap();
let from_2m_1m = YcdFileUtil::read_digits(&layout_2m_1m, start, len).unwrap();
assert_eq!(
from_1m, from_2m_1m,
"layouts differ at start={start} len={len}"
);
assert_eq!(
from_1m,
&golden[start - 1..start - 1 + len],
"mismatch with golden at start={start} len={len}"
);
}
}
#[test]
fn read_digits_non_zero_blockid_first_file() {
let golden = golden_digits();
let files = [one_million_path(1), one_million_path(2)];
assert_eq!(
YcdFileUtil::read_digits(&files, 1_000_001, 10).unwrap(),
"3092756283"
);
assert_eq!(
YcdFileUtil::read_digits(&files, 1_000_001, 10).unwrap(),
&golden[1_000_000..1_000_010]
);
assert_eq!(
YcdFileUtil::read_digits(&files, 1_999_996, 10).unwrap(),
&golden[1_999_995..2_000_005]
);
}
#[test]
fn read_digits_last_digit_of_list() {
let golden = golden_digits();
let files = [
one_million_path(0),
one_million_path(1),
one_million_path(2),
];
assert_eq!(YcdFileUtil::read_digits(&files, 3_000_000, 1).unwrap(), "3");
assert_eq!(
YcdFileUtil::read_digits(&files, 3_000_000, 1).unwrap(),
&golden[2_999_999..3_000_000]
);
assert_eq!(
YcdFileUtil::read_digits(&files, 2_999_996, 5).unwrap(),
"43943"
);
assert_eq!(
YcdFileUtil::read_digits(&files, 2_999_996, 5).unwrap(),
&golden[2_999_995..3_000_000]
);
}
#[test]
fn read_digits_leading_zeros_in_result() {
let golden = golden_digits();
let files = [one_million_path(0)];
assert_eq!(&golden[31..32], "0"); assert_eq!(YcdFileUtil::read_digits(&files, 32, 1).unwrap(), "0");
assert_eq!(
YcdFileUtil::read_digits(&files, 32, 4).unwrap(),
&golden[31..35]
);
assert_eq!(&golden[76..77], "0");
assert_eq!(
YcdFileUtil::read_digits(&files, 77, 19).unwrap(),
&golden[76..95]
);
assert_eq!(&golden[306..308], "00");
assert_eq!(YcdFileUtil::read_digits(&files, 307, 5).unwrap(), "00660");
assert_eq!(
YcdFileUtil::read_digits(&files, 307, 5).unwrap(),
&golden[306..311]
);
}
#[test]
fn read_digits_spanning_multiple_files() {
let golden = golden_digits();
let files = [
one_million_path(0),
one_million_path(1),
one_million_path(2),
];
assert_eq!(
YcdFileUtil::read_digits(&files, 999_990, 20).unwrap(),
&golden[999_989..1_000_009]
);
assert_eq!(
YcdFileUtil::read_digits(&files, 1_999_990, 20).unwrap(),
&golden[1_999_989..2_000_009]
);
let result = YcdFileUtil::read_digits(&files, 999_990, 1_000_020).unwrap();
assert_eq!(result, &golden[999_989..2_000_009]);
}
#[test]
fn read_digits_error_invalid_basic_params() {
let files = [one_million_path(0)];
let empty: [&str; 0] = [];
assert_eq!(
YcdFileUtil::read_digits(&empty, 1, 1).unwrap_err().kind(),
io::ErrorKind::InvalidInput
);
assert_eq!(
YcdFileUtil::read_digits(&files, 0, 1).unwrap_err().kind(),
io::ErrorKind::InvalidInput
);
assert_eq!(
YcdFileUtil::read_digits(&files, 1, 0).unwrap_err().kind(),
io::ErrorKind::InvalidInput
);
}
#[test]
fn read_digits_error_out_of_range() {
let single_file = [one_million_path(1)];
assert_eq!(
YcdFileUtil::read_digits(&single_file, 1_000_000, 1)
.unwrap_err()
.kind(),
io::ErrorKind::InvalidInput
);
assert_eq!(
YcdFileUtil::read_digits(&single_file, 2_000_001, 1)
.unwrap_err()
.kind(),
io::ErrorKind::InvalidInput
);
assert_eq!(
YcdFileUtil::read_digits(&single_file, 2_000_000, 2)
.unwrap_err()
.kind(),
io::ErrorKind::InvalidInput
);
let first_file = [one_million_path(0)];
assert_eq!(
YcdFileUtil::read_digits(&first_file, 1_000_001, 1)
.unwrap_err()
.kind(),
io::ErrorKind::InvalidInput
);
}
#[test]
fn read_digits_error_overflow() {
let files = [one_million_path(0)];
assert_eq!(
YcdFileUtil::read_digits(&files, 1, usize::MAX)
.unwrap_err()
.kind(),
io::ErrorKind::InvalidData
);
}
#[test]
fn read_digits_error_list_continuity() {
let files_0 = [one_million_path(0)];
let files_1 = [one_million_path(1)];
assert_eq!(
YcdFileUtil::read_digits(&[one_million_path(0), one_million_path(2)], 1, 10)
.unwrap_err()
.kind(),
io::ErrorKind::InvalidInput
);
assert_eq!(
YcdFileUtil::read_digits(&[one_million_path(0), one_million_path(0)], 1, 10)
.unwrap_err()
.kind(),
io::ErrorKind::InvalidInput
);
assert_eq!(
YcdFileUtil::read_digits(&[one_million_path(1), one_million_path(0)], 1_000_001, 10)
.unwrap_err()
.kind(),
io::ErrorKind::InvalidInput
);
assert_eq!(
YcdFileUtil::read_digits(
&[
one_million_path(0),
one_million_path(1),
one_million_path(3)
],
1,
100
)
.unwrap_err()
.kind(),
io::ErrorKind::InvalidInput
);
let _ = (files_0, files_1);
}
#[test]
fn read_digits_error_bad_file_content() {
let no_blocksize = b"FileVersion: 1.1.0\n\
Base: 10\n\
FirstDigits: 3.14\n\
TotalDigits: 0\n\
BlockID: 0\n\
EndHeader\n\n\0";
let f = TempYcd::from_bytes(no_blocksize).unwrap();
assert_eq!(
YcdFileUtil::read_digits(&[&f.path], 1, 1)
.unwrap_err()
.kind(),
io::ErrorKind::InvalidData
);
let base16 = b"FileVersion: 1.1.0\n\
Base: 16\n\
FirstDigits: 3.14\n\
TotalDigits: 0\n\
Blocksize: 19\n\
BlockID: 0\n\
EndHeader\n\n\0";
let f = TempYcd::from_bytes(base16).unwrap();
assert_eq!(
YcdFileUtil::read_digits(&[&f.path], 1, 1)
.unwrap_err()
.kind(),
io::ErrorKind::InvalidData
);
let f = TempYcd::from_bytes(&make_corrupt_block_ycd(19)).unwrap();
assert_eq!(
YcdFileUtil::read_digits(&[&f.path], 1, 19)
.unwrap_err()
.kind(),
io::ErrorKind::InvalidData
);
let f = TempYcd::from_bytes(&make_truncated_payload_ycd(38)).unwrap();
assert_eq!(
YcdFileUtil::read_digits(&[&f.path], 1, 38)
.unwrap_err()
.kind(),
io::ErrorKind::UnexpectedEof
);
assert_eq!(
YcdFileUtil::read_digits(&["does-not-exist.ycd"], 1, 1)
.unwrap_err()
.kind(),
io::ErrorKind::NotFound
);
}
#[test]
fn seek_params_unit_tests() {
let (bi, oib, btr) = compute_seek_params(0, 1);
assert_eq!(bi, 0);
assert_eq!(oib, 0);
assert_eq!(btr, 1);
let (bi, oib, btr) = compute_seek_params(0, 19);
assert_eq!(bi, 0);
assert_eq!(oib, 0);
assert_eq!(btr, 1);
let (bi, oib, btr) = compute_seek_params(0, 20);
assert_eq!(bi, 0);
assert_eq!(oib, 0);
assert_eq!(btr, 2);
for local_start in [0_usize, 1, 18, 19, 37, 38, 100, 1_000_000] {
for length in [1_usize, 2, 18, 19, 20, 38, 39, 100] {
let (_, offset_in_block, btr) = compute_seek_params(local_start, length);
let expected_btr = (offset_in_block + length).div_ceil(19);
assert_eq!(
btr, expected_btr,
"local_start={local_start} length={length}"
);
}
}
let local_start_1m = 999_994_usize;
let (bi, oib, btr) = compute_seek_params(local_start_1m, 6);
assert!(bi > 0, "block_index should be non-zero near 1M: got {bi}");
assert_eq!(bi, local_start_1m / 19); assert_eq!(oib, local_start_1m % 19); assert_eq!(btr, 1);
let (bi0, oib0, btr0) = compute_seek_params(999_994, 6);
assert_eq!((bi0, oib0, btr0), (52631, 5, 1));
let (bi1, oib1, btr1) = compute_seek_params(0, 14);
assert_eq!((bi1, oib1, btr1), (0, 0, 1));
}