xref: /aosp_15_r20/external/toolchain-utils/llvm_tools/patch_manager_unittest.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
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