1# Copyright (C) 2020 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"""Test manifest split.""" 15 16import hashlib 17import mock 18import os 19import subprocess 20import tempfile 21import unittest 22import xml.etree.ElementTree as ET 23 24import manifest_split 25 26 27class ManifestSplitTest(unittest.TestCase): 28 29 def test_read_config(self): 30 with tempfile.NamedTemporaryFile('w+t') as test_config: 31 test_config.write(""" 32 <config> 33 <add_project name="add1" /> 34 <add_project name="add2" /> 35 <remove_project name="remove1" /> 36 <remove_project name="remove2" /> 37 </config>""") 38 test_config.flush() 39 remove_projects, add_projects = manifest_split.read_config( 40 test_config.name) 41 self.assertEqual(remove_projects, set(['remove1', 'remove2'])) 42 self.assertEqual(add_projects, set(['add1', 'add2'])) 43 44 def test_get_repo_projects(self): 45 with tempfile.NamedTemporaryFile('w+t') as repo_list_file: 46 repo_list_file.write(""" 47 system/project1 : platform/project1 48 system/project2 : platform/project2""") 49 repo_list_file.flush() 50 repo_projects = manifest_split.get_repo_projects(repo_list_file.name) 51 self.assertEqual( 52 repo_projects, { 53 'system/project1': 'platform/project1', 54 'system/project2': 'platform/project2', 55 }) 56 57 def test_get_module_info(self): 58 with tempfile.NamedTemporaryFile('w+t') as module_info_file: 59 module_info_file.write("""{ 60 "target1a": { "path": ["system/project1"] }, 61 "target1b": { "path": ["system/project1"] }, 62 "target2": { "path": ["out/project2"] }, 63 "target3": { "path": ["vendor/google/project3"] } 64 }""") 65 module_info_file.flush() 66 repo_projects = { 67 'system/project1': 'platform/project1', 68 'vendor/google/project3': 'vendor/project3', 69 } 70 module_info = manifest_split.get_module_info(module_info_file.name, 71 repo_projects) 72 self.assertEqual( 73 module_info, { 74 'platform/project1': set(['target1a', 'target1b']), 75 'vendor/project3': set(['target3']), 76 }) 77 78 def test_get_module_info_raises_on_unknown_module_path(self): 79 with tempfile.NamedTemporaryFile('w+t') as module_info_file: 80 module_info_file.write("""{ 81 "target1": { "path": ["system/unknown/project1"] } 82 }""") 83 module_info_file.flush() 84 repo_projects = {} 85 with self.assertRaisesRegex(ValueError, 86 'Unknown module path for module target1'): 87 manifest_split.get_module_info(module_info_file.name, repo_projects) 88 89 @mock.patch.object(subprocess, 'check_output', autospec=True) 90 def test_get_kati_makefiles(self, mock_check_output): 91 with tempfile.TemporaryDirectory() as temp_dir: 92 os.chdir(temp_dir) 93 94 makefiles = [ 95 'device/oem1/product1.mk', 96 'device/oem2/product2.mk', 97 'device/google/google_product.mk', 98 'overlays/oem_overlay/device/oem3/product3.mk', 99 'packages/apps/Camera/Android.mk', 100 ] 101 for makefile in makefiles: 102 os.makedirs(os.path.dirname(makefile)) 103 os.mknod(makefile) 104 105 symlink_src = os.path.join(temp_dir, 'vendor/oem4/symlink_src.mk') 106 os.makedirs(os.path.dirname(symlink_src)) 107 os.mknod(symlink_src) 108 symlink_dest = 'device/oem4/symlink_dest.mk' 109 os.makedirs(os.path.dirname(symlink_dest)) 110 os.symlink(symlink_src, symlink_dest) 111 # Only append the symlink destination, not where the symlink points to. 112 # (The Kati stamp file does not resolve symlink sources.) 113 makefiles.append(symlink_dest) 114 115 # Mock the output of ckati_stamp_dump: 116 mock_check_output.side_effect = [ 117 '\n'.join(makefiles).encode(), 118 ] 119 120 kati_makefiles = manifest_split.get_kati_makefiles( 121 'stamp-file', ['overlays/oem_overlay/']) 122 self.assertEqual( 123 kati_makefiles, 124 set([ 125 # Regular product makefiles 126 'device/oem1/product1.mk', 127 'device/oem2/product2.mk', 128 # Product makefile remapped from an overlay 129 'device/oem3/product3.mk', 130 # Product makefile symlink and its source 131 'device/oem4/symlink_dest.mk', 132 'vendor/oem4/symlink_src.mk', 133 ])) 134 135 def test_scan_repo_projects(self): 136 repo_projects = { 137 'system/project1': 'platform/project1', 138 'system/project2': 'platform/project2', 139 } 140 self.assertEqual( 141 manifest_split.scan_repo_projects(repo_projects, 142 'system/project1/path/to/file.h'), 143 'system/project1') 144 self.assertEqual( 145 manifest_split.scan_repo_projects( 146 repo_projects, 'system/project2/path/to/another_file.cc'), 147 'system/project2') 148 self.assertIsNone( 149 manifest_split.scan_repo_projects( 150 repo_projects, 'system/project3/path/to/unknown_file.h')) 151 152 def test_get_input_projects(self): 153 repo_projects = { 154 'system/project1': 'platform/project1', 155 'system/project2': 'platform/project2', 156 'system/project4': 'platform/project4', 157 } 158 inputs = [ 159 'system/project1/path/to/file.h', 160 'out/path/to/out/file.h', 161 'system/project2/path/to/another_file.cc', 162 'system/project3/path/to/unknown_file.h', 163 '/tmp/absolute/path/file.java', 164 ] 165 self.assertEqual( 166 manifest_split.get_input_projects(repo_projects, inputs), 167 set(['platform/project1', 'platform/project2'])) 168 169 def test_update_manifest(self): 170 manifest_contents = """ 171 <manifest> 172 <project name="platform/project1" path="system/project1" /> 173 <project name="platform/project2" path="system/project2" /> 174 <project name="platform/project3" path="system/project3" /> 175 </manifest>""" 176 input_projects = set(['platform/project1', 'platform/project3']) 177 remove_projects = set(['platform/project3']) 178 manifest = manifest_split.update_manifest( 179 ET.ElementTree(ET.fromstring(manifest_contents)), input_projects, 180 remove_projects) 181 182 projects = manifest.getroot().findall('project') 183 self.assertEqual(len(projects), 1) 184 self.assertEqual( 185 ET.tostring(projects[0]).strip().decode(), 186 '<project name="platform/project1" path="system/project1" />') 187 188 def test_create_manifest_sha1_element(self): 189 manifest = ET.ElementTree(ET.fromstring('<manifest></manifest>')) 190 manifest_sha1 = hashlib.sha1(ET.tostring(manifest.getroot())).hexdigest() 191 self.assertEqual( 192 ET.tostring( 193 manifest_split.create_manifest_sha1_element( 194 manifest, 'test_manifest')).decode(), 195 '<hash name="test_manifest" type="sha1" value="%s" />' % manifest_sha1) 196 197 @mock.patch.object(subprocess, 'check_output', autospec=True) 198 def test_create_split_manifest(self, mock_check_output): 199 with tempfile.NamedTemporaryFile('w+t') as repo_list_file, \ 200 tempfile.NamedTemporaryFile('w+t') as manifest_file, \ 201 tempfile.NamedTemporaryFile('w+t') as module_info_file, \ 202 tempfile.NamedTemporaryFile('w+t') as config_file, \ 203 tempfile.NamedTemporaryFile('w+t') as split_manifest_file: 204 205 repo_list_file.write(""" 206 system/project1 : platform/project1 207 system/project2 : platform/project2 208 system/project3 : platform/project3 209 system/project4 : platform/project4 210 system/project5 : platform/project5 211 system/project6 : platform/project6""") 212 repo_list_file.flush() 213 214 manifest_file.write(""" 215 <manifest> 216 <project name="platform/project1" path="system/project1" /> 217 <project name="platform/project2" path="system/project2" /> 218 <project name="platform/project3" path="system/project3" /> 219 <project name="platform/project4" path="system/project4" /> 220 <project name="platform/project5" path="system/project5" /> 221 <project name="platform/project6" path="system/project6" /> 222 </manifest>""") 223 manifest_file.flush() 224 225 module_info_file.write("""{ 226 "droid": { "path": ["system/project1"] }, 227 "target_a": { "path": ["out/project2"] }, 228 "target_b": { "path": ["system/project3"] }, 229 "target_c": { "path": ["system/project4"] }, 230 "target_d": { "path": ["system/project5"] }, 231 "target_e": { "path": ["system/project6"] } 232 }""") 233 module_info_file.flush() 234 235 # droid needs inputs from project1 and project3 236 ninja_inputs_droid = b""" 237 system/project1/file1 238 system/project1/file2 239 system/project3/file1 240 """ 241 242 # target_b (indirectly included due to being in project3) needs inputs 243 # from project3 and project4 244 ninja_inputs_target_b = b""" 245 system/project3/file2 246 system/project4/file1 247 """ 248 249 # target_c (indirectly included due to being in project4) needs inputs 250 # from only project4 251 ninja_inputs_target_c = b""" 252 system/project4/file2 253 system/project4/file3 254 """ 255 256 mock_check_output.side_effect = [ 257 ninja_inputs_droid, 258 b'', # Unused kati makefiles. This is tested in its own method. 259 ninja_inputs_target_b, 260 ninja_inputs_target_c, 261 ] 262 263 # The config file says to manually include project6 264 config_file.write(""" 265 <config> 266 <add_project name="platform/project6" /> 267 </config>""") 268 config_file.flush() 269 270 manifest_split.create_split_manifest( 271 ['droid'], manifest_file.name, split_manifest_file.name, 272 [config_file.name], repo_list_file.name, 'build-target.ninja', 273 'ninja', module_info_file.name, 'unused kati stamp', 274 ['unused overlay']) 275 split_manifest = ET.parse(split_manifest_file.name) 276 split_manifest_projects = [ 277 child.attrib['name'] 278 for child in split_manifest.getroot().findall('project') 279 ] 280 self.assertEqual( 281 split_manifest_projects, 282 [ 283 # From droid 284 'platform/project1', 285 # From droid 286 'platform/project3', 287 # From target_b (module within project3, indirect dependency) 288 'platform/project4', 289 # Manual inclusion from config file 290 'platform/project6', 291 ]) 292 293 294if __name__ == '__main__': 295 unittest.main() 296