use std::collections::HashSet;
use std::path::PathBuf;
use std::process::Command;
use linthis::tools::install::{
is_tool_supported_on_current_platform, resolve_install_cmds, supported_platforms, Os, ToolRole,
TOOL_INSTALLS,
};
use linthis::Language;
#[test]
fn table_wellformed_no_duplicate_language_role_pairs() {
let mut seen: HashSet<(Language, ToolRole)> = HashSet::new();
for spec in TOOL_INSTALLS {
assert!(
seen.insert((spec.language, spec.role)),
"duplicate (language, role) entry for tool '{}': {:?} {:?}",
spec.tool,
spec.language,
spec.role,
);
}
}
#[test]
fn table_wellformed_every_platform_has_at_least_one_cmd() {
for spec in TOOL_INSTALLS {
for platform in spec.platforms {
assert!(
!platform.cmds.is_empty(),
"{} on {:?} has no commands",
spec.tool,
platform.os,
);
for cmd in platform.cmds {
assert!(
!cmd.is_empty(),
"{} on {:?} has an empty command",
spec.tool,
platform.os,
);
assert!(
!cmd[0].is_empty(),
"{} on {:?} has an empty program name",
spec.tool,
platform.os,
);
}
}
}
}
#[test]
fn table_wellformed_swiftlint_is_macos_only() {
let platforms = supported_platforms("swiftlint");
assert_eq!(platforms, vec![Os::MacOs]);
}
#[test]
fn table_wellformed_dart_is_not_registered() {
assert!(!TOOL_INSTALLS.iter().any(|s| s.language == Language::Dart));
}
#[test]
fn resolve_returns_empty_for_unregistered_pair() {
assert!(resolve_install_cmds(Language::Dart, ToolRole::Checker).is_empty());
assert!(resolve_install_cmds(Language::Dart, ToolRole::Formatter).is_empty());
}
#[cfg(not(target_os = "macos"))]
#[test]
fn resolve_returns_empty_for_unsupported_platform_combo() {
assert!(resolve_install_cmds(Language::Swift, ToolRole::Checker).is_empty());
assert!(resolve_install_cmds(Language::Swift, ToolRole::Formatter).is_empty());
}
#[test]
fn stylua_formatter_first_candidate_on_current_platform_matches_expected() {
let cmds = resolve_install_cmds(Language::Lua, ToolRole::Formatter);
assert!(!cmds.is_empty(), "stylua must resolve on current platform");
let first = &cmds[0];
match Os::current() {
Os::MacOs => assert_eq!(
first,
&vec![
"brew".to_string(),
"install".to_string(),
"stylua".to_string()
]
),
Os::Windows => assert_eq!(
first,
&vec![
"scoop".to_string(),
"install".to_string(),
"stylua".to_string()
]
),
Os::Linux => assert_eq!(
first,
&vec![
"cargo".to_string(),
"install".to_string(),
"stylua".to_string()
]
),
}
}
#[test]
fn is_tool_supported_agrees_with_resolve() {
for spec in TOOL_INSTALLS {
let in_platforms = spec.platforms.iter().any(|p| p.os == Os::current());
if in_platforms {
assert!(
is_tool_supported_on_current_platform(spec.tool),
"{} should be supported on current platform",
spec.tool,
);
} else {
assert!(
!is_tool_supported_on_current_platform(spec.tool),
"{} should NOT be supported on current platform",
spec.tool,
);
}
}
}
fn linthis_bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_linthis"))
}
fn fixture_dir(lang_subdir: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/all-langs")
.join(lang_subdir)
}
fn which(bin: &str) -> Option<PathBuf> {
let output = Command::new(if cfg!(target_os = "windows") {
"where"
} else {
"which"
})
.arg(bin)
.output()
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
stdout.lines().next().map(|line| PathBuf::from(line.trim()))
}
fn path_binary_for(tool: &str) -> &str {
match tool {
"clippy" => "cargo-clippy",
other => other,
}
}
fn lang_subdir(lang: Language) -> &'static str {
match lang {
Language::Python => "python",
Language::Rust => "rust",
Language::Go => "go",
Language::TypeScript | Language::JavaScript => "typescript",
Language::Cpp | Language::ObjectiveC => "cpp",
Language::Java => "java",
Language::Shell => "shell",
Language::Lua => "lua",
Language::Kotlin => "kotlin",
Language::Ruby => "ruby",
Language::Php => "php",
Language::Scala => "scala",
Language::CSharp => "csharp",
Language::Swift => "swift",
Language::Dart => "dart",
}
}
#[test]
#[ignore]
fn auto_install_lint_cycle() {
let linthis = linthis_bin();
for spec in TOOL_INSTALLS {
if !spec.platforms.iter().any(|p| p.os == Os::current()) {
eprintln!("skip: {} not supported on {:?}", spec.tool, Os::current());
continue;
}
let lang_dir = fixture_dir(lang_subdir(spec.language));
if !lang_dir.exists() {
eprintln!("skip: no fixture for {}", spec.tool);
continue;
}
let bin_name = path_binary_for(spec.tool);
if which(bin_name).is_some() {
eprintln!("pre-existing: {} (binary={})", spec.tool, bin_name);
continue;
}
let status = Command::new(&linthis)
.args(["-i", lang_dir.to_str().unwrap()])
.env("LINTHIS_INSTALL_MODE", "auto")
.status()
.expect("failed to spawn linthis");
assert!(
status.code().is_some() || cfg!(target_os = "windows"),
"linthis did not exit normally for tool {} (status={:?})",
spec.tool,
status,
);
assert!(
which(bin_name).is_some(),
"{} (binary={}) not found on PATH after auto-install (language={:?})",
spec.tool,
bin_name,
spec.language,
);
}
}