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