xref: /aosp_15_r20/external/toolchain-utils/llvm_tools/get_llvm_hash_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 for retrieving the LLVM hash."""
7
8import contextlib
9from pathlib import Path
10import shutil
11import subprocess
12import tempfile
13import textwrap
14import unittest
15from unittest import mock
16
17import get_llvm_hash
18import llvm_next
19import subprocess_helpers
20
21
22# We grab protected stuff from get_llvm_hash. That's OK.
23# pylint: disable=protected-access
24
25
26def mock_run_results(returncode: int, stderr: bytes) -> mock.MagicMock:
27    m = mock.MagicMock()
28    m.returncode = returncode
29    m.stderr = stderr
30    return m
31
32
33class TestGetLLVMHash(unittest.TestCase):
34    """The LLVMHash test class."""
35
36    def make_tempdir(self):
37        d = Path(tempfile.mkdtemp(prefix="get_llvm_hash_unittest_"))
38        self.addCleanup(shutil.rmtree, d)
39        return d
40
41    def setUp(self):
42        # We mock out quite a bit. Ensure every test is self-contained.
43        get_llvm_hash.GetLLVMMajorVersion.cache_clear()
44
45    @mock.patch.object(subprocess, "run")
46    def testCloneRepoSucceedsWhenGitSucceeds(self, run_mock):
47        run_mock.return_value = mock_run_results(returncode=0, stderr=b"")
48        llvm_hash = get_llvm_hash.LLVMHash()
49
50        into_tempdir = "/tmp/tmpTest"
51        llvm_hash.CloneLLVMRepo(into_tempdir)
52        run_mock.assert_called_with(
53            ["git", "clone", get_llvm_hash._LLVM_GIT_URL, into_tempdir],
54            check=False,
55            stderr=subprocess.PIPE,
56        )
57
58    @mock.patch.object(subprocess, "run")
59    def testCloneRepoFailsWhenGitFails(self, run_mock):
60        run_mock.return_value = mock_run_results(
61            returncode=1, stderr=b"some stderr"
62        )
63
64        with self.assertRaisesRegex(ValueError, "Failed to clone.*some stderr"):
65            get_llvm_hash.LLVMHash().CloneLLVMRepo("/tmp/tmp1")
66
67    @mock.patch.object(get_llvm_hash, "GetGitHashFrom")
68    def testGetGitHashWorks(self, mock_get_git_hash):
69        mock_get_git_hash.return_value = "a13testhash2"
70
71        self.assertEqual(
72            get_llvm_hash.GetGitHashFrom("/tmp/tmpTest", 100), "a13testhash2"
73        )
74
75        mock_get_git_hash.assert_called_once()
76
77    @mock.patch.object(get_llvm_hash.LLVMHash, "GetLLVMHash")
78    @mock.patch.object(get_llvm_hash, "GetGoogle3LLVMVersion")
79    def testReturnGoogle3LLVMHash(
80        self, mock_google3_llvm_version, mock_get_llvm_hash
81    ):
82        mock_get_llvm_hash.return_value = "a13testhash3"
83        mock_google3_llvm_version.return_value = 1000
84        self.assertEqual(
85            get_llvm_hash.LLVMHash().GetGoogle3LLVMHash(), "a13testhash3"
86        )
87        mock_get_llvm_hash.assert_called_once_with(1000)
88
89    @mock.patch.object(get_llvm_hash.LLVMHash, "GetLLVMHash")
90    @mock.patch.object(get_llvm_hash, "GetGoogle3LLVMVersion")
91    def testReturnGoogle3UnstableLLVMHash(
92        self, mock_google3_llvm_version, mock_get_llvm_hash
93    ):
94        mock_get_llvm_hash.return_value = "a13testhash3"
95        mock_google3_llvm_version.return_value = 1000
96        self.assertEqual(
97            get_llvm_hash.LLVMHash().GetGoogle3UnstableLLVMHash(),
98            "a13testhash3",
99        )
100        mock_get_llvm_hash.assert_called_once_with(1000)
101
102    @mock.patch.object(subprocess, "check_output")
103    def testSuccessfullyGetGitHashFromToTOfLLVM(self, mock_check_output):
104        mock_check_output.return_value = "a123testhash1 path/to/main\n"
105        self.assertEqual(
106            get_llvm_hash.LLVMHash().GetTopOfTrunkGitHash(), "a123testhash1"
107        )
108        mock_check_output.assert_called_once()
109
110    @mock.patch.object(subprocess, "Popen")
111    def testCheckoutBranch(self, mock_popen):
112        mock_popen.return_value = contextlib.nullcontext(
113            mock.MagicMock(communicate=lambda: (None, None), returncode=0)
114        )
115        get_llvm_hash.CheckoutBranch("fake/src_dir", "fake_branch")
116        self.assertEqual(
117            mock_popen.call_args_list[0][0],
118            (["git", "-C", "fake/src_dir", "checkout", "fake_branch"],),
119        )
120        self.assertEqual(
121            mock_popen.call_args_list[1][0],
122            (["git", "-C", "fake/src_dir", "pull"],),
123        )
124
125    def testParseLLVMMajorVersion(self):
126        cmakelist_42 = (
127            "set(CMAKE_BUILD_WITH_INSTALL_NAME_DIR ON)\n"
128            "if(NOT DEFINED LLVM_VERSION_MAJOR)\n"
129            "  set(LLVM_VERSION_MAJOR 42)\n"
130            "endif()"
131        )
132        self.assertEqual(
133            get_llvm_hash.ParseLLVMMajorVersion(cmakelist_42), "42"
134        )
135
136    def testParseLLVMMajorVersionInvalid(self):
137        invalid_cmakelist = "invalid cmakelist.txt contents"
138        self.assertIsNone(
139            get_llvm_hash.ParseLLVMMajorVersion(invalid_cmakelist)
140        )
141
142    @mock.patch.object(get_llvm_hash, "GetAndUpdateLLVMProjectInLLVMTools")
143    @mock.patch.object(subprocess_helpers, "CheckCommand")
144    def testGetLLVMMajorVersionWithOldPath(
145        self,
146        _mock_check_command,
147        mock_update_project,
148    ):
149        src_dir = self.make_tempdir()
150        mock_update_project.return_value = str(src_dir)
151
152        cmakelists = Path(src_dir) / "llvm" / "CMakeLists.txt"
153        cmakelists.parent.mkdir(parents=True)
154        cmakelists.write_text(
155            textwrap.dedent(
156                """
157                if(NOT DEFINED LLVM_VERSION_MAJOR)
158                  set(LLVM_VERSION_MAJOR 12345)
159                endif()
160                """
161            ),
162            encoding="utf-8",
163        )
164        self.assertEqual(get_llvm_hash.GetLLVMMajorVersion(), "12345")
165
166    @mock.patch.object(get_llvm_hash, "GetAndUpdateLLVMProjectInLLVMTools")
167    @mock.patch.object(subprocess_helpers, "CheckCommand")
168    def testGetLLVMMajorVersionWithNewPath(
169        self,
170        _mock_check_command,
171        mock_update_project,
172    ):
173        src_dir = self.make_tempdir()
174        mock_update_project.return_value = str(src_dir)
175
176        old_cmakelists = Path(src_dir) / "llvm" / "CMakeLists.txt"
177        old_cmakelists.parent.mkdir(parents=True)
178        old_cmakelists.write_text(
179            textwrap.dedent(
180                """
181                Some text
182                that has
183                nothing to do with
184                LLVM_VERSION_MAJOR
185                """
186            ),
187            encoding="utf-8",
188        )
189
190        new_cmakelists = (
191            Path(src_dir) / "cmake" / "Modules" / "LLVMVersion.cmake"
192        )
193        new_cmakelists.parent.mkdir(parents=True)
194        new_cmakelists.write_text(
195            textwrap.dedent(
196                """
197                if(NOT DEFINED LLVM_VERSION_MAJOR)
198                  set(LLVM_VERSION_MAJOR 5432)
199                endif()
200                """
201            ),
202            encoding="utf-8",
203        )
204
205        self.assertEqual(get_llvm_hash.GetLLVMMajorVersion(), "5432")
206
207    def testGetLLVMNextHash(self):
208        self.assertEqual(
209            get_llvm_hash.LLVMHash().GetCrOSLLVMNextHash(),
210            llvm_next.LLVM_NEXT_HASH,
211        )
212
213
214if __name__ == "__main__":
215    unittest.main()
216