Skip to content

Commit

Permalink
Add tests for Tool base class implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
rmartin16 committed May 7, 2023
1 parent 4fe2741 commit 6552f3a
Show file tree
Hide file tree
Showing 25 changed files with 976 additions and 470 deletions.
21 changes: 12 additions & 9 deletions src/briefcase/commands/upgrade.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
from operator import attrgetter
from typing import List, Set, Type

Expand All @@ -21,10 +22,10 @@ class UpgradeCommand(BaseCommand):
def platform(self):
"""The upgrade command always reports as the local platform."""
return {
"Darwin": "macOS",
"Linux": "linux",
"Windows": "windows",
}[self.tools.host_os]
"darwin": "macOS",
"linux": "linux",
"win32": "windows",
}[sys.platform]

def bundle_path(self, app):
"""A placeholder; Upgrade command doesn't have a bundle path."""
Expand Down Expand Up @@ -61,7 +62,7 @@ def get_tools_to_upgrade(self, tool_list: Set[str]) -> List[ManagedTool]:
if tool_list:
if invalid_tools := tool_list - set(tool_registry):
raise UpgradeToolError(
f"Briefcase does not know how to manage {', '.join(invalid_tools)}."
f"Briefcase does not know how to manage {', '.join(sorted(invalid_tools))}."
)
upgrade_list = {
tool for name, tool in tool_registry.items() if name in tool_list
Expand All @@ -73,7 +74,7 @@ def get_tools_to_upgrade(self, tool_list: Set[str]) -> List[ManagedTool]:
for tool_klass in upgrade_list:
if issubclass(tool_klass, ManagedTool):
try:
tool = tool_klass.verify(self.tools, install=False)
tool = tool_klass.verify(tools=self.tools, install=False)
except (BriefcaseCommandError, UnsupportedHostError):
pass
else:
Expand All @@ -83,7 +84,9 @@ def get_tools_to_upgrade(self, tool_list: Set[str]) -> List[ManagedTool]:
# Let the user know if any requested tools are not being managed
if tool_list:
if unmanaged_tools := tool_list - {tool.name for tool in tools_to_upgrade}:
error_msg = f"Briefcase is not managing {', '.join(unmanaged_tools)}."
error_msg = (
f"Briefcase is not managing {', '.join(sorted(unmanaged_tools))}."
)
if not tools_to_upgrade:
raise UpgradeToolError(error_msg)
else:
Expand All @@ -98,9 +101,9 @@ def __call__(self, tool_list: List[str], list_tools: bool = False, **options):
:param list_tools: Boolean to only list upgradeable tools (default False).
"""
if tools_to_upgrade := self.get_tools_to_upgrade(set(tool_list)):
action = "is managing" if list_tools else "will upgrade"
self.logger.info(
f"Briefcase {action} the following tools:", prefix=self.command
f"Briefcase {'is managing' if list_tools else 'will upgrade'} the following tools:",
prefix=self.command,
)
for tool in tools_to_upgrade:
self.logger.info(f" - {tool.full_name} ({tool.name})")
Expand Down
4 changes: 2 additions & 2 deletions src/briefcase/integrations/android_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,8 @@ def managed_install(self) -> bool:
return True

def uninstall(self):
"""Uninstall the Android SDK."""
# TODO:PR: implement this
"""The Android SDK is upgraded in-place instead of being reinstalled."""
pass

def install(self):
"""Download and install the Android SDK."""
Expand Down
4 changes: 2 additions & 2 deletions src/briefcase/integrations/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def _buildx_installed(cls, tools: ToolCache):
except subprocess.CalledProcessError:
raise BriefcaseCommandError(cls.BUILDX_PLUGIN_MISSING)

def check_output(self, args: list[SubprocessArgT], image_tag: str):
def check_output(self, args: list[SubprocessArgT], image_tag: str) -> str:
"""Run a process inside a Docker container, capturing output.
This is a bare Docker invocation; it's really only useful for running
Expand Down Expand Up @@ -470,7 +470,7 @@ def check_output(
cwd: Path = None,
mounts: dict[str | Path, str | Path] = None,
**kwargs,
):
) -> str:
"""Run a process inside a Docker container, capturing output."""
# Any exceptions from running the process are *not* caught.
# This ensures that "docker.check_output()" behaves as closely to
Expand Down
4 changes: 2 additions & 2 deletions src/briefcase/platforms/windows/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,9 @@ def default_packaging_format(self):

