Skip to content

Commit

Permalink
✅ Increases ios target test coverage
Browse files Browse the repository at this point in the history
This is a follow-up for kivy#1160 and kivy#1168.
Addresses the following:
- grows `buildozer/targets/ios.py` target coverage from 24% to 56%
- fixes `AttributeError` on `TargetIos` error call

Next up should be improving:
- code signing process (toggle off not fully integrated)
- actual deployment (not yet tested)
- further increase `buildozer/targets/ios.py` test coverage
  • Loading branch information
AndreMiras committed Jun 25, 2020
1 parent 3f851ec commit da32f4e
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 14 deletions.
20 changes: 15 additions & 5 deletions buildozer/targets/ios.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,20 @@
class TargetIos(Target):
targetname = "ios"

def __init__(self, buildozer):
super().__init__(buildozer)
executable = sys.executable or 'python'
self._toolchain_cmd = f"{executable} toolchain.py "
self._xcodebuild_cmd = "xcodebuild "
# set via install_platform()
self.ios_dir = None
self.ios_deploy_dir = None

def check_requirements(self):
if sys.platform != "darwin":
raise NotImplementedError("Only macOS is supported for iOS target")
checkbin = self.buildozer.checkbin
cmd = self.buildozer.cmd
executable = sys.executable or 'python'
self._toolchain_cmd = f"{executable} toolchain.py "
self._xcodebuild_cmd = "xcodebuild "

checkbin('Xcode xcodebuild', 'xcodebuild')
checkbin('Xcode xcode-select', 'xcode-select')
Expand Down Expand Up @@ -95,6 +101,10 @@ def check_requirements(self):
self.buildozer.debug(' -> found {0}'.format(xcode))

def install_platform(self):
"""
Clones `kivy/kivy-ios` and `phonegap/ios-deploy` then sets `ios_dir`
and `ios_deploy_dir` accordingly.
"""
self.ios_dir = self.install_or_update_repo('kivy-ios', platform='ios')
self.ios_deploy_dir = self.install_or_update_repo('ios-deploy',
platform='ios',
Expand Down Expand Up @@ -385,10 +395,10 @@ def _unlock_keychain(self):
if not error:
correct = True
break
self.error('Invalid keychain password')
self.buildozer.error('Invalid keychain password')

if not correct:
self.error('Unable to unlock the keychain, exiting.')
self.buildozer.error('Unable to unlock the keychain, exiting.')
raise BuildozerCommandException()

# maybe user want to save it for further reuse?
Expand Down
10 changes: 2 additions & 8 deletions tests/targets/test_android.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,15 @@
init_buildozer,
patch_buildozer,
patch_buildozer_checkbin,
patch_buildozer_cmd,
patch_buildozer_file_exists,
)


def patch_buildozer_cmd():
return patch_buildozer("cmd")


def patch_buildozer_cmd_expect():
return patch_buildozer("cmd_expect")


def patch_buildozer_file_exists():
return patch_buildozer("file_exists")


def patch_buildozer_download():
return patch_buildozer("download")

Expand Down
141 changes: 140 additions & 1 deletion tests/targets/test_ios.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,19 @@

import pytest

from buildozer import BuildozerCommandException
from buildozer.targets.ios import TargetIos
from tests.targets.utils import init_buildozer, patch_buildozer_checkbin
from tests.targets.utils import (
init_buildozer,
patch_buildozer_checkbin,
patch_buildozer_cmd,
patch_buildozer_error,
patch_buildozer_file_exists,
)


def patch_target_ios(method):
return mock.patch("buildozer.targets.ios.TargetIos.{method}".format(method=method))


def init_target(temp_dir, options=None):
Expand Down Expand Up @@ -78,3 +89,131 @@ def test_check_configuration_tokens(self):
]
)
]

def test_get_available_packages(self):
"""Checks the toolchain `recipes --compact` output is parsed correctly to return recipe list."""
target = init_target(self.temp_dir)
with patch_target_ios("toolchain") as m_toolchain:
m_toolchain.return_value = ("hostpython3 kivy pillow python3 sdl2", None, 0)
available_packages = target.get_available_packages()
assert m_toolchain.call_args_list == [
mock.call("recipes --compact", get_stdout=True)
]
assert available_packages == [
"hostpython3",
"kivy",
"pillow",
"python3",
"sdl2",
]

