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 //! Read, update, and write TEST_MAPPING files. 16 17 mod blueprint; 18 mod json; 19 mod rdeps; 20 21 use std::{ 22 collections::BTreeSet, 23 fs::{read_to_string, remove_file, write}, 24 io, 25 path::PathBuf, 26 str::Utf8Error, 27 }; 28 29 use android_bp::BluePrint; 30 use blueprint::RustTests; 31 use json::{TestMappingJson, TestMappingPath}; 32 use rdeps::ReverseDeps; 33 use rooted_path::RootedPath; 34 use thiserror::Error; 35 36 #[derive(Error, Debug)] 37 pub enum TestMappingError { 38 #[error("Blueprint file {0} not found")] 39 BlueprintNotFound(PathBuf), 40 #[error("Blueprint parse error: {0}")] 41 BlueprintParseError(String), 42 #[error("Blueprint rule has no name")] 43 RuleWithoutName(String), 44 45 #[error("Error stripping JSON comments")] 46 StripJsonCommentsError(io::Error), 47 #[error("JSON parse error")] 48 JsonParseError(#[from] serde_json::Error), 49 50 #[error("IO error")] 51 IoError(#[from] io::Error), 52 #[error("Command output not UTF-8")] 53 Utf8Error(#[from] Utf8Error), 54 #[error("Failed to split grep output line {0} on '/'")] 55 GrepParseError(String), 56 } 57 58 #[derive(Debug)] 59 pub struct TestMapping { 60 path: RootedPath, 61 json: TestMappingJson, 62 bp: BluePrint, 63 } 64 65 impl TestMapping { 66 /// Read the TEST_MAPPING file in the specified directory. 67 /// If there is no TEST_MAPPING file, a default value is returned. 68 /// Also reads the Android.bp in the directory, because we need that 69 /// information to update the TEST_MAPPING. read(path: RootedPath) -> Result<TestMapping, TestMappingError>70 pub fn read(path: RootedPath) -> Result<TestMapping, TestMappingError> { 71 let bpfile = path.join("Android.bp").unwrap(); 72 if !bpfile.abs().exists() { 73 return Err(TestMappingError::BlueprintNotFound(bpfile.rel().to_path_buf())); 74 } 75 let bp = BluePrint::from_file(bpfile) 76 .map_err(|e: String| TestMappingError::BlueprintParseError(e))?; 77 let test_mapping_path = path.join("TEST_MAPPING").unwrap(); 78 let json = if path.abs().exists() { 79 TestMappingJson::parse(read_to_string(test_mapping_path)?)? 80 } else { 81 TestMappingJson::default() 82 }; 83 Ok(TestMapping { path, json, bp }) 84 } 85 /// Write the TEST_MAPPING file. write(&self) -> Result<(), TestMappingError>86 pub fn write(&self) -> Result<(), TestMappingError> { 87 if self.json.is_empty() && self.test_mapping().abs().exists() { 88 remove_file(self.test_mapping())?; 89 } else { 90 let mut contents = serde_json::to_string_pretty(&self.json)?; 91 contents.push('\n'); 92 write(self.test_mapping(), contents)?; 93 } 94 Ok(()) 95 } 96 /// Update the presubmit and presubmit_rust fields to the 97 /// set of test targets in the Android.bp file. 98 /// Since adding new tests directly to presubmits is discouraged, 99 /// It's preferable to use add_new_tests_to_postsubmit and 100 /// convert_postsubmit_tests instead. update_presubmits(&mut self) -> Result<(), TestMappingError>101 pub fn update_presubmits(&mut self) -> Result<(), TestMappingError> { 102 self.json.set_presubmits(&self.bp.rust_tests()?); 103 Ok(()) 104 } 105 /// Add tests that aren't already mentioned in TEST_MAPPING 106 /// as post-submit tests. add_new_tests_to_postsubmit(&mut self) -> Result<bool, TestMappingError>107 pub fn add_new_tests_to_postsubmit(&mut self) -> Result<bool, TestMappingError> { 108 Ok(self.json.add_new_tests_to_postsubmit(&self.bp.rust_tests()?)) 109 } 110 /// Convert post-submit tests to run at presubmit. convert_postsubmit_tests(&mut self) -> bool111 pub fn convert_postsubmit_tests(&mut self) -> bool { 112 self.json.convert_postsubmit_tests() 113 } 114 /// Fix the import paths of TEST_MAPPING files to refer to the monorepo. 115 /// Essentially, replace external/rust/crates with external/rust/android-crates-io/crates. fix_import_paths(&mut self) -> bool116 pub fn fix_import_paths(&mut self) -> bool { 117 let mut changed = false; 118 for import in self.json.imports.iter_mut() { 119 if import.path.starts_with("external/rust/crates") { 120 let new_path = import 121 .path 122 .replace("external/rust/crates", "external/rust/android-crates-io/crates"); 123 if self.path.with_same_root(new_path.clone()).unwrap().abs().exists() { 124 import.path = new_path; 125 changed = true; 126 } 127 } 128 } 129 changed 130 } 131 /// Update the imports section of TEST_MAPPING to contain all the 132 /// paths that depend on this crate. update_imports(&mut self) -> Result<(), TestMappingError>133 pub fn update_imports(&mut self) -> Result<(), TestMappingError> { 134 let all_rdeps = ReverseDeps::for_repo(self.path.root()); 135 let mut rdeps = BTreeSet::new(); 136 for lib in self.libs()? { 137 if let Some(paths) = all_rdeps.get(lib.as_str()) { 138 rdeps.append(&mut paths.clone()); 139 } 140 } 141 let self_path = self.path.rel().to_str().unwrap(); 142 self.json.imports = rdeps 143 .iter() 144 .filter(|path| path.as_str() != self_path) 145 .map(|t| TestMappingPath { path: t.to_string() }) 146 .collect(); 147 Ok(()) 148 } test_mapping(&self) -> RootedPath149 fn test_mapping(&self) -> RootedPath { 150 self.path.join("TEST_MAPPING").unwrap() 151 } libs(&self) -> Result<Vec<String>, TestMappingError>152 fn libs(&self) -> Result<Vec<String>, TestMappingError> { 153 let mut libs = Vec::new(); 154 for module in &self.bp.modules { 155 if matches!( 156 module.typ.as_str(), 157 "rust_library" | "rust_library_rlib" | "rust_library_host" | "rust_proc_macro" 158 ) { 159 libs.push( 160 module 161 .get_string("name") 162 .ok_or(TestMappingError::RuleWithoutName(module.typ.clone()))? 163 .clone(), 164 ); 165 } 166 } 167 Ok(libs) 168 } 169 } 170