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