FreeCAD

Форк
0
412 строк · 20.0 Кб
1
# SPDX-License-Identifier: LGPL-2.1-or-later
2
# ***************************************************************************
3
# *                                                                         *
4
# *   Copyright (c) 2022 FreeCAD Project Association                        *
5
# *                                                                         *
6
# *   This file is part of FreeCAD.                                         *
7
# *                                                                         *
8
# *   FreeCAD is free software: you can redistribute it and/or modify it    *
9
# *   under the terms of the GNU Lesser General Public License as           *
10
# *   published by the Free Software Foundation, either version 2.1 of the  *
11
# *   License, or (at your option) any later version.                       *
12
# *                                                                         *
13
# *   FreeCAD is distributed in the hope that it will be useful, but        *
14
# *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
15
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
16
# *   Lesser General Public License for more details.                       *
17
# *                                                                         *
18
# *   You should have received a copy of the GNU Lesser General Public      *
19
# *   License along with FreeCAD. If not, see                               *
20
# *   <https://www.gnu.org/licenses/>.                                      *
21
# *                                                                         *
22
# ***************************************************************************
23

24
"""Contains the unit test class for addonmanager_installer.py non-GUI functionality."""
25

26
import unittest
27
from unittest.mock import Mock
28
import os
29
import shutil
30
import tempfile
31
from zipfile import ZipFile
32
import sys
33

34
sys.path.append("../../")  # So the IDE can find the imports below
35

36
import FreeCAD
37
from addonmanager_installer import InstallationMethod, AddonInstaller, MacroInstaller
38
from addonmanager_git import GitManager, initialize_git
39
from addonmanager_metadata import MetadataReader
40
from Addon import Addon
41
from AddonManagerTest.app.mocks import MockAddon, MockMacro
42

43

44
class TestAddonInstaller(unittest.TestCase):
45
    """Test class for addonmanager_installer.py non-GUI functionality"""
46

47
    MODULE = "test_installer"  # file name without extension
48

49
    def setUp(self):
50
        """Initialize data needed for all tests"""
51
        # self.start_time = time.perf_counter()
52
        self.test_data_dir = os.path.join(
53
            FreeCAD.getHomePath(), "Mod", "AddonManager", "AddonManagerTest", "data"
54
        )
55
        self.real_addon = Addon(
56
            "TestAddon",
57
            "https://github.com/FreeCAD/FreeCAD-addons",
58
            Addon.Status.NOT_INSTALLED,
59
            "master",
60
        )
61
        self.mock_addon = MockAddon()
62

63
    def tearDown(self):
64
        """Finalize the test."""
65
        # end_time = time.perf_counter()
66
        # print(f"Test '{self.id()}' ran in {end_time-self.start_time:.4f} seconds")
67

68
    def test_validate_object(self):
69
        """An object is valid if it has a name, url, and branch attribute."""
70

71
        AddonInstaller._validate_object(self.real_addon)  # Won't raise
72
        AddonInstaller._validate_object(self.mock_addon)  # Won't raise
73

74
        class NoName:
75
            def __init__(self):
76
                self.url = "https://github.com/FreeCAD/FreeCAD-addons"
77
                self.branch = "master"
78

79
        no_name = NoName()
80
        with self.assertRaises(RuntimeError):
81
            AddonInstaller._validate_object(no_name)
82

83
        class NoUrl:
84
            def __init__(self):
85
                self.name = "TestAddon"
86
                self.branch = "master"
87

88
        no_url = NoUrl()
89
        with self.assertRaises(RuntimeError):
90
            AddonInstaller._validate_object(no_url)
91

92
        class NoBranch:
93
            def __init__(self):
94
                self.name = "TestAddon"
95
                self.url = "https://github.com/FreeCAD/FreeCAD-addons"
96

97
        no_branch = NoBranch()
98
        with self.assertRaises(RuntimeError):
99
            AddonInstaller._validate_object(no_branch)
100

101
    def test_update_metadata(self):
102
        """If a metadata file exists in the installation location, it should be
103
        loaded."""
104
        addon = Mock()
105
        addon.name = "MockAddon"
