use std::path::{Path, PathBuf};
use std::process::Command;
fn cargo_brief_bin() -> PathBuf {
env!("CARGO_BIN_EXE_cargo-brief").into()
}
fn project_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn test_workspace() -> PathBuf {
project_root().join("test_workspace")
}
fn run(cwd: &Path, args: &[&str]) -> (String, String, bool) {
let output = Command::new(cargo_brief_bin())
.current_dir(cwd)
.args(args)
.output()
.expect("Failed to execute cargo-brief");
(
String::from_utf8_lossy(&output.stdout).to_string(),
String::from_utf8_lossy(&output.stderr).to_string(),
output.status.success(),
)
}
fn run_ok(cwd: &Path, args: &[&str]) -> String {
let (stdout, stderr, success) = run(cwd, args);
assert!(
success,
"Expected success but got failure.\nArgs: {args:?}\nStderr:\n{stderr}"
);
stdout
}
fn run_err(cwd: &Path, args: &[&str]) -> String {
let (stdout, stderr, success) = run(cwd, args);
assert!(
!success,
"Expected failure but got success.\nArgs: {args:?}\nStdout:\n{stdout}"
);
stderr
}
#[test]
fn explicit_core_lib() {
let out = run_ok(&test_workspace(), &["api", "core-lib"]);
assert!(out.contains("pub struct Config"), "missing Config struct");
assert!(
out.contains("pub trait Processor"),
"missing Processor trait"
);
}
#[test]
fn explicit_app() {
let out = run_ok(&test_workspace(), &["api", "app"]);
assert!(out.contains("pub struct App"), "missing App struct");
}
#[test]
fn explicit_underscore_normalization() {
let out = run_ok(&test_workspace(), &["api", "core_lib"]);
assert!(
out.contains("pub struct Config"),
"underscore normalization failed — missing Config struct"
);
}
#[test]
fn self_from_core_lib() {
let out = run_ok(&test_workspace().join("core-lib"), &["api", "self"]);
assert!(out.contains("pub struct Config"), "missing Config struct");
}
#[test]
fn self_from_app() {
let out = run_ok(&test_workspace().join("app"), &["api", "self"]);
assert!(out.contains("pub struct App"), "missing App struct");
}
#[test]
fn self_module_from_core_lib() {
let out = run_ok(&test_workspace().join("core-lib"), &["api", "self::utils"]);
assert!(
out.contains("pub fn format_name"),
"missing format_name in utils module"
);
}
#[test]
fn self_from_virtual_root() {
let stderr = run_err(&test_workspace(), &["api", "self"]);
assert!(
stderr.contains("package") || stderr.contains("workspace"),
"Expected error about no package at virtual root.\nStderr:\n{stderr}"
);
}
#[test]
fn crate_module_syntax() {
let out = run_ok(&test_workspace(), &["api", "core-lib::utils"]);
assert!(
out.contains("pub fn format_name"),
"missing format_name in utils"
);
assert!(out.contains("pub enum LogLevel"), "missing LogLevel enum");
}
#[test]
fn file_path_from_package_dir() {
let out = run_ok(&test_workspace().join("core-lib"), &["api", "src/utils.rs"]);
assert!(
out.contains("pub fn format_name"),
"missing format_name via file path"
);
}
#[test]
fn self_with_file_path() {
let out = run_ok(
&test_workspace().join("core-lib"),
&["api", "self", "src/utils.rs"],
);
assert!(
out.contains("pub fn format_name"),
"missing format_name via self + file path"
);
}
#[test]
#[ignore = "blocked: file path not resolved relative to package dir when cwd != package dir"]
fn pkg_with_file_path() {
let out = run_ok(&test_workspace(), &["api", "core-lib", "src/utils.rs"]);
assert!(
out.contains("pub fn format_name"),
"missing format_name via pkg + file path"
);
}
#[test]
fn external_crate_either() {
let out = run_ok(&test_workspace(), &["api", "either"]);
assert!(
out.contains("pub enum Either"),
"missing Either enum from external crate"
);
}
#[test]
fn auto_visibility_cross_crate() {
let out = run_ok(&test_workspace().join("app"), &["api", "core-lib"]);
assert!(
!out.contains("InternalState"),
"pub(crate) InternalState should be hidden in cross-crate view"
);
assert!(
!out.contains("internal_helper"),
"pub(crate) internal_helper should be hidden in cross-crate view"
);
assert!(
out.contains("pub struct Config"),
"pub Config should be visible"
);
}
#[test]
fn auto_visibility_same_crate() {
let out = run_ok(&test_workspace().join("core-lib"), &["api", "core-lib"]);
assert!(
out.contains("InternalState"),
"pub(crate) InternalState should be visible in same-crate view"
);
assert!(
out.contains("internal_helper"),
"pub(crate) internal_helper should be visible in same-crate view"
);
}
#[test]
fn auto_visibility_reverse() {
let out = run_ok(&test_workspace().join("core-lib"), &["api", "app"]);
assert!(
!out.contains("shutdown_internal"),
"pub(crate) shutdown_internal should be hidden in cross-crate view"
);
assert!(out.contains("pub struct App"), "pub App should be visible");
}
#[test]
fn at_package_cross_crate() {
let out = run_ok(
&test_workspace(),
&["api", "core-lib", "--at-package", "app"],
);
assert!(
!out.contains("InternalState"),
"pub(crate) InternalState should be hidden with --at-package app"
);
assert!(
!out.contains("internal_helper"),
"pub(crate) internal_helper should be hidden with --at-package app"
);
}
#[test]
fn at_package_same_crate() {
let out = run_ok(
&test_workspace(),
&["api", "core-lib", "--at-package", "core-lib"],
);
assert!(
out.contains("InternalState"),
"pub(crate) InternalState should be visible with --at-package core-lib"
);
assert!(
out.contains("internal_helper"),
"pub(crate) internal_helper should be visible with --at-package core-lib"
);
}
#[test]
fn depth_zero() {
let out = run_ok(&test_workspace(), &["api", "core-lib", "--depth", "0"]);
assert!(
out.contains("mod utils"),
"module header should still appear at depth 0"
);
assert!(
!out.contains("pub fn format_name"),
"format_name should not appear at depth 0 (module collapsed)"
);
}
#[test]
fn recursive() {
let out = run_ok(&test_workspace(), &["api", "core-lib", "--recursive"]);
assert!(
out.contains("pub fn format_name"),
"format_name should appear with --recursive"
);
}
#[test]
fn no_structs() {
let out = run_ok(&test_workspace(), &["api", "core-lib", "--no-structs"]);
assert!(
!out.contains("struct Config"),
"struct Config should be excluded by --no-structs"
);
assert!(
out.contains("pub trait Processor"),
"trait Processor should still be present"
);
}
#[test]
fn no_functions() {
let out = run_ok(&test_workspace(), &["api", "core-lib", "--no-functions"]);
assert!(
!out.contains("fn create_default_config"),
"create_default_config should be excluded by --no-functions"
);
assert!(
out.contains("pub struct Config"),
"struct Config should still be present"
);
}
#[test]
fn nonexistent_crate() {
let _stderr = run_err(&test_workspace(), &["api", "nonexistent-crate"]);
}
#[test]
fn self_from_non_package() {
let stderr = run_err(&test_workspace(), &["api", "self"]);
assert!(
stderr.contains("package") || stderr.contains("workspace"),
"Expected error about virtual workspace.\nStderr:\n{stderr}"
);
}
#[test]
fn bare_cargo_brief_api_from_package_dir() {
let out = run_ok(&test_workspace().join("core-lib"), &["api"]);
assert!(
out.contains("pub struct Config"),
"bare `cargo-brief api` from package dir should show Config struct"
);
}
#[test]
fn bare_cargo_brief_api_from_virtual_root() {
let stderr = run_err(&test_workspace(), &["api"]);
assert!(
stderr.contains("package") || stderr.contains("workspace"),
"Expected error about no package at virtual root.\nStderr:\n{stderr}"
);
}
#[test]
#[ignore = "network: fetches from crates.io"]
fn cli_crates_serde() {
let out = run_ok(&test_workspace(), &["-C", "api", "serde"]);
assert!(!out.is_empty(), "-C api serde should produce output");
assert!(
out.contains("Serialize"),
"-C api serde should contain Serialize"
);
}
#[test]
#[ignore = "network: fetches from crates.io"]
fn cli_crates_version() {
let out = run_ok(&test_workspace(), &["-C", "api", "serde@1"]);
assert!(!out.is_empty(), "-C api serde@1 should produce output");
}