Skip to content

Commit

Permalink
Almost at 100%...
Browse files Browse the repository at this point in the history
  • Loading branch information
freakboy3742 committed Oct 2, 2022
1 parent fd02607 commit ed9ab08
Show file tree
Hide file tree
Showing 19 changed files with 313 additions and 58 deletions.
33 changes: 18 additions & 15 deletions src/briefcase/integrations/android_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -792,19 +792,20 @@ def select_target_device(self, device_or_avd):
avd = choice[1:]
else:
# Either a running emulator, or a physical device. Regardless,
# we need to check if the device is developer enabled
try:
details = running_devices[choice]
if not details["authorized"]:
# An unauthorized physical device
raise AndroidDeviceNotAuthorized(choice)

# Return the device ID and name.
device = choice
name = device_choices[choice]
avd = details.get("avd")
except KeyError as e:
raise InvalidDeviceError("device ID", choice) from e
# we need to check if the device is developer enabled.
# Functionally, we know the the device *must* be in the list of
# choices; which means it's also in the list of running devices
# and the list of device choices, so any KeyError on those lookups
# indicates a deeper problem.
details = running_devices[choice]
if not details["authorized"]:
# An unauthorized physical device
raise AndroidDeviceNotAuthorized(choice)

# Return the device ID and name.
device = choice
name = device_choices[choice]
avd = details.get("avd")

if avd:
self.tools.logger.info(
Expand Down Expand Up @@ -983,6 +984,7 @@ def start_emulator(self, avd):
"""
if avd not in set(self.emulators()):
raise InvalidDeviceError("emulator AVD", avd)

emulator_popen = self.tools.subprocess.Popen(
[
os.fsdecode(self.emulator_path),
Expand Down Expand Up @@ -1041,8 +1043,9 @@ def start_emulator(self, avd):
adb = None
known_devices.add(device)

# Try again in 2 seconds...
self.sleep(2)
# If we haven't found a device, try again in 2 seconds...
if adb is None:
self.sleep(2)

# Phase 2: Wait for the boot process to complete
if not adb.has_booted():
Expand Down
2 changes: 1 addition & 1 deletion src/briefcase/integrations/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def verify_git_is_installed(tools):
# Check whether the git executable could be imported.
try:
import git
except ImportError as e:
except ImportError as e: # pragma: no cover
# macOS provides git as part of the Xcode command line tools,
# and also hijacks /usr/bin/git with a trigger that prompts the
# installation of those tools. Customize the message to account
Expand Down
40 changes: 20 additions & 20 deletions src/briefcase/integrations/xcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,26 +183,26 @@ def ensure_xcode_is_installed(
line for line in output.split("\n") if line.startswith("Xcode ")
]
if version_lines:
try:
# Split the content after the first space
# and split that content on the dots.
# Append 0's to fill any gaps caused by
# version numbers that don't have a minor version.
version = tuple(
int(v) for v in version_lines[0].split(" ")[1].split(".")
) + (0, 0)

if version < min_version:
min_version = ".".join(str(v) for v in min_version)
version = ".".join(str(v) for v in version)
raise BriefcaseCommandError(
f"Xcode {min_version} is required; {version} is installed. Please update Xcode."
)
else:
# Version number is acceptable
return
except IndexError:
pass
# Split the content after the first space
# and split that content on the dots.
# Append 0's to fill any gaps caused by
# version numbers that don't have a minor version.
# At this point, version lines *must* have at least one element,
# and each line *must* have a string with at least one space,
# so if either array lookup fails, something weird is happening.
version = tuple(
int(v) for v in version_lines[0].split(" ")[1].split(".")
) + (0, 0)

if version < min_version:
min_version = ".".join(str(v) for v in min_version)
version = ".".join(str(v) for v in version)
raise BriefcaseCommandError(
f"Xcode {min_version} is required; {version} is installed. Please update Xcode."
)
else:
# Version number is acceptable
return

tools.logger.warning(
"""
Expand Down
4 changes: 4 additions & 0 deletions tests/console/test_Log.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ def test_save_log_to_file_no_exception(tmp_path, now):
logger.save_log = True
logger.debug("this is debug output")
logger.info("this is info output")
logger.info("this is [bold]info output with markup[/bold]")
logger.info("this is [bold]info output with escaped markup[/bold]", markup=True)
logger.warning("this is warning output")
logger.error("this is error output")
logger.print("this is print output")
Expand All @@ -75,6 +77,8 @@ def test_save_log_to_file_no_exception(tmp_path, now):
assert log_contents.startswith("Date/Time: 2022-06-25 16:12:29")
assert f"{Log.DEBUG_PREFACE}this is debug output" in log_contents
assert "this is info output" in log_contents
assert "this is [bold]info output with markup[/bold]" in log_contents
assert "this is info output with escaped markup" in log_contents
assert "this is warning output" in log_contents
assert "this is error output" in log_contents
assert "this is print output" in log_contents
Expand Down
13 changes: 13 additions & 0 deletions tests/integrations/android_sdk/ADB/test_start_app.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from subprocess import CalledProcessError
from unittest.mock import MagicMock

import pytest
Expand Down Expand Up @@ -62,3 +63,15 @@ def test_invalid_device(mock_tools):

with pytest.raises(InvalidDeviceError):
adb.start_app("com.example.sample.package", "com.example.sample.activity")


def test_unable_to_start(mock_tools):
"""If the adb calls for other reasons, the error is caught."""
adb = ADB(mock_tools, "exampleDevice")
adb.run = MagicMock(side_effect=CalledProcessError(cmd=["adb"], returncode=1))

with pytest.raises(
BriefcaseCommandError,
match=r"Unable to start com.example.sample.package/com.example.sample.activity on exampleDevice",
):
adb.start_app("com.example.sample.package", "com.example.sample.activity")
8 changes: 8 additions & 0 deletions tests/integrations/android_sdk/AndroidSDK/test_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,11 @@ def test_bad_emulator_abi(mock_tools, android_sdk, host_os, host_arch):
match=rf"The Android emulator does not currently support {host_os} {host_arch} hardware.",
):
android_sdk.emulator_abi