106
        installer = AddonInstaller(addon, [])
107
        installer._update_metadata()  # Does nothing, but should not crash
108

109
        installer = AddonInstaller(self.real_addon, [])
110
        with tempfile.TemporaryDirectory() as temp_dir:
111
            installer.installation_path = temp_dir
112
            installer._update_metadata()
113
            addon_dir = os.path.join(temp_dir, self.real_addon.name)
114
            os.mkdir(addon_dir)
115
            shutil.copy(
116
                os.path.join(self.test_data_dir, "good_package.xml"),
117
                os.path.join(addon_dir, "package.xml"),
118
            )
119
            good_metadata = MetadataReader.from_file(os.path.join(addon_dir, "package.xml"))
120
            installer._update_metadata()
121
            self.assertEqual(self.real_addon.installed_version, good_metadata.version)
122

123
    def test_finalize_zip_installation_non_github(self):
124
        """Ensure that zip files are correctly extracted."""
125
        with tempfile.TemporaryDirectory() as temp_dir:
126
            test_simple_repo = os.path.join(self.test_data_dir, "test_simple_repo.zip")
127
            non_gh_mock = MockAddon()
128
            non_gh_mock.url = test_simple_repo[:-4]
129
            non_gh_mock.name = "NonGitHubMock"
130
            installer = AddonInstaller(non_gh_mock, [])
131
            installer.installation_path = temp_dir
132
            installer._finalize_zip_installation(test_simple_repo)
133
            expected_location = os.path.join(temp_dir, non_gh_mock.name, "README")
134
            self.assertTrue(os.path.isfile(expected_location), "Non-GitHub zip extraction failed")
135

136
    def test_finalize_zip_installation_github(self):
137
        with tempfile.TemporaryDirectory() as temp_dir:
138
            test_github_style_repo = os.path.join(self.test_data_dir, "test_github_style_repo.zip")
139
            self.mock_addon.url = "https://github.com/something/test_github_style_repo"
140
            self.mock_addon.branch = "master"
141
            installer = AddonInstaller(self.mock_addon, [])
142
            installer.installation_path = temp_dir
143
            installer._finalize_zip_installation(test_github_style_repo)
144
            expected_location = os.path.join(temp_dir, self.mock_addon.name, "README")
145
            self.assertTrue(os.path.isfile(expected_location), "GitHub zip extraction failed")
146

147
    def test_code_in_branch_subdirectory_true(self):
148
        """When there is a subdirectory with the branch name in it, find it"""
149
        self.mock_addon.url = "https://something.com/something_else/something"
150
        installer = AddonInstaller(self.mock_addon, [])
151
        with tempfile.TemporaryDirectory() as temp_dir:
152
            os.mkdir(os.path.join(temp_dir, f"something-{self.mock_addon.branch}"))
153
            result = installer._code_in_branch_subdirectory(temp_dir)
154
            self.assertTrue(result, "Failed to find ZIP subdirectory")
155

156
    def test_code_in_branch_subdirectory_false(self):
157
        """When there is not a subdirectory with the branch name in it, don't find
158
        one"""
159
        installer = AddonInstaller(self.mock_addon, [])
160
        with tempfile.TemporaryDirectory() as temp_dir:
161
            result = installer._code_in_branch_subdirectory(temp_dir)
162
            self.assertFalse(result, "Found ZIP subdirectory when there was none")
163

164
    def test_code_in_branch_subdirectory_more_than_one(self):
165
        """When there are multiple subdirectories, never find a branch subdirectory"""
166
        installer = AddonInstaller(self.mock_addon, [])
167
        with tempfile.TemporaryDirectory() as temp_dir:
168
            os.mkdir(os.path.join(temp_dir, f"{self.mock_addon.name}-{self.mock_addon.branch}"))
169
            os.mkdir(os.path.join(temp_dir, "AnotherSubdir"))
170
            result = installer._code_in_branch_subdirectory(temp_dir)
171
            self.assertFalse(result, "Found ZIP subdirectory when there were multiple subdirs")
172

173
    def test_move_code_out_of_subdirectory(self):
