#![forbid(unsafe_code)]
use proptest::prelude::*;
use crate::core::limits::Limits;
use crate::core::materialize::DecoderContext;
use crate::tests::hostile_media::corpus::{graph_seed_expectations, graph_seeds};
use crate::tests::hostile_media::{
ExhibitKind, Expect, LIMIT_SETS, parse_graph_spec, run_graph_oracle, tight_limits,
};
const MAX_GRAPH_INPUT: usize = 4096;
#[test]
fn graph_seeds_materialize_to_pinned_content() {
let limits = Limits::default();
let expectations = graph_seed_expectations();
let seeds = graph_seeds();
assert_eq!(
seeds.len(),
expectations.len(),
"every graph seed must have an expectation"
);
for (name, bytes) in &seeds {
let (_, expect, expected) = expectations
.iter()
.find(|(n, _, _)| n == name)
.unwrap_or_else(|| panic!("no expectation for seed {name}"));
let spec = parse_graph_spec(bytes);
let outcome = run_graph_oracle(&spec, &limits)
.unwrap_or_else(|e| panic!("seed {name}: oracle invariant violated: {e}"));
match expect {
Expect::MustAccept => {
let len = match outcome {
crate::tests::hostile_media::GraphOutcome::Ok { len } => len,
crate::tests::hostile_media::GraphOutcome::Rejected(e) => {
panic!("seed {name} must materialize, rejected: {e}")
}
};
let expected = expected.as_ref().expect("accepted seed pins content");
assert_eq!(
len,
expected.len() as u64,
"seed {name}: materialized length differs"
);
let resolver =
crate::tests::hostile_media::HostileResolver::from_spec(&spec, &limits);
let entry = resolver
.fetch_descriptor(&spec.entry)
.expect("entry resolves");
let out = crate::core::materialize::materialize_to_vec(&entry, &resolver, &limits)
.expect("materializes");
assert_eq!(
&out, expected,
"seed {name}: materialized bytes differ from the pinned content"
);
}
Expect::MustReject => {
assert!(
matches!(
outcome,
crate::tests::hostile_media::GraphOutcome::Rejected(_)
),
"seed {name} must be rejected typed"
);
}
Expect::Either => unreachable!("graph seeds are accept or reject, not either"),
}
}
}
#[test]
fn graph_seeds_bounded_under_tight_limits() {
let limits = tight_limits();
for (name, bytes) in graph_seeds() {
let spec = parse_graph_spec(&bytes);
run_graph_oracle(&spec, &limits)
.unwrap_or_else(|e| panic!("seed {name}: tight oracle invariant violated: {e}"));
}
}
#[test]
fn graph_exhibits_pass() {
for set in LIMIT_SETS {
let limits = match set {
"tight" => tight_limits(),
_ => Limits::default(),
};
for ex in crate::tests::hostile_media::corpus::exhibits()
.into_iter()
.filter(|e| e.kind == ExhibitKind::Graph)
{
let spec = parse_graph_spec(&ex.bytes);
let outcome = run_graph_oracle(&spec, &limits)
.unwrap_or_else(|e| panic!("[{set}] exhibit {}: {e}", ex.name));
match ex.expect {
Expect::MustReject => {
assert!(
matches!(
outcome,
crate::tests::hostile_media::GraphOutcome::Rejected(_)
),
"[{set}] exhibit {} must be rejected typed",
ex.name
);
}
Expect::MustAccept => {
assert!(
matches!(
outcome,
crate::tests::hostile_media::GraphOutcome::Ok { .. }
),
"[{set}] exhibit {} must materialize boundedly",
ex.name
);
}
Expect::Either => {
}
}
}
}
}
fn mutate_bytes(bytes: &mut Vec<u8>, ops: &[(u8, u8, u8)]) {
for (op, a, b) in ops {
let a = *a as usize;
match op % 6 {
0 => {
if !bytes.is_empty() {
let i = a % bytes.len();
bytes[i] ^= b | 1;
}
}
1 => {
if !bytes.is_empty() {
let i = a % bytes.len();
bytes[i] = *b;
}
}
2 => {
let i = if bytes.is_empty() {
0
} else {
a % (bytes.len() + 1)
};
bytes.insert(i, *b);
}
3 => {
if !bytes.is_empty() {
let i = a % bytes.len();
bytes.remove(i);
}
}
4 => {
if !bytes.is_empty() {
let i = a % bytes.len();
bytes.truncate(i);
}
}
_ => {
if !bytes.is_empty() {
let start = a % bytes.len();
let mut rng = a as u64 ^ (*b as u64) << 8;
for k in start..bytes.len().min(start + 8) {
rng = rng
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
bytes[k] ^= (rng >> 32) as u8 | 1;
}
}
}
}
}
}
fn mutated_graph_strategy() -> impl Strategy<Value = Vec<u8>> {
let seeds = graph_seeds();
let seed_bytes: Vec<Vec<u8>> = seeds.iter().map(|(_, b)| b.clone()).collect();
prop::sample::select(seed_bytes).prop_flat_map(|seed| {
prop::collection::vec(any::<(u8, u8, u8)>(), 0..=10).prop_map(move |ops| {
let mut bytes = seed.clone();
mutate_bytes(&mut bytes, &ops);
bytes
})
})
}
fn noise_strategy() -> impl Strategy<Value = Vec<u8>> {
prop::collection::vec(any::<u8>(), 0..=MAX_GRAPH_INPUT)
}
fn spliced_graph_strategy() -> impl Strategy<Value = Vec<u8>> {
let seeds = graph_seeds();
let seed_bytes: Vec<Vec<u8>> = seeds.iter().map(|(_, b)| b.clone()).collect();
prop::sample::select(seed_bytes).prop_flat_map(|seed| {
(
0..seed.len(),
1usize..32,
prop::collection::vec(any::<u8>(), 0..=32),
)
.prop_map(move |(start, len, blob)| {
let mut bytes = seed.clone();
let end = (start + len).min(bytes.len());
if end > start {
bytes.splice(start..end, blob);
}
bytes
})
})
}
proptest! {
#[test]
fn mutated_graphs_oracle(bytes in mutated_graph_strategy()) {
for set in LIMIT_SETS {
let limits = match set { "tight" => tight_limits(), _ => Limits::default() };
let spec = parse_graph_spec(&bytes);
run_graph_oracle(&spec, &limits)
.unwrap_or_else(|e| panic!("[{set}] mutated graph {} bytes: {e}", bytes.len()));
}
}
#[test]
fn noise_graphs_oracle(bytes in noise_strategy()) {
for set in LIMIT_SETS {
let limits = match set { "tight" => tight_limits(), _ => Limits::default() };
let spec = parse_graph_spec(&bytes);
run_graph_oracle(&spec, &limits)
.unwrap_or_else(|e| panic!("[{set}] noise graph {} bytes: {e}", bytes.len()));
}
}
#[test]
fn spliced_graphs_oracle(bytes in spliced_graph_strategy()) {
for set in LIMIT_SETS {
let limits = match set { "tight" => tight_limits(), _ => Limits::default() };
let spec = parse_graph_spec(&bytes);
run_graph_oracle(&spec, &limits)
.unwrap_or_else(|e| panic!("[{set}] spliced graph {} bytes: {e}", bytes.len()));
}
}
}