I want to print out ordered by mods and with indentation, removing duplicate prefixes with the function namespace.
For example, this output:
test e2e::web::api::v1::contexts::category::contract::it_should_not_allow_adding_duplicated_categories ... ok
test e2e::web::api::v1::contexts::tag::contract::it_should_not_allow_adding_duplicated_tags ... ok
test e2e::web::api::v1::contexts::category::contract::it_should_not_allow_non_admins_to_delete_categories ... ok
test e2e::web::api::v1::contexts::tag::contract::it_should_not_allow_adding_a_tag_with_an_empty_name ... ok
test e2e::web::api::v1::contexts::tag::contract::it_should_not_allow_guests_to_delete_tags ... ok
test e2e::web::api::v1::contexts::category::contract::it_should_allow_admins_to_delete_categories ... ok
test e2e::web::api::v1::contexts::user::contract::banned_user_list::it_should_allow_an_admin_to_ban_a_user ... ok
test e2e::web::api::v1::contexts::tag::contract::it_should_allow_admins_to_delete_tags ... ok
test e2e::web::api::v1::contexts::user::contract::banned_user_list::it_should_not_allow_a_non_admin_to_ban_a_user ... ok
test e2e::web::api::v1::contexts::tag::contract::it_should_not_allow_non_admins_to_delete_tags ... ok
should produce this output:
test
e2e
web
api
v1
contexts
category
contract
it_should_not_allow_adding_duplicated_categories ... ok
it_should_not_allow_non_admins_to_delete_categories ... ok
it_should_allow_admins_to_delete_categories ... ok
tag
contract
it_should_not_allow_adding_duplicated_tags ... ok
it_should_not_allow_adding_a_tag_with_an_empty_name ... ok
it_should_not_allow_guests_to_delete_tags ... ok
it_should_allow_admins_to_delete_tags ... ok
it_should_not_allow_non_admins_to_delete_tags ... ok
user
contract
banned_user_list
it_should_allow_an_admin_to_ban_a_user ... ok
it_should_not_allow_a_non_admin_to_ban_a_user ... ok
Also you can use the following code (100~ lines in total) with https://rust-script.org/ to achieve your goal.
cargo install rust-script
execute the code
save the code in a file named pretty-test
chmod +x ./pretty-test
add it in your environment, like mv pretty-test ~/.cargo/bin
run pretty-test in your project
// src/lib.rs
#[test] fn works() {}
mod a {
mod b {
#[test] fn c() { panic!() }
}
#[test] fn d() {}
}
// output:
test
├── a
│ ├── b
│ │ └─ ❌ c
│ └─ ✅ d
└─ ✅ works
#!/usr/bin/env rust-script
//!
//! ```cargo
//! [dependencies]
//! termtree = "0.4.1"
//! regex-lite = "0.1"
//! ```
use regex_lite::Regex;
use std::{
collections::{btree_map::Entry, BTreeMap},
process::Command,
};
use termtree::{GlyphPalette, Tree};
fn main() {
let output = Command::new("cargo").arg("test").output().unwrap();
let text = String::from_utf8_lossy(&output.stdout);
let re = Regex::new(r"(?m)^test \S+ \.\.\. \S+$").unwrap();
println!(
"{}",
pretty_test(re.find_iter(&text).map(|m| m.as_str())).unwrap()
);
}
#[derive(Debug)]
enum Node<'s> {
Path(BTreeMap<&'s str, Node<'s>>),
Status(&'s str),
}
fn pretty_test<'s>(lines: impl Iterator<Item = &'s str>) -> Option<String> {
let mut path = BTreeMap::new();
for line in lines {
let mut iter = line.splitn(3, ' ');
let mut split = iter.nth(1)?.split("::");
let next = split.next();
let status = iter.next()?;
make_mods(split, status, &mut path, next);
}
let mut tree = Tree::new("test");
for (root, child) in path {
make_tree(root, &child, &mut tree);
}
Some(tree.to_string())
}
// Add paths to Node
fn make_mods<'s>(
mut split: impl Iterator<Item = &'s str>,
status: &'s str,
path: &mut BTreeMap<&'s str, Node<'s>>,
key: Option<&'s str>,
) {
let Some(key) = key else { return };
let next = split.next();
match path.entry(key) {
Entry::Vacant(empty) => {
if next.is_some() {
let mut btree = BTreeMap::new();
make_mods(split, status, &mut btree, next);
empty.insert(Node::Path(btree));
} else {
empty.insert(Node::Status(status));
}
}
Entry::Occupied(mut btree) => {
if let Node::Path(btree) = btree.get_mut() {
make_mods(split, status, btree, next)
}
}
}
}
// Add Node to Tree
fn make_tree<'s>(root: &'s str, node: &Node<'s>, parent: &mut Tree<&'s str>) {
match node {
Node::Path(btree) => {
let mut t = Tree::new(root);
for (path, child) in btree {
make_tree(path, child, &mut t);
}
parent.push(t);
}
Node::Status(s) => {
parent.push(Tree::new(root).with_glyphs(set_status(s)));
}
}
}
// Display with a status icon
fn set_status(status: &str) -> GlyphPalette {
let mut glyph = GlyphPalette::new();
glyph.item_indent = if status.ends_with("ok") {
"─ ✅ "
} else {
"─ ❌ "
};
glyph
}
Awesome @vague you made my day! It's something I'd missed a lot. I knew I would need some kind of tree structure and that's why I was looking for a crate.
We can move it to your account on GitHub, or I can make you an owner if you agree (and with whatever the LICENSE you want to use). I've made some changes, like getting the input from the standard input, but I do not like it because if there is no input, it waits until the user cancels it with Ctrl+D or Ctrl+Z.
But it requires the user to copy & paste part of the output from cargo test to your cli, which is inconvenient. That's why I obtain and handle the cargo test output in the script.
In README, the usage with rust-script is wrong because there is no dependencies declaration in the script (or root file). You have already put it in the project, so it'd be better deleting the rust-script usage.
FYI with RFC 3455 merged, there is now a t-testing-devex team that will be looking at improving things. We're a bit slow getting started up as we are trying to roll off of other projects. One big focus I expect from this effort is to at least make programmtic output more meaningful so its easier to generate these custom UIs even if we don't bake a UI like this into it.