#![allow(clippy::approx_constant)]
use android_abx::{AbxWriter, Attribute, AttributeValue, Event};
mod common;
fn assert_single_attr_roundtrip(value: AttributeValue, attr_bytes: Vec<u8>) {
let mut w = AbxWriter::new(Vec::new()).unwrap();
w.write_event(&Event::StartDocument).unwrap();
w.write_event(&Event::StartTag {
name: "tag".into(),
attributes: vec![Attribute {
name: "a".into(),
value,
}],
})
.unwrap();
w.write_event(&Event::EndTag { name: "tag".into() })
.unwrap();
w.write_event(&Event::EndDocument).unwrap();
let mut start = common::start_tag("tag");
start.extend(attr_bytes);
let expected = common::document(&[start, interned_backref(common::CMD_END_TAG, 0)]);
assert_eq!(w.into_inner(), expected);
}
#[test]
fn writer_writes_magic_header() {
let w = AbxWriter::new(Vec::new()).unwrap();
assert_eq!(w.into_inner(), android_abx::MAGIC.to_vec());
}
#[test]
fn writer_encodes_empty_document() {
let mut w = AbxWriter::new(Vec::new()).unwrap();
w.write_event(&Event::StartDocument).unwrap();
w.write_event(&Event::EndDocument).unwrap();
assert_eq!(w.into_inner(), common::document(&[]));
}
fn interned_backref(cmd: u8, idx: u16) -> Vec<u8> {
let mut v = vec![common::TYPE_STRING_INTERNED | cmd];
v.extend(common::interned_ref(idx));
v
}
#[test]
fn writer_encodes_start_and_end_tag() {
let mut w = AbxWriter::new(Vec::new()).unwrap();
w.write_event(&Event::StartDocument).unwrap();
w.write_event(&Event::StartTag {
name: "root".into(),
attributes: vec![],
})
.unwrap();
w.write_event(&Event::EndTag {
name: "root".into(),
})
.unwrap();
w.write_event(&Event::EndDocument).unwrap();
let expected = common::document(&[
common::start_tag("root"),
interned_backref(common::CMD_END_TAG, 0),
]);
assert_eq!(w.into_inner(), expected);
}
#[test]
fn writer_interns_repeated_tag_name() {
let mut w = AbxWriter::new(Vec::new()).unwrap();
w.write_event(&Event::StartDocument).unwrap();
w.write_event(&Event::StartTag {
name: "pkg".into(),
attributes: vec![],
})
.unwrap();
w.write_event(&Event::EndTag { name: "pkg".into() })
.unwrap();
w.write_event(&Event::StartTag {
name: "pkg".into(),
attributes: vec![],
})
.unwrap();
w.write_event(&Event::EndTag { name: "pkg".into() })
.unwrap();
w.write_event(&Event::EndDocument).unwrap();
let expected = common::document(&[
common::start_tag("pkg"),
interned_backref(common::CMD_END_TAG, 0),
interned_backref(common::CMD_START_TAG, 0),
interned_backref(common::CMD_END_TAG, 0),
]);
assert_eq!(w.into_inner(), expected);
}
#[test]
fn writer_encodes_attr_null() {
assert_single_attr_roundtrip(AttributeValue::Null, common::attr_null("a"));
}
#[test]
fn writer_encodes_attr_string() {
assert_single_attr_roundtrip(
AttributeValue::String("hello".into()),
common::attr_string("a", "hello"),
);
}
#[test]
fn writer_encodes_attr_bytes_hex() {
assert_single_attr_roundtrip(
AttributeValue::BytesHex(vec![0xDE, 0xAD, 0xBE, 0xEF]),
common::attr_bytes_hex("a", &[0xDE, 0xAD, 0xBE, 0xEF]),
);
}
#[test]
fn writer_encodes_attr_bytes_base64() {
assert_single_attr_roundtrip(
AttributeValue::BytesBase64(vec![1, 2, 3]),
common::attr_bytes_base64("a", &[1, 2, 3]),
);
}
#[test]
fn writer_encodes_attr_int() {
assert_single_attr_roundtrip(AttributeValue::Int(-42), common::attr_int("a", -42));
}
#[test]
fn writer_encodes_attr_int_hex() {
assert_single_attr_roundtrip(
AttributeValue::IntHex(0xCAFEBABE),
common::attr_int_hex("a", 0xCAFEBABE),
);
}
#[test]
fn writer_encodes_attr_long() {
assert_single_attr_roundtrip(
AttributeValue::Long(-123456789012),
common::attr_long("a", -123456789012),
);
}
#[test]
fn writer_encodes_attr_long_hex() {
assert_single_attr_roundtrip(
AttributeValue::LongHex(0xDEADBEEFCAFEBABE),
common::attr_long_hex("a", 0xDEADBEEFCAFEBABE),
);
}
#[test]
fn writer_encodes_attr_float() {
assert_single_attr_roundtrip(AttributeValue::Float(3.5), common::attr_float("a", 3.5));
}
#[test]
fn writer_encodes_attr_double() {
assert_single_attr_roundtrip(
AttributeValue::Double(2.71828),
common::attr_double("a", 2.71828),
);
}
#[test]
fn writer_encodes_attr_boolean_true() {
assert_single_attr_roundtrip(AttributeValue::Boolean(true), common::attr_bool("a", true));
}
#[test]
fn writer_encodes_attr_boolean_false() {
assert_single_attr_roundtrip(
AttributeValue::Boolean(false),
common::attr_bool("a", false),
);
}
#[test]
fn writer_does_not_intern_repeated_attribute_string_value() {
let mut w = AbxWriter::new(Vec::new()).unwrap();
w.write_event(&Event::StartDocument).unwrap();
w.write_event(&Event::StartTag {
name: "tag".into(),
attributes: vec![
Attribute {
name: "a".into(),
value: AttributeValue::String("dup".into()),
},
Attribute {
name: "b".into(),
value: AttributeValue::String("dup".into()),
},
],
})
.unwrap();
w.write_event(&Event::EndTag { name: "tag".into() })
.unwrap();
w.write_event(&Event::EndDocument).unwrap();
let mut start = common::start_tag("tag");
start.extend(common::attr_string("a", "dup"));
start.extend(common::attr_string("b", "dup"));
let expected = common::document(&[start, interned_backref(common::CMD_END_TAG, 0)]);
assert_eq!(w.into_inner(), expected);
}
fn assert_text_like_roundtrip(cmd: u8, make_event: impl Fn(String) -> Event) {
for s in ["", "hello"] {
let mut w = AbxWriter::new(Vec::new()).unwrap();
w.write_event(&Event::StartDocument).unwrap();
w.write_event(&make_event(s.to_string())).unwrap();
w.write_event(&Event::EndDocument).unwrap();
let expected = common::document(&[common::text_token(cmd, s)]);
assert_eq!(
w.into_inner(),
expected,
"mismatch for cmd {cmd:#x} with {s:?}"
);
}
}
#[test]
fn writer_encodes_text() {
assert_text_like_roundtrip(common::CMD_TEXT, Event::Text);
}
#[test]
fn writer_encodes_cdata_section() {
assert_text_like_roundtrip(common::CMD_CDSECT, Event::CdataSection);
}
#[test]
fn writer_encodes_comment() {
assert_text_like_roundtrip(common::CMD_COMMENT, Event::Comment);
}
#[test]
fn writer_encodes_processing_instruction() {
assert_text_like_roundtrip(
common::CMD_PROCESSING_INSTRUCTION,
Event::ProcessingInstruction,
);
}
#[test]
fn writer_encodes_entity_reference() {
assert_text_like_roundtrip(common::CMD_ENTITY_REF, Event::EntityReference);
}
#[test]
fn writer_encodes_ignorable_whitespace() {
assert_text_like_roundtrip(common::CMD_IGNORABLE_WHITESPACE, Event::IgnorableWhitespace);
}
#[test]
fn writer_encodes_docdecl() {
assert_text_like_roundtrip(common::CMD_DOCDECL, Event::DocDecl);
}
#[test]
fn events_to_abx_round_trips_through_decoder() {
let events = vec![
Event::StartDocument,
Event::StartTag {
name: "packages".into(),
attributes: vec![],
},
Event::StartTag {
name: "pkg".into(),
attributes: vec![
Attribute {
name: "name".into(),
value: AttributeValue::String("com.example.app".into()),
},
Attribute {
name: "version".into(),
value: AttributeValue::Int(3),
},
],
},
Event::Text("hello".into()),
Event::EndTag { name: "pkg".into() },
Event::EndTag {
name: "packages".into(),
},
Event::EndDocument,
];
let bytes = android_abx::events_to_abx(&events).unwrap();
let decoded = android_abx::AbxParser::new(&bytes)
.unwrap()
.collect_events()
.unwrap();
assert_eq!(decoded, events);
}
#[test]
fn writer_encodes_string_at_max_length_boundary() {
let s = "a".repeat(65_535);
let mut w = AbxWriter::new(Vec::new()).unwrap();
w.write_event(&Event::StartDocument).unwrap();
w.write_event(&Event::Text(s.clone())).unwrap();
w.write_event(&Event::EndDocument).unwrap();
let bytes = w.into_inner();
let evs = android_abx::AbxParser::new(&bytes)
.unwrap()
.collect_events()
.unwrap();
assert_eq!(evs[1], Event::Text(s));
}
#[test]
fn writer_errors_on_oversized_text() {
let s = "a".repeat(65_536);
let mut w = AbxWriter::new(Vec::new()).unwrap();
w.write_event(&Event::StartDocument).unwrap();
let err = w.write_event(&Event::Text(s)).unwrap_err();
assert!(matches!(
err,
android_abx::AbxError::ValueTooLong {
len: 65_536,
max: 65_535
}
));
}
#[test]
fn writer_errors_on_oversized_attr_string() {
let s = "a".repeat(65_536);
let mut w = AbxWriter::new(Vec::new()).unwrap();
w.write_event(&Event::StartDocument).unwrap();
let err = w
.write_event(&Event::StartTag {
name: "tag".into(),
attributes: vec![Attribute {
name: "a".into(),
value: AttributeValue::String(s),
}],
})
.unwrap_err();
assert!(matches!(
err,
android_abx::AbxError::ValueTooLong {
len: 65_536,
max: 65_535
}
));
}
#[test]
fn writer_errors_on_oversized_bytes_blob() {
let b = vec![0u8; 65_536];
let mut w = AbxWriter::new(Vec::new()).unwrap();
w.write_event(&Event::StartDocument).unwrap();
let err = w
.write_event(&Event::StartTag {
name: "tag".into(),
attributes: vec![Attribute {
name: "a".into(),
value: AttributeValue::BytesHex(b),
}],
})
.unwrap_err();
assert!(matches!(
err,
android_abx::AbxError::ValueTooLong {
len: 65_536,
max: 65_535
}
));
}
#[test]
fn writer_gracefully_degrades_past_interned_pool_limit() {
let mut w = AbxWriter::new(Vec::new()).unwrap();
w.write_event(&Event::StartDocument).unwrap();
for i in 0..0xFFFFu32 {
w.write_event(&Event::StartTag {
name: format!("n{i}").into(),
attributes: vec![],
})
.unwrap();
}
w.write_event(&Event::StartTag {
name: "over".into(),
attributes: vec![],
})
.unwrap();
w.write_event(&Event::StartTag {
name: "n0".into(),
attributes: vec![],
})
.unwrap();
w.write_event(&Event::EndDocument).unwrap();
let events = android_abx::AbxParser::new(&w.into_inner())
.unwrap()
.collect_events()
.unwrap();
assert_eq!(
events[events.len() - 3],
Event::StartTag {
name: "over".into(),
attributes: vec![]
}
);
assert_eq!(
events[events.len() - 2],
Event::StartTag {
name: "n0".into(),
attributes: vec![]
}
);
}