use crate::cli::doctor::{DoctorArgs, missing_tools_line};
use crate::languages::spec::{LanguageSupport, ToolSpec};
use crate::test_support::write_executable;
fn args(path: &std::path::Path) -> DoctorArgs {
DoctorArgs {
path: path.to_path_buf(),
config: None,
}
}
#[tokio::test]
async fn skipped_tool_is_not_a_problem() {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::write(dir.path().join("a.py"), "x = 1\n").expect("a.py");
let mut out = Vec::new();
let exit = super::run_scoped(&mut out, &args(dir.path()), dir.path())
.await
.expect("run_to");
assert_eq!(exit, crate::Exit::Clean);
let rendered = String::from_utf8(out).expect("utf8");
let ruff_line = rendered
.lines()
.find(|line| line.trim_start().starts_with("ruff:"))
.expect("a ruff: line must appear under Deterministic checks");
assert!(
ruff_line.contains("not configured"),
"skipped tool reports `not configured`; got: {ruff_line:?}"
);
assert!(
!rendered.contains("configured tool(s) are missing"),
"a skipped tool must not produce a missing-tools block; rendered:\n{rendered}"
);
}
#[tokio::test]
async fn nested_workspace_configuration_is_reported_ready() {
let dir = tempfile::tempdir().expect("tempdir");
let bin = dir.path().join("venv/bin/ruff");
std::fs::create_dir_all(bin.parent().unwrap()).expect("bin dir");
write_executable(&bin, "#!/bin/sh\nexit 0\n");
let member = dir.path().join("apps/web");
std::fs::create_dir_all(&member).expect("member");
std::fs::write(member.join("pyproject.toml"), "").expect("config");
std::fs::write(member.join("a.py"), "x = 1\n").expect("source");
let mut out = Vec::new();
super::run_scoped(&mut out, &args(dir.path()), dir.path())
.await
.expect("run_to");
let rendered = String::from_utf8(out).expect("utf8");
assert!(
rendered.contains("ruff: ready in 1 workspace(s)"),
"nested configuration must be discovered: {rendered}"
);
}
#[tokio::test]
async fn root_configuration_keeps_the_plain_ready_status() {
let dir = tempfile::tempdir().expect("tempdir");
let bin = dir.path().join("venv/bin/ruff");
std::fs::create_dir_all(bin.parent().unwrap()).expect("bin dir");
write_executable(&bin, "#!/bin/sh\nexit 0\n");
std::fs::write(dir.path().join("pyproject.toml"), "").expect("config");
std::fs::write(dir.path().join("a.py"), "x = 1\n").expect("source");
let mut out = Vec::new();
super::run_scoped(&mut out, &args(dir.path()), dir.path())
.await
.expect("run_to");
let rendered = String::from_utf8(out).expect("utf8");
let ruff_line = rendered
.lines()
.find(|line| line.trim_start().starts_with("ruff:"))
.expect("ruff status");
assert_eq!(
ruff_line.trim(),
"ruff: ready",
"a single root configuration is not a nested workspace summary"
);
}
#[tokio::test]
async fn missing_tools_line_renders_the_expected_sentence() {
let line = missing_tools_line(&["ruff"]).expect("non-empty");
assert!(
line.contains("1 configured tool(s) are missing: ruff"),
"got: {line:?}"
);
assert!(line.contains("drep exits 2"), "got: {line:?}");
}
#[tokio::test]
async fn empty_missing_tools_line_is_none() {
assert!(missing_tools_line(&[]).is_none());
}
static GHOST_TOOL: ToolSpec = ToolSpec {
name: "ghost-linter",
command: &["drep-ghost-linter-no-machine-has-this"],
local_paths: &[],
config_files: &["ghost.config"],
config_flag: None,
output_format: "json",
diagnostics_stream: "stdout",
timeout_secs: 120,
timeout_context: None,
establishes_compilation: false,
serial_in_repository: false,
accepts_files: true,
};
static GHOST_LANG: LanguageSupport = LanguageSupport {
name: "ghost",
display_name: "Ghost",
extensions: &[".ghost"],
tools: &[&GHOST_TOOL],
conventions: &[],
vendored_dirs: &[],
};
static SPECTRE_LANG: LanguageSupport = LanguageSupport {
name: "spectre",
display_name: "Spectre",
extensions: &[".spectre"],
tools: &[&GHOST_TOOL],
conventions: &[],
vendored_dirs: &[],
};
#[tokio::test]
async fn a_configured_tool_that_cannot_resolve_is_returned_as_missing() {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::write(dir.path().join("ghost.config"), "").expect("config");
let mut out = Vec::new();
let missing =
crate::cli::doctor::write_tools_section(&mut out, &[(&GHOST_LANG, Vec::new())], dir.path())
.expect("write_tools_section");
assert_eq!(
missing,
["ghost-linter"],
"a configured tool that cannot resolve must be reported missing"
);
}
#[tokio::test]
async fn a_tool_shared_by_two_languages_is_returned_once() {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::write(dir.path().join("ghost.config"), "").expect("config");
let mut out = Vec::new();
let missing = crate::cli::doctor::write_tools_section(
&mut out,
&[(&GHOST_LANG, Vec::new()), (&SPECTRE_LANG, Vec::new())],
dir.path(),
)
.expect("write_tools_section");
assert_eq!(
missing,
["ghost-linter"],
"one tool shared by two languages is one missing tool"
);
}