use std::process::Command;
use std::sync::Once;
static INIT: Once = Once::new();
fn ensure_binary_built() {
INIT.call_once(|| {
let status = Command::new("cargo")
.args(&["build", "--release"])
.status()
.expect("Failed to build systemd-lsp");
assert!(status.success(), "Failed to build binary");
});
}
fn run_systemd_lsp(args: &[&str]) -> (String, String, i32) {
ensure_binary_built();
let output = Command::new("./target/release/systemd-lsp")
.args(args)
.output()
.expect("Failed to execute systemd-lsp");
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let exit_code = output.status.code().unwrap_or(-1);
(stdout, stderr, exit_code)
}
#[test]
fn test_cli_example_with_errors() {
let (stdout, _stderr, exit_code) = run_systemd_lsp(&["examples/example-with-errors.service"]);
assert_eq!(exit_code, 1, "Expected exit code 1 for file with errors");
assert!(
stdout.contains("Invalid Type value 'invalid-type'"),
"Should detect invalid Type value"
);
assert!(
stdout.contains("ExecStart cannot be empty"),
"Should detect empty ExecStart"
);
assert!(
stdout.contains("Invalid Restart value 'unknown-policy'"),
"Should detect invalid Restart value"
);
assert!(
stdout.contains("Unknown directive 'InvalidDirective' in [Service] section"),
"Should detect unknown directive in Service section"
);
assert!(
stdout.contains("Invalid ProtectSystem value 'invalid'"),
"Should detect invalid ProtectSystem value"
);
assert!(
stdout.contains("Invalid StandardOutput value 'invalid-stream'"),
"Should detect invalid StandardOutput value"
);
assert!(
stdout.contains("Invalid NotifyAccess value 'invalid-access'"),
"Should detect invalid NotifyAccess value"
);
assert!(
stdout.contains("Unknown section: [UnknownSection]"),
"Should detect unknown section"
);
assert!(
stdout.contains("Unknown directive 'AnotherBadDirective' in [Unit] section"),
"Should detect unknown directive in Unit section"
);
assert!(stdout.contains("error"), "Should label errors as 'error'");
assert!(
stdout.contains("warning"),
"Should label warnings as 'warning'"
);
assert!(
stdout.contains("example-with-errors.service"),
"Should show file path"
);
assert!(
stdout.contains("✗"),
"Should show failure indicator in summary"
);
}
#[test]
fn test_cli_valid_service_file() {
let (stdout, _stderr, exit_code) = run_systemd_lsp(&["examples/example.service"]);
assert_eq!(exit_code, 0, "Expected exit code 0 for valid file");
assert!(stdout.contains("✓"), "Should show success indicator");
assert!(
stdout.contains("valid"),
"Should indicate files are valid"
);
}
#[test]
fn test_cli_multiple_files() {
let (stdout, _stderr, exit_code) =
run_systemd_lsp(&["examples/example.service", "examples/example-with-errors.service"]);
assert_eq!(
exit_code, 1,
"Expected exit code 1 when any file has errors"
);
assert!(
stdout.contains("example-with-errors.service"),
"Should mention file with errors"
);
assert!(
stdout.contains("out of 2 total"),
"Should show total file count"
);
}
#[test]
fn test_cli_nonexistent_file() {
let (_stdout, stderr, exit_code) = run_systemd_lsp(&["nonexistent.service"]);
assert_eq!(exit_code, 1, "Should exit with error code 1");
assert!(
stderr.contains("No systemd unit files found") || stderr.contains("Error reading"),
"Should mention no files found or read error"
);
}
#[test]
fn test_cli_warnings_only() {
let (stdout, _stderr, exit_code) = run_systemd_lsp(&["examples/warnings-only.service"]);
assert_eq!(exit_code, 0, "Expected exit code 0 for warnings only");
assert!(stdout.contains("⚠"), "Should show warning indicator");
assert!(
stdout.contains("warning"),
"Should mention warnings"
);
assert!(!stdout.contains("✗"), "Should not show error indicator");
}
#[test]
fn test_cli_directory_non_recursive() {
let (stdout, _stderr, exit_code) = run_systemd_lsp(&["examples"]);
assert!(
stdout.contains("example-with-errors.service") || stdout.contains("total"),
"Should process files in examples directory"
);
assert_eq!(
exit_code, 1,
"Should exit with error code 1 due to example-with-errors.service"
);
}
#[test]
fn test_cli_directory_recursive() {
let (stdout, _stderr, _exit_code) = run_systemd_lsp(&["examples", "--recursive"]);
assert!(
stdout.contains("example-with-errors.service") || stdout.contains("total"),
"Should process files from examples directory recursively"
);
}
#[test]
fn test_expected_error_count() {
let (stdout, _stderr, exit_code) = run_systemd_lsp(&["examples/example-with-errors.service"]);
assert_eq!(exit_code, 1, "Expected exit code 1 for file with errors");
let diagnostic_lines: Vec<&str> = stdout
.lines()
.filter(|line| line.contains("error") || line.contains("warning") || line.contains("unknown"))
.collect();
assert!(
diagnostic_lines.len() >= 9,
"Expected at least 9 diagnostics, found {}. Diagnostics:\n{}",
diagnostic_lines.len(),
diagnostic_lines.join("\n")
);
assert!(
stdout.contains("7 error(s)") && stdout.contains("2 warning(s)"),
"Summary should show 7 errors and 2 warnings"
);
}
#[test]
fn test_line_numbers_in_output() {
let (stdout, _stderr, exit_code) = run_systemd_lsp(&["examples/example-with-errors.service"]);
assert_eq!(exit_code, 1, "Expected exit code 1");
let diagnostic_lines: Vec<&str> = stdout
.lines()
.filter(|line| {
line.starts_with(" ")
&& (line.contains("error") || line.contains("warning") || line.contains("unknown"))
})
.collect();
for line in &diagnostic_lines {
assert!(
line.matches(':').count() >= 3,
"Diagnostic line should contain path:line:column:severity format. Got: {}",
line
);
}
}