# Copyright (C) 2020 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. """Test manifest split.""" import hashlib import mock import os import subprocess import tempfile import unittest import xml.etree.ElementTree as ET import manifest_split class ManifestSplitTest(unittest.TestCase): def test_read_config(self): with tempfile.NamedTemporaryFile('w+t') as test_config: test_config.write(""" """) test_config.flush() remove_projects, add_projects = manifest_split.read_config( test_config.name) self.assertEqual(remove_projects, set(['remove1', 'remove2'])) self.assertEqual(add_projects, set(['add1', 'add2'])) def test_get_repo_projects(self): with tempfile.NamedTemporaryFile('w+t') as repo_list_file: repo_list_file.write(""" system/project1 : platform/project1 system/project2 : platform/project2""") repo_list_file.flush() repo_projects = manifest_split.get_repo_projects(repo_list_file.name) self.assertEqual( repo_projects, { 'system/project1': 'platform/project1', 'system/project2': 'platform/project2', }) def test_get_module_info(self): with tempfile.NamedTemporaryFile('w+t') as module_info_file: module_info_file.write("""{ "target1a": { "path": ["system/project1"] }, "target1b": { "path": ["system/project1"] }, "target2": { "path": ["out/project2"] }, "target3": { "path": ["vendor/google/project3"] } }""") module_info_file.flush() repo_projects = { 'system/project1': 'platform/project1', 'vendor/google/project3': 'vendor/project3', } module_info = manifest_split.get_module_info(module_info_file.name, repo_projects) self.assertEqual( module_info, { 'platform/project1': set(['target1a', 'target1b']), 'vendor/project3': set(['target3']), }) def test_get_module_info_raises_on_unknown_module_path(self): with tempfile.NamedTemporaryFile('w+t') as module_info_file: module_info_file.write("""{ "target1": { "path": ["system/unknown/project1"] } }""") module_info_file.flush() repo_projects = {} with self.assertRaisesRegex(ValueError, 'Unknown module path for module target1'): manifest_split.get_module_info(module_info_file.name, repo_projects) @mock.patch.object(subprocess, 'check_output', autospec=True) def test_get_kati_makefiles(self, mock_check_output): with tempfile.TemporaryDirectory() as temp_dir: os.chdir(temp_dir) makefiles = [ 'device/oem1/product1.mk', 'device/oem2/product2.mk', 'device/google/google_product.mk', 'overlays/oem_overlay/device/oem3/product3.mk', 'packages/apps/Camera/Android.mk', ] for makefile in makefiles: os.makedirs(os.path.dirname(makefile)) os.mknod(makefile) symlink_src = os.path.join(temp_dir, 'vendor/oem4/symlink_src.mk') os.makedirs(os.path.dirname(symlink_src)) os.mknod(symlink_src) symlink_dest = 'device/oem4/symlink_dest.mk' os.makedirs(os.path.dirname(symlink_dest)) os.symlink(symlink_src, symlink_dest) # Only append the symlink destination, not where the symlink points to. # (The Kati stamp file does not resolve symlink sources.) makefiles.append(symlink_dest) # Mock the output of ckati_stamp_dump: mock_check_output.side_effect = [ '\n'.join(makefiles).encode(), ] kati_makefiles = manifest_split.get_kati_makefiles( 'stamp-file', ['overlays/oem_overlay/']) self.assertEqual( kati_makefiles, set([ # Regular product makefiles 'device/oem1/product1.mk', 'device/oem2/product2.mk', # Product makefile remapped from an overlay 'device/oem3/product3.mk', # Product makefile symlink and its source 'device/oem4/symlink_dest.mk', 'vendor/oem4/symlink_src.mk', ])) def test_scan_repo_projects(self): repo_projects = { 'system/project1': 'platform/project1', 'system/project2': 'platform/project2', } self.assertEqual( manifest_split.scan_repo_projects(repo_projects, 'system/project1/path/to/file.h'), 'system/project1') self.assertEqual( manifest_split.scan_repo_projects( repo_projects, 'system/project2/path/to/another_file.cc'), 'system/project2') self.assertIsNone( manifest_split.scan_repo_projects( repo_projects, 'system/project3/path/to/unknown_file.h')) def test_get_input_projects(self): repo_projects = { 'system/project1': 'platform/project1', 'system/project2': 'platform/project2', 'system/project4': 'platform/project4', } inputs = [ 'system/project1/path/to/file.h', 'out/path/to/out/file.h', 'system/project2/path/to/another_file.cc', 'system/project3/path/to/unknown_file.h', '/tmp/absolute/path/file.java', ] self.assertEqual( manifest_split.get_input_projects(repo_projects, inputs), set(['platform/project1', 'platform/project2'])) def test_update_manifest(self): manifest_contents = """ """ input_projects = set(['platform/project1', 'platform/project3']) remove_projects = set(['platform/project3']) manifest = manifest_split.update_manifest( ET.ElementTree(ET.fromstring(manifest_contents)), input_projects, remove_projects) projects = manifest.getroot().findall('project') self.assertEqual(len(projects), 1) self.assertEqual( ET.tostring(projects[0]).strip().decode(), '') def test_create_manifest_sha1_element(self): manifest = ET.ElementTree(ET.fromstring('')) manifest_sha1 = hashlib.sha1(ET.tostring(manifest.getroot())).hexdigest() self.assertEqual( ET.tostring( manifest_split.create_manifest_sha1_element( manifest, 'test_manifest')).decode(), '' % manifest_sha1) @mock.patch.object(subprocess, 'check_output', autospec=True) def test_create_split_manifest(self, mock_check_output): with tempfile.NamedTemporaryFile('w+t') as repo_list_file, \ tempfile.NamedTemporaryFile('w+t') as manifest_file, \ tempfile.NamedTemporaryFile('w+t') as module_info_file, \ tempfile.NamedTemporaryFile('w+t') as config_file, \ tempfile.NamedTemporaryFile('w+t') as split_manifest_file: repo_list_file.write(""" system/project1 : platform/project1 system/project2 : platform/project2 system/project3 : platform/project3 system/project4 : platform/project4 system/project5 : platform/project5 system/project6 : platform/project6""") repo_list_file.flush() manifest_file.write(""" """) manifest_file.flush() module_info_file.write("""{ "droid": { "path": ["system/project1"] }, "target_a": { "path": ["out/project2"] }, "target_b": { "path": ["system/project3"] }, "target_c": { "path": ["system/project4"] }, "target_d": { "path": ["system/project5"] }, "target_e": { "path": ["system/project6"] } }""") module_info_file.flush() # droid needs inputs from project1 and project3 ninja_inputs_droid = b""" system/project1/file1 system/project1/file2 system/project3/file1 """ # target_b (indirectly included due to being in project3) needs inputs # from project3 and project4 ninja_inputs_target_b = b""" system/project3/file2 system/project4/file1 """ # target_c (indirectly included due to being in project4) needs inputs # from only project4 ninja_inputs_target_c = b""" system/project4/file2 system/project4/file3 """ mock_check_output.side_effect = [ ninja_inputs_droid, b'', # Unused kati makefiles. This is tested in its own method. ninja_inputs_target_b, ninja_inputs_target_c, ] # The config file says to manually include project6 config_file.write(""" """) config_file.flush() manifest_split.create_split_manifest( ['droid'], manifest_file.name, split_manifest_file.name, [config_file.name], repo_list_file.name, 'build-target.ninja', 'ninja', module_info_file.name, 'unused kati stamp', ['unused overlay']) split_manifest = ET.parse(split_manifest_file.name) split_manifest_projects = [ child.attrib['name'] for child in split_manifest.getroot().findall('project') ] self.assertEqual( split_manifest_projects, [ # From droid 'platform/project1', # From droid 'platform/project3', # From target_b (module within project3, indirect dependency) 'platform/project4', # Manual inclusion from config file 'platform/project6', ]) if __name__ == '__main__': unittest.main()