# SPDX-License-Identifier: LGPL-2.1-or-later # *************************************************************************** # * * # * Copyright (c) 2022 FreeCAD Project Association * # * * # * This file is part of FreeCAD. * # * * # * FreeCAD is free software: you can redistribute it and/or modify it * # * under the terms of the GNU Lesser General Public License as * # * published by the Free Software Foundation, either version 2.1 of the * # * License, or (at your option) any later version. * # * * # * FreeCAD is distributed in the hope that it will be useful, but * # * WITHOUT ANY WARRANTY; without even the implied warranty of * # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * # * Lesser General Public License for more details. * # * * # * You should have received a copy of the GNU Lesser General Public * # * License along with FreeCAD. If not, see * # * . * # * * # *************************************************************************** import functools import os import subprocess import tempfile from time import sleep import unittest from addonmanager_dependency_installer import DependencyInstaller class CompleteProcessMock(subprocess.CompletedProcess): def __init__(self): super().__init__(["fake_arg"], 0) self.stdout = "Mock subprocess call stdout result" class SubprocessMock: def __init__(self): self.arg_log = [] self.called = False self.call_count = 0 self.delay = 0 self.succeed = True def subprocess_interceptor(self, args): self.arg_log.append(args) self.called = True self.call_count += 1 sleep(self.delay) if self.succeed: return CompleteProcessMock() raise subprocess.CalledProcessError(1, " ".join(args), "Unit test mock output") class FakeFunction: def __init__(self): self.called = False self.call_count = 0 self.return_value = None self.arg_log = [] def func_call(self, *args): self.arg_log.append(args) self.called = True self.call_count += 1 return self.return_value class TestDependencyInstaller(unittest.TestCase): """Test the dependency installation class""" def setUp(self): self.subprocess_mock = SubprocessMock() self.test_object = DependencyInstaller([], ["required_py_package"], ["optional_py_package"]) self.test_object._subprocess_wrapper = self.subprocess_mock.subprocess_interceptor self.signals_caught = [] self.test_object.failure.connect(functools.partial(self.catch_signal, "failure")) self.test_object.finished.connect(functools.partial(self.catch_signal, "finished")) self.test_object.no_pip.connect(functools.partial(self.catch_signal, "no_pip")) self.test_object.no_python_exe.connect( functools.partial(self.catch_signal, "no_python_exe") ) def tearDown(self): pass def catch_signal(self, signal_name, *_): self.signals_caught.append(signal_name) def test_run_no_pip(self): self.test_object._verify_pip = lambda: False self.test_object.run() self.assertIn("finished", self.signals_caught) def test_run_with_pip(self): ff = FakeFunction() self.test_object._verify_pip = lambda: True self.test_object._install_python_packages = ff.func_call self.test_object.run() self.assertIn("finished", self.signals_caught) self.assertTrue(ff.called) def test_run_with_no_packages(self): ff = FakeFunction() self.test_object._verify_pip = lambda: True self.test_object._install_python_packages = ff.func_call self.test_object.python_requires = [] self.test_object.python_optional = [] self.test_object.run() self.assertIn("finished", self.signals_caught) self.assertFalse(ff.called) def test_install_python_packages_new_location(self): ff_required = FakeFunction() ff_optional = FakeFunction() self.test_object._install_required = ff_required.func_call self.test_object._install_optional = ff_optional.func_call with tempfile.TemporaryDirectory() as td: self.test_object.location = os.path.join(td, "UnitTestLocation") self.test_object._install_python_packages() self.assertTrue(ff_required.called) self.assertTrue(ff_optional.called) self.assertTrue(os.path.exists(self.test_object.location)) def test_install_python_packages_existing_location(self): ff_required = FakeFunction() ff_optional = FakeFunction() self.test_object._install_required = ff_required.func_call self.test_object._install_optional = ff_optional.func_call with tempfile.TemporaryDirectory() as td: self.test_object.location = td self.test_object._install_python_packages() self.assertTrue(ff_required.called) self.assertTrue(ff_optional.called) def test_verify_pip_no_pip(self): sm = SubprocessMock() sm.succeed = False self.test_object._subprocess_wrapper = sm.subprocess_interceptor self.test_object._get_python = lambda: "fake_python" result = self.test_object._verify_pip() self.assertFalse(result) self.assertIn("no_pip", self.signals_caught) def test_verify_pip_with_pip(self): sm = SubprocessMock() sm.succeed = True self.test_object._subprocess_wrapper = sm.subprocess_interceptor self.test_object._get_python = lambda: "fake_python" result = self.test_object._verify_pip() self.assertTrue(result) self.assertNotIn("no_pip", self.signals_caught) def test_install_required_loops(self): sm = SubprocessMock() sm.succeed = True self.test_object._subprocess_wrapper = sm.subprocess_interceptor self.test_object._get_python = lambda: "fake_python" self.test_object.python_requires = ["test1", "test2", "test3"] self.test_object._install_required("vendor_path") self.assertEqual(sm.call_count, 3) def test_install_required_failure(self): sm = SubprocessMock() sm.succeed = False self.test_object._subprocess_wrapper = sm.subprocess_interceptor self.test_object._get_python = lambda: "fake_python" self.test_object.python_requires = ["test1", "test2", "test3"] self.test_object._install_required("vendor_path") self.assertEqual(sm.call_count, 1) self.assertIn("failure", self.signals_caught) def test_install_optional_loops(self): sm = SubprocessMock() sm.succeed = True self.test_object._subprocess_wrapper = sm.subprocess_interceptor self.test_object._get_python = lambda: "fake_python" self.test_object.python_optional = ["test1", "test2", "test3"] self.test_object._install_optional("vendor_path") self.assertEqual(sm.call_count, 3) def test_install_optional_failure(self): sm = SubprocessMock() sm.succeed = False self.test_object._subprocess_wrapper = sm.subprocess_interceptor self.test_object._get_python = lambda: "fake_python" self.test_object.python_optional = ["test1", "test2", "test3"] self.test_object._install_optional("vendor_path") self.assertEqual(sm.call_count, 3) def test_run_pip(self): pass