diff --git a/Cargo.lock b/Cargo.lock index 8674f1b7..07455dc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,7 @@ dependencies = [ "ignore", "indoc", "miette", + "owo-colors", "pallas-addresses", "pallas-codec", "pallas-crypto", @@ -110,7 +111,6 @@ dependencies = [ "hex", "ignore", "miette", - "owo-colors", "pallas", "pallas-traverse", "petgraph", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 11855d96..a6f9215c 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -15,6 +15,7 @@ hex = "0.4.3" ignore = "0.4.18" indoc = "1.0" miette = { version = "5.3.0", features = ["fancy"] } +owo-colors = "3.5.0" pallas-addresses = "0.14.0" pallas-codec = "0.14.0" pallas-crypto = "0.14.0" diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index a2fb3581..985822c6 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1,13 +1,19 @@ -pub mod cmd; +use std::{env, path::PathBuf}; -use aiken_project::{config::Config, Project}; +use aiken_project::{ + config::Config, + telemetry::{self, TestInfo}, + Project, +}; use miette::IntoDiagnostic; -use std::env; -use std::path::PathBuf; +use owo_colors::OwoColorize; +use uplc::machine::cost_model::ExBudget; + +pub mod cmd; pub fn with_project(directory: Option, mut action: A) -> miette::Result<()> where - A: FnMut(&mut Project) -> Result<(), aiken_project::error::Error>, + A: FnMut(&mut Project) -> Result<(), aiken_project::error::Error>, { let project_path = if let Some(d) = directory { d @@ -17,7 +23,7 @@ where let config = Config::load(project_path.clone()).into_diagnostic()?; - let mut project = Project::new(config, project_path); + let mut project = Project::new(config, project_path, Terminal::default()); let build_result = action(&mut project); @@ -37,3 +43,97 @@ where Ok(()) } + +#[derive(Debug, Default, Clone, Copy)] +pub struct Terminal; + +impl telemetry::EventListener for Terminal { + fn handle_event(&self, event: telemetry::Event) { + match event { + telemetry::Event::CompilingPackage { .. } => todo!(), + telemetry::Event::RunningTests => { + println!("\n{}\n", "Running tests...".bold().underline().purple()); + } + telemetry::Event::FinishedTests { tests } => { + let (max_mem, max_cpu) = tests.iter().fold( + (0, 0), + |(max_mem, max_cpu), TestInfo { spent_budget, .. }| { + if spent_budget.mem >= max_mem && spent_budget.cpu >= max_cpu { + (spent_budget.mem, spent_budget.cpu) + } else if spent_budget.mem > max_mem { + (spent_budget.mem, max_cpu) + } else if spent_budget.cpu > max_cpu { + (max_mem, spent_budget.cpu) + } else { + (max_mem, max_cpu) + } + }, + ); + + let max_mem = max_mem.to_string().len() as i32; + let max_cpu = max_cpu.to_string().len() as i32; + + for test_info in &tests { + println!("{}", fmt_test(test_info, max_mem, max_cpu)) + } + + let (n_passed, n_failed) = + tests + .iter() + .fold((0, 0), |(n_passed, n_failed), test_info| { + if test_info.is_passing { + (n_passed + 1, n_failed) + } else { + (n_passed, n_failed + 1) + } + }); + + println!( + "{}", + format!( + "\n Summary: {} test(s), {}; {}.", + tests.len(), + format!("{} passed", n_passed).bright_green(), + format!("{} failed", n_failed).bright_red() + ) + .bold() + ) + } + } + } +} + +fn fmt_test(test_info: &TestInfo, max_mem: i32, max_cpu: i32) -> String { + let TestInfo { + is_passing, + test, + spent_budget, + } = test_info; + + let ExBudget { mem, cpu } = spent_budget; + + format!( + " [{}] [mem: {}, cpu: {}] {}::{}", + if *is_passing { + "PASS".bold().green().to_string() + } else { + "FAIL".bold().red().to_string() + }, + pad_left(mem.to_string(), max_mem, " "), + pad_left(cpu.to_string(), max_cpu, " "), + test.module.blue(), + test.name.bright_blue() + ) +} + +fn pad_left(mut text: String, n: i32, delimiter: &str) -> String { + let diff = n - text.len() as i32; + + if diff.is_positive() { + for _ in 0..diff { + text.insert_str(0, delimiter); + } + } + + text +} diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 42330a62..2f2188f5 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -23,4 +23,3 @@ walkdir = "2.3.2" hex = "0.4.3" pallas = "0.14.0" pallas-traverse = "0.14.0" -owo-colors = "3.5.0" diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index cbbdb857..1875047c 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -9,6 +9,7 @@ pub mod error; pub mod format; pub mod module; pub mod script; +pub mod telemetry; use aiken_lang::{ ast::{Definition, Function, ModuleKind, TypedFunction}, @@ -18,7 +19,6 @@ use aiken_lang::{ IdGenerator, }; use miette::NamedSource; -use owo_colors::OwoColorize; use pallas::{ codec::minicbor, ledger::{addresses::Address, primitives::babbage}, @@ -26,6 +26,7 @@ use pallas::{ use pallas_traverse::ComputeHash; use script::Script; use serde_json::json; +use telemetry::{EventListener, TestInfo}; use uplc::{ ast::{DeBruijn, Program}, machine::cost_model::ExBudget, @@ -35,6 +36,7 @@ use crate::{ config::Config, error::{Error, Warning}, module::{CheckedModule, CheckedModules, ParsedModule, ParsedModules}, + telemetry::Event, }; #[derive(Debug)] @@ -51,7 +53,10 @@ pub const MINT: &str = "mint"; pub const WITHDRAWL: &str = "withdrawl"; pub const VALIDATOR_NAMES: [&str; 4] = [SPEND, CERT, MINT, WITHDRAWL]; -pub struct Project { +pub struct Project +where + T: EventListener, +{ config: Config, defined_modules: HashMap, id_gen: IdGenerator, @@ -59,10 +64,14 @@ pub struct Project { root: PathBuf, sources: Vec, pub warnings: Vec, + event_listener: T, } -impl Project { - pub fn new(config: Config, root: PathBuf) -> Project { +impl Project +where + T: EventListener, +{ + pub fn new(config: Config, root: PathBuf, event_listener: T) -> Project { let id_gen = IdGenerator::new(); let mut module_types = HashMap::new(); @@ -78,6 +87,7 @@ impl Project { root, sources: vec![], warnings: vec![], + event_listener, } } @@ -492,7 +502,7 @@ impl Project { }; if !tests.is_empty() { - println!("\n{}\n", "Running tests...".bold().underline().purple()); + self.event_listener.handle_event(Event::RunningTests); } let mut results = Vec::new(); @@ -500,75 +510,28 @@ impl Project { for test in tests { match test.program.eval(initial_budget) { (Ok(..), remaining_budget, _) => { - results.push((true, test, initial_budget - remaining_budget)); - // println!("{}", fmt_tests); + let test_info = TestInfo { + is_passing: true, + test, + spent_budget: initial_budget - remaining_budget, + }; + + results.push(test_info); } (Err(_), remaining_budget, _) => { - results.push((false, test, initial_budget - remaining_budget)); - // println!("{}", fmt_tests()); + let test_info = TestInfo { + is_passing: false, + test, + spent_budget: initial_budget - remaining_budget, + }; + + results.push(test_info); } } } - let (max_mem, max_cpu) = - results - .iter() - .fold((0, 0), |(max_mem, max_cpu), (_, _, budget)| { - if budget.mem >= max_mem && budget.cpu >= max_cpu { - (budget.mem, budget.cpu) - } else if budget.mem > max_mem { - (budget.mem, max_cpu) - } else if budget.cpu > max_cpu { - (max_mem, budget.cpu) - } else { - (max_mem, max_cpu) - } - }); - - let max_mem = max_mem.to_string().len() as i32; - let max_cpu = max_cpu.to_string().len() as i32; - - let fmt_tests = |is_passing: &bool, test: &Script, spent_budget: &ExBudget| -> String { - let ExBudget { mem, cpu } = spent_budget; - format!( - " [{}] [mem: {}, cpu: {}] {}::{}", - if *is_passing { - "PASS".bold().green().to_string() - } else { - "FAIL".bold().red().to_string() - }, - pad_left(mem.to_string(), max_mem, " "), - pad_left(cpu.to_string(), max_cpu, " "), - test.module.blue(), - test.name.bright_blue() - ) - }; - - for (is_passing, test, spent_budget) in &results { - println!("{}", fmt_tests(is_passing, test, spent_budget)) - } - - let (n_passed, n_failed) = - results - .iter() - .fold((0, 0), |(n_passed, n_failed), (is_passing, _, _)| { - if *is_passing { - (n_passed + 1, n_failed) - } else { - (n_passed, n_failed + 1) - } - }); - - println!( - "{}", - format!( - "\n Summary: {} test(s), {}; {}.", - results.len(), - format!("{} passed", n_passed).bright_green(), - format!("{} failed", n_failed).bright_red() - ) - .bold() - ) + self.event_listener + .handle_event(Event::FinishedTests { tests: results }); } fn write_build_outputs(&self, programs: Vec