Files
dev/src/nix-style-linter/lib_test.rs

282 lines
8.1 KiB
Rust

use std::fs;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicUsize, Ordering};
use nix_style_linter::RunStatus;
static TEST_DIRECTORY_ID: AtomicUsize = AtomicUsize::new(0);
#[test]
fn malformed_file_reports_parse_error() {
let workspace = TestWorkspace::new("malformed_file_reports_parse_error");
let path = workspace.write("broken.nix", "let x = ; in x\n");
let output = run_linter(&[path]);
assert_eq!(output.status, RunStatus::Diagnostics);
assert_eq!(output.stderr, "");
assert!(output.stdout.starts_with(&format!(
"{}:1:9: parse-error: unexpected TOKEN_SEMICOLON at 8..9",
workspace.path("broken.nix").display()
)));
assert!(!output.stdout.contains("let-order"));
}
#[test]
fn forward_reference_reports_let_order() {
let workspace = TestWorkspace::new("forward_reference_reports_let_order");
let path = workspace.write(
"forward.nix",
"let\n app = package;\n package = \"hello\";\nin app\n",
);
let output = run_linter(&[path]);
assert_eq!(output.status, RunStatus::Diagnostics);
assert_eq!(output.stderr, "");
assert_eq!(
output.stdout,
format!(
"{}:2:3: let-order: `app` references later let binding `package`\n",
workspace.path("forward.nix").display()
)
);
}
#[test]
fn dependency_ordered_bindings_exit_clean() {
let workspace = TestWorkspace::new("dependency_ordered_bindings_exit_clean");
let path = workspace.write(
"ordered.nix",
"let\n package = \"hello\";\n app = package;\nin app\n",
);
let output = run_linter(&[path]);
assert_eq!(output.status, RunStatus::Clean);
assert_eq!(output.stdout, "");
assert_eq!(output.stderr, "");
}
#[test]
fn outer_scope_reference_exits_clean() {
let workspace = TestWorkspace::new("outer_scope_reference_exits_clean");
let path = workspace.write(
"outer.nix",
"let\n package = \"hello\";\nin\nlet\n app = package;\nin app\n",
);
let output = run_linter(&[path]);
assert_eq!(output.status, RunStatus::Clean);
assert_eq!(output.stdout, "");
assert_eq!(output.stderr, "");
}
#[test]
fn nested_lambda_parameter_shadows_later_binding() {
let workspace = TestWorkspace::new("nested_lambda_parameter_shadows_later_binding");
let path = workspace.write(
"lambda.nix",
"let\n call = package: package;\n package = \"hello\";\nin call\n",
);
let output = run_linter(&[path]);
assert_eq!(output.status, RunStatus::Clean);
assert_eq!(output.stdout, "");
assert_eq!(output.stderr, "");
}
#[test]
fn nested_let_binding_shadows_later_binding() {
let workspace = TestWorkspace::new("nested_let_binding_shadows_later_binding");
let path = workspace.write(
"nested-let.nix",
"let\n app = let package = \"inner\"; in package;\n package = \"outer\";\nin app\n",
);
let output = run_linter(&[path]);
assert_eq!(output.status, RunStatus::Clean);
assert_eq!(output.stdout, "");
assert_eq!(output.stderr, "");
}
#[test]
fn earlier_sibling_constant_shared_by_multiple_bindings_exits_clean() {
let workspace = TestWorkspace::new("earlier_sibling_constant_shared_by_multiple_bindings");
let path = workspace.write(
"shared.nix",
"let\n package = \"hello\";\n app = package;\n shell = package;\nin [ app shell ]\n",
);
let output = run_linter(&[path]);
assert_eq!(output.status, RunStatus::Clean);
assert_eq!(output.stdout, "");
assert_eq!(output.stderr, "");
}
#[test]
fn previous_line_suppression_disables_let_order() {
let workspace = TestWorkspace::new("previous_line_suppression_disables_let_order");
let path = workspace.write(
"suppressed.nix",
"let\n # nix-style-linter: disable-next-line let-order -- intentional recursion\n app = package;\n package = app;\nin app\n",
);
let output = run_linter(&[path]);
assert_eq!(output.status, RunStatus::Clean);
assert_eq!(output.stdout, "");
assert_eq!(output.stderr, "");
}
#[test]
fn malformed_suppression_does_not_disable_let_order() {
let workspace = TestWorkspace::new("malformed_suppression_does_not_disable_let_order");
let path = workspace.write(
"unsuppressed.nix",
"let\n # nix-style-linter: disable-next-line let-order\n app = package;\n package = \"hello\";\nin app\n",
);
let output = run_linter(&[path]);
assert_eq!(output.status, RunStatus::Diagnostics);
assert_eq!(
output.stdout,
format!(
"{}:3:3: let-order: `app` references later let binding `package`\n",
workspace.path("unsuppressed.nix").display()
)
);
assert_eq!(output.stderr, "");
}
#[test]
fn unsupported_binding_syntax_is_skipped() {
let workspace = TestWorkspace::new("unsupported_binding_syntax_is_skipped");
let path = workspace.write(
"unsupported.nix",
"let\n attrs.value = package;\n inherit package;\n package = \"hello\";\nin attrs\n",
);
let output = run_linter(&[path]);
assert_eq!(output.status, RunStatus::Clean);
assert_eq!(output.stdout, "");
assert_eq!(output.stderr, "");
}
#[test]
fn valid_file_exits_clean() {
let workspace = TestWorkspace::new("valid_file_exits_clean");
let path = workspace.write("valid.nix", "let x = 1; in x\n");
let output = run_linter(&[path]);
assert_eq!(output.status, RunStatus::Clean);
assert_eq!(output.stdout, "");
assert_eq!(output.stderr, "");
}
#[test]
fn missing_path_reports_tool_error() {
let workspace = TestWorkspace::new("missing_path_reports_tool_error");
let path = workspace.path("missing.nix");
let output = run_linter(&[path.clone()]);
assert_eq!(output.status, RunStatus::ToolError);
assert_eq!(output.stdout, "");
assert!(output.stderr.starts_with(&format!(
"error: failed to inspect {}:",
path.display()
)));
}
#[test]
fn directory_inputs_recurse_through_nix_files() {
let workspace = TestWorkspace::new("directory_inputs_recurse_through_nix_files");
workspace.write("ignored.txt", "let x = ; in x\n");
workspace.write("nested/broken.nix", "let\n x = 1;\n y = ;\nin y\n");
let output = run_linter(&[workspace.root.clone()]);
assert_eq!(output.status, RunStatus::Diagnostics);
assert_eq!(output.stderr, "");
assert!(output.stdout.starts_with(&format!(
"{}:3:7: parse-error: unexpected TOKEN_SEMICOLON at 19..20",
workspace.path("nested/broken.nix").display()
)));
assert!(!output.stdout.contains("ignored.txt"));
}
#[test]
fn empty_invocation_reports_tool_error() {
let output = run_linter(&[]);
assert_eq!(output.status, RunStatus::ToolError);
assert_eq!(output.stdout, "");
assert_eq!(output.stderr, "error: expected at least one path\n");
}
struct LinterOutput {
status: RunStatus,
stdout: String,
stderr: String,
}
fn run_linter(paths: &[PathBuf]) -> LinterOutput {
let mut stdout = Vec::new();
let mut stderr = Vec::new();
let status = nix_style_linter::run(paths, &mut stdout, &mut stderr).unwrap();
LinterOutput {
status,
stdout: String::from_utf8(stdout).unwrap(),
stderr: String::from_utf8(stderr).unwrap(),
}
}
struct TestWorkspace {
root: PathBuf,
}
impl TestWorkspace {
fn new(name: &str) -> Self {
let id = TEST_DIRECTORY_ID.fetch_add(1, Ordering::Relaxed);
let root = std::env::temp_dir().join(format!(
"nix-style-linter-{process}-{name}-{id}",
process = std::process::id()
));
if root.exists() {
fs::remove_dir_all(&root).unwrap();
}
fs::create_dir_all(&root).unwrap();
Self { root }
}
fn path(&self, relative: impl AsRef<Path>) -> PathBuf {
self.root.join(relative)
}
fn write(&self, relative: impl AsRef<Path>, contents: &str) -> PathBuf {
let path = self.path(relative);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(&path, contents).unwrap();
path
}
}
impl Drop for TestWorkspace {
fn drop(&mut self) {
fs::remove_dir_all(&self.root).unwrap();
}
}