blob: 9c76cb12a2a4792cbfd015fda314415032d3a6cd [file] [log] [blame]
// 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.
use {
crate::constants::*,
crate::daemon_manager::{DaemonManager, DefaultDaemonManager},
anyhow::{Error, Result},
async_std::future::timeout,
ffx_core::ffx_plugin,
ffx_doctor_args::DoctorCommand,
fidl::endpoints::create_proxy,
fidl_fuchsia_developer_bridge::{DaemonProxy, Target},
fidl_fuchsia_developer_remotecontrol::RemoteControlMarker,
std::collections::HashSet,
std::io::{stdout, Write},
std::time::Duration,
termion::{color, style},
};
mod constants;
mod daemon_manager;
fn format_err(e: Error) -> String {
format!("{}\n\t{:?}", FAILED_WITH_ERROR, e)
}
fn print_status_line(writer: &mut impl Write, s: &str) {
write!(writer, "{}", s).unwrap();
writer.flush().unwrap();
}
#[ffx_plugin()]
pub async fn doctor_cmd(cmd: DoctorCommand) -> Result<()> {
let mut writer = Box::new(stdout());
let daemon_manager = DefaultDaemonManager {};
let delay = Duration::from_millis(cmd.retry_delay);
let ffx: ffx_lib_args::Ffx = argh::from_env();
let target_str = ffx.target.unwrap_or(String::default());
doctor(
&mut writer,
&daemon_manager,
&target_str,
cmd.retry_count,
delay,
cmd.force_daemon_restart,
)
.await
}
async fn doctor<W: Write>(
writer: &mut W,
daemon_manager: &impl DaemonManager,
target_str: &str,
retry_count: usize,
retry_delay: Duration,
force_daemon_restart: bool,
) -> Result<()> {
let mut proxy_opt: Option<DaemonProxy> = None;
let mut targets_opt: Option<Vec<Target>> = None;
writeln!(writer, "{}{}{}", style::Bold, DAEMON_CHECK_INTRO, style::Reset).unwrap();
for i in 0..retry_count {
proxy_opt = None;
if i > 0 {
daemon_manager.kill_all().await?;
writeln!(writer, "\n\nAttempt {} of {}", i + 1, retry_count).unwrap();
} else if force_daemon_restart {
writeln!(writer, "{}", FORCE_DAEMON_RESTART_MESSAGE).unwrap();
daemon_manager.kill_all().await?;
}
print_status_line(writer, DAEMON_RUNNING_CHECK);
if !daemon_manager.is_running().await {
writeln!(writer, "{}", NONE_RUNNING).unwrap();
print_status_line(writer, KILLING_ZOMBIE_DAEMONS);
if daemon_manager.kill_all().await? {
writeln!(writer, "{}", ZOMBIE_KILLED).unwrap();
} else {
writeln!(writer, "{}", NONE_RUNNING).unwrap();
}
print_status_line(writer, SPAWNING_DAEMON);
daemon_manager.spawn().await?;
writeln!(writer, "{}", SUCCESS).unwrap();
} else {
writeln!(writer, "{}", FOUND).unwrap();
}
print_status_line(writer, CONNECTING_TO_DAEMON);
match timeout(retry_delay, daemon_manager.find_and_connect()).await {
Ok(Ok(p)) => {
proxy_opt = Some(p);
}
Ok(Err(e)) => {
writeln!(writer, "{}", format_err(e.into())).unwrap();
continue;
}
Err(_) => {
writeln!(writer, "{}", FAILED_TIMEOUT).unwrap();
continue;
}
}
writeln!(writer, "{}", SUCCESS).unwrap();
print_status_line(writer, COMMUNICATING_WITH_DAEMON);
match timeout(retry_delay, proxy_opt.as_ref().unwrap().echo_string("test")).await {
Err(_) => {
writeln!(writer, "{}", FAILED_TIMEOUT).unwrap();
proxy_opt = None;
continue;
}
Ok(Err(e)) => {
writeln!(writer, "{}", format_err(e.into())).unwrap();
proxy_opt = None;
continue;
}
Ok(_) => {
writeln!(writer, "{}", SUCCESS).unwrap();
}
}
let status_str;
if target_str.is_empty() {
status_str = String::from(LISTING_TARGETS_NO_FILTER);
} else {
status_str = format!("Attempting to list targets with filter '{}'...", target_str);
}
print_status_line(writer, &status_str);
targets_opt = match timeout(
retry_delay,
proxy_opt.as_ref().unwrap().list_targets(target_str),
)
.await
{
Err(_) => {
writeln!(writer, "{}", FAILED_TIMEOUT).unwrap();
continue;
}
Ok(Err(e)) => {
writeln!(writer, "{}", format_err(e.into())).unwrap();
continue;
}
Ok(t) => {
writeln!(writer, "{}", SUCCESS).unwrap();
Some(t.unwrap())
}
};
if targets_opt.is_some() && targets_opt.as_ref().unwrap().len() == 0 {
writeln!(writer, "{}", NO_TARGETS_FOUND_SHORT).unwrap();
continue;
}
break;
}
if proxy_opt.is_none() {
writeln!(
writer,
"\n{}{}{}{}",
style::Bold,
color::Fg(color::Red),
DAEMON_CHECKS_FAILED,
style::Reset
)
.unwrap();
writeln!(writer, "Bug link: {}", BUG_URL).unwrap();
return Ok(());
}
if targets_opt.is_none() || targets_opt.as_ref().unwrap().len() == 0 {
writeln!(writer, "\n{}", NO_TARGETS_FOUND_EXTENDED).unwrap();
writeln!(writer, "Bug link: {}", BUG_URL).unwrap();
return Ok(());
}
let targets = targets_opt.unwrap();
let daemon = proxy_opt.take().unwrap();
let mut successful_targets = HashSet::new();
for target in targets.iter() {
writeln!(
writer,
"{}\nChecking target: '{}'. {}{}",
style::Bold,
target.nodename.as_ref().unwrap_or(&"UNKNOWN".to_string()),
TARGET_CHOICE_HELP,
style::Reset
)
.unwrap();
for i in 0..retry_count {
if i > 0 {
writeln!(writer, "\n\nAttempt {} of {}", i + 1, retry_count).unwrap();
}
// TODO(jwing): SSH into the device and kill Overnet+RCS if anything below this fails
print_status_line(writer, CONNECTING_TO_RCS);
let (remote_proxy, remote_server_end) = create_proxy::<RemoteControlMarker>()?;
match timeout(
retry_delay,
daemon.get_remote_control(&target.nodename.as_ref().unwrap(), remote_server_end),
)
.await
{
Err(_) => {
writeln!(writer, "{}", FAILED_TIMEOUT).unwrap();
continue;
}
Ok(Err(e)) => {
writeln!(writer, "{}", format_err(e.into())).unwrap();
continue;
}
Ok(Ok(_)) => {
writeln!(writer, "{}", SUCCESS).unwrap();
}
};
print_status_line(writer, COMMUNICATING_WITH_RCS);
match timeout(retry_delay, remote_proxy.identify_host()).await {
Err(_) => {
writeln!(writer, "{}", FAILED_TIMEOUT).unwrap();
continue;
}
Ok(Err(e)) => {
writeln!(writer, "{}", format_err(e.into())).unwrap();
continue;
}
Ok(Ok(_)) => {
writeln!(writer, "{}", SUCCESS).unwrap();
successful_targets.insert(target.nodename.as_ref().unwrap()).to_string();
break;
}
};
}
}
writeln!(writer, "{}", style::Bold).unwrap();
writeln!(writer, "{}", TARGET_SUMMARY).unwrap();
for target in targets.iter() {
let nodename = target.nodename.as_ref().unwrap();
if successful_targets.contains(&nodename.clone()) {
writeln!(writer, "{}✓ {}", color::Fg(color::Green), nodename).unwrap();
} else {
writeln!(writer, "{}✗ {}", color::Fg(color::Red), nodename).unwrap();
}
}
writeln!(writer, "{}", style::Reset).unwrap();
if targets.len() != successful_targets.len() {
writeln!(writer, "{}", RCS_TERMINAL_FAILURE).unwrap();
writeln!(writer, "{}", RCS_TERMINAL_FAILURE_BUG_INSTRUCTIONS).unwrap();
writeln!(writer, "{}", BUG_URL).unwrap();
}
Ok(())
}
#[cfg(test)]
mod test {
use {
super::*,
async_std::sync::{Arc, Mutex},
async_trait::async_trait,
fidl::endpoints::{spawn_local_stream_handler, Request, ServerEnd, ServiceMarker},
fidl_fuchsia_developer_bridge::{
DaemonRequest, RemoteControlState, Target, TargetState, TargetType,
},
fidl_fuchsia_developer_remotecontrol::{
IdentifyHostResponse, RemoteControlMarker, RemoteControlRequest,
},
fuchsia_async as fasync,
futures::channel::oneshot::{self, Receiver},
futures::future::Shared,
futures::{Future, FutureExt, TryFutureExt, TryStreamExt},
std::io::BufWriter,
};
const NODENAME: &str = "fake-nodename";
const UNRESPONSIVE_NODENAME: &str = "fake-nodename-unresponsive";
const NON_EXISTENT_NODENAME: &str = "extra-fake-nodename";
const LISTING_TARGETS_WITH_FILTER: &str =
"Attempting to list targets with filter 'fake-nodename'...";
const LISTING_TARGETS_WITH_FAKE_FILTER: &str =
"Attempting to list targets with filter 'extra-fake-nodename'...";
const CHOSE_TARGET: &str = "Checking target: 'fake-nodename'. ";
const CHOSE_UNRESPONSIVE_TARGET: &str = "Checking target: 'fake-nodename-unresponsive'. ";
const SUCCESSFUL_TARGET: &str = "✓ fake-nodename";
const FAILED_TARGET: &str = "✗ fake-nodename-unresponsive";
const DEFAULT_RETRY_DELAY: Duration = Duration::from_millis(2000);
struct FakeStateManager {
kill_results: Vec<Result<bool>>,
daemons_running_results: Vec<bool>,
spawn_results: Vec<Result<()>>,
find_and_connect_results: Vec<Result<DaemonProxy>>,
}
struct FakeDaemonManager {
state_manager: Arc<Mutex<FakeStateManager>>,
}
impl FakeDaemonManager {
fn new(
daemons_running_results: Vec<bool>,
kill_results: Vec<Result<bool>>,
spawn_results: Vec<Result<()>>,
find_and_connect_results: Vec<Result<DaemonProxy>>,
) -> Self {
return FakeDaemonManager {
state_manager: Arc::new(Mutex::new(FakeStateManager {
kill_results,
daemons_running_results,
spawn_results,
find_and_connect_results,
})),
};
}
async fn assert_no_leftover_calls(&self) {
let state = self.state_manager.lock().await;
assert!(
state.kill_results.is_empty(),
format!("too few calls to kill_all. remaining entries: {:?}", state.kill_results)
);
assert!(
state.daemons_running_results.is_empty(),
format!(
"too few calls to is_running. remaining entries: {:?}",
state.daemons_running_results
)
);
assert!(
state.spawn_results.is_empty(),
format!("too few calls to spawn. remaining entries: {:?}", state.spawn_results)
);
assert!(
state.find_and_connect_results.is_empty(),
format!(
"too few calls to find_and_connect. remaining entries: {:?}",
state.find_and_connect_results
)
);
}
}
#[async_trait]
impl DaemonManager for FakeDaemonManager {
async fn kill_all(&self) -> Result<bool> {
let mut state = self.state_manager.lock().await;
assert!(!state.kill_results.is_empty(), "too many calls to kill_all");
state.kill_results.remove(0)
}
async fn is_running(&self) -> bool {
let mut state = self.state_manager.lock().await;
assert!(!state.daemons_running_results.is_empty(), "too many calls to is_running");
state.daemons_running_results.remove(0)
}
async fn spawn(&self) -> Result<()> {
let mut state = self.state_manager.lock().await;
assert!(!state.spawn_results.is_empty(), "too many calls to spawn");
state.spawn_results.remove(0)
}
async fn find_and_connect(&self) -> Result<DaemonProxy> {
let mut state = self.state_manager.lock().await;
assert!(
!state.find_and_connect_results.is_empty(),
"too many calls to find_and_connect"
);
state.find_and_connect_results.remove(0)
}
}
fn print_full_output(output: &str) {
println!("BEGIN DOCTOR OUTPUT");
println!("{}", &output);
println!("END DOCTOR OUTPUT");
}
fn serve_stream<T, F, Fut>(stream: T::RequestStream, mut f: F)
where
T: ServiceMarker,
F: FnMut(Request<T>) -> Fut + 'static + std::marker::Send,
Fut: Future<Output = ()> + 'static + std::marker::Send,
{
fasync::Task::spawn(
stream
.try_for_each(move |r| f(r).map(Ok))
.unwrap_or_else(|e| panic!(format!("failed to handle request: {:?}", e))),
)
.detach();
}
fn setup_responsive_daemon_server() -> DaemonProxy {
spawn_local_stream_handler(move |req| async move {
match req {
DaemonRequest::GetRemoteControl { remote: _, target: _, responder } => {
responder.send(&mut Ok(())).unwrap();
}
DaemonRequest::EchoString { value, responder } => {
responder.send(&value).unwrap();
}
DaemonRequest::ListTargets { value: _, responder } => {
responder.send(&mut vec![].drain(..)).unwrap();
}
_ => {
assert!(false, format!("got unexpected request: {:?}", req));
}
}
})
.unwrap()
}
fn serve_responsive_rcs(server_end: ServerEnd<RemoteControlMarker>) {
serve_stream::<RemoteControlMarker, _, _>(
server_end.into_stream().unwrap(),
move |req| async move {
match req {
RemoteControlRequest::IdentifyHost { responder } => responder
.send(&mut Ok(IdentifyHostResponse {
addresses: Some(vec![]),
nodename: Some(NODENAME.to_string()),
}))
.unwrap(),
_ => panic!("Unexpected request: {:?}", req),
}
},
);
}
fn serve_unresponsive_rcs(
server_end: ServerEnd<RemoteControlMarker>,
waiter: Shared<Receiver<()>>,
) {
serve_stream::<RemoteControlMarker, _, _>(server_end.into_stream().unwrap(), move |req| {
let waiter = waiter.clone();
async move {
match req {
RemoteControlRequest::IdentifyHost { responder: _ } => {
waiter.await.unwrap();
}
_ => panic!("Unexpected request: {:?}", req),
}
}
});
}
fn setup_responsive_daemon_server_with_targets(waiter: Shared<Receiver<()>>) -> DaemonProxy {
spawn_local_stream_handler(move |req| {
let waiter = waiter.clone();
async move {
match req {
DaemonRequest::GetRemoteControl { remote, target, responder } => {
if target == NODENAME {
serve_responsive_rcs(remote);
} else if target == UNRESPONSIVE_NODENAME {
serve_unresponsive_rcs(remote, waiter);
} else {
panic!("got unexpected target string: '{}'", target);
}
responder.send(&mut Ok(())).unwrap();
}
DaemonRequest::EchoString { value, responder } => {
responder.send(&value).unwrap();
}
DaemonRequest::ListTargets { value, responder } => {
if !value.is_empty() && value != NODENAME && value != UNRESPONSIVE_NODENAME
{
responder.send(&mut vec![].drain(..)).unwrap();
} else if value == NODENAME {
responder
.send(
&mut vec![Target {
nodename: Some(NODENAME.to_string()),
addresses: Some(vec![]),
age_ms: Some(0),
rcs_state: Some(RemoteControlState::Unknown),
target_type: Some(TargetType::Unknown),
target_state: Some(TargetState::Unknown),
}]
.drain(..),
)
.unwrap();
} else {
responder
.send(
&mut vec![
Target {
nodename: Some(NODENAME.to_string()),
addresses: Some(vec![]),
age_ms: Some(0),
rcs_state: Some(RemoteControlState::Unknown),
target_type: Some(TargetType::Unknown),
target_state: Some(TargetState::Unknown),
},
Target {
nodename: Some(UNRESPONSIVE_NODENAME.to_string()),
addresses: Some(vec![]),
age_ms: Some(0),
rcs_state: Some(RemoteControlState::Unknown),
target_type: Some(TargetType::Unknown),
target_state: Some(TargetState::Unknown),
},
]
.drain(..),
)
.unwrap();
}
}
_ => {
assert!(false, format!("got unexpected request: {:?}", req));
}
}
}
})
.unwrap()
}
fn setup_daemon_server_list_fails() -> DaemonProxy {
spawn_local_stream_handler(move |req| async move {
match req {
DaemonRequest::GetRemoteControl { remote: _, target: _, responder: _ } => {
panic!("unexpected daemon call");
}
DaemonRequest::EchoString { value, responder } => {
responder.send(&value).unwrap();
}
DaemonRequest::ListTargets { value: _, responder: _ } => {
// Do nothing
}
_ => {
assert!(false, format!("got unexpected request: {:?}", req));
}
}
})
.unwrap()
}
fn setup_daemon_server_echo_hangs(waiter: Shared<Receiver<()>>) -> DaemonProxy {
spawn_local_stream_handler(move |req| {
let waiter = waiter.clone();
async move {
match req {
DaemonRequest::GetRemoteControl { remote: _, target: _, responder: _ } => {
panic!("unexpected daemon call");
}
DaemonRequest::EchoString { value: _, responder: _ } => {
waiter.await.unwrap();
}
DaemonRequest::ListTargets { value: _, responder: _ } => {
panic!("unexpected daemon call");
}
_ => {
assert!(false, format!("got unexpected request: {:?}", req));
}
}
}
})
.unwrap()
}
fn verify_lines(output: &str, line_substrings: Vec<String>) {
for (line, expected) in output.lines().zip(line_substrings.iter()) {
if !expected.is_empty() {
assert!(
line.contains(expected),
format!("'{}' does not contain expected string '{}'", line, expected)
);
}
}
// Verify that there aren't any additional lines in the actual output that didn't get
// compared in the previous loop.
assert!(output.lines().collect::<Vec<_>>().len() <= line_substrings.len());
}
#[fasync::run_singlethreaded(test)]
async fn test_single_try_no_daemon_running_no_targets() {
let fake = FakeDaemonManager::new(
vec![false],
vec![Ok(false)],
vec![Ok(())],
vec![Ok(setup_responsive_daemon_server())],
);
let mut output = String::new();
{
let mut writer = unsafe { BufWriter::new(output.as_mut_vec()) };
doctor(&mut writer, &fake, "", 1, DEFAULT_RETRY_DELAY, false).await.unwrap();
}
print_full_output(&output);
verify_lines(
&output,
vec![
format!("{}", DAEMON_CHECK_INTRO),
format!("{}{}", DAEMON_RUNNING_CHECK, NONE_RUNNING),
format!("{}{}", KILLING_ZOMBIE_DAEMONS, NONE_RUNNING),
format!("{}{}", SPAWNING_DAEMON, SUCCESS),
format!("{}{}", CONNECTING_TO_DAEMON, SUCCESS),
format!("{}{}", COMMUNICATING_WITH_DAEMON, SUCCESS),
format!("{}{}", LISTING_TARGETS_NO_FILTER, SUCCESS),
String::from("No targets found"),
String::default(),
String::from("No targets found"),
String::default(),
BUG_URL.to_string(),
],
);
fake.assert_no_leftover_calls().await;
}
#[fasync::run_singlethreaded(test)]
async fn test_single_try_daemon_running_no_targets() {
let fake = FakeDaemonManager::new(
vec![true],
vec![],
vec![],
vec![Ok(setup_responsive_daemon_server())],
);
let mut output = String::new();
{
let mut writer = unsafe { BufWriter::new(output.as_mut_vec()) };
doctor(&mut writer, &fake, "", 1, DEFAULT_RETRY_DELAY, false).await.unwrap();
}
print_full_output(&output);
verify_lines(
&output,
vec![
format!("{}", DAEMON_CHECK_INTRO),
format!("{}{}", DAEMON_RUNNING_CHECK, FOUND),
format!("{}{}", CONNECTING_TO_DAEMON, SUCCESS),
format!("{}{}", COMMUNICATING_WITH_DAEMON, SUCCESS),
format!("{}{}", LISTING_TARGETS_NO_FILTER, SUCCESS),
String::from("No targets found"),
String::default(),
String::from("No targets found"),
String::default(),
BUG_URL.to_string(),
],
);
fake.assert_no_leftover_calls().await;
}
#[fasync::run_singlethreaded(test)]
async fn test_two_tries_daemon_running_list_fails() {
let fake = FakeDaemonManager::new(
vec![true, false],
vec![Ok(true), Ok(false)],
vec![Ok(())],
vec![Ok(setup_daemon_server_list_fails()), Ok(setup_daemon_server_list_fails())],
);
let mut output = String::new();
{
let mut writer = unsafe { BufWriter::new(output.as_mut_vec()) };
doctor(&mut writer, &fake, "", 2, DEFAULT_RETRY_DELAY, false).await.unwrap();
}
print_full_output(&output);
verify_lines(
&output,
vec![
format!("{}", DAEMON_CHECK_INTRO),
format!("{}{}", DAEMON_RUNNING_CHECK, FOUND),
format!("{}{}", CONNECTING_TO_DAEMON, SUCCESS),
format!("{}{}", COMMUNICATING_WITH_DAEMON, SUCCESS),
format!("{}{}", LISTING_TARGETS_NO_FILTER, FAILED_WITH_ERROR),
String::from("PEER_CLOSED"),
String::default(),
String::default(),
String::from("PEER_CLOSED"),
String::default(),
String::default(),
String::from("Attempt 2 of 2"),
format!("{}{}", DAEMON_RUNNING_CHECK, NONE_RUNNING),
format!("{}{}", KILLING_ZOMBIE_DAEMONS, NONE_RUNNING),
format!("{}{}", SPAWNING_DAEMON, SUCCESS),
format!("{}{}", CONNECTING_TO_DAEMON, SUCCESS),
format!("{}{}", COMMUNICATING_WITH_DAEMON, SUCCESS),
format!("{}{}", LISTING_TARGETS_NO_FILTER, FAILED_WITH_ERROR),
String::from("PEER_CLOSED"),
String::default(),
String::default(),
String::from("PEER_CLOSED"),
String::default(),
String::from("No targets found"),
String::default(),
BUG_URL.to_string(),
],
);
fake.assert_no_leftover_calls().await;
}
#[fasync::run_singlethreaded(test)]
async fn test_two_tries_no_daemon_running_echo_timeout() {
let (tx, rx) = oneshot::channel::<()>();
let fake = FakeDaemonManager::new(
vec![false, true],
vec![Ok(false), Ok(true)],
vec![Ok(())],
vec![
Ok(setup_daemon_server_echo_hangs(rx.shared())),
Ok(setup_responsive_daemon_server()),
],
);
let mut output = String::new();
{
let mut writer = unsafe { BufWriter::new(output.as_mut_vec()) };
doctor(&mut writer, &fake, "", 2, DEFAULT_RETRY_DELAY, false).await.unwrap();
tx.send(()).unwrap();
}
print_full_output(&output);
verify_lines(
&output,
vec![
format!("{}", DAEMON_CHECK_INTRO),
format!("{}{}", DAEMON_RUNNING_CHECK, NONE_RUNNING),
format!("{}{}", KILLING_ZOMBIE_DAEMONS, NONE_RUNNING),
format!("{}{}", SPAWNING_DAEMON, SUCCESS),
format!("{}{}", CONNECTING_TO_DAEMON, SUCCESS),
format!("{}{}", COMMUNICATING_WITH_DAEMON, FAILED_TIMEOUT),
String::default(),
String::default(),
String::from("Attempt 2 of 2"),
format!("{}{}", DAEMON_RUNNING_CHECK, FOUND),
format!("{}{}", CONNECTING_TO_DAEMON, SUCCESS),
format!("{}{}", COMMUNICATING_WITH_DAEMON, SUCCESS),
format!("{}{}", LISTING_TARGETS_NO_FILTER, SUCCESS),
String::from("No targets found"),
String::default(),
String::from("No targets found"),
String::default(),
BUG_URL.to_string(),
],
);
fake.assert_no_leftover_calls().await;
}
#[fasync::run_singlethreaded(test)]
async fn test_finds_target_connects_to_rcs() {
let (tx, rx) = oneshot::channel::<()>();
let fake = FakeDaemonManager::new(
vec![true],
vec![],
vec![],
vec![Ok(setup_responsive_daemon_server_with_targets(rx.shared()))],
);
let mut output = String::new();
{
let mut writer = unsafe { BufWriter::new(output.as_mut_vec()) };
doctor(&mut writer, &fake, "", 1, DEFAULT_RETRY_DELAY, false).await.unwrap();
tx.send(()).unwrap();
}
print_full_output(&output);
verify_lines(
&output,
vec![
format!("{}", DAEMON_CHECK_INTRO),
format!("{}{}", DAEMON_RUNNING_CHECK, FOUND),
format!("{}{}", CONNECTING_TO_DAEMON, SUCCESS),
format!("{}{}", COMMUNICATING_WITH_DAEMON, SUCCESS),
format!("{}{}", LISTING_TARGETS_NO_FILTER, SUCCESS),
String::default(),
format!("{}{}", CHOSE_TARGET, TARGET_CHOICE_HELP),
format!("{}{}", CONNECTING_TO_RCS, SUCCESS),
format!("{}{}", COMMUNICATING_WITH_RCS, SUCCESS),
String::default(),
format!("{}{}", CHOSE_UNRESPONSIVE_TARGET, TARGET_CHOICE_HELP),
format!("{}{}", CONNECTING_TO_RCS, SUCCESS),
format!("{}{}", COMMUNICATING_WITH_RCS, FAILED_TIMEOUT),
String::default(),
String::from(TARGET_SUMMARY),
String::from(SUCCESSFUL_TARGET),
String::from(FAILED_TARGET),
String::default(),
String::from(RCS_TERMINAL_FAILURE),
String::from(RCS_TERMINAL_FAILURE_BUG_INSTRUCTIONS),
String::from(BUG_URL),
],
);
fake.assert_no_leftover_calls().await;
}
#[fasync::run_singlethreaded(test)]
async fn test_finds_target_with_filter() {
let (tx, rx) = oneshot::channel::<()>();
let fake = FakeDaemonManager::new(
vec![true],
vec![],
vec![],
vec![Ok(setup_responsive_daemon_server_with_targets(rx.shared()))],
);
let mut output = String::new();
{
let mut writer = unsafe { BufWriter::new(output.as_mut_vec()) };
doctor(&mut writer, &fake, &NODENAME, 2, DEFAULT_RETRY_DELAY, false).await.unwrap();
tx.send(()).unwrap();
}
print_full_output(&output);
verify_lines(
&output,
vec![
String::from(DAEMON_CHECK_INTRO),
format!("{}{}", DAEMON_RUNNING_CHECK, FOUND),
format!("{}{}", CONNECTING_TO_DAEMON, SUCCESS),
format!("{}{}", COMMUNICATING_WITH_DAEMON, SUCCESS),
format!("{}{}", LISTING_TARGETS_WITH_FILTER, SUCCESS),
String::default(),
format!("{}{}", CHOSE_TARGET, TARGET_CHOICE_HELP),
format!("{}{}", CONNECTING_TO_RCS, SUCCESS),
format!("{}{}", COMMUNICATING_WITH_RCS, SUCCESS),
String::default(),
String::from(TARGET_SUMMARY),
String::from(SUCCESSFUL_TARGET),
String::default(),
],
);
fake.assert_no_leftover_calls().await;
}
#[fasync::run_singlethreaded(test)]
async fn test_invalid_filter_finds_no_targets() {
let (tx, rx) = oneshot::channel::<()>();
let fake = FakeDaemonManager::new(
vec![true],
vec![],
vec![],
vec![Ok(setup_responsive_daemon_server_with_targets(rx.shared()))],
);
let mut output = String::new();
{
let mut writer = unsafe { BufWriter::new(output.as_mut_vec()) };
doctor(&mut writer, &fake, &NON_EXISTENT_NODENAME, 1, DEFAULT_RETRY_DELAY, false)
.await
.unwrap();
tx.send(()).unwrap();
}
print_full_output(&output);
verify_lines(
&output,
vec![
format!("{}", DAEMON_CHECK_INTRO),
format!("{}{}", DAEMON_RUNNING_CHECK, FOUND),
format!("{}{}", CONNECTING_TO_DAEMON, SUCCESS),
format!("{}{}", COMMUNICATING_WITH_DAEMON, SUCCESS),
format!("{}{}", LISTING_TARGETS_WITH_FAKE_FILTER, SUCCESS),
String::from("No targets found"),
String::default(),
String::from("No targets found"),
String::default(),
String::from(BUG_URL),
],
);
fake.assert_no_leftover_calls().await;
}
#[fasync::run_singlethreaded(test)]
async fn test_single_try_daemon_running_force_restart() {
let fake = FakeDaemonManager::new(
vec![false],
vec![Ok(true), Ok(false)],
vec![Ok(())],
vec![Ok(setup_responsive_daemon_server())],
);
let mut output = String::new();
{
let mut writer = unsafe { BufWriter::new(output.as_mut_vec()) };
doctor(&mut writer, &fake, "", 1, DEFAULT_RETRY_DELAY, true).await.unwrap();
}
print_full_output(&output);
verify_lines(
&output,
vec![
format!("{}", DAEMON_CHECK_INTRO),
String::from(FORCE_DAEMON_RESTART_MESSAGE),
format!("{}{}", DAEMON_RUNNING_CHECK, NONE_RUNNING),
format!("{}{}", KILLING_ZOMBIE_DAEMONS, NONE_RUNNING),
format!("{}{}", SPAWNING_DAEMON, SUCCESS),
format!("{}{}", CONNECTING_TO_DAEMON, SUCCESS),
format!("{}{}", COMMUNICATING_WITH_DAEMON, SUCCESS),
format!("{}{}", LISTING_TARGETS_NO_FILTER, SUCCESS),
String::from("No targets found"),
String::default(),
String::from("No targets found"),
String::default(),
BUG_URL.to_string(),
],
);
fake.assert_no_leftover_calls().await;
}
}