#[cfg(test)]
pub(crate) fn document(name: &str) -> &'static str {
match name {
"network.json" => include_str!("../../fixtures/documents/network.json"),
"network.yaml" => include_str!("../../fixtures/documents/network.yaml"),
"network.toml" => include_str!("../../fixtures/documents/network.toml"),
"network.ini" => include_str!("../../fixtures/documents/network.ini"),
"network.env" => include_str!("../../fixtures/documents/network.env"),
"network.csv" => include_str!("../../fixtures/documents/network.csv"),
"access.log" => include_str!("../../fixtures/documents/access.log"),
"ambiguous.txt" => include_str!("../../fixtures/documents/ambiguous.txt"),
"normalization.txt" => include_str!("../../fixtures/documents/normalization.txt"),
"classes.txt" => include_str!("../../fixtures/documents/classes.txt"),
other => panic!("the corpus has no document named {other}"),
}
}
#[cfg(test)]
mod tests {
use serde::Deserialize;
use super::document;
use crate::extract::policy::{Context, Reading, Reason, read};
use crate::extract::{Class, Kind, find};
const CORPUS: &str = include_str!("../../fixtures/extraction.json");
#[derive(Debug, Deserialize)]
struct Corpus {
normalization: Vec<Normalization>,
classification: Vec<Classification>,
ambiguity: Vec<Ambiguity>,
documents: Vec<Document>,
}
#[derive(Debug, Deserialize)]
struct Normalization {
input: String,
expected: String,
why: String,
}
#[derive(Debug, Deserialize)]
struct Classification {
input: String,
kind: Kind,
class: Class,
}
#[derive(Debug, Deserialize)]
struct Ambiguity {
input: String,
reason: Reason,
#[serde(default)]
key: Option<String>,
}
#[derive(Debug, Deserialize)]
struct Document {
name: String,
file: String,
format: String,
expected: Vec<Expected>,
}
#[derive(Debug, Deserialize, PartialEq, Eq)]
struct Expected {
text: String,
line: usize,
column: usize,
key: Option<String>,
normalized: Option<String>,
class: Option<Class>,
refused: Option<Reason>,
}
fn corpus() -> Corpus {
serde_json::from_str(CORPUS).expect("the corpus is valid JSON")
}
#[test]
fn every_pinned_normalization_follows_rfc_5952() {
let corpus = corpus();
assert!(!corpus.normalization.is_empty(), "the corpus pins nothing");
for case in corpus.normalization {
let Some(Reading::Address { normalized, .. }) = read(&case.input, Context::default())
else {
panic!("{} was not read as an address", case.input);
};
assert_eq!(normalized, case.expected, "{}: {}", case.input, case.why);
}
}
#[test]
fn the_corpus_pins_the_rules_that_are_easy_to_get_wrong() {
let pinned: Vec<String> = corpus()
.normalization
.into_iter()
.map(|case| case.expected)
.collect();
for form in ["2001:db8::1:0:0:1", "1:0:0:2::3", "::ffff:192.0.2.128"] {
assert!(pinned.contains(&form.to_string()), "the corpus lost {form}");
}
}
#[test]
fn every_pinned_classification_reproduces() {
for case in corpus().classification {
let Some(Reading::Address { kind, class, .. }) = read(&case.input, Context::default())
else {
panic!("{} was not read as an address", case.input);
};
assert_eq!(kind, case.kind, "{}", case.input);
assert_eq!(class, case.class, "{}", case.input);
}
}
#[test]
fn the_corpus_covers_every_class_and_every_kind() {
let corpus = corpus();
for class in [
Class::Loopback,
Class::Private,
Class::LinkLocal,
Class::Cgnat,
Class::Multicast,
Class::Broadcast,
Class::Reserved,
Class::Documentation,
Class::UniqueLocal,
Class::Global,
] {
assert!(
corpus.classification.iter().any(|case| case.class == class),
"no corpus case reaches {class:?}"
);
}
for kind in [Kind::Ipv4, Kind::Ipv6, Kind::Cidr, Kind::Mac] {
assert!(
corpus.classification.iter().any(|case| case.kind == kind),
"no corpus case reaches {kind:?}"
);
}
}
#[test]
fn every_ambiguous_case_is_refused_by_name() {
let corpus = corpus();
assert!(!corpus.ambiguity.is_empty(), "the corpus pins nothing");
for case in corpus.ambiguity {
let context = Context::from_key(case.key.as_deref());
let Some(Reading::Refused { refusal, .. }) = read(&case.input, context) else {
panic!("{} was not refused", case.input);
};
assert_eq!(refusal.reason, case.reason, "{}", case.input);
}
}
#[test]
fn the_corpus_covers_every_refusal_reason() {
let corpus = corpus();
for reason in [
Reason::AmbiguousVersion,
Reason::MalformedAddress,
Reason::OctalHazard,
Reason::IntegerForm,
Reason::PrefixOutOfRange,
Reason::MacAmbiguous,
] {
assert!(
corpus.ambiguity.iter().any(|case| case.reason == reason),
"no corpus case produces {reason:?}"
);
}
}
#[test]
fn every_corpus_document_reproduces() {
let corpus = corpus();
assert!(!corpus.documents.is_empty(), "the corpus is empty");
for case in corpus.documents {
let found: Vec<Expected> = find(document(&case.file), &case.format)
.into_iter()
.map(|one| Expected {
text: one.text,
line: one.line,
column: one.column,
key: one.key,
normalized: one.normalized,
class: one.class,
refused: one.refused.map(|refusal| refusal.reason),
})
.collect();
assert_eq!(found, case.expected, "{}", case.name);
}
}
#[test]
fn the_corpus_covers_every_key_reader() {
let corpus = corpus();
for format in [
"json", "yaml", "toml", "ini", "env", "csv", "log", "unknown",
] {
assert!(
corpus.documents.iter().any(|case| case.format == format),
"no corpus document is read as {format}"
);
}
}
#[test]
#[should_panic(expected = "no document named")]
fn a_document_the_corpus_does_not_carry_is_a_broken_test() {
document("nothing.json");
}
}