Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more comprehensive test coverage to rbx_xml #295

Merged
merged 6 commits into from
Jun 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rbx_xml/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ xml-rs = "0.8.4"
[dev-dependencies]
env_logger = "0.9.0"
insta = { version = "1.14.1", features = ["yaml"] }
heck = "0.4.1"
2 changes: 2 additions & 0 deletions rbx_xml/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ mod types;

#[cfg(test)]
mod test_util;
#[cfg(test)]
mod tests;

use std::io::{Read, Write};

Expand Down
18 changes: 9 additions & 9 deletions rbx_xml/tests/basic.rs → rbx_xml/src/tests/basic.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Temporary tests while re-bootstrapping rbx_xml
//! Basic functionality tests

use rbx_dom_weak::types::{
Attributes, BinaryString, BrickColor, Color3, ColorSequence, ColorSequenceKeypoint,
Expand All @@ -21,7 +21,7 @@ fn with_bool() {
</roblox>
"#;

let tree = rbx_xml::from_str_default(document).unwrap();
let tree = crate::from_str_default(document).unwrap();

let root = tree.root();
let child = tree.get_by_ref(root.children()[0]).unwrap();
Expand All @@ -45,7 +45,7 @@ fn read_tags() {
</roblox>
"#;

let dom = rbx_xml::from_str_default(document).unwrap();
let dom = crate::from_str_default(document).unwrap();
let folder = dom.get_by_ref(dom.root().children()[0]).unwrap();

let mut tags = Tags::new();
Expand All @@ -63,7 +63,7 @@ fn write_empty_tags() {
let dom = WeakDom::new(part);

let mut encoded = Vec::new();
rbx_xml::to_writer_default(&mut encoded, &dom, &[dom.root_ref()]).unwrap();
crate::to_writer_default(&mut encoded, &dom, &[dom.root_ref()]).unwrap();
insta::assert_snapshot!(std::str::from_utf8(&encoded).unwrap());
}

Expand All @@ -79,7 +79,7 @@ fn write_tags() {
let dom = WeakDom::new(part);

let mut encoded = Vec::new();
rbx_xml::to_writer_default(&mut encoded, &dom, &[dom.root_ref()]).unwrap();
crate::to_writer_default(&mut encoded, &dom, &[dom.root_ref()]).unwrap();
insta::assert_snapshot!(std::str::from_utf8(&encoded).unwrap());
}

Expand All @@ -105,7 +105,7 @@ fn read_attributes() {
</roblox>
"#;

let dom = rbx_xml::from_str_default(document).unwrap();
let dom = crate::from_str_default(document).unwrap();
let folder = dom.get_by_ref(dom.root().children()[0]).unwrap();

assert_eq!(folder.properties.get("AttributesSerialize"), None);
Expand Down Expand Up @@ -205,13 +205,13 @@ fn read_unique_id() {
</roblox>
"#;

let tree = rbx_xml::from_str(
let tree = crate::from_str(
document,
rbx_xml::DecodeOptions::new()
crate::DecodeOptions::new()
// This is necessary at the moment because we do not actually
// have UniqueId properties in our reflection database. This may
// change, but it should in general be safe.
.property_behavior(rbx_xml::DecodePropertyBehavior::ReadUnknown),
.property_behavior(crate::DecodePropertyBehavior::ReadUnknown),
)
.unwrap();

Expand Down
32 changes: 32 additions & 0 deletions rbx_xml/src/tests/edge_cases.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//! Tests to cover edge cases encountered over time.

use super::test_suite;

use std::path::PathBuf;

use heck::ToKebabCase;

macro_rules! edge_cases {
($($test_name: ident,)*) => {
$(
#[test]
fn $test_name() {
let _ = env_logger::try_init();
let mut test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
assert!(test_path.pop());

test_path.push("test-files");
test_path.push("edge-cases");
test_path.push(stringify!($test_name).to_kebab_case());
test_path.push("xml.rbxmx");

test_suite(test_path).unwrap()
}
)*
};
}

edge_cases! {
empty_font,
xml_unknown_type,
}
175 changes: 175 additions & 0 deletions rbx_xml/src/tests/formatting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
//! Test to ensure the formatting of files does not change.

use rbx_dom_weak::DomViewer;

const INPUT: &str = r#"<roblox version="4">
<Item class="TestClass" referent="Parent">
<Properties>
<Axes name="TestAxes"><axes>5</axes></Axes>
<BinaryString name="TestBinaryString">SGVsbG8sIHdvcmxkIQ==</BinaryString>
<bool name="TestBool">true</bool>
<CoordinateFrame name="TestCFrame">
<X>123</X>
<Y>456</Y>
<Z>789</Z>
<R00>987</R00>
<R01>654</R01>
<R02>432</R02>
<R10>210</R10>
<R11>0</R11>
<R12>-12345</R12>
<R20>765</R20>
<R21>234</R21>
<R22>123123</R22>
</CoordinateFrame>
<ColorSequence name="TestColorSequence">0 0 0.5 1 0 1 1 0.5 0 0 </ColorSequence>
<Color3 name="TestColor3">
<R>1</R>
<G>0.5</G>
<B>125600</B>
</Color3>
<Content name="TestContent1"><url>Wow!</url></Content>
<Content name="TestContent2"><null></null></Content>
</Properties>
<Item class="TestClass" referent="Child1">
<Properties>
<double name="TestDouble">INF</double>
<Faces name="TestFace"><faces>42</faces></Faces>
<float name="TestFloat">NAN</float>
<Font name="TestFont">
<Family><url>Font Family</url></Family>
<Weight>100</Weight>
<Style>Normal</Style>
<CachedFaceId><null></null></CachedFaceId>
</Font>
<int name="TestInt">1337</int>
<int64 name="TestInt64">8675309</int64>
</Properties>
</Item>
<Item class="TestClass" referent="Child2">
<Properties>
<NumberRange name="TestNumberRange">-1337 1337</NumberRange>
<NumberSequence name="TestNumberSequence">0 10 20 1 30 40</NumberSequence>
<OptionalCoordinateFrame name="TestOptionCFrame1"></OptionalCoordinateFrame>
<OptionalCoordinateFrame name="TestOptionCFrame2">
<CFrame>
<X>100</X>
<Y>200</Y>
<Z>300</Z>
<R00>-100</R00>
<R01>-200</R01>
<R02>-300</R02>
<R10>123</R10>
<R11>456</R11>
<R12>-123</R12>
<R20>-456</R20>
<R21>INF</R21>
<R22>-INF</R22>
</CFrame>
</OptionalCoordinateFrame>
</Properties>
<Item class="TestClass" referent="Grandchild">
<Properties>
<PhysicalProperties name="TestPhysicalProperties1">
<CustomPhysics>false</CustomPhysics>
</PhysicalProperties>
<PhysicalProperties name="TestPhysicalProperties2">
<CustomPhysics>true</CustomPhysics>
<Density>1</Density>
<Friction>-1</Friction>
<Elasticity>0.15625</Elasticity>
<FrictionWeight>-0.15625</FrictionWeight>
<ElasticityWeight>NAN</ElasticityWeight>
</PhysicalProperties>
<ProtectedString name="TestProtectedString">Hello world, again!</ProtectedString>
<Ray name="TestRay">
<origin>
<X>10</X>
<Y>20</Y>
<Z>30</Z>
</origin>
<direction>
<X>30</X>
<Y>20</Y>
<Z>10</Z>
</direction>
</Ray>
<Rect2D name="TestRect">
<min>
<X>1</X>
<Y>2</Y>
</min>
<max>
<X>0.0</X>
<Y>INF</Y>
</max>
</Rect2D>
<Ref name="TestRef1">Parent</Ref>
<Ref name="TestRef2">null</Ref>
<SharedString name="TestSharedString">TestHash</SharedString>
<string name="TestString">Hello, world!</string>
<token name="TestEnum">1337</token>
<UDim name="TestUDim">
<S>1234.5</S>
<O>-123</O>
</UDim>
<UDim2 name="TestUDim2">
<XS>1234.5</XS>
<XO>-123</XO>
<YS>-1234.5</YS>
<YO>123</YO>
</UDim2>
<UniqueId name="TestUniqueId1">00000000000000000000000000000000</UniqueId>
<UniqueId name="TestUniqueId2">1234567890abcdef00c0ffeebadf00d0</UniqueId>
<Vector2 name="TestVector2">
<X>INF</X>
<Y>0</Y>
</Vector2>
<Vector3 name="TestVector3">
<X>0</X>
<Y>INF</Y>
<Z>123</Z>
</Vector3>
<Vector3int16 name="TestVector3int16">
<X>-10</X>
<Y>0</Y>
<Z>10</Z>
</Vector3int16>
</Properties>
</Item>
</Item>
</Item>
<SharedStrings>
<SharedString md5="TestHash">SGVsbG8sIHdvcmxkIQ==</SharedString>
</SharedStrings>
</roblox>"#;

#[test]
fn formatting() {
let _ = env_logger::try_init();

let de = crate::from_str(
INPUT,
crate::DecodeOptions::new().property_behavior(crate::DecodePropertyBehavior::NoReflection),
)
.map_err(|e| panic!("cannot deserialize: {}", e))
.unwrap();

insta::assert_yaml_snapshot!("deserialized", DomViewer::new().view_children(&de));

let mut ser = Vec::with_capacity(INPUT.len());
crate::to_writer(
&mut ser,
&de,
de.root().children(),
crate::EncodeOptions::new().property_behavior(crate::EncodePropertyBehavior::NoReflection),
)
.map_err(|e| panic!("cannot serialize: {}", e))
.unwrap();

let ser_str = String::from_utf8(ser)
.map_err(|e| panic!("serialized result not string: {}", e))
.unwrap();

insta::assert_snapshot!("serialized", ser_str)
}
75 changes: 75 additions & 0 deletions rbx_xml/src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! Tests to ensure the functionality of the parser.
mod basic;
mod edge_cases;
mod formatting;
mod models;

use std::{fmt, fs, path::PathBuf};

use rbx_dom_weak::DomViewer;

/// Runs several tests over the provided file.
pub fn test_suite(path: PathBuf) -> Result<(), Error> {
// It would be difficult to run into a situation where this could panic
let test_name = path
.parent()
.unwrap()
.file_stem()
.unwrap()
.to_str()
.unwrap();

let contents = fs::read(&path).map_err(|e| Error::new(e, "read"))?;

let decoded = crate::from_reader_default(contents.as_slice())
.map_err(|e| Error::new(e, "deserialize"))?;
insta::assert_yaml_snapshot!(
format!("{test_name}__decoded"),
DomViewer::new().view_children(&decoded)
);

let mut encoded = Vec::new();
crate::to_writer_default(&mut encoded, &decoded, decoded.root().children())
.map_err(|e| Error::new(e, "serialize"))?;

// We don't have the means to display this format as text raw, so the only
// way to validate it decoded correctly is to deserialize it again. Sad
// but nothing we can fix right now.

let roundtrip =
crate::from_reader_default(encoded.as_slice()).map_err(|e| Error::new(e, "roundtrip"))?;
insta::assert_yaml_snapshot!(
format!("{test_name}__roundtrip"),
DomViewer::new().view_children(&roundtrip)
);

Ok(())
}

pub struct Error {
inner: Box<dyn std::error::Error>,
op: String,
}

// This isn't something that is normally a good idea, but panicking in errors
// tends to be very unhelpful because it doesn't explain the error.
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!(
"could not {} file because {}",
self.op, self.inner
))
}
}

impl Error {
pub fn new<E>(err: E, op: &str) -> Self
where
E: std::error::Error + 'static,
{
Self {
inner: Box::from(err),
op: op.to_string(),
}
}
}
Loading