174
        """All files are moved out and the subdirectory is deleted"""
175
        self.mock_addon.url = "https://something.com/something_else/something"
176
        installer = AddonInstaller(self.mock_addon, [])
177
        with tempfile.TemporaryDirectory() as temp_dir:
178
            subdir = os.path.join(temp_dir, f"something-{self.mock_addon.branch}")
179
            os.mkdir(subdir)
180
            with open(os.path.join(subdir, "README.txt"), "w", encoding="utf-8") as f:
181
                f.write("# Test file for unit testing")
182
            with open(os.path.join(subdir, "AnotherFile.txt"), "w", encoding="utf-8") as f:
183
                f.write("# Test file for unit testing")
184
            installer._move_code_out_of_subdirectory(temp_dir)
185
            self.assertTrue(os.path.isfile(os.path.join(temp_dir, "README.txt")))
186
            self.assertTrue(os.path.isfile(os.path.join(temp_dir, "AnotherFile.txt")))
187
            self.assertFalse(os.path.isdir(subdir))
188

189
    def test_install_by_git(self):
190
        """Test using git to install. Depends on there being a local git
191
        installation: the test is skipped if there is no local git."""
192
        git_manager = initialize_git()
193
        if not git_manager:
194
            self.skipTest("git not found, skipping git installer tests")
195
            return
196

197
        # Our test git repo has to be in a zipfile, otherwise it cannot itself be
198
        # stored in git, since it has a .git subdirectory.
199
        with tempfile.TemporaryDirectory() as temp_dir:
200
            git_repo_zip = os.path.join(self.test_data_dir, "test_repo.zip")
201
            with ZipFile(git_repo_zip, "r") as zip_repo:
202
                zip_repo.extractall(temp_dir)
203

204
            mock_addon = MockAddon()
205
            mock_addon.url = os.path.join(temp_dir, "test_repo")
206
            mock_addon.branch = "main"
207
            installer = AddonInstaller(mock_addon, [])
208
            installer.installation_path = os.path.join(temp_dir, "installed_addon")
209
            installer._install_by_git()
210

211
            self.assertTrue(os.path.exists(installer.installation_path))
212
            addon_name_dir = os.path.join(installer.installation_path, mock_addon.name)
213
            self.assertTrue(os.path.exists(addon_name_dir))
214
            readme = os.path.join(addon_name_dir, "README.md")
215
            self.assertTrue(os.path.exists(readme))
216

217
    def test_install_by_copy(self):
218
        """Test using a simple filesystem copy to install an addon."""
219
        with tempfile.TemporaryDirectory() as temp_dir:
220
            git_repo_zip = os.path.join(self.test_data_dir, "test_repo.zip")
221
            with ZipFile(git_repo_zip, "r") as zip_repo:
222
                zip_repo.extractall(temp_dir)
223

224
            mock_addon = MockAddon()
225
            mock_addon.url = os.path.join(temp_dir, "test_repo")
226
            mock_addon.branch = "main"
227
            installer = AddonInstaller(mock_addon, [])
228
            installer.addon_to_install = mock_addon
229
            installer.installation_path = os.path.join(temp_dir, "installed_addon")
230
            installer._install_by_copy()
231

232
            self.assertTrue(os.path.exists(installer.installation_path))
233
            addon_name_dir = os.path.join(installer.installation_path, mock_addon.name)
234
            self.assertTrue(os.path.exists(addon_name_dir))
235
            readme = os.path.join(addon_name_dir, "README.md")
236
            self.assertTrue(os.path.exists(readme))
237

238
    def test_determine_install_method_local_path(self):
239
        """Test which install methods are accepted for a local path"""
240

241
        with tempfile.TemporaryDirectory() as temp_dir:
242
            installer = AddonInstaller(self.mock_addon, [])
243
            method = installer._determine_install_method(temp_dir, InstallationMethod.COPY)
244
            self.assertEqual(method, InstallationMethod.COPY)
245
            git_manager = initialize_git()
246
            if git_manager:
247
                method = installer._determine_install_method(temp_dir, InstallationMethod.GIT)
