blob: 54b4012cc762bbfbfcaf8c3e92f89a70218fc756 [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2023 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Simple FFX host tool E2E test."""
import json
import logging
import subprocess
import ffxtestcase
from honeydew.transports.ffx.errors import FfxCommandError
from mobly import asserts, test_runner
_LOGGER: logging.Logger = logging.getLogger(__name__)
class FfxTest(ffxtestcase.FfxTestCase):
"""FFX host tool E2E test."""
def test_component_list(self) -> None:
"""Test `ffx component list` output returns as expected."""
output = self.dut.ffx.run(["component", "list"])
asserts.assert_true(
len(output.splitlines()) > 0,
f"stdout is unexpectedly empty: {output}",
)
def test_target_list_includes_port(self) -> None:
"""Test `ffx target list` output returns as expected."""
# NOTE: This test fails if the device under test is an user-mode networking emulator.
output = self.dut.ffx.run(["target", "list", "--format", "a"])
asserts.assert_true(
":22" in output, f"expected stdout to contain ':22',got {output}"
)
def test_target_show(self) -> None:
"""Test `ffx target show` output returns as expected."""
output = self.dut.ffx.get_target_information()
got_device_name = output.target.name
# Assert FFX's target show device name matches Honeydew's.
asserts.assert_equal(got_device_name, self.dut.device_name)
def test_target_echo_repeat(self) -> None:
"""Test `ffx target echo --repeat` is resilient to daemon failure."""
with self.dut.ffx.popen(
["target", "echo", "--repeat"],
stdout=subprocess.PIPE,
text=False,
) as process:
try:
line = process.stdout.readline()
asserts.assert_true(
line.startswith(b"SUCCESS"),
f"First ping didn't succeed: {line}",
)
self.dut.ffx.run(["daemon", "stop"])
while True:
line = process.stdout.readline()
if not line.startswith(b"ERROR") and not line.startswith(
b"Waiting for"
):
break
print(line)
asserts.assert_true(
line.startswith(b"SUCCESS"),
f"Success didn't resume after error: {line}",
)
finally:
process.kill()
# Note: in this test we do _not_ want to probe the device, since we will try
# to probe every device visible in the builder. But in EngProd environments,
# that could be dozens of devices
# TODO(b/355292969): re-enable when client-side discovery is re-enabled
# (see libtarget::is_discover_enabled())
def _test_target_list_without_discovery(self) -> None:
"""Test `ffx target list` output returns as expected when discovery is off."""
self.dut.ffx.run(["daemon", "stop"])
output = self.dut.ffx.run(
[
"--machine",
"json",
"-c",
"ffx.isolated=true",
"target",
"list",
]
)
output_json = json.loads(output)
devices = [
o for o in output_json if o["nodename"] == self.dut.device_name
]
# Assert ffx's target list device name contain's Honeydew's device.
asserts.assert_greater(len(devices), 0)
# Assert that we are not probing the device to identify the RCS state
asserts.assert_equal(devices[0]["rcs_state"], "N")
# Assert that we are not probing the device to identify the type
asserts.assert_equal(devices[0]["target_type"], "Unknown")
with asserts.assert_raises(FfxCommandError):
self.dut.ffx.run(["-c", "daemon.autostart=false", "daemon", "echo"])
# TODO(b/355292969): re-enable when client-side discovery is re-enabled
# (see libtarget::is_discover_enabled())
def _test_target_list_nodename_without_discovery(self) -> None:
"""Test `ffx target list <nodename>` output returns as expected.
This is for when discovery is off.
"""
self.dut.ffx.run(["daemon", "stop"], capture_output=False)
output = self.dut.ffx.run(
[
"--machine",
"json",
"-c",
"ffx.isolated=true",
"target",
"list",
self.dut.device_name,
]
)
output_json = json.loads(output)
devices = [
o for o in output_json if o["nodename"] == self.dut.device_name
]
# Assert Honeydew's device is the only device returned.
asserts.assert_equal(len(devices), 1)
# Assert that we can correctly identify the RCS state
asserts.assert_equal(devices[0]["rcs_state"], "Y")
# Assert that we can correctly identify the product
asserts.assert_not_equal(devices[0]["target_type"], "Unknown")
# Make sure the daemon hadn't started running
with asserts.assert_raises(FfxCommandError):
self.dut.ffx.run(["-c", "daemon.autostart=false", "daemon", "echo"])
def test_local_discovery(self) -> None:
"""Test that we can resolve a target locally"""
# Let's make sure the CLI believes that discovery is turned off,
# by setting ffx.isolated=true
cmd = [
"-c",
"ffx.isolated=true",
"-t",
f"{self.dut.ffx._target}",
"target",
"echo",
]
output = self.run_ffx(cmd)
# Unfortunately we're not checking _that_ this is being resolved
# locally. To do that we'd probably want to run a test in which the
# daemon isn't running, but honeydew isn't set up for that.
asserts.assert_equal(output, 'SUCCESS: received "Ffx"\n')
def test_wait_with_local_discovery(self) -> None:
"""Test waiting for a target when daemon discovery is disabled"""
# Let's make sure the CLI believes that discovery is turned off,
# by setting ffx.isolated=true
cmd = [
"-c",
"ffx.isolated=true",
"-t",
f"{self.dut.ffx._target}",
"target",
"wait",
]
output = self.run_ffx(cmd)
asserts.assert_equal(output, "")
def test_machine_errors(self) -> None:
"""Test machine formattable errors."""
cmd = [
"--machine",
"json",
"-t",
"this-should-not-exist",
"target",
"show",
]
(code, stdout, stderr) = self.run_ffx_unchecked(cmd)
output_json = json.loads(stdout)
asserts.assert_equal(stderr, "")
asserts.assert_equal(output_json["type"], "user")
asserts.assert_equal(
output_json["message"],
(
"Failed to create remote control proxy: "
'Timeout attempting to reach target "this-should-not-exist". '
"Please check the connection to the target; "
"`ffx doctor -v` may help diagnose the issue."
),
)
asserts.assert_equal(output_json["code"], 1)
def test_machine_user_error(self) -> None:
"""Test machine formattable errors for a user error kind."""
cmd = [
"--machine",
"json",
"repository",
"server",
"start",
"--background",
"--foreground",
]
(code, stdout, stderr) = self.run_ffx_unchecked(cmd)
try:
output_json = json.loads(stdout)
except json.JSONDecodeError as e:
raise ValueError(f"could not parse string as JSON: {e}. {stdout}")
# This is an expected message on stderr. The log file is changed for package servers.
asserts.assert_in("Switching log file to", stderr)
asserts.assert_equal(output_json["type"], "user")
asserts.assert_equal(
output_json["message"],
"--background and --foreground are mutually exclusive",
)
asserts.assert_equal(output_json["code"], 1)
def test_machine_config_error(self) -> None:
"""Test machine formattable errors for a user error kind."""
cmd = [
"--machine",
"json",
"-t",
"foo",
"-t",
"bar",
"target",
"show",
]
(code, stdout, stderr) = self.run_ffx_unchecked(cmd)
output_json = json.loads(stdout)
asserts.assert_equal(stderr, "")
asserts.assert_equal(output_json["type"], "config")
asserts.assert_equal(
output_json["message"],
"Error parsing option '-t' with value 'bar': duplicate values provided\n",
)
asserts.assert_equal(output_json["code"], 1)
def test_arg_parse_error_formats(self) -> None:
"""Test machine formattable errors for a user error kind."""
cmd = [
"-t",
"foo",
"-t",
"bar",
"target",
"show",
]
(code, stdout, stderr) = self.run_ffx_unchecked(cmd)
output_json = json.loads(stdout)
asserts.assert_equal(stderr, "")
asserts.assert_equal(output_json["type"], "config")
asserts.assert_equal(
output_json["message"],
"Error parsing option '-t' with value 'bar': duplicate values provided\n",
)
asserts.assert_equal(output_json["code"], 1)
def test_machine_unexpected_error(self) -> None:
"""Test machine formattable errors for a user error kind."""
cmd = [
"--machine",
"json",
"-t",
"foo,bar",
"target",
"show",
]
(code, stdout, stderr) = self.run_ffx_unchecked(cmd)
output_json = json.loads(stdout)
asserts.assert_equal(stderr, "")
asserts.assert_equal(output_json["type"], "unexpected")
asserts.assert_equal(
output_json["message"],
"--config must either be a file path, /\n a valid JSON object, or comma separated key=value pairs.",
)
asserts.assert_equal(output_json["code"], 1)
def test_machine_help(self) -> None:
"""Test machine formattable help."""
cmd = [
"--machine",
"json",
"--help",
]
(code, stdout, stderr) = self.run_ffx_unchecked(cmd)
json.loads(stdout)
asserts.assert_equal(stderr, "")
asserts.assert_equal(code, 0)
def test_daemon_start_background_works_with_autostart_false(self) -> None:
"""Test that `ffx daemon start --background` works even if daemon.autostart=false"""
self.dut.ffx.run(["daemon", "stop"])
# We're validating that this command doesn't throw an exception
self.dut.ffx.run(
["-c", "daemon.autostart=false", "daemon", "start", "--background"]
)
def test_shared_data(self) -> None:
"""Test `ffx -c shared_dir=<dir>` will use the value passed in for $SHARED_DATA"""
(_code, stdout, _stderr) = self.run_ffx_unchecked(
["-c", "shared_data=foo", "config", "get", "monitor.pid_file"]
)
asserts.assert_true(
"foo/monitor" in stdout,
"Expected SHARED_DATA to be correctly set",
stdout,
)
if __name__ == "__main__":
test_runner.main()