[fidlcat] Integration test for fidlcat.
Also includes support for cleanly shutting down the agent on target
from fidlcat.
Also includes changes to core product configuration to run e2e tests.
Bug: 6515
Change-Id: I04668a31489525341b3d44fc54a00e625dca9d8f
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/376998
Commit-Queue: Jeremy Manson <jeremymanson@google.com>
Reviewed-by: Vincent Belliard <vbelliard@google.com>
Reviewed-by: Brett Wilson <brettw@google.com>
Testability-Review: Brett Wilson <brettw@google.com>
diff --git a/src/tests/end_to_end/fidlcat/BUILD.gn b/src/tests/end_to_end/fidlcat/BUILD.gn
new file mode 100644
index 0000000..414b7e3
--- /dev/null
+++ b/src/tests/end_to_end/fidlcat/BUILD.gn
@@ -0,0 +1,99 @@
+# Copyright 2020 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.
+
+import("//build/dart/test.gni")
+import("//build/host.gni")
+import("//build/testing/environments.gni")
+
+_shared_out_dir = get_label_info(":bogus(${target_toolchain})", "root_out_dir")
+
+dart_test("fidlcat_test") {
+ sources = [ "fidlcat_test.dart" ]
+
+ deps = [
+ "//sdk/testing/sl4f/client",
+ "//third_party/dart-pkg/pub/logging",
+ "//third_party/dart-pkg/pub/mockito",
+ "//third_party/dart-pkg/pub/test",
+ ]
+
+ non_dart_deps = [ ":runtime_deps" ]
+
+ args = [ "--data-dir=" + rebase_path(_shared_out_dir) ]
+
+ environments = [
+ # Runs on "main" builders (try and ci) in QEMU environments.
+ qemu_env,
+ ]
+}
+
+# Extract the symbols for the given ELF file from the .build-id directory.
+template("generate_symbols") {
+ assert(defined(invoker.library_label), "Must define 'library_label'")
+ assert(defined(invoker.library_path), "Must define 'library_path'")
+ assert(defined(invoker.output), "Must define 'output'")
+
+ action(target_name) {
+ deps = [ invoker.library_label ]
+ inputs = [ invoker.library_path ]
+ outputs = [ invoker.output ]
+
+ script = "generate_debug.sh"
+
+ args = [
+ "--build-id-dir",
+ rebase_path("$root_build_dir/.build-id"),
+ "--build-id-script",
+ rebase_path("//build/images/elfinfo.py"),
+ "--binary",
+ rebase_path(invoker.library_path),
+ "--output",
+ rebase_path(invoker.output),
+ ]
+ }
+}
+
+generate_symbols("echo_client_cpp_sym") {
+ library_label =
+ "//garnet/examples/fidl/echo_client_cpp:bin($target_toolchain)"
+ library_path = "$_shared_out_dir/echo_client_cpp"
+ output = "${target_gen_dir}/echo_client_cpp.debug"
+}
+
+copy("runtime_deps") {
+ testonly = true
+
+ _data_dir = "$target_gen_dir/runtime_deps"
+
+ sources = [
+ "$_shared_out_dir/fidling/gen/garnet/examples/fidl/services/echo.fidl.json",
+ "$_shared_out_dir/gen/sdk/core.fidl_json.txt",
+ "$host_tools_dir/fidlcat",
+ "${target_gen_dir}/echo_client_cpp.debug",
+ ]
+
+ outputs = [ "$_data_dir/{{source_file_part}}" ]
+
+ metadata = {
+ test_runtime_deps = [ "$_data_dir/fidlcat" ]
+ }
+
+ deps = [
+ ":echo_client_cpp_sym",
+ "//garnet/examples/fidl/echo_client_cpp",
+ "//garnet/examples/fidl/echo_server_cpp",
+ "//garnet/examples/fidl/services:echo",
+ "//garnet/packages/prod:run",
+ "//garnet/packages/tools:sl4f",
+ "//sdk:core_fidl_json($target_toolchain)",
+ "//src/developer/debug/debug_agent",
+ "//tools/fidlcat:fidlcat_host($host_toolchain)",
+ ]
+}
+
+group("tests") {
+ testonly = true
+
+ deps = [ ":fidlcat_test($host_toolchain)" ]
+}
diff --git a/src/tests/end_to_end/fidlcat/OWNERS b/src/tests/end_to_end/fidlcat/OWNERS
new file mode 100644
index 0000000..b04eacb
--- /dev/null
+++ b/src/tests/end_to_end/fidlcat/OWNERS
@@ -0,0 +1,3 @@
+jeremymanson@google.com
+vbelliard@google.com
+*
\ No newline at end of file
diff --git a/src/tests/end_to_end/fidlcat/analysis_options.yaml b/src/tests/end_to_end/fidlcat/analysis_options.yaml
new file mode 100644
index 0000000..05dcaef
--- /dev/null
+++ b/src/tests/end_to_end/fidlcat/analysis_options.yaml
@@ -0,0 +1,5 @@
+# Copyright 2020 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.
+
+include: ../../../../topaz/tools/analysis_options.yaml
diff --git a/src/tests/end_to_end/fidlcat/generate_debug.sh b/src/tests/end_to_end/fidlcat/generate_debug.sh
new file mode 100755
index 0000000..557a662
--- /dev/null
+++ b/src/tests/end_to_end/fidlcat/generate_debug.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+# Copyright 2020 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.
+
+# Copies the debug symbol file in the |build-id-dir| directory with the build
+# id of the given |binary| to |output|. Requires a |build-id-script| to read
+# the build id of |binary|. The goal is to provide the debug symbols with a
+# filename statically known to the build system, so that it can be depended on
+# explicitly.
+
+set -e
+
+while [ $# != 0 ]; do
+ case "$1" in
+ --output)
+ OUTPUT="$2"
+ shift
+ ;;
+ --build-id-script)
+ BUILD_ID_SCRIPT="$2"
+ shift
+ ;;
+ --build-id-dir)
+ BUILD_ID_DIR="$2"
+ shift
+ ;;
+ --binary)
+ BINARY="$2"
+ shift
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+BUILD_ID=$("${BUILD_ID_SCRIPT}" --build-id "${BINARY}")
+
+BUILD_ID_DIR_1=$(printf "${BUILD_ID}" | head -c 2)
+BUILD_ID_DIR_2=$(printf "${BUILD_ID}" | tail -c +3)
+
+/bin/cp "${BUILD_ID_DIR}"/"${BUILD_ID_DIR_1}"/"${BUILD_ID_DIR_2}".debug "${OUTPUT}"
diff --git a/src/tests/end_to_end/fidlcat/pubspec.yaml b/src/tests/end_to_end/fidlcat/pubspec.yaml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/tests/end_to_end/fidlcat/pubspec.yaml
diff --git a/src/tests/end_to_end/fidlcat/test/fidlcat_test.dart b/src/tests/end_to_end/fidlcat/test/fidlcat_test.dart
new file mode 100644
index 0000000..7b3b198
--- /dev/null
+++ b/src/tests/end_to_end/fidlcat/test/fidlcat_test.dart
@@ -0,0 +1,147 @@
+// Copyright 2020 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.
+
+import 'dart:convert';
+import 'dart:io'
+ show
+ Directory,
+ File,
+ FileMode,
+ FileSystemException,
+ Platform,
+ Process,
+ ProcessResult;
+
+import 'package:args/args.dart';
+import 'package:logging/logging.dart';
+import 'package:test/test.dart';
+import 'package:sl4f/sl4f.dart' as sl4f;
+
+const _timeout = Timeout(Duration(minutes: 5));
+
+void main(List<String> arguments) {
+ final log = Logger('fidlcat_test');
+
+ /// fuchsia-pkg URL for the debug agent.
+ const String debugAgentUrl =
+ 'fuchsia-pkg://fuchsia.com/debug_agent#meta/debug_agent.cmx';
+
+ /// Location of the fidlcat executable.
+ final String fidlcatPath =
+ Platform.script.resolve('runtime_deps/fidlcat').toFilePath();
+
+ /// Location of the IR for the core SDK APIs.
+ String fidlIrPath;
+
+ /// A convenient directory for temporary files
+ Directory tempDir;
+
+ /// The core.fidl_json.txt file passed to this program contains a list of
+ /// FIDL IR files relative to the root build directory. This test program
+ /// is not run out of the root build directory, so we create a new file
+ /// containing absolute paths.
+ void setPath() {
+ final parser = ArgParser()..addOption('data-dir');
+ final argResults = parser.parse(arguments);
+ final String dataDir = argResults['data-dir'];
+
+ tempDir = Directory.systemTemp.createTempSync();
+
+ final int timestamp = (DateTime.now()).microsecondsSinceEpoch;
+ final String tempPath = tempDir.path;
+ fidlIrPath = '$tempPath/core.fidl_json_processed-$timestamp.txt';
+
+ final outFile = File(fidlIrPath)..createSync(recursive: true);
+
+ final String fidlIrPathInit =
+ Platform.script.resolve('runtime_deps/core.fidl_json.txt').toFilePath();
+ File(fidlIrPathInit)
+ .openRead()
+ .transform(utf8.decoder)
+ .transform(LineSplitter())
+ .forEach((line) => {
+ outFile.writeAsStringSync('$dataDir/$line\n',
+ mode: FileMode.append)
+ });
+ }
+
+ void cleanup() {
+ try {
+ tempDir.deleteSync(recursive: true);
+ } on FileSystemException {
+ // Do nothing.
+ }
+ }
+
+ sl4f.Sl4f sl4fDriver;
+
+ setUp(() async {
+ sl4fDriver = sl4f.Sl4f.fromEnvironment();
+ await sl4fDriver.startServer();
+ setPath();
+ });
+
+ tearDown(() async {
+ await sl4fDriver.stopServer();
+ sl4fDriver.close();
+ cleanup();
+ });
+
+ /// Formats an IP address so that fidlcat can understand it (removes % part,
+ /// adds brackets around it.)
+ String formatTarget(String target) {
+ log.info('$target: target');
+ try {
+ Uri.parseIPv4Address(target);
+ return target;
+ } on FormatException {
+ try {
+ Uri.parseIPv6Address(target);
+ return '[$target]';
+ } on FormatException {
+ try {
+ Uri.parseIPv6Address(target.split('%')[0]);
+ return '[$target]';
+ } on FormatException {
+ return null;
+ }
+ }
+ }
+ }
+
+ group('fidlcat', () {
+ test('Simple test of echo client output and shutdown', () async {
+ int port = await sl4fDriver.ssh.pickUnusedPort();
+ log.info('Chose port: $port');
+ Future<ProcessResult> agentResult =
+ sl4fDriver.ssh.run('run $debugAgentUrl --port=$port');
+ String target = formatTarget(sl4fDriver.ssh.target);
+ log.info('Target: $target');
+
+ final String symbolPath = Platform.script
+ .resolve('runtime_deps/echo_client_cpp.debug')
+ .toFilePath();
+ final String echoIr =
+ Platform.script.resolve('runtime_deps/echo.fidl.json').toFilePath();
+ ProcessResult processResult;
+ do {
+ processResult = await Process.run(fidlcatPath, [
+ '--connect=$target:$port',
+ '--quit-agent-on-exit',
+ '--fidl-ir-path=@$fidlIrPath',
+ '--fidl-ir-path=$echoIr',
+ '-s',
+ '$symbolPath',
+ 'run',
+ 'fuchsia-pkg://fuchsia.com/echo_client_cpp#meta/echo_client_cpp.cmx',
+ ]);
+ } while (processResult.exitCode == 2); // 2 means can't connect (yet).
+ expect(
+ processResult.stdout.toString(),
+ contains(
+ 'sent request fidl.examples.echo/Echo.EchoString = {"value":"hello world"}'));
+ await agentResult;
+ });
+ }, timeout: _timeout);
+}
diff --git a/tools/fidlcat/command_line_options.cc b/tools/fidlcat/command_line_options.cc
index f2662f0..de4035c 100644
--- a/tools/fidlcat/command_line_options.cc
+++ b/tools/fidlcat/command_line_options.cc
@@ -30,13 +30,18 @@
record all fidl calls invoked by the process. The command may be of the form
"run <component URL>", in which case the given component will be launched.
+ fidlcat will return the code 1 if its parameters are invalid.
+
+ fidlcat expects a debug agent to be running on the target device. It will
+ return the code 2 if it cannot connect to the debug agent.
+
Options:
)";
const char* const kRemoteHostHelp = R"( --connect
- The host and port of the target Fuchsia instance, of the form
- [<ipv6_addr>]:port.)";
+ The host and port of the debug agent running on the target Fuchsia
+ instance, of the form [<ipv6_addr>]:port.)";
const char* const kRemotePidHelp = R"( --remote-pid
The koid of the remote process. Can be passed multiple times.)";
@@ -144,6 +149,11 @@
const char* const kCompareHelp = R"( --compare=<path>
Compare output with the one stored in the given file)";
+const char kQuitAgentOnExit[] = R"( --quit-agent-on-exit
+ Will send a quit message to a connected debug agent in order for it to
+ shutdown. This is so that fidlcat doesn't leak unwanted debug agents on
+ "on-the-fly" debugging sessions.)";
+
const char* const kHelpHelp = R"( --help
-h
Prints all command-line switches.)";
@@ -217,6 +227,8 @@
parser.AddSwitch("verbose", 'v', kVerbosityHelp, &CommandLineOptions::verbose);
parser.AddSwitch("quiet", 'q', kQuietHelp, &CommandLineOptions::quiet);
parser.AddSwitch("log-file", 0, kLogFileHelp, &CommandLineOptions::log_file);
+ parser.AddSwitch("quit-agent-on-exit", 0, kQuitAgentOnExit,
+ &CommandLineOptions::quit_agent_on_exit);
bool requested_help = false;
parser.AddGeneralSwitch("help", 'h', kHelpHelp, [&requested_help]() { requested_help = true; });
diff --git a/tools/fidlcat/command_line_options.h b/tools/fidlcat/command_line_options.h
index 745a541..155b7eb 100644
--- a/tools/fidlcat/command_line_options.h
+++ b/tools/fidlcat/command_line_options.h
@@ -29,6 +29,7 @@
std::string colors = "auto";
int columns = 0;
bool dump_messages = false;
+ bool quit_agent_on_exit = false;
std::optional<std::string> verbose;
std::optional<std::string> quiet;
diff --git a/tools/fidlcat/command_line_options_test.cc b/tools/fidlcat/command_line_options_test.cc
index 004ac88..567a587 100644
--- a/tools/fidlcat/command_line_options_test.cc
+++ b/tools/fidlcat/command_line_options_test.cc
@@ -93,8 +93,9 @@
// Test to ensure that non-existent files are reported accordingly.
TEST_F(CommandLineOptionsTest, BadOptionsTest) {
// Parse the command line.
- std::vector<const char*> argv = {"fakebinary", "--fidl-ir-path", "blah.fidl.json", "--remote-pid",
- "3141", "--fidl-ir-path", "@all_files.txt"};
+ std::vector<const char*> argv = {
+ "fakebinary", "--fidl-ir-path", "blah.fidl.json", "--remote-pid",
+ "3141", "--fidl-ir-path", "@all_files.txt", "--quit-agent-on-exit"};
CommandLineOptions options;
DecodeOptions decode_options;
DisplayOptions display_options;
diff --git a/tools/fidlcat/interception_tests/interception_workflow_test.cc b/tools/fidlcat/interception_tests/interception_workflow_test.cc
index 83dd472..ec97d3d 100644
--- a/tools/fidlcat/interception_tests/interception_workflow_test.cc
+++ b/tools/fidlcat/interception_tests/interception_workflow_test.cc
@@ -430,7 +430,7 @@
initialized_ = true;
global_dispatcher = dispatcher.get();
std::vector<std::string> blank;
- workflow_.Initialize(blank, blank, "", blank, std::move(dispatcher));
+ workflow_.Initialize(blank, blank, "", blank, std::move(dispatcher), false);
// Create fake processes and threads.
InjectProcesses(session);
diff --git a/tools/fidlcat/lib/interception_workflow.cc b/tools/fidlcat/lib/interception_workflow.cc
index 328cb74..ddde0e8 100644
--- a/tools/fidlcat/lib/interception_workflow.cc
+++ b/tools/fidlcat/lib/interception_workflow.cc
@@ -189,8 +189,13 @@
void InterceptionWorkflow::Initialize(
const std::vector<std::string>& symbol_paths, const std::vector<std::string>& symbol_repo_paths,
const std::string& symbol_cache_path, const std::vector<std::string>& symbol_servers,
- std::unique_ptr<SyscallDecoderDispatcher> syscall_decoder_dispatcher) {
+ std::unique_ptr<SyscallDecoderDispatcher> syscall_decoder_dispatcher, bool quit_agent_on_exit) {
syscall_decoder_dispatcher_ = std::move(syscall_decoder_dispatcher);
+
+ if (quit_agent_on_exit) {
+ session_->system().settings().SetBool(zxdb::ClientSettings::System::kQuitAgentOnExit, true);
+ }
+
// 1) Set up symbol index.
// Stolen from console/console_main.cc
diff --git a/tools/fidlcat/lib/interception_workflow.h b/tools/fidlcat/lib/interception_workflow.h
index c0f2273..ab35eba 100644
--- a/tools/fidlcat/lib/interception_workflow.h
+++ b/tools/fidlcat/lib/interception_workflow.h
@@ -101,7 +101,8 @@
const std::vector<std::string>& symbol_repo_paths,
const std::string& symbol_cache_path,
const std::vector<std::string>& symbol_servers,
- std::unique_ptr<SyscallDecoderDispatcher> syscall_decoder_dispatcher);
+ std::unique_ptr<SyscallDecoderDispatcher> syscall_decoder_dispatcher,
+ bool quit_agent_on_exit);
// Connect the workflow to the host/port pair given. |and_then| is posted to
// the loop on completion.
diff --git a/tools/fidlcat/main.cc b/tools/fidlcat/main.cc
index 470ac53..73ce94e 100644
--- a/tools/fidlcat/main.cc
+++ b/tools/fidlcat/main.cc
@@ -79,14 +79,15 @@
uint16_t port;
zxdb::Err parse_err = zxdb::ParseHostPort(*(options.connect), &host, &port);
if (!parse_err.ok()) {
- FXL_LOG(FATAL) << "Could not parse host/port pair: " << parse_err.msg();
+ fprintf(stderr, "Could not parse host/port pair: %s", parse_err.msg().c_str());
+ exit(1);
}
auto attach = [workflow, process_koids, remote_name = options.remote_name,
params](const zxdb::Err& err) {
if (!err.ok()) {
- FXL_LOG(FATAL) << "Unable to connect: " << err.msg();
- return;
+ fprintf(stderr, "Unable to connect: %s", err.msg().c_str());
+ exit(2);
}
FXL_LOG(INFO) << "Connected!";
if (!process_koids.empty()) {
@@ -164,7 +165,8 @@
InterceptionWorkflow workflow;
workflow.Initialize(options.symbol_paths, options.symbol_repo_paths, options.symbol_cache_path,
- options.symbol_servers, std::move(decoder_dispatcher));
+ options.symbol_servers, std::move(decoder_dispatcher),
+ options.quit_agent_on_exit);
if (workflow.HasSymbolServers()) {
for (const auto& server : workflow.GetSymbolServers()) {