use super::{LIMIT, Symbol, TooLong};
mod oracle;
mod tables;
fn payload(version: usize, len: usize) -> Vec<u8> {
(0..len)
.map(|at| u8::try_from((at * 31 + version) % 256).unwrap_or(0))
.collect()
}
fn as_oracle(symbol: &Symbol) -> String {
(0..symbol.side())
.map(|y| {
(0..symbol.side())
.map(|x| if symbol.dark(x, y) { '#' } else { '.' })
.collect::<String>()
})
.collect::<Vec<_>>()
.join("\n")
}
fn differ(got: &str, want: &str) -> Option<String> {
got.lines()
.zip(want.lines())
.enumerate()
.find(|(_, (a, b))| a != b)
.map(|(row, (a, b))| {
let column = a.chars().zip(b.chars()).position(|(x, y)| x != y);
format!("row {row}, first differing column {column:?}\n got {a}\n want {b}")
})
.or_else(|| {
(got.lines().count() != want.lines().count()).then(|| {
format!(
"{} rows, oracle has {}",
got.lines().count(),
want.lines().count()
)
})
})
}
#[test]
fn the_difference_reporter_names_the_row_the_column_and_a_missing_row() {
assert_eq!(
differ("##\n..", "##\n.."),
None,
"identical is no difference"
);
let row = differ("##\n..", "##\n.#").unwrap_or_default();
assert!(row.contains("row 1"), "{row}");
assert!(row.contains("Some(1)"), "the differing column: {row}");
let short = differ("##", "##\n..").unwrap_or_default();
assert!(short.contains("1 rows, oracle has 2"), "{short}");
}
#[test]
fn the_pinned_symbols_match_the_oracle_module_for_module() {
let cases: [(&str, Vec<u8>); 3] = [
(oracle::V1, b"hello".to_vec()),
(oracle::V7, payload(7, 122)),
(oracle::V10, payload(10, 213)),
];
for (want, bytes) in cases {
let symbol = Symbol::encode(&bytes).expect("the oracle's payload fits");
assert_eq!(
differ(&as_oracle(&symbol), want),
None,
"a {}-module symbol disagrees with the oracle",
symbol.side()
);
}
}
#[test]
fn every_version_has_the_side_mask_and_dark_count_the_oracle_reports() {
for &(version, len, side, mask, dark) in &tables::FULL {
let symbol = Symbol::encode(&payload(version, len)).expect("the stated capacity fits");
let counted = (0..side)
.flat_map(|y| (0..side).map(move |x| (x, y)))
.filter(|&(x, y)| symbol.dark(x, y))
.count();
assert_eq!(
(symbol.side(), symbol.mask(), counted),
(side, mask, dark),
"version {version} at {len} bytes"
);
}
assert_eq!(
tables::FULL.len(),
40,
"the version walk enumerated nothing"
);
}
#[test]
fn one_byte_past_a_capacity_is_the_next_version_up() {
for pair in tables::FULL.windows(2) {
let [(version, len, ..), (next, ..)] = pair else {
continue;
};
let over = Symbol::encode(&payload(*version, len + 1)).expect("the next version holds it");
assert_eq!(over.side(), 17 + 4 * next, "one past version {version}");
}
}
#[test]
fn a_payload_past_the_ceiling_is_refused_by_name() {
assert_eq!(
Symbol::encode(&vec![0; LIMIT + 1]),
Err(TooLong { len: LIMIT + 1 })
);
assert!(
Symbol::encode(&vec![0; LIMIT]).is_ok(),
"the ceiling itself"
);
assert!(
TooLong { len: LIMIT + 1 }
.to_string()
.contains(&LIMIT.to_string())
);
}
#[test]
fn nothing_to_encode_is_the_smallest_symbol_there_is() {
let symbol = Symbol::encode(&[]).expect("an empty payload still fits");
assert_eq!(symbol.side(), 21);
}
#[test]
fn every_coordinate_outside_the_symbol_is_light() {
let symbol = Symbol::encode(b"edge").expect("it fits");
let side = symbol.side();
assert!(!symbol.dark(side, 0), "past the right edge");
assert!(!symbol.dark(0, side), "past the bottom edge");
assert!(symbol.dark(0, 0), "and the corner finder is still dark");
}
mod block;
mod structure;