Skip to content

Commit

Permalink
Add iOS log streaming.
Browse files Browse the repository at this point in the history
  • Loading branch information
freakboy3742 committed Jun 6, 2021
1 parent c60f2a0 commit 1bdd8df
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 67 deletions.
20 changes: 20 additions & 0 deletions src/briefcase/platforms/iOS/xcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,26 @@ def run_app(self, app: BaseConfig, udid=None, **kwargs):
)
)

# Start streaming logs for the app.
try:
print()
print("[{app.app_name}] Following simulator log output (type CTRL-C to stop log)...".format(app=app))
print("=" * 75)
self.subprocess.run(
[
"xcrun", "simctl", "spawn", udid,
"log", "stream",
"--style", "compact",
"--predicate", 'senderImagePath ENDSWITH "/{app.formal_name}"'.format(app=app)
],
check=True,
)
except subprocess.CalledProcessError:
print()
raise BriefcaseCommandError(
"Unable to start log stream for app {app.app_name}.".format(app=app)
)

# Preserve the device selection as state.
return {
'udid': udid
Expand Down
20 changes: 4 additions & 16 deletions src/briefcase/platforms/macOS/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ def run_app(self, app: BaseConfig, **kwargs):
app=app
))
try:
print()
self.subprocess.run(
[
'open',
Expand All @@ -50,18 +49,6 @@ def run_app(self, app: BaseConfig, **kwargs):
"Unable to start app {app.app_name}.".format(app=app)
)

# Find the PID of the new instance using pgrep
try:
pid = self.subprocess.check_output(
["pgrep", "-n", app.formal_name],
universal_newlines=True,
).strip()
except subprocess.CalledProcessError:
print()
raise BriefcaseCommandError(
"Unable to find PID for app {app.app_name}.".format(app=app)
)

# Start streaming logs for the app.
try:
print()
Expand All @@ -71,16 +58,17 @@ def run_app(self, app: BaseConfig, **kwargs):
[
"log",
"stream",
"--process", pid,
"--style", "compact",
"--type", "log",
"--predicate", 'senderImagePath=="{sender}"'.format(
sender=str(self.binary_path(app) / "Contents" / "MacOS" / app.formal_name)
)
],
check=True,
)
except subprocess.CalledProcessError:
print()
raise BriefcaseCommandError(
"Unable to start log stream for app {app.app_name} (pid {pid}).".format(app=app, pid=pid)
"Unable to start log stream for app {app.app_name}.".format(app=app)
)


Expand Down
123 changes: 123 additions & 0 deletions tests/platforms/iOS/xcode/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ def test_run_app_simulator_booted(first_app_config, tmp_path):
'com.example.first-app'
],
check=True
),
# Start tailing the log
mock.call(
[
'xcrun', 'simctl', 'spawn',
'2D3503A3-6EB9-4B37-9B17-C7EFEF2FA32D',
"log", "stream",
"--style", "compact",
"--predicate", 'senderImagePath ENDSWITH "/First App"'
],
check=True
)
])

Expand Down Expand Up @@ -131,6 +142,17 @@ def test_run_app_simulator_shut_down(first_app_config, tmp_path):
'com.example.first-app'
],
check=True
),
# Start tailing the log
mock.call(
[
'xcrun', 'simctl', 'spawn',
'2D3503A3-6EB9-4B37-9B17-C7EFEF2FA32D',
"log", "stream",
"--style", "compact",
"--predicate", 'senderImagePath ENDSWITH "/First App"'
],
check=True
)
])

Expand Down Expand Up @@ -210,6 +232,17 @@ def test_run_app_simulator_shutting_down(first_app_config, tmp_path):
'com.example.first-app'
],
check=True
),
# Start tailing the log
mock.call(
[
'xcrun', 'simctl', 'spawn',
'2D3503A3-6EB9-4B37-9B17-C7EFEF2FA32D',
"log", "stream",
"--style", "compact",
"--predicate", 'senderImagePath ENDSWITH "/First App"'
],
check=True
)
])

Expand Down Expand Up @@ -498,3 +531,93 @@ def test_run_app_simulator_launch_failure(first_app_config, tmp_path):
check=True
)
])


def test_run_app_simulator_log_stream_failure(first_app_config, tmp_path):
"If the log stream fails to start, raise an error"
command = iOSXcodeRunCommand(base_path=tmp_path)

# A valid target device will be selected.
command.select_target_device = mock.MagicMock(
return_value=(
'2D3503A3-6EB9-4B37-9B17-C7EFEF2FA32D', '13.2', 'iPhone 11'
)
)

# Simulator is shut down
command.get_device_state = mock.MagicMock(return_value=DeviceState.SHUTDOWN)

# Call to boot and open simulator, uninstall and install succeed, but launch fails.
command.subprocess = mock.MagicMock()
command.subprocess.run.side_effect = [
0,
0,
0,
0,
0,
subprocess.CalledProcessError(
cmd=['xcrun', 'simctl', 'spawn', '...'],
returncode=1
),
]

# Run the app
with pytest.raises(BriefcaseCommandError):
command.run_app(first_app_config)

