• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #[cfg(test)]
18 mod tests {
19     use anyhow::{anyhow, bail, ensure, Result};
20     use itertools::Itertools;
21     use std::fmt::Debug;
22     use std::io::Read;
23     use xml::attribute::OwnedAttribute;
24     use xml::reader::{ParserConfig, XmlEvent};
25 
get_attribute(attributes: &[OwnedAttribute], tag: &str, key: &str) -> Result<String>26     fn get_attribute(attributes: &[OwnedAttribute], tag: &str, key: &str) -> Result<String> {
27         attributes
28             .iter()
29             .find_map(
30                 |attr| {
31                     if attr.name.local_name == key {
32                         Some(attr.value.clone())
33                     } else {
34                         None
35                     }
36                 },
37             )
38             .ok_or_else(|| anyhow!("tag {}: missing attribute {}", tag, key))
39     }
40 
verify_xml<R: Read>(mut source: R) -> Result<()>41     fn verify_xml<R: Read>(mut source: R) -> Result<()> {
42         #[derive(Debug)]
43         struct Sdk {
44             id: String,
45             shortname: String,
46             name: String,
47             reference: String,
48         }
49 
50         #[derive(Debug)]
51         struct Symbol {
52             #[allow(dead_code)]
53             jar: String,
54             #[allow(dead_code)]
55             pattern: String,
56             sdks: Vec<String>,
57         }
58 
59         // this will error out on XML syntax errors
60         let reader = ParserConfig::new().create_reader(&mut source);
61         let events: Vec<_> = reader.into_iter().collect::<Result<Vec<_>, _>>()?;
62 
63         // parse XML
64         let mut sdks = vec![];
65         let mut symbols = vec![];
66         for (name, attributes) in events.into_iter().filter_map(|e| match e {
67             XmlEvent::StartElement { name, attributes, namespace: _ } => {
68                 Some((name.local_name, attributes))
69             }
70             _ => None,
71         }) {
72             match name.as_str() {
73                 "sdk-extensions-info" => {}
74                 "sdk" => {
75                     let sdk = Sdk {
76                         id: get_attribute(&attributes, "sdk", "id")?,
77                         shortname: get_attribute(&attributes, "sdk", "shortname")?,
78                         name: get_attribute(&attributes, "sdk", "name")?,
79                         reference: get_attribute(&attributes, "sdk", "reference")?,
80                     };
81                     sdks.push(sdk);
82                 }
83                 "symbol" => {
84                     let symbol = Symbol {
85                         jar: get_attribute(&attributes, "symbol", "jar")?,
86                         pattern: get_attribute(&attributes, "symbol", "pattern")?,
87                         sdks: get_attribute(&attributes, "symbol", "sdks")?
88                             .split(',')
89                             .map(|s| s.to_owned())
90                             .collect(),
91                     };
92                     symbols.push(symbol);
93                 }
94                 _ => bail!("unknown tag '{}'", name),
95             }
96         }
97 
98         // verify all Sdk fields are unique across all Sdk items
99         let dupes = sdks.iter().duplicates_by(|sdk| &sdk.id).collect::<Vec<_>>();
100         ensure!(dupes.is_empty(), "{:?}: multiple sdk entries with identical id value", dupes);
101 
102         let dupes = sdks.iter().duplicates_by(|sdk| &sdk.shortname).collect::<Vec<_>>();
103         ensure!(
104             dupes.is_empty(),
105             "{:?}: multiple sdk entries with identical shortname value",
106             dupes
107         );
108 
109         let dupes = sdks.iter().duplicates_by(|sdk| &sdk.name).collect::<Vec<_>>();
110         ensure!(dupes.is_empty(), "{:?}: multiple sdk entries with identical name value", dupes);
111 
112         let dupes = sdks.iter().duplicates_by(|sdk| &sdk.reference).collect::<Vec<_>>();
113         ensure!(
114             dupes.is_empty(),
115             "{:?}: multiple sdk entries with identical reference value",
116             dupes
117         );
118 
119         // verify Sdk id field has the expected format (positive integer)
120         for sdk in sdks.iter() {
121             ensure!(sdk.id.parse::<usize>().is_ok(), "{:?}: id not a positive int", sdk);
122         }
123 
124         // verify individual Symbol elements
125         let sdk_shortnames: Vec<_> = sdks.iter().map(|sdk| &sdk.shortname).collect();
126         for symbol in symbols.iter() {
127             ensure!(
128                 symbol.sdks.iter().duplicates().collect::<Vec<_>>().is_empty(),
129                 "{:?}: symbol contains duplicate references to the same sdk",
130                 symbol
131             );
132             ensure!(
133                 !symbol.jar.contains(char::is_whitespace),
134                 "{:?}: jar contains whitespace",
135                 symbol
136             );
137             ensure!(
138                 !symbol.pattern.contains(char::is_whitespace),
139                 "{:?}: pattern contains whitespace",
140                 symbol
141             );
142             if symbol.sdks.contains(&String::from("AD_SERVICES-ext")) {
143                 ensure!(
144                     symbol.sdks.len() == 1,
145                     "{:?}: AD_SERVICES-ext is mutually exclusive to all other sdks",
146                     symbol
147                 );
148             }
149             for id in symbol.sdks.iter() {
150                 ensure!(
151                     sdk_shortnames.contains(&id),
152                     "{:?}: symbol refers to non-existent sdk {}",
153                     symbol,
154                     id
155                 );
156             }
157         }
158 
159         Ok(())
160     }
161 
162     #[test]
test_get_attribute()163     fn test_get_attribute() {
164         use xml::EventReader;
165 
166         let mut iter = EventReader::from_str(r#"<tag a="A" b="B" c="C"/>"#).into_iter();
167         let _ = iter.next().unwrap(); // skip start of doc
168         let Ok(XmlEvent::StartElement { attributes, .. }) = iter.next().unwrap() else {
169             panic!();
170         };
171         assert_eq!(get_attribute(&attributes, "tag", "a").unwrap(), "A");
172         assert!(get_attribute(&attributes, "tag", "no-such-attribute").is_err());
173     }
174 
175     #[test]
test_verify_xml_correct_input()176     fn test_verify_xml_correct_input() {
177         verify_xml(&include_bytes!("testdata/correct.xml")[..]).unwrap();
178     }
179 
180     #[test]
test_verify_xml_incorrect_input()181     fn test_verify_xml_incorrect_input() {
182         macro_rules! assert_err {
183             ($input_path:expr, $expected_error:expr) => {
184                 let error = verify_xml(&include_bytes!($input_path)[..]).unwrap_err().to_string();
185                 assert_eq!(error, $expected_error);
186             };
187         }
188 
189         assert_err!(
190             "testdata/corrupt-xml.xml",
191             "25:1 Unexpected end of stream: still inside the root element"
192         );
193         assert_err!(
194             "testdata/duplicate-sdk-id.xml",
195             r#"[Sdk { id: "1", shortname: "bar", name: "The bar extensions", reference: "android/os/Build$BAR" }]: multiple sdk entries with identical id value"#
196         );
197         assert_err!(
198             "testdata/duplicate-sdk-shortname.xml",
199             r#"[Sdk { id: "2", shortname: "foo", name: "The bar extensions", reference: "android/os/Build$BAR" }]: multiple sdk entries with identical shortname value"#
200         );
201         assert_err!(
202             "testdata/duplicate-sdk-name.xml",
203             r#"[Sdk { id: "2", shortname: "bar", name: "The foo extensions", reference: "android/os/Build$BAR" }]: multiple sdk entries with identical name value"#
204         );
205         assert_err!(
206             "testdata/duplicate-sdk-reference.xml",
207             r#"[Sdk { id: "2", shortname: "bar", name: "The bar extensions", reference: "android/os/Build$FOO" }]: multiple sdk entries with identical reference value"#
208         );
209         assert_err!(
210             "testdata/incorrect-sdk-id-format.xml",
211             r#"Sdk { id: "1.0", shortname: "foo", name: "The foo extensions", reference: "android/os/Build$FOO" }: id not a positive int"#
212         );
213         assert_err!(
214             "testdata/duplicate-symbol-sdks.xml",
215             r#"Symbol { jar: "framework-something", pattern: "*", sdks: ["foo", "bar", "bar"] }: symbol contains duplicate references to the same sdk"#
216         );
217         assert_err!(
218             "testdata/symbol-refers-to-non-existent-sdk.xml",
219             r#"Symbol { jar: "framework-something", pattern: "*", sdks: ["foo", "does-not-exist", "bar"] }: symbol refers to non-existent sdk does-not-exist"#
220         );
221         assert_err!(
222             "testdata/whitespace-in-jar.xml",
223             r#"Symbol { jar: "framework something", pattern: "*", sdks: ["foo", "bar"] }: jar contains whitespace"#
224         );
225         assert_err!(
226             "testdata/whitespace-in-pattern.xml",
227             r#"Symbol { jar: "framework-something-else", pattern: "android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder\n                .addIndexableNestedProperties ", sdks: ["bar"] }: pattern contains whitespace"#
228         );
229         assert_err!(
230             "testdata/adservices-sdk-mixed-with-other-sdk.xml",
231             r#"Symbol { jar: "framework-something", pattern: "*", sdks: ["AD_SERVICES-ext", "foo"] }: AD_SERVICES-ext is mutually exclusive to all other sdks"#
232         );
233     }
234 
235     #[test]
test_actual_sdk_extensions_info_contents()236     fn test_actual_sdk_extensions_info_contents() {
237         verify_xml(&include_bytes!("../sdk-extensions-info.xml")[..]).unwrap();
238     }
239 }
240