def verify_tools(self):
super().verify_tools()
WiX.verify(self.tools)
WiX.verify(tools=self.tools)
if self._windows_sdk_needed:
WindowsSDK.verify(self.tools)
WindowsSDK.verify(tools=self.tools)

def add_options(self, parser):
super().add_options(parser)
Expand Down
2 changes: 1 addition & 1 deletion src/briefcase/platforms/windows/visualstudio.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class WindowsVisualStudioBuildCommand(WindowsVisualStudioMixin, BuildCommand):

def verify_tools(self):
super().verify_tools()
VisualStudio.verify(self.tools)
VisualStudio.verify(tools=self.tools)

def build_app(self, app: BaseConfig, **kwargs):
"""Build the Visual Studio project.
Expand Down
11 changes: 11 additions & 0 deletions tests/commands/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from unittest.mock import MagicMock

import git as git_
import pytest


@pytest.fixture
def mock_git():
git = MagicMock(spec_set=git_)
git.exc = git_.exc
return git
182 changes: 112 additions & 70 deletions tests/commands/upgrade/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@

import pytest

import briefcase.commands.upgrade
from briefcase.commands import UpgradeCommand
from briefcase.console import Console, Log
from briefcase.exceptions import MissingToolError
from briefcase.integrations.base import ManagedTool, Tool


@pytest.fixture
def upgrade_command(tmp_path):
command = DummyUpgradeCommand(base_path=tmp_path)
command.tools.host_os = "wonky"
return command


class DummyUpgradeCommand(UpgradeCommand):
Expand All @@ -31,88 +40,121 @@ def binary_path(self, app):


@pytest.fixture
def ManagedSDK1():
sdk = MagicMock()
sdk.verify.return_value = sdk
sdk.name = "managed-1"
sdk.full_name = "Managed 1"
sdk.exists.return_value = True
sdk.managed_install = True
# No plugins defined on SDK1
sdk.plugins.values.side_effect = AttributeError
return sdk
def mock_tool_registry(monkeypatch):
"""Tool registry with all dummy tools."""
tool_list = [
DummyTool,
DummyManagedTool1,
DummyManagedTool2,
DummyManagedTool3,
DummyUnManagedManagedTool,
DummyNotInstalledManagedTool,
]

tool_registry = dict()
for tool in tool_list:
monkeypatch.setattr(tool, "verify", MagicMock(wraps=tool.verify))
tool_registry[tool.name] = tool

@pytest.fixture
def ManagedSDK2Plugin1():
sdk = MagicMock()
sdk.verify.return_value = sdk
sdk.name = "managed-2-plugin-1"
sdk.full_name = "Managed 2 plugin 1"
sdk.exists.return_value = True
sdk.managed_install = True
return sdk
monkeypatch.setattr(briefcase.commands.upgrade, "tool_registry", tool_registry)


@pytest.fixture
def ManagedSDK2Plugin2():
sdk = MagicMock()
sdk.verify.return_value = sdk
sdk.name = "managed-2-plugin-2"
sdk.full_name = "Managed 2 plugin 2"
sdk.exists.return_value = True
sdk.managed_install = True
return sdk
def mock_no_managed_tool_registry(monkeypatch):
"""Tool registry without any installed managed tools."""
tool_list = [
DummyTool,
DummyUnManagedManagedTool,
DummyNotInstalledManagedTool,
]

tool_registry = dict()
for tool in tool_list:
monkeypatch.setattr(tool, "verify", MagicMock(wraps=tool.verify))
tool_registry[tool.name] = tool

@pytest.fixture
def ManagedSDK2Plugin3():
sdk = MagicMock()
sdk.verify.return_value = sdk
sdk.name = "managed-2-plugin-3"
sdk.full_name = "Managed 2 plugin 3"
sdk.exists.return_value = False
sdk.verify.side_effect = MissingToolError("managed-2-plugin-3")
sdk.managed_install = True
return sdk
monkeypatch.setattr(briefcase.commands.upgrade, "tool_registry", tool_registry)


@pytest.fixture
def ManagedSDK2(ManagedSDK2Plugin1, ManagedSDK2Plugin2, ManagedSDK2Plugin3):
sdk = MagicMock()
sdk.verify.return_value = sdk
sdk.name = "managed-2"
sdk.full_name = "Managed 2"
sdk.exists.return_value = True
sdk.managed_install = True
sdk.plugins = {
"managed2-plugin1": ManagedSDK2Plugin1,
"managed2-plugin2": ManagedSDK2Plugin2,
"managed2-plugin3": ManagedSDK2Plugin3,
}
return sdk
class DummyToolBase(Tool):
name = "dummy_tool_base"
supported_host_os = {"wonky"}

@classmethod
def verify_install(cls, tools, **kwargs):
return cls(tools=tools)

@pytest.fixture
def NonManagedSDK():
sdk = MagicMock()
sdk.verify.return_value = sdk
sdk.name = "non-managed"
sdk.full_name = "Non Managed"
sdk.exists.return_value = True
sdk.managed_install = False
return sdk

class DummyManagedToolBase(ManagedTool):
name = "dummy_managed_tool_base"
supported_host_os = {"wonky"}

@pytest.fixture
def NonInstalledSDK():
sdk = MagicMock()
sdk.name = "non-installed"
sdk.full_name = "Non Installed"
sdk.verify.side_effect = MissingToolError("non-installed")
return sdk
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.actions = []

@classmethod
def verify_install(cls, tools, **kwargs):
# add to ToolCache so accessible after upgrade
setattr(tools, cls.name, cls(tools=tools))
return getattr(tools, cls.name)

@pytest.fixture
def upgrade_command(tmp_path, ManagedSDK1, ManagedSDK2, NonManagedSDK, NonInstalledSDK):
return DummyUpgradeCommand(base_path=tmp_path)
def exists(self) -> bool:
self.actions.append("exists")
return True

def install(self, *args, **kwargs):
self.actions.append("install")

def uninstall(self, *args, **kwargs):
self.actions.append("uninstall")


class DummyTool(DummyToolBase):
"""Unmanaged Tool testing class."""

name = "unmanaged"
full_name = "Unmanaged Dummy Tool"


class DummyUnManagedManagedTool(DummyManagedToolBase):
"""Managed Tool without a managed install testing class."""

name = "unmanaged_managed"
full_name = "Unmanaged Managed Dummy Tool"

@property
def managed_install(self) -> bool:
return False


class DummyNotInstalledManagedTool(DummyManagedToolBase):
"""Managed Tool without a managed install testing class."""

name = "not_installed"
full_name = "Not Installed Managed Dummy Tool"

@classmethod
def verify_install(cls, tools, **kwargs):
raise MissingToolError(cls.full_name)


class DummyManagedTool1(DummyManagedToolBase):
"""Managed Tool testing class."""

name = "managed_1"
full_name = "Managed Dummy Tool 1"


class DummyManagedTool2(DummyManagedToolBase):
"""Managed Tool testing class."""

name = "managed_2"
full_name = "Managed Dummy Tool 2"


class DummyManagedTool3(DummyManagedToolBase):
"""Managed Tool testing class."""

name = "managed_3"
full_name = "Managed Dummy Tool 3"
Loading

0 comments on commit 6552f3a

Please sign in to comment.