# The correct sequence of commands was issued.
command.subprocess.run.assert_has_calls([
# Boot the device
mock.call(
['xcrun', 'simctl', 'boot', '2D3503A3-6EB9-4B37-9B17-C7EFEF2FA32D'],
check=True,
),
# Open the simulator
mock.call(
[
'open',
'-a', 'Simulator',
'--args',
'-CurrentDeviceUDID', '2D3503A3-6EB9-4B37-9B17-C7EFEF2FA32D'
],
check=True
),
# Uninstall the old app
mock.call(
[
'xcrun', 'simctl', 'uninstall',
'2D3503A3-6EB9-4B37-9B17-C7EFEF2FA32D',
'com.example.first-app'
],
check=True
),
# Install the new app
mock.call(
[
'xcrun', 'simctl', 'install',
'2D3503A3-6EB9-4B37-9B17-C7EFEF2FA32D',
tmp_path / 'iOS' / 'Xcode' / 'First App' / 'build' / 'Debug-iphonesimulator' / 'First App.app'
],
check=True
),
# Launch the new app
mock.call(
[
'xcrun', 'simctl', 'launch',
'2D3503A3-6EB9-4B37-9B17-C7EFEF2FA32D',
'com.example.first-app'
],
check=True
),
# Start tailing the log
mock.call(
[
'xcrun', 'simctl', 'spawn',
'2D3503A3-6EB9-4B37-9B17-C7EFEF2FA32D',
"log", "stream",
"--style", "compact",
"--predicate", 'senderImagePath ENDSWITH "/First App"'
],
check=True
)
])
68 changes: 17 additions & 51 deletions tests/platforms/macOS/app/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,29 @@


def test_run_app(first_app_config, tmp_path):
"A macOS App can be started"
"A macOS app can be started"
command = macOSAppRunCommand(base_path=tmp_path)
command.subprocess = mock.MagicMock()
command.subprocess.check_output.return_value = "3742"

command.run_app(first_app_config)

# Calls were made to start the app and to start a log stream.
bin_path = str(command.binary_path(first_app_config))
command.subprocess.run.assert_has_calls([
mock.call(
['open', '-n', str(command.binary_path(first_app_config))],
['open', '-n', bin_path],
check=True
),
mock.call(
['log', 'stream', '--process', '3742', '--style', 'compact', '--type', 'log'],
[
'log', 'stream',
'--style', 'compact',
'--predicate', 'senderImagePath=="{bin_path}/Contents/MacOS/First App"'.format(bin_path=bin_path)
],
check=True,
)
])

# A call to pgrep was also made
command.subprocess.check_output.assert_called_with(
['pgrep', '-n', 'First App'],
universal_newlines=True
)


def test_run_app_failed(first_app_config, tmp_path):
"If there's a problem started the app, an exception is raised"
Expand All @@ -52,44 +50,13 @@ def test_run_app_failed(first_app_config, tmp_path):
check=True
)

# but no attempt was made to find the PID
command.subprocess.check_output.assert_not_called()


def test_run_app_no_pid(first_app_config, tmp_path):
"If there's a problem finding the PID of the app, an exception is raised"
command = macOSAppRunCommand(base_path=tmp_path)
command.subprocess = mock.MagicMock()
command.subprocess.check_output.side_effect = subprocess.CalledProcessError(
cmd=['pgrep', '-n', 'invalid app'],
returncode=1
)

with pytest.raises(BriefcaseCommandError):
command.run_app(first_app_config)

# The run command was still invoked, but the log stream wasn't
command.subprocess.run.assert_has_calls([
mock.call(
['open', '-n', str(command.binary_path(first_app_config))],
check=True
),
])

# and an attempt was made to but no attempt was made to find the PID
command.subprocess.check_output.assert_called_with(
['pgrep', '-n', 'First App'],
universal_newlines=True
)


def test_run_app_log_failed(first_app_config, tmp_path):
def test_run_app_log_stream_failed(first_app_config, tmp_path):
"If the log can't be streamed, the app still starts"
command = macOSAppRunCommand(base_path=tmp_path)
command.subprocess = mock.MagicMock()
command.subprocess.check_output.return_value = "3742"
command.subprocess.run.side_effect = [
None,
0,
subprocess.CalledProcessError(
cmd=['log', 'stream'],
returncode=1
Expand All @@ -101,19 +68,18 @@ def test_run_app_log_failed(first_app_config, tmp_path):
command.run_app(first_app_config)

# Calls were made to start the app and to start a log stream.
bin_path = str(command.binary_path(first_app_config))
command.subprocess.run.assert_has_calls([
mock.call(
['open', '-n', str(command.binary_path(first_app_config))],
['open', '-n', bin_path],
check=True
),
mock.call(
['log', 'stream', '--process', '3742', '--style', 'compact', '--type', 'log'],
[
'log', 'stream',
'--style', 'compact',
'--predicate', 'senderImagePath=="{bin_path}/Contents/MacOS/First App"'.format(bin_path=bin_path)
],
check=True,
)
])

# A call to pgrep was also made
command.subprocess.check_output.assert_called_with(
['pgrep', '-n', 'First App'],
universal_newlines=True
)

0 comments on commit 1bdd8df

Please sign in to comment.