248
                self.assertEqual(method, InstallationMethod.GIT)
249
            method = installer._determine_install_method(temp_dir, InstallationMethod.ZIP)
250
            self.assertIsNone(method)
251
            method = installer._determine_install_method(temp_dir, InstallationMethod.ANY)
252
            self.assertEqual(method, InstallationMethod.COPY)
253

254
    def test_determine_install_method_file_url(self):
255
        """Test which install methods are accepted for a file:// url"""
256

257
        with tempfile.TemporaryDirectory() as temp_dir:
258
            installer = AddonInstaller(self.mock_addon, [])
259
            temp_dir = "file://" + temp_dir.replace(os.path.sep, "/")
260
            method = installer._determine_install_method(temp_dir, InstallationMethod.COPY)
261
            self.assertEqual(method, InstallationMethod.COPY)
262
            git_manager = initialize_git()
263
            if git_manager:
264
                method = installer._determine_install_method(temp_dir, InstallationMethod.GIT)
265
                self.assertEqual(method, InstallationMethod.GIT)
266
            method = installer._determine_install_method(temp_dir, InstallationMethod.ZIP)
267
            self.assertIsNone(method)
268
            method = installer._determine_install_method(temp_dir, InstallationMethod.ANY)
269
            self.assertEqual(method, InstallationMethod.COPY)
270

271
    def test_determine_install_method_local_zip(self):
272
        """Test which install methods are accepted for a local path to a zipfile"""
273

274
        with tempfile.TemporaryDirectory() as temp_dir:
275
            installer = AddonInstaller(self.mock_addon, [])
276
            temp_file = os.path.join(temp_dir, "dummy.zip")
277
            method = installer._determine_install_method(temp_file, InstallationMethod.COPY)
278
            self.assertEqual(method, InstallationMethod.ZIP)
279
            method = installer._determine_install_method(temp_file, InstallationMethod.GIT)
280
            self.assertIsNone(method)
281
            method = installer._determine_install_method(temp_file, InstallationMethod.ZIP)
282
            self.assertEqual(method, InstallationMethod.ZIP)
283
            method = installer._determine_install_method(temp_file, InstallationMethod.ANY)
284
            self.assertEqual(method, InstallationMethod.ZIP)
285

286
    def test_determine_install_method_remote_zip(self):
287
        """Test which install methods are accepted for a remote path to a zipfile"""
288

289
        installer = AddonInstaller(self.mock_addon, [])
290

291
        temp_file = "https://freecad.org/dummy.zip"  # Doesn't have to actually exist!
292

293
        method = installer._determine_install_method(temp_file, InstallationMethod.COPY)
294
        self.assertIsNone(method)
295
        method = installer._determine_install_method(temp_file, InstallationMethod.GIT)
296
        self.assertIsNone(method)
297
        method = installer._determine_install_method(temp_file, InstallationMethod.ZIP)
298
        self.assertEqual(method, InstallationMethod.ZIP)
299
        method = installer._determine_install_method(temp_file, InstallationMethod.ANY)
300
        self.assertEqual(method, InstallationMethod.ZIP)
301

302
    def test_determine_install_method_https_known_sites_copy(self):
303
        """Test which install methods are accepted for an https GitHub URL"""
304

305
        installer = AddonInstaller(self.mock_addon, [])
306
        installer.git_manager = True
307

308
        for site in ["github.org", "gitlab.org", "framagit.org", "salsa.debian.org"]:
309
            with self.subTest(site=site):
310
                temp_file = f"https://{site}/dummy/dummy"  # Doesn't have to actually exist!
311
                method = installer._determine_install_method(temp_file, InstallationMethod.COPY)
312
                self.assertIsNone(method, f"Allowed copying from {site} URL")
313

314
    def test_determine_install_method_https_known_sites_git(self):
315
        """Test which install methods are accepted for an https GitHub URL"""
316

317
        installer = AddonInstaller(self.mock_addon, [])
318
        installer.git_manager = True
319

320
        for site in ["github.org", "gitlab.org", "framagit.org", "salsa.debian.org"]:
321
            with self.subTest(site=site):