def test_install_platform(self):
"""Checks `install_platform()` calls clone commands and sets `ios_dir` and `ios_deploy_dir` attributes."""
target = init_target(self.temp_dir)
assert target.ios_dir is None
assert target.ios_deploy_dir is None
with patch_buildozer_cmd() as m_cmd:
target.install_platform()
assert m_cmd.call_args_list == [
mock.call("git clone https://github.com/kivy/kivy-ios", cwd=mock.ANY),
mock.call(
"git clone --branch 1.10.0 https://github.com/phonegap/ios-deploy",
cwd=mock.ANY,
),
]
assert target.ios_dir.endswith(".buildozer/ios/platform/kivy-ios")
assert target.ios_deploy_dir.endswith(".buildozer/ios/platform/ios-deploy")

def test_compile_platform(self):
"""Checks the `toolchain build` command is called on the ios requirements."""
target = init_target(self.temp_dir)
target.ios_deploy_dir = "/ios/deploy/dir"
# fmt: off
with patch_target_ios("get_available_packages") as m_get_available_packages, \
patch_target_ios("toolchain") as m_toolchain, \
patch_buildozer_file_exists() as m_file_exists:
m_get_available_packages.return_value = ["hostpython3", "python3"]
m_file_exists.return_value = True
target.compile_platform()
# fmt: on
assert m_get_available_packages.call_args_list == [mock.call()]
assert m_toolchain.call_args_list == [mock.call("build python3")]
assert m_file_exists.call_args_list == [
mock.call(target.ios_deploy_dir, "ios-deploy")
]

def test_get_package(self):
"""Checks default package values and checks it can be overridden."""
# default value
target = init_target(self.temp_dir)
package = target._get_package()
assert package == "org.test.myapp"
# override
target = init_target(
self.temp_dir,
{"package.domain": "com.github.kivy", "package.name": "buildozer"},
)
package = target._get_package()
assert package == "com.github.kivy.buildozer"

def test_unlock_keychain_wrong_password(self):
"""A `BuildozerCommandException` should be raised on wrong password 3 times."""
target = init_target(self.temp_dir)
# fmt: off
with mock.patch("buildozer.targets.ios.getpass") as m_getpass, \
patch_buildozer_cmd() as m_cmd, \
pytest.raises(BuildozerCommandException):
m_getpass.return_value = "password"
# the `security unlock-keychain` command returned an error
# hence we'll get prompted to enter the password
m_cmd.return_value = (None, None, 123)
target._unlock_keychain()
# fmt: on
assert m_getpass.call_args_list == [
mock.call("Password to unlock the default keychain:"),
mock.call("Password to unlock the default keychain:"),
mock.call("Password to unlock the default keychain:"),
]

def test_build_package_no_signature(self):
"""Code signing is currently required to go through final `xcodebuild` steps."""
target = init_target(self.temp_dir)
target.ios_dir = "/ios/dir"
# fmt: off
with patch_target_ios("_unlock_keychain") as m_unlock_keychain, \
patch_buildozer_error() as m_error, \
patch_target_ios("xcodebuild") as m_xcodebuild, \
mock.patch("buildozer.targets.ios.plistlib.readPlist") as m_readPlist, \
mock.patch("buildozer.targets.ios.plistlib.writePlist") as m_writePlist, \
patch_buildozer_cmd() as m_cmd:
m_readPlist.return_value = {}
target.build_package()
# fmt: on
assert m_unlock_keychain.call_args_list == [mock.call()]
assert m_error.call_args_list == [
mock.call(
"Cannot create the IPA package without signature. "
'You must fill the "ios.codesign.debug" token.'
)
]
assert m_xcodebuild.call_args_list == [
mock.call(
"-configuration Debug ENABLE_BITCODE=NO "
"CODE_SIGNING_ALLOWED=NO clean build",
cwd="/ios/dir/myapp-ios",
)
]
assert m_readPlist.call_args_list == [
mock.call("/ios/dir/myapp-ios/myapp-Info.plist")
]
assert m_writePlist.call_args_list == [
mock.call(
{
"CFBundleIdentifier": "org.test.myapp",
"CFBundleShortVersionString": "0.1",
"CFBundleVersion": "0.1.None",
},
"/ios/dir/myapp-ios/myapp-Info.plist",
)
]
assert m_cmd.call_args_list == [mock.call(mock.ANY, cwd=target.ios_dir)]
12 changes: 12 additions & 0 deletions tests/targets/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,22 @@ def patch_buildozer(method):
return mock.patch("buildozer.Buildozer.{method}".format(method=method))


def patch_buildozer_cmd():
return patch_buildozer("cmd")


def patch_buildozer_checkbin():
return patch_buildozer("checkbin")


def patch_buildozer_file_exists():
return patch_buildozer("file_exists")


def patch_buildozer_error():
return patch_buildozer("error")


def default_specfile_path():
return os.path.join(os.path.dirname(buildozer_module.__file__), "default.spec")

Expand Down

0 comments on commit da32f4e

Please sign in to comment.