blob: 02a624f438ea77b50362358e02ee61ef475e7e7d [file] [log] [blame] [edit]
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#[cfg(test)]
mod tests {
use anyhow::{anyhow, bail, ensure, Result};
use itertools::Itertools;
use std::fmt::Debug;
use std::io::Read;
use xml::attribute::OwnedAttribute;
use xml::reader::{ParserConfig, XmlEvent};
fn get_attribute(attributes: &[OwnedAttribute], tag: &str, key: &str) -> Result<String> {
attributes
.iter()
.find_map(
|attr| {
if attr.name.local_name == key {
Some(attr.value.clone())
} else {
None
}
},
)
.ok_or_else(|| anyhow!("tag {}: missing attribute {}", tag, key))
}
fn verify_xml<R: Read>(mut source: R) -> Result<()> {
#[derive(Debug)]
struct Sdk {
id: String,
shortname: String,
name: String,
reference: String,
}
#[derive(Debug)]
struct Symbol {
#[allow(dead_code)]
jar: String,
#[allow(dead_code)]
pattern: String,
sdks: Vec<String>,
}
// this will error out on XML syntax errors
let reader = ParserConfig::new().create_reader(&mut source);
let events: Vec<_> = reader.into_iter().collect::<Result<Vec<_>, _>>()?;
// parse XML
let mut sdks = vec![];
let mut symbols = vec![];
for (name, attributes) in events.into_iter().filter_map(|e| match e {
XmlEvent::StartElement { name, attributes, namespace: _ } => {
Some((name.local_name, attributes))
}
_ => None,
}) {
match name.as_str() {
"sdk-extensions-info" => {}
"sdk" => {
let sdk = Sdk {
id: get_attribute(&attributes, "sdk", "id")?,
shortname: get_attribute(&attributes, "sdk", "shortname")?,
name: get_attribute(&attributes, "sdk", "name")?,
reference: get_attribute(&attributes, "sdk", "reference")?,
};
sdks.push(sdk);
}
"symbol" => {
let symbol = Symbol {
jar: get_attribute(&attributes, "symbol", "jar")?,
pattern: get_attribute(&attributes, "symbol", "pattern")?,
sdks: get_attribute(&attributes, "symbol", "sdks")?
.split(',')
.map(|s| s.to_owned())
.collect(),
};
symbols.push(symbol);
}
_ => bail!("unknown tag '{}'", name),
}
}
// verify all Sdk fields are unique across all Sdk items
ensure!(
sdks.iter().duplicates_by(|sdk| &sdk.id).collect::<Vec<_>>().is_empty(),
"multiple sdk entries with identical id value"
);
ensure!(
sdks.iter().duplicates_by(|sdk| &sdk.shortname).collect::<Vec<_>>().is_empty(),
"multiple sdk entries with identical shortname value"
);
ensure!(
sdks.iter().duplicates_by(|sdk| &sdk.name).collect::<Vec<_>>().is_empty(),
"multiple sdk entries with identical name value"
);
ensure!(
sdks.iter().duplicates_by(|sdk| &sdk.reference).collect::<Vec<_>>().is_empty(),
"multiple sdk entries with identical reference value"
);
// verify Sdk id field has the expected format (positive integer)
for id in sdks.iter().map(|sdk| &sdk.id) {
ensure!(id.parse::<usize>().is_ok(), "sdk id {} not a positive int", id);
}
// verify individual Symbol elements
let sdk_shortnames: Vec<_> = sdks.iter().map(|sdk| &sdk.shortname).collect();
for symbol in symbols.iter() {
ensure!(
symbol.sdks.iter().duplicates().collect::<Vec<_>>().is_empty(),
"symbol contains duplicate references to the same sdk"
);
for id in symbol.sdks.iter() {
ensure!(sdk_shortnames.contains(&id), "symbol refers to non-existent sdk {}", id);
}
}
Ok(())
}
#[test]
fn test_get_attribute() {
use xml::EventReader;
let mut iter = EventReader::from_str(r#"<tag a="A" b="B" c="C"/>"#).into_iter();
let _ = iter.next().unwrap(); // skip start of doc
let Ok(XmlEvent::StartElement { attributes, .. }) = iter.next().unwrap() else {
panic!();
};
assert_eq!(get_attribute(&attributes, "tag", "a").unwrap(), "A");
assert!(get_attribute(&attributes, "tag", "no-such-attribute").is_err());
}
#[test]
fn test_verify_xml_correct_input() {
verify_xml(&include_bytes!("testdata/correct.xml")[..]).unwrap();
}
#[test]
fn test_verify_xml_incorrect_input() {
macro_rules! assert_err {
($input_path:expr, $expected_error:expr) => {
let error = verify_xml(&include_bytes!($input_path)[..]).unwrap_err().to_string();
assert_eq!(error, $expected_error);
};
}
assert_err!(
"testdata/corrupt-xml.xml",
"25:1 Unexpected end of stream: still inside the root element"
);
assert_err!(
"testdata/duplicate-sdk-id.xml",
"multiple sdk entries with identical id value"
);
assert_err!(
"testdata/duplicate-sdk-shortname.xml",
"multiple sdk entries with identical shortname value"
);
assert_err!(
"testdata/duplicate-sdk-name.xml",
"multiple sdk entries with identical name value"
);
assert_err!(
"testdata/duplicate-sdk-reference.xml",
"multiple sdk entries with identical reference value"
);
assert_err!("testdata/incorrect-sdk-id-format.xml", "sdk id 1.0 not a positive int");
assert_err!(
"testdata/duplicate-symbol-sdks.xml",
"symbol contains duplicate references to the same sdk"
);
assert_err!(
"testdata/symbol-refers-to-non-existent-sdk.xml",
"symbol refers to non-existent sdk does-not-exist"
);
}
#[test]
fn test_actual_sdk_extensions_info_contents() {
verify_xml(&include_bytes!("../sdk-extensions-info.xml")[..]).unwrap();
}
}