use tempfile::TempDir;
use super::support::*;
use crate::languages::runner::*;
#[tokio::test]
async fn a_silent_non_zero_exit_is_unavailable_not_a_clean_pass() {
let dir = TempDir::new().unwrap();
let bin = dir.path().join("failtool");
write_executable(&bin, "#!/bin/sh\necho 'fatal: bad config' >&2\nexit 2\n");
std::fs::write(dir.path().join("pyproject.toml"), "").unwrap();
let spec = ToolSpec {
name: "failtool",
command: &["failtool"],
local_paths: &["failtool"],
config_files: &["pyproject.toml"],
output_format: OutputFormat::Lines,
diagnostics_stream: DiagnosticsStream::Stdout,
..ToolSpec::default()
};
let outcome = run_tool(&spec, dir.path(), &["a.py".to_owned()]).await;
assert_eq!(
outcome.status,
ToolStatus::Unavailable,
"a silent non-zero exit must not read as a clean pass, got {outcome:?}"
);
assert!(
outcome.detail.contains("fatal: bad config"),
"the other stream carries the real error and must reach the detail: {}",
outcome.detail
);
assert!(
outcome.detail.contains("exited 2 "),
"the tool's own exit code must reach the diagnostic: {}",
outcome.detail
);
}
#[tokio::test]
async fn a_nonzero_exit_with_only_unmatched_lines_is_unavailable_for_skip_parsers() {
for (format, stream, error_line) in [
(
OutputFormat::Msbuild,
DiagnosticsStream::Stdout,
"src/x.csproj : error NETSDK1004: Assets file 'project.assets.json' not found. Run a NuGet package restore to generate this file.",
),
(
OutputFormat::Tsc,
DiagnosticsStream::Stdout,
"error TS5023: Unknown compiler option '--strictNullChecking'.",
),
(
OutputFormat::Position,
DiagnosticsStream::Stderr,
"go: cannot find main module; see 'go help modules'",
),
] {
let dir = TempDir::new().unwrap();
let bin = dir.path().join("errtool");
let emit = if stream == DiagnosticsStream::Stderr {
">&2"
} else {
""
};
write_executable(
&bin,
format!("#!/bin/sh\nprintf '%s\\n' '{error_line}' {emit}\nexit 1\n"),
);
std::fs::write(dir.path().join("marker"), "").unwrap();
let spec = ToolSpec {
name: "errtool",
command: &["errtool"],
local_paths: &["errtool"],
config_files: &["marker"],
output_format: format,
diagnostics_stream: stream,
accepts_files: false,
..ToolSpec::default()
};
let outcome = run_tool(&spec, dir.path(), &["a.x".to_owned()]).await;
assert_eq!(
outcome.status,
ToolStatus::Unavailable,
"{format:?} exiting 1 with only unrecognised lines must not be a clean pass"
);
assert!(
outcome.detail.contains("error") || outcome.detail.contains("go:"),
"the unmatched lines are the diagnostic and must reach the detail: {}",
outcome.detail
);
assert!(
outcome.detail.contains("exited 1 "),
"{format:?}: the tool's own exit code must reach the diagnostic: {}",
outcome.detail
);
}
}
#[tokio::test]
async fn a_nonzero_exit_with_parsed_findings_is_still_ok() {
let dir = TempDir::new().unwrap();
let bin = dir.path().join("dotnet");
let reported = dir.path().join("A.cs");
write_executable(
&bin,
format!(
"#!/bin/sh\nprintf '%s\\n' '{}(3,5): error WHITESPACE: Fix it [{}/cs.csproj]'\nexit 1\n",
reported.to_string_lossy(),
dir.path().to_string_lossy()
),
);
std::fs::write(dir.path().join("marker"), "").unwrap();
let spec = ToolSpec {
name: "dotnet format",
command: &["dotnet"],
local_paths: &["dotnet"],
config_files: &["marker"],
output_format: OutputFormat::Msbuild,
diagnostics_stream: DiagnosticsStream::Stdout,
accepts_files: false,
..ToolSpec::default()
};
let outcome = run_tool(&spec, dir.path(), &["A.cs".to_owned()]).await;
assert_eq!(
outcome.status,
ToolStatus::Ok,
"parseable violations on a non-zero exit are the findings path, got {outcome:?}"
);
assert_eq!(outcome.findings.len(), 1);
}