xref: /aosp_15_r20/development/tools/external_crates/test_mapping/src/blueprint.rs (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1 // Copyright (C) 2024 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Convenience methods for working with Android blueprint files.
16 
17 use std::{
18     collections::{BTreeSet, HashSet},
19     sync::LazyLock,
20 };
21 
22 use android_bp::{BluePrint, Value};
23 
24 use crate::TestMappingError;
25 
26 /// Extract rust test rules from a blueprint file.
27 pub(crate) trait RustTests {
28     /// Returns the names of all rust_test and rust_test_host rules.
rust_tests(&self) -> Result<BTreeSet<String>, TestMappingError>29     fn rust_tests(&self) -> Result<BTreeSet<String>, TestMappingError>;
30 }
31 
32 impl RustTests for BluePrint {
rust_tests(&self) -> Result<BTreeSet<String>, TestMappingError>33     fn rust_tests(&self) -> Result<BTreeSet<String>, TestMappingError> {
34         let mut tests = BTreeSet::new();
35         for module in &self.modules {
36             if matches!(module.typ.as_str(), "rust_test" | "rust_test_host") {
37                 let name = module
38                     .get_string("name")
39                     .ok_or(TestMappingError::RuleWithoutName(module.typ.clone()))?
40                     .clone();
41                 if !EXCLUDED_TESTS.contains(name.as_str()) {
42                     tests.insert(name);
43                 }
44             }
45         }
46         Ok(tests)
47     }
48 }
49 
50 /// Finds all the rustlib dependencies mentioned in a blueprint file.
51 pub(crate) trait RustDeps {
52     // Finds all the rustlibs mentioned in a blueprint file.
53     // This does a limited amount of evaluation, by doing concatenation and resolving
54     // identifiers. So you can have `common_rustlibs = ["foo", "bar"] and do
55     // rust_library { rustlibs = common_rustlibs + ["baz"] }`
rust_deps(&self) -> BTreeSet<String>56     fn rust_deps(&self) -> BTreeSet<String>;
57 }
58 
59 impl RustDeps for BluePrint {
rust_deps(&self) -> BTreeSet<String>60     fn rust_deps(&self) -> BTreeSet<String> {
61         let mut rustlibs = BTreeSet::new();
62         for module in &self.modules {
63             if let Some(v) = module.get("rustlibs") {
64                 match v {
65                     Value::Array(_) => rustlibs.extend(v.as_string_vec()),
66                     Value::ConcatExpr(_) => rustlibs.extend(v.eval(self)),
67                     _ => {
68                         println!("Only know how to handle Array and ConcatExpr");
69                     }
70                 }
71             }
72         }
73         rustlibs
74     }
75 }
76 
77 // Convenience accessor for arrays of strings.
78 trait AsStringVec {
79     // Interpret a android_bp::Value as an array of strings, and convert it to
80     // an array of owned strings. Any element that isn't a string is skipped.
as_string_vec(&self) -> Vec<String>81     fn as_string_vec(&self) -> Vec<String>;
82 }
83 
84 impl AsStringVec for Value {
as_string_vec(&self) -> Vec<String>85     fn as_string_vec(&self) -> Vec<String> {
86         if let Value::Array(vec) = self {
87             vec.iter()
88                 .filter_map(|v| match v {
89                     Value::String(s) => Some(s.to_string()),
90                     _ => {
91                         println!("Array element is not a string");
92                         None
93                     }
94                 })
95                 .collect()
96         } else {
97             println!("Value is not an array");
98             vec![]
99         }
100     }
101 }
102 
103 // Evaluate concatenations and resolve identifiers.
104 trait EvalConcat {
105     // Evaluate a concatenation expression, resolving it into a single vector of strings.
106     // The elements being concatenated are assumed to be either identifiers or
107     // arrays of strings. Otherwise, they are skipped.
eval(&self, bp: &BluePrint) -> Vec<String>108     fn eval(&self, bp: &BluePrint) -> Vec<String>;
109 }
110 
111 impl EvalConcat for Value {
eval(&self, bp: &BluePrint) -> Vec<String>112     fn eval(&self, bp: &BluePrint) -> Vec<String> {
113         let mut strings = Vec::new();
114         if let Value::ConcatExpr(expr) = self {
115             for term in expr {
116                 match term {
117                     Value::Array(_) => strings.extend(term.as_string_vec()),
118                     Value::Ident(ident) => {
119                         if let Some(ident_val) = bp.variables.get(ident) {
120                             strings.extend(ident_val.as_string_vec());
121                         }
122                     }
123                     _ => {
124                         println!("Concat term is neither ident nor array");
125                     }
126                 }
127             }
128         } else {
129             println!("Value is not a ConcatExpr");
130         }
131         strings
132     }
133 }
134 
135 // Taken from update_crate_tests.py
136 static EXCLUDED_TESTS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
137     HashSet::from([
138         "ash_test_src_lib",
139         "ash_test_tests_constant_size_arrays",
140         "ash_test_tests_display",
141         "shared_library_test_src_lib",
142         "vulkano_test_src_lib",
143         // These are helper binaries for aidl_integration_test
144         // and aren't actually meant to run as individual tests.
145         "aidl_test_rust_client",
146         "aidl_test_rust_service",
147         "aidl_test_rust_service_async",
148         // This is a helper binary for AuthFsHostTest and shouldn't
149         // be run directly.
150         "open_then_run",
151         // TODO: Remove when b/198197213 is closed.
152         "diced_client_test",
153         "CoverageRustSmokeTest",
154         "libtrusty-rs-tests",
155         "terminal-size_test_src_lib",
156     ])
157 });
158 
159 #[cfg(test)]
160 mod tests {
161     use super::*;
162 
163     #[test]
rust_tests() -> Result<(), TestMappingError>164     fn rust_tests() -> Result<(), TestMappingError> {
165         let bp = BluePrint::parse(
166             r###"
167 rust_test { name: "foo" }
168 rust_test_host { name: "bar" }
169 "###,
170         )
171         .expect("Blueprint parse error");
172         assert_eq!(bp.rust_tests()?, BTreeSet::from(["foo".to_string(), "bar".to_string()]));
173         Ok(())
174     }
175 
176     #[test]
rust_deps()177     fn rust_deps() {
178         let bp = BluePrint::parse(
179             r###"
180 rust_library { rustlibs: ["foo", "bar"] }
181 rust_library { rustlibs: ["bar", "baz"] }
182 "###,
183         )
184         .expect("Blueprint parse error");
185         assert_eq!(
186             bp.rust_deps(),
187             BTreeSet::from(["foo".to_string(), "bar".to_string(), "baz".to_string()])
188         );
189     }
190 
191     #[test]
rust_deps_eval()192     fn rust_deps_eval() {
193         let bp = BluePrint::parse(
194             r###"
195 foo = ["foo"]
196 rust_library { rustlibs: foo + ["bar"] }
197 "###,
198         )
199         .expect("Blueprint parse error");
200         assert_eq!(bp.rust_deps(), BTreeSet::from(["foo".to_string(), "bar".to_string()]));
201     }
202 }
203