def test_adb_for_device(mock_tools, android_sdk):
"An ADB instance can be bound to a device."
adb = android_sdk.adb("some-device")

assert adb.tools == mock_tools
assert adb.device == "some-device"
86 changes: 82 additions & 4 deletions tests/integrations/android_sdk/AndroidSDK/test_start_emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,86 @@ def test_start_emulator(mock_tools, android_sdk):
]
)

# Took a total of 5 naps.
assert android_sdk.sleep.call_count == 4
# Took a total of 3 naps.
assert android_sdk.sleep.call_count == 3


def test_start_emulator_fast_start(mock_tools, android_sdk):
"""If the emulator starts quickly, that's OK."""
# If the emulator starts *really* fast, there will only be 1 call to devices.
devices = {
"041234567892009a": {
"name": "Unknown device (not authorized for development)",
"authorized": False,
},
"KABCDABCDA1513": {
"name": "Kogan Agora 9",
"authorized": True,
},
"emulator-5554": {
"name": "generic_x86",
"authorized": True,
},
}

android_sdk.devices = MagicMock(
side_effect=[
devices,
]
)

# There will be 3 calls on adb.run (2 calls to avd_name, then
# 1 call to getprop)
android_sdk.mock_run.side_effect = [
# emu avd_name
subprocess.CalledProcessError(
returncode=1, cmd="emu avd name"
), # phyiscal device
"idleEmulator\nOK", # running emulator
# shell getprop sys.boot_completed
"1\n",
]

# poll() on the process continues to return None, indicating no problem.
emu_popen = MagicMock(spec_set=subprocess.Popen)
emu_popen.poll.return_value = None
mock_tools.subprocess.Popen.return_value = emu_popen

# Start the emulator
device, name = android_sdk.start_emulator("idleEmulator")

# The device details are as expected
assert device == "emulator-5554"
assert name == "@idleEmulator (running emulator)"

# The process was started.
mock_tools.subprocess.Popen.assert_called_with(
[
os.fsdecode(android_sdk.emulator_path),
"@idleEmulator",
"-dns-server",
"8.8.8.8",
],
env=android_sdk.env,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
bufsize=1,
start_new_session=True,
)

# There were 3 calls to run
android_sdk.mock_run.assert_has_calls(
[
# 2 calls to get avd name
call("emu", "avd", "name"),
call("emu", "avd", "name"),
# 1 calls to get boot property
call("shell", "getprop", "sys.boot_completed"),
]
)

# Took no naps, as everything was ready when we found it.
assert android_sdk.sleep.call_count == 0


def test_emulator_fail_to_start(mock_tools, android_sdk):
Expand Down Expand Up @@ -332,5 +410,5 @@ def test_emulator_fail_to_boot(mock_tools, android_sdk):
]
)

# Took a total of 5 naps.
assert android_sdk.sleep.call_count == 5
# Took a total of 4 naps.
assert android_sdk.sleep.call_count == 4
2 changes: 2 additions & 0 deletions tests/integrations/android_sdk/AndroidSDK/test_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ def mock_unpack(filename, extract_dir):
(extract_dir / "cmdline-tools" / "bin").mkdir(parents=True)
(extract_dir / "cmdline-tools" / "bin" / "sdkmanager").touch(mode=0o644)
(extract_dir / "cmdline-tools" / "bin" / "avdmanager").touch(mode=0o644)
# Include an extra tool that is already executable.
(extract_dir / "cmdline-tools" / "bin" / "other").touch(mode=0o755)


