blob: 2bb50754b6df6657bc635e2cb18144f531bf8a3f [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::{
config::{self, OutputFormat, ProgramStateHolder},
Options,
},
anyhow::{bail, Context as _, Error},
fuchsia_triage::{
analyze, analyze_structured, analyze_verbose, ActionResultFormatter, ActionResults,
DiagnosticData, ParseResult, TriageOutput,
},
};
/// The entry point for the CLI app.
pub struct App {
options: Options,
}
impl App {
/// Creates a new App with the given options.
pub fn new(options: Options) -> App {
App { options }
}
/// Runs the App.
///
/// This method consumes self and calls run_structured or run_unstructured
/// depending on if structured output is in options. It then collects the results
/// and writes the results to the dest. If an error occurs during the running of the app
/// it will be returned as an Error.
pub fn run(self, dest: &mut dyn std::io::Write) -> Result<bool, Error> {
// TODO(https://fxbug.dev/42127530): Use 'argh' crate.
let ProgramStateHolder { parse_result, diagnostic_data, output_format } =
config::initialize(self.options.clone())?;
match output_format {
OutputFormat::Structured => {
let structured_run_result = self.run_structured(diagnostic_data, parse_result)?;
structured_run_result.write_report(dest)?;
Ok(structured_run_result.has_reportable_issues())
}
OutputFormat::Text | OutputFormat::VerboseText => {
let run_result =
self.run_unstructured(diagnostic_data, parse_result, output_format)?;
run_result.write_report(dest)?;
Ok(run_result.has_problems())
}
}
}
fn run_unstructured(
self,
diagnostic_data: Vec<DiagnosticData>,
parse_result: ParseResult,
output_format: OutputFormat,
) -> Result<RunResult, Error> {
let action_results = match output_format {
OutputFormat::Text => analyze(&diagnostic_data, &parse_result)?,
OutputFormat::VerboseText => analyze_verbose(&diagnostic_data, &parse_result)?,
_ => unreachable!(),
};
Ok(RunResult::new(output_format, action_results))
}
fn run_structured(
self,
diagnostic_data: Vec<DiagnosticData>,
parse_result: ParseResult,
) -> Result<StructuredRunResult, Error> {
let triage_output = analyze_structured(&diagnostic_data, &parse_result)?;
Ok(StructuredRunResult { triage_output })
}
}
/// The result of calling App::run.
pub struct RunResult {
output_format: OutputFormat,
action_results: ActionResults,
}
impl RunResult {
/// Creates a new RunResult struct. This method is intended to be used by the
/// App:run method.
fn new(output_format: OutputFormat, action_results: ActionResults) -> RunResult {
RunResult { output_format, action_results }
}
/// Returns true if at least one ActionResults has a warning or errorF.
fn has_problems(&self) -> bool {
!(self.action_results.warnings.is_empty() && self.action_results.errors.is_empty())
}
/// Writes the contents of the run to the provided writer.
///
/// This method can be used to output the results to a file or stdout.
pub fn write_report(&self, dest: &mut dyn std::io::Write) -> Result<(), Error> {
if self.output_format != OutputFormat::Text
&& self.output_format != OutputFormat::VerboseText
{
bail!("BUG: Incorrect output format requested");
}
let results_formatter = ActionResultFormatter::new(&self.action_results);
let output = results_formatter.to_string();
dest.write_fmt(format_args!("{}", output)).context("failed to write to destination")?;
Ok(())
}
}
/// The result of calling App::run_structured.
pub struct StructuredRunResult {
triage_output: TriageOutput,
}
impl StructuredRunResult {
/// Writes the contents of the run_structured to the provided writer.
///
/// This method can be used to output the results to a file or stdout.
pub fn write_report(&self, dest: &mut dyn std::io::Write) -> Result<(), Error> {
let output = serde_json::to_string(&self.triage_output)?;
dest.write_fmt(format_args!("{}\n", output)).context("failed to write to destination")?;
Ok(())
}
/// Returns true if the result contains a reportable warning, error, or significant Problem
pub fn has_reportable_issues(&self) -> bool {
self.triage_output.has_reportable_issues()
}
}
#[cfg(test)]
mod tests {
use {super::*, fuchsia_triage::Action};
#[fuchsia::test]
fn test_output_text_no_warnings() -> Result<(), Error> {
let action_results = ActionResults::new();
let run_result = RunResult::new(OutputFormat::Text, action_results);
let mut dest = vec![];
run_result.write_report(&mut dest)?;
let output = String::from_utf8(dest)?;
assert_eq!("", output);
Ok(())
}
#[fuchsia::test]
fn test_output_text_with_alerts() -> Result<(), Error> {
let mut action_results = ActionResults::new();
action_results.infos.push("hmmm".to_string());
action_results.warnings.push("oops".to_string());
action_results.errors.push("fail".to_string());
let readable_run_result = RunResult::new(OutputFormat::Text, action_results.clone());
action_results.verbose = true;
let verbose_run_result = RunResult::new(OutputFormat::VerboseText, action_results.clone());
let mut readable_dest = vec![];
readable_run_result.write_report(&mut readable_dest)?;
let mut verbose_dest = vec![];
verbose_run_result.write_report(&mut verbose_dest)?;
let output = String::from_utf8(readable_dest)?;
assert_eq!("Errors\n------\nfail\n\nWarnings\n--------\noops\n\n", output,);
let output = String::from_utf8(verbose_dest)?;
assert_eq!(
"Errors\n------\nfail\n\nWarnings\n--------\noops\n\nInfo\n----\nhmmm\n\n",
output,
);
Ok(())
}
#[fuchsia::test]
fn test_output_text_with_gauges() -> Result<(), Error> {
let mut action_results = ActionResults::new();
action_results.gauges.push("gauge".to_string());
let run_result = RunResult::new(OutputFormat::Text, action_results);
let mut dest = vec![];
run_result.write_report(&mut dest)?;
let output = String::from_utf8(dest)?;
assert_eq!("Featured Values\n---------------\ngauge\n\n", output);
Ok(())
}
#[fuchsia::test]
fn test_structured_output_no_warnings() -> Result<(), Error> {
let triage_output = TriageOutput::new(Vec::new());
let structured_run_result = StructuredRunResult { triage_output };
let mut dest = vec![];
structured_run_result.write_report(&mut dest)?;
let output = String::from_utf8(dest)?;
assert_eq!(
"{\"actions\":{},\"metrics\":{},\"plugin_results\":{},\"triage_errors\":[]}\n",
output
);
Ok(())
}
#[fuchsia::test]
fn test_structured_output_with_warnings() -> Result<(), Error> {
let mut triage_output = TriageOutput::new(vec!["file".to_string()]);
triage_output.add_action(
"file".to_string(),
"warning_name".to_string(),
Action::new_synthetic_warning("fail".to_string()),
);
let structured_run_result = StructuredRunResult { triage_output };
let mut dest = vec![];
structured_run_result.write_report(&mut dest)?;
let output = String::from_utf8(dest)?;
assert_eq!(
"{\"actions\":{\"file\":{\"warning_name\":{\"type\":\"Alert\",\"trigger\":\
{\"metric\":{\"Eval\":{\"raw_expression\":\"True()\",\"parsed_expression\":{\"Function\":\
[\"True\",[]]}}},\"cached_value\":\
{\"Bool\":true}},\"print\":\"fail\",\"file_bug\":null,\"tag\":null,\
\"severity\":\"Warning\"}}},\"metrics\":\
{\"file\":{}},\"plugin_results\":{},\"triage_errors\":[]}\n",
output
);
Ok(())
}
#[fuchsia::test]
fn test_structured_output_with_gauges() -> Result<(), Error> {
let mut triage_output = TriageOutput::new(vec!["file".to_string()]);
triage_output.add_action(
"file".to_string(),
"gauge_name".to_string(),
Action::new_synthetic_string_gauge("gauge".to_string(), None, None),
);
let structured_run_result = StructuredRunResult { triage_output };
let mut dest = vec![];
structured_run_result.write_report(&mut dest)?;
let output = String::from_utf8(dest)?;
assert_eq!(
"{\"actions\":{\"file\":{\"gauge_name\":{\"type\":\"Gauge\",\"value\":{\"metric\":\
{\"Eval\":{\"raw_expression\":\"'gauge'\",\"parsed_expression\":{\"Value\":\
{\"String\":\"gauge\"}}}},\"cached_value\":{\"String\":\"gauge\"}},\"format\":null,\
\"tag\":null}}},\"metrics\":{\"file\":{}},\"plugin_results\":{},\"triage_errors\":[]}\n",
output
);
Ok(())
}
}