322
                temp_file = f"https://{site}/dummy/dummy"  # Doesn't have to actually exist!
323
                method = installer._determine_install_method(temp_file, InstallationMethod.GIT)
324
                self.assertEqual(
325
                    method,
326
                    InstallationMethod.GIT,
327
                    f"Failed to allow git access to {site} URL",
328
                )
329

330
    def test_determine_install_method_https_known_sites_zip(self):
331
        """Test which install methods are accepted for an https GitHub URL"""
332

333
        installer = AddonInstaller(self.mock_addon, [])
334
        installer.git_manager = True
335

336
        for site in ["github.org", "gitlab.org", "framagit.org", "salsa.debian.org"]:
337
            with self.subTest(site=site):
338
                temp_file = f"https://{site}/dummy/dummy"  # Doesn't have to actually exist!
339
                method = installer._determine_install_method(temp_file, InstallationMethod.ZIP)
340
                self.assertEqual(
341
                    method,
342
                    InstallationMethod.ZIP,
343
                    f"Failed to allow zip access to {site} URL",
344
                )
345

346
    def test_determine_install_method_https_known_sites_any_gm(self):
347
        """Test which install methods are accepted for an https GitHub URL"""
348

349
        installer = AddonInstaller(self.mock_addon, [])
350
        installer.git_manager = True
351

352
        for site in ["github.org", "gitlab.org", "framagit.org", "salsa.debian.org"]:
353
            with self.subTest(site=site):
354
                temp_file = f"https://{site}/dummy/dummy"  # Doesn't have to actually exist!
355
                method = installer._determine_install_method(temp_file, InstallationMethod.ANY)
356
                self.assertEqual(
357
                    method,
358
                    InstallationMethod.GIT,
359
                    f"Failed to allow git access to {site} URL",
360
                )
361

362
    def test_determine_install_method_https_known_sites_any_no_gm(self):
363
        """Test which install methods are accepted for an https GitHub URL"""
364

365
        installer = AddonInstaller(self.mock_addon, [])
366
        installer.git_manager = None
367

368
        for site in ["github.org", "gitlab.org", "framagit.org", "salsa.debian.org"]:
369
            with self.subTest(site=site):
370
                temp_file = f"https://{site}/dummy/dummy"  # Doesn't have to actually exist!
371
                method = installer._determine_install_method(temp_file, InstallationMethod.ANY)
372
                self.assertEqual(
373
                    method,
374
                    InstallationMethod.ZIP,
375
                    f"Failed to allow zip access to {site} URL",
376
                )
377

378
    def test_fcmacro_copying(self):
379
        with tempfile.TemporaryDirectory() as temp_dir:
380
            mock_addon = MockAddon()
381
            mock_addon.url = os.path.join(self.test_data_dir, "test_addon_with_fcmacro.zip")
382
            installer = AddonInstaller(mock_addon, [])
383
            installer.installation_path = temp_dir
384
            installer.macro_installation_path = os.path.join(temp_dir, "Macros")
385
            installer.run()
386
            self.assertTrue(
387
                os.path.exists(os.path.join(temp_dir, "Macros", "TestMacro.FCMacro")),
388
                "FCMacro file was not copied to macro installation location",
389
            )
390

391

392
class TestMacroInstaller(unittest.TestCase):
393
    MODULE = "test_installer"  # file name without extension
394

395
    def setUp(self):
396
        """Set up the mock objects"""
397

398
        self.mock = MockAddon()
399
        self.mock.macro = MockMacro()
400

401
    def test_installation(self):
402
        """Test the wrapper around the macro installer"""
403

404
        # Note that this doesn't test the underlying Macro object's install function,
405
        # it only tests whether that function is called appropriately by the
406
        # MacroInstaller wrapper.
407
        with tempfile.TemporaryDirectory() as temp_dir:
408
            installer = MacroInstaller(self.mock)
409
            installer.installation_path = temp_dir
410
            installation_succeeded = installer.run()
411
            self.assertTrue(installation_succeeded)
412
            self.assertTrue(os.path.exists(os.path.join(temp_dir, self.mock.macro.filename)))
413

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.