1#!/usr/bin/env python3 2# Copyright 2019 The ChromiumOS Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Unit tests when handling patches.""" 7 8import json 9from pathlib import Path 10import tempfile 11from typing import Callable 12import unittest 13from unittest import mock 14 15import atomic_write_file 16import patch_manager 17import patch_utils 18 19 20class PatchManagerTest(unittest.TestCase): 21 """Test class when handling patches of packages.""" 22 23 # Simulate behavior of 'os.path.isdir()' when the path is not a directory. 24 @mock.patch.object(Path, "is_dir", return_value=False) 25 def testInvalidDirectoryPassedAsCommandLineArgument(self, mock_isdir): 26 src_dir = "/some/path/that/is/not/a/directory" 27 patch_metadata_file = "/some/path/that/is/not/a/file" 28 29 # Verify the exception is raised when the command line argument for 30 # '--filesdir_path' or '--src_path' is not a directory. 31 with self.assertRaises(ValueError): 32 patch_manager.main( 33 [ 34 "--src_path", 35 src_dir, 36 "--patch_metadata_file", 37 patch_metadata_file, 38 ] 39 ) 40 mock_isdir.assert_called_once() 41 42 # Simulate behavior of 'os.path.isfile()' when the patch metadata file is 43 # does not exist. 44 @mock.patch.object(Path, "is_file", return_value=False) 45 def testInvalidPathToPatchMetadataFilePassedAsCommandLineArgument( 46 self, mock_isfile 47 ): 48 src_dir = "/some/path/that/is/not/a/directory" 49 patch_metadata_file = "/some/path/that/is/not/a/file" 50 51 # Verify the exception is raised when the command line argument for 52 # '--filesdir_path' or '--src_path' is not a directory. 53 with mock.patch.object(Path, "is_dir", return_value=True): 54 with self.assertRaises(ValueError): 55 patch_manager.main( 56 [ 57 "--src_path", 58 src_dir, 59 "--patch_metadata_file", 60 patch_metadata_file, 61 ] 62 ) 63 mock_isfile.assert_called_once() 64 65 @mock.patch("builtins.print") 66 @mock.patch.object(patch_utils, "git_clean_context") 67 def testCheckPatchApplies(self, _, mock_git_clean_context): 68 """Tests whether we can apply a single patch for a given svn_version.""" 69 mock_git_clean_context.return_value = mock.MagicMock() 70 with tempfile.TemporaryDirectory( 71 prefix="patch_manager_unittest" 72 ) as dirname: 73 dirpath = Path(dirname) 74 patch_entries = [ 75 patch_utils.PatchEntry( 76 dirpath, 77 metadata=None, 78 platforms=[], 79 rel_patch_path="another.patch", 80 version_range={ 81 "from": 9, 82 "until": 20, 83 }, 84 ), 85 patch_utils.PatchEntry( 86 dirpath, 87 metadata=None, 88 platforms=["chromiumos"], 89 rel_patch_path="example.patch", 90 version_range={ 91 "from": 1, 92 "until": 10, 93 }, 94 ), 95 patch_utils.PatchEntry( 96 dirpath, 97 metadata=None, 98 platforms=["chromiumos"], 99 rel_patch_path="patch_after.patch", 100 version_range={ 101 "from": 1, 102 "until": 5, 103 }, 104 ), 105 ] 106 patches_path = dirpath / "PATCHES.json" 107 with atomic_write_file.atomic_write( 108 patches_path, encoding="utf-8" 109 ) as f: 110 json.dump([pe.to_dict() for pe in patch_entries], f) 111 112 def _harness1( 113 version: int, 114 return_value: patch_utils.PatchResult, 115 expected: patch_manager.GitBisectionCode, 116 ): 117 with mock.patch.object( 118 patch_utils.PatchEntry, 119 "apply", 120 return_value=return_value, 121 ) as m: 122 result = patch_manager.CheckPatchApplies( 123 version, 124 dirpath, 125 patches_path, 126 "example.patch", 127 ) 128 self.assertEqual(result, expected) 129 m.assert_called() 130 131 _harness1( 132 1, 133 patch_utils.PatchResult(True, {}), 134 patch_manager.GitBisectionCode.GOOD, 135 ) 136 _harness1( 137 2, 138 patch_utils.PatchResult(True, {}), 139 patch_manager.GitBisectionCode.GOOD, 140 ) 141 _harness1( 142 2, 143 patch_utils.PatchResult(False, {}), 144 patch_manager.GitBisectionCode.BAD, 145 ) 146 _harness1( 147 11, 148 patch_utils.PatchResult(False, {}), 149 patch_manager.GitBisectionCode.BAD, 150 ) 151 152 def _harness2( 153 version: int, 154 application_func: Callable, 155 expected: patch_manager.GitBisectionCode, 156 ): 157 with mock.patch.object( 158 patch_utils, 159 "apply_single_patch_entry", 160 application_func, 161 ): 162 result = patch_manager.CheckPatchApplies( 163 version, 164 dirpath, 165 patches_path, 166 "example.patch", 167 ) 168 self.assertEqual(result, expected) 169 170 # Check patch can apply and fail with good return codes. 171 def _apply_patch_entry_mock1(v, _, patch_entry, _func, **__): 172 return patch_entry.can_patch_version(v), None 173 174 _harness2( 175 1, 176 _apply_patch_entry_mock1, 177 patch_manager.GitBisectionCode.GOOD, 178 ) 179 _harness2( 180 11, 181 _apply_patch_entry_mock1, 182 patch_manager.GitBisectionCode.BAD, 183 ) 184 185 # Early exit check, shouldn't apply later failing patch. 186 def _apply_patch_entry_mock2(v, _, patch_entry, _func, **__): 187 if ( 188 patch_entry.can_patch_version(v) 189 and patch_entry.rel_patch_path == "patch_after.patch" 190 ): 191 return False, {"filename": mock.Mock()} 192 return True, None 193 194 _harness2( 195 1, 196 _apply_patch_entry_mock2, 197 patch_manager.GitBisectionCode.GOOD, 198 ) 199 200 # Skip check, should exit early on the first patch. 201 def _apply_patch_entry_mock3(v, _, patch_entry, _func, **__): 202 if ( 203 patch_entry.can_patch_version(v) 204 and patch_entry.rel_patch_path == "another.patch" 205 ): 206 return False, {"filename": mock.Mock()} 207 return True, None 208 209 _harness2( 210 9, 211 _apply_patch_entry_mock3, 212 patch_manager.GitBisectionCode.SKIP, 213 ) 214 215 216if __name__ == "__main__": 217 unittest.main() 218