def accept_license(android_sdk_root_path):
Expand Down
31 changes: 31 additions & 0 deletions tests/integrations/subprocess/test_Subprocess__check_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,37 @@ def test_calledprocesserror_exception_logging(mock_sub, capsys):
assert capsys.readouterr().out == expected_output


def test_calledprocesserror_exception_logging_no_cmd_output(mock_sub, capsys):
"""If command errors, and there is no command output, errors are still
printed."""
mock_sub.tools.logger.verbosity = 2

called_process_error = CalledProcessError(
returncode=-1,
cmd="hello world",
output=None,
stderr="error line 1\nerror line 2",
)
mock_sub._subprocess.check_output.side_effect = called_process_error

with pytest.raises(CalledProcessError):
mock_sub.check_output(["hello", "world"])

expected_output = (
"\n"
">>> Running Command:\n"
">>> hello world\n"
">>> Working Directory:\n"
f">>> {Path.cwd()}\n"
">>> Command Error Output (stderr):\n"
">>> error line 1\n"
">>> error line 2\n"
">>> Return code: -1\n"
)

assert capsys.readouterr().out == expected_output


@pytest.mark.parametrize(
"in_kwargs, kwargs",
[
Expand Down
13 changes: 13 additions & 0 deletions tests/integrations/subprocess/test_Subprocess__final_kwargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ def test_no_overrides(mock_sub):
}


def test_explicit_no_overrides(mock_sub):
"""With explicitly no overrides, there are still kwargs."""
assert mock_sub.final_kwargs(env=None) == {
"env": {
"VAR1": "Value 1",
"PS1": "\nLine 2\n\nLine 4",
"PWD": "/home/user/",
},
"text": True,
"encoding": CONSOLE_ENCODING,
}


def test_env_overrides(mock_sub):
"""If environmental overrides are provided, they supercede the default
environment."""
Expand Down
16 changes: 16 additions & 0 deletions tests/integrations/subprocess/test_get_process_id_by_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
info=dict(cmdline=["/bin/cmd.sh", "--input", "data"], create_time=20, pid=100)
)
]

process_list_two_procs_diff_cmd = [
Process(
info=dict(
Expand All @@ -23,6 +24,7 @@
)
),
]

process_list_two_procs_same_cmd = [
Process(
info=dict(cmdline=["/bin/cmd.sh", "--input", "data"], create_time=20, pid=100)
Expand Down Expand Up @@ -114,3 +116,17 @@ def test_get_process_id_by_command_w_command(
found_pid = get_process_id_by_command(command=command, logger=Log())
assert found_pid == expected_pid
assert capsys.readouterr().out == expected_stdout


def test_get_process_id_no_logging(monkeypatch, capsys):
"""If no logger is provided, warnings about ambiguous matches aren't
printed."""
monkeypatch.setattr(
"psutil.process_iter",
lambda attrs: process_list_two_procs_same_cmd,
)
found_pid = get_process_id_by_command(
command_list=["/bin/cmd.sh", "--input", "data"]
)
assert found_pid == 100
assert capsys.readouterr().out == ""
6 changes: 6 additions & 0 deletions tests/integrations/xcode/simctl/single-device-booted.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@

],
"com.apple.CoreSimulator.SimRuntime.iOS-13-2" : [
{
"state" : "Shutdown",
"udid" : "91314875-180C-41BB-AE5C-968C8B89897D",
"isAvailable" : true,
"name" : "iPhone 14"
},
{
"state" : "Booted",
"isAvailable" : true,
Expand Down
6 changes: 6 additions & 0 deletions tests/integrations/xcode/simctl/single-device-shutdown.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@

],
"com.apple.CoreSimulator.SimRuntime.iOS-13-2" : [
{
"state" : "Shutdown",
"udid" : "91314875-180C-41BB-AE5C-968C8B89897D",
"isAvailable" : true,
"name" : "iPhone 14"
},
{
"state" : "Shutdown",
"isAvailable" : true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@

],
"com.apple.CoreSimulator.SimRuntime.iOS-13-2" : [
{
"state" : "Shutdown",
"udid" : "91314875-180C-41BB-AE5C-968C8B89897D",
"isAvailable" : true,
"name" : "iPhone 14"
},
{
"state" : "Shutting Down",
"isAvailable" : true,
Expand Down
Loading

0 comments on commit ed9ab08

Please sign in to comment.