1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright 2019 The Chromium OS Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7# pylint: disable=protected-access 8 9"""Unit tests when creating the arguments for the patch manager.""" 10 11from __future__ import print_function 12from collections import namedtuple 13import os 14import unittest 15import unittest.mock as mock 16 17from failure_modes import FailureModes 18import get_llvm_hash 19import llvm_patch_management 20import patch_manager 21import subprocess_helpers 22 23 24class LlvmPatchManagementTest(unittest.TestCase): 25 """Test class when constructing the arguments for the patch manager.""" 26 27 # Simulate the behavior of `os.path.isdir()` when the chroot path does not 28 # exist or is not a directory. 29 @mock.patch.object(os.path, 'isdir', return_value=False) 30 def testInvalidChrootPathWhenGetPathToFilesDir(self, mock_isdir): 31 chroot_path = '/some/path/to/chroot' 32 package = 'sys-devel/llvm' 33 34 # Verify the exception is raised when an invalid absolute path to the chroot 35 # is passed in. 36 with self.assertRaises(ValueError) as err: 37 llvm_patch_management.GetPathToFilesDirectory(chroot_path, package) 38 39 self.assertEqual( 40 str(err.exception), 'Invalid chroot provided: %s' % chroot_path) 41 42 mock_isdir.assert_called_once() 43 44 # Simulate the behavior of 'os.path.isdir()' when a valid chroot path is 45 # passed in. 46 @mock.patch.object(os.path, 'isdir', return_value=True) 47 @mock.patch.object(subprocess_helpers, 'ChrootRunCommand') 48 @mock.patch.object(llvm_patch_management, '_GetRelativePathOfChrootPath') 49 def testSuccessfullyGetPathToFilesDir( 50 self, mock_get_relative_path_of_chroot_path, mock_chroot_cmd, mock_isdir): 51 52 package_chroot_path = '/mnt/host/source/path/to/llvm/llvm.ebuild' 53 54 # Simulate behavior of 'ChrootRunCommand()' when successfully 55 # retrieved the absolute chroot path to the package's ebuild. 56 mock_chroot_cmd.return_value = package_chroot_path 57 58 # Simulate behavior of '_GetRelativePathOfChrootPath()' when successfully 59 # removed '/mnt/host/source' of the absolute chroot path to the package's 60 # ebuild. 61 # 62 # Returns relative path after '/mnt/host/source/'. 63 mock_get_relative_path_of_chroot_path.return_value = 'path/to/llvm' 64 65 chroot_path = '/some/path/to/chroot' 66 67 package = 'sys-devel/llvm' 68 69 self.assertEqual( 70 llvm_patch_management.GetPathToFilesDirectory(chroot_path, package), 71 '/some/path/to/chroot/path/to/llvm/files/') 72 73 mock_isdir.assert_called_once() 74 75 mock_chroot_cmd.assert_called_once() 76 77 mock_get_relative_path_of_chroot_path.assert_called_once_with( 78 '/mnt/host/source/path/to/llvm') 79 80 def testInvalidPrefixForChrootPath(self): 81 package_chroot_path = '/path/to/llvm' 82 83 # Verify the exception is raised when the chroot path does not start with 84 # '/mnt/host/source/'. 85 with self.assertRaises(ValueError) as err: 86 llvm_patch_management._GetRelativePathOfChrootPath(package_chroot_path) 87 88 self.assertEqual( 89 str(err.exception), 90 'Invalid prefix for the chroot path: %s' % package_chroot_path) 91 92 def testValidPrefixForChrootPath(self): 93 package_chroot_path = '/mnt/host/source/path/to/llvm' 94 95 package_rel_path = 'path/to/llvm' 96 97 self.assertEqual( 98 llvm_patch_management._GetRelativePathOfChrootPath(package_chroot_path), 99 package_rel_path) 100 101 # Simulate behavior of 'os.path.isfile()' when the patch metadata file does 102 # not exist. 103 @mock.patch.object(os.path, 'isfile', return_value=False) 104 def testInvalidFileForPatchMetadataPath(self, mock_isfile): 105 abs_path_to_patch_file = '/abs/path/to/files/test.json' 106 107 # Verify the exception is raised when the absolute path to the patch 108 # metadata file does not exist. 109 with self.assertRaises(ValueError) as err: 110 llvm_patch_management._CheckPatchMetadataPath(abs_path_to_patch_file) 111 112 self.assertEqual( 113 str(err.exception), 114 'Invalid file provided: %s' % abs_path_to_patch_file) 115 116 mock_isfile.assert_called_once() 117 118 # Simulate behavior of 'os.path.isfile()' when the absolute path to the 119 # patch metadata file exists. 120 @mock.patch.object(os.path, 'isfile', return_value=True) 121 def testPatchMetadataFileDoesNotEndInJson(self, mock_isfile): 122 abs_path_to_patch_file = '/abs/path/to/files/PATCHES' 123 124 # Verify the exception is raised when the patch metadata file does not end 125 # in '.json'. 126 with self.assertRaises(ValueError) as err: 127 llvm_patch_management._CheckPatchMetadataPath(abs_path_to_patch_file) 128 129 self.assertEqual( 130 str(err.exception), 131 'File does not end in ".json": %s' % abs_path_to_patch_file) 132 133 mock_isfile.assert_called_once() 134 135 @mock.patch.object(os.path, 'isfile') 136 def testValidPatchMetadataFile(self, mock_isfile): 137 abs_path_to_patch_file = '/abs/path/to/files/PATCHES.json' 138 139 # Simulate behavior of 'os.path.isfile()' when the absolute path to the 140 # patch metadata file exists. 141 mock_isfile.return_value = True 142 143 llvm_patch_management._CheckPatchMetadataPath(abs_path_to_patch_file) 144 145 mock_isfile.assert_called_once() 146 147 # Simulate `GetGitHashFrom()` when successfully retrieved the git hash 148 # of the version passed in. 149 @mock.patch.object( 150 get_llvm_hash, 'GetGitHashFrom', return_value='a123testhash1') 151 # Simulate `CreateTempLLVMRepo()` when successfully created a work tree from 152 # the LLVM repo copy in `llvm_tools` directory. 153 @mock.patch.object(get_llvm_hash, 'CreateTempLLVMRepo') 154 # Simulate behavior of `_MoveSrcTreeHEADToGitHash()` when successfully moved 155 # the head pointer to the git hash of the revision. 156 @mock.patch.object(llvm_patch_management, '_MoveSrcTreeHEADToGitHash') 157 @mock.patch.object(llvm_patch_management, 'GetPathToFilesDirectory') 158 @mock.patch.object(llvm_patch_management, '_CheckPatchMetadataPath') 159 def testExceptionIsRaisedWhenUpdatingAPackagesMetadataFile( 160 self, mock_check_patch_metadata_path, mock_get_filesdir_path, 161 mock_move_head_pointer, mock_create_temp_llvm_repo, mock_get_git_hash): 162 163 abs_path_to_patch_file = \ 164 '/some/path/to/chroot/some/path/to/filesdir/PATCHES' 165 166 # Simulate the behavior of '_CheckPatchMetadataPath()' when the patch 167 # metadata file in $FILESDIR does not exist or does not end in '.json'. 168 def InvalidPatchMetadataFile(patch_metadata_path): 169 self.assertEqual(patch_metadata_path, abs_path_to_patch_file) 170 171 raise ValueError( 172 'File does not end in ".json": %s' % abs_path_to_patch_file) 173 174 # Use the test function to simulate behavior of '_CheckPatchMetadataPath()'. 175 mock_check_patch_metadata_path.side_effect = InvalidPatchMetadataFile 176 177 abs_path_to_filesdir = '/some/path/to/chroot/some/path/to/filesdir' 178 179 # Simulate the behavior of 'GetPathToFilesDirectory()' when successfully 180 # constructed the absolute path to $FILESDIR of a package. 181 mock_get_filesdir_path.return_value = abs_path_to_filesdir 182 183 temp_work_tree = '/abs/path/to/tmpWorkTree' 184 185 # Simulate the behavior of returning the absolute path to a worktree via 186 # `git worktree add`. 187 mock_create_temp_llvm_repo.return_value.__enter__.return_value.name = \ 188 temp_work_tree 189 190 chroot_path = '/some/path/to/chroot' 191 revision = 1000 192 patch_file_name = 'PATCHES' 193 package_name = 'test-package/package1' 194 195 # Verify the exception is raised when a package is constructing the 196 # arguments for the patch manager to update its patch metadata file and an 197 # exception is raised in the process. 198 with self.assertRaises(ValueError) as err: 199 llvm_patch_management.UpdatePackagesPatchMetadataFile( 200 chroot_path, revision, patch_file_name, [package_name], 201 FailureModes.FAIL) 202 203 self.assertEqual( 204 str(err.exception), 205 'File does not end in ".json": %s' % abs_path_to_patch_file) 206 207 mock_get_filesdir_path.assert_called_once_with(chroot_path, package_name) 208 209 mock_get_git_hash.assert_called_once() 210 211 mock_check_patch_metadata_path.assert_called_once() 212 213 mock_move_head_pointer.assert_called_once() 214 215 mock_create_temp_llvm_repo.assert_called_once() 216 217 # Simulate `CleanSrcTree()` when successfully removed changes from the 218 # worktree. 219 @mock.patch.object(patch_manager, 'CleanSrcTree') 220 # Simulate `GetGitHashFrom()` when successfully retrieved the git hash 221 # of the version passed in. 222 @mock.patch.object( 223 get_llvm_hash, 'GetGitHashFrom', return_value='a123testhash1') 224 # Simulate `CreateTempLLVMRepo()` when successfully created a work tree from 225 # the LLVM repo copy in `llvm_tools` directory. 226 @mock.patch.object(get_llvm_hash, 'CreateTempLLVMRepo') 227 # Simulate behavior of `_MoveSrcTreeHEADToGitHash()` when successfully moved 228 # the head pointer to the git hash of the revision. 229 @mock.patch.object(llvm_patch_management, '_MoveSrcTreeHEADToGitHash') 230 @mock.patch.object(llvm_patch_management, 'GetPathToFilesDirectory') 231 @mock.patch.object(llvm_patch_management, '_CheckPatchMetadataPath') 232 @mock.patch.object(patch_manager, 'HandlePatches') 233 def testSuccessfullyRetrievedPatchResults( 234 self, mock_handle_patches, mock_check_patch_metadata_path, 235 mock_get_filesdir_path, mock_move_head_pointer, 236 mock_create_temp_llvm_repo, mock_get_git_hash, mock_clean_src_tree): 237 238 abs_path_to_filesdir = '/some/path/to/chroot/some/path/to/filesdir' 239 240 abs_path_to_patch_file = \ 241 '/some/path/to/chroot/some/path/to/filesdir/PATCHES.json' 242 243 # Simulate the behavior of 'GetPathToFilesDirectory()' when successfully 244 # constructed the absolute path to $FILESDIR of a package. 245 mock_get_filesdir_path.return_value = abs_path_to_filesdir 246 247 PatchInfo = namedtuple('PatchInfo', [ 248 'applied_patches', 'failed_patches', 'non_applicable_patches', 249 'disabled_patches', 'removed_patches', 'modified_metadata' 250 ]) 251 252 # Simulate the behavior of 'HandlePatches()' when successfully iterated 253 # through every patch in the patch metadata file and a dictionary is 254 # returned that contains information about the patches' status. 255 mock_handle_patches.return_value = PatchInfo( 256 applied_patches=['fixes_something.patch'], 257 failed_patches=['disables_output.patch'], 258 non_applicable_patches=[], 259 disabled_patches=[], 260 removed_patches=[], 261 modified_metadata=None) 262 263 temp_work_tree = '/abs/path/to/tmpWorkTree' 264 265 # Simulate the behavior of returning the absolute path to a worktree via 266 # `git worktree add`. 267 mock_create_temp_llvm_repo.return_value.__enter__.return_value.name = \ 268 temp_work_tree 269 270 expected_patch_results = { 271 'applied_patches': ['fixes_something.patch'], 272 'failed_patches': ['disables_output.patch'], 273 'non_applicable_patches': [], 274 'disabled_patches': [], 275 'removed_patches': [], 276 'modified_metadata': None 277 } 278 279 chroot_path = '/some/path/to/chroot' 280 revision = 1000 281 patch_file_name = 'PATCHES.json' 282 package_name = 'test-package/package2' 283 284 patch_info = llvm_patch_management.UpdatePackagesPatchMetadataFile( 285 chroot_path, revision, patch_file_name, [package_name], 286 FailureModes.CONTINUE) 287 288 self.assertDictEqual(patch_info, {package_name: expected_patch_results}) 289 290 mock_get_filesdir_path.assert_called_once_with(chroot_path, package_name) 291 292 mock_check_patch_metadata_path.assert_called_once_with( 293 abs_path_to_patch_file) 294 295 mock_handle_patches.assert_called_once() 296 297 mock_create_temp_llvm_repo.assert_called_once() 298 299 mock_get_git_hash.assert_called_once() 300 301 mock_move_head_pointer.assert_called_once() 302 303 mock_clean_src_tree.assert_called_once() 304 305 306if __name__ == '__main__': 307 unittest.main() 308