eh/eh/tests/basic.rs
NotAShelf c3321858c4
chore: use nightly rustfmt rules
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ia3745c82c92a78e8326d2e0e446f20d66a6a6964
2025-11-14 21:54:35 +03:00

234 lines
6.5 KiB
Rust

use std::{fs, process::Command};
use eh::util::{
DefaultNixErrorClassifier,
DefaultNixFileFixer,
HashExtractor,
NixErrorClassifier,
NixFileFixer,
RegexHashExtractor,
};
use tempfile::TempDir;
#[test]
fn test_hash_extraction_from_real_nix_errors() {
// Test hash extraction from actual Nix error messages
let extractor = RegexHashExtractor;
let test_cases = [
(
r#"error: hash mismatch in fixed-output derivation '/nix/store/xxx-foo.drv':
specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
got: sha256-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="#,
Some("sha256-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=".to_string()),
),
(
"actual: sha256-abc123def456",
Some("sha256-abc123def456".to_string()),
),
("have: sha256-xyz789", Some("sha256-xyz789".to_string())),
("no hash here", None),
];
for (input, expected) in test_cases {
assert_eq!(extractor.extract_hash(input), expected);
}
}
#[test]
fn test_error_classification_for_retry_logic() {
// Test that the classifier correctly identifies errors that should be retried
let classifier = DefaultNixErrorClassifier;
// These should trigger retries
let retry_cases = [
"Package 'discord-1.0.0' has an unfree license ('unfree'), refusing to \
evaluate.",
"Package 'openssl-1.1.1' has been marked as insecure, refusing to \
evaluate.",
"Package 'broken-1.0' has been marked as broken, refusing to evaluate.",
"hash mismatch in fixed-output derivation\ngot: sha256-newhash",
];
for error in retry_cases {
assert!(classifier.should_retry(error), "Should retry: {}", error);
}
// These should NOT trigger retries
let no_retry_cases = [
"build failed",
"random error",
"permission denied",
"network error",
];
for error in no_retry_cases {
assert!(
!classifier.should_retry(error),
"Should not retry: {}",
error
);
}
}
#[test]
fn test_hash_fixing_in_nix_files() {
// Test that hash fixing actually works on real Nix files
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let fixer = DefaultNixFileFixer;
// Create a mock Nix file with various hash formats
let nix_content = r#"
stdenv.mkDerivation {
name = "test-package";
src = fetchurl {
url = "https://example.com.tar.gz";
hash = "sha256-oldhash123";
};
buildInputs = [ fetchurl {
url = "https://deps.com.tar.gz";
sha256 = "sha256-oldhash456";
}];
outputHash = "sha256-oldhash789";
}
"#;
let file_path = temp_dir.path().join("test.nix");
fs::write(&file_path, nix_content).expect("Failed to write test file");
// Test hash replacement
let new_hash = "sha256-newhashabc";
let was_fixed = fixer
.fix_hash_in_file(&file_path, new_hash)
.expect("Failed to fix hash");
assert!(was_fixed, "File should have been modified");
let updated_content =
fs::read_to_string(&file_path).expect("Failed to read updated file");
// All hash formats should be updated
assert!(updated_content.contains(&format!(r#"hash = "{}""#, new_hash)));
assert!(updated_content.contains(&format!(r#"sha256 = "{}""#, new_hash)));
assert!(updated_content.contains(&format!(r#"outputHash = "{}""#, new_hash)));
// Old hashes should be gone
assert!(!updated_content.contains("oldhash123"));
assert!(!updated_content.contains("oldhash456"));
assert!(!updated_content.contains("oldhash789"));
}
#[test]
fn test_multicall_binary_dispatch() {
// Test that multicall binaries work without needing actual Nix evaluation
let commands = [("nb", "build"), ("nr", "run"), ("ns", "shell")];
for (binary_name, _expected_command) in &commands {
// Test that the binary starts and handles invalid arguments gracefully
let output = Command::new("timeout")
.args(["5", "cargo", "run", "--bin", "eh", "--"])
.env("CARGO_BIN_NAME", binary_name)
.arg("invalid-package-ref")
.output()
.expect("Failed to execute command");
// Should fail gracefully (not panic or hang)
assert!(
output.status.code().is_some(),
"{} should exit with a code",
binary_name
);
// Should show an error message, not crash
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Error:")
|| stderr.contains("error:")
|| stderr.contains("failed"),
"{} should show error for invalid package",
binary_name
);
}
}
#[test]
fn test_invalid_expression_handling() {
// Test that invalid Nix expressions fail fast with proper error messages
let invalid_refs = [
"invalid-flake-ref",
"nonexistent-package",
"file:///nonexistent/path",
];
for invalid_ref in invalid_refs {
let output = Command::new("timeout")
.args([
"10",
"cargo",
"run",
"--bin",
"eh",
"--",
"build",
invalid_ref,
])
.output()
.expect("Failed to execute command");
// Should fail with a proper error, not hang or crash
assert!(
!output.status.success(),
"Invalid ref '{}' should fail",
invalid_ref
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Error:")
|| stderr.contains("error:")
|| stderr.contains("failed"),
"Should show error message for invalid ref '{}': {}",
invalid_ref,
stderr
);
}
}
#[test]
fn test_nix_file_discovery() {
// Test that the fixer can find Nix files in a directory structure
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let fixer = DefaultNixFileFixer;
// Create directory structure with Nix files
fs::create_dir_all(temp_dir.path().join("subdir"))
.expect("Failed to create subdir");
let files = [
("test.nix", "stdenv.mkDerivation { name = \"test\"; }"),
("subdir/other.nix", "pkgs.hello"),
("not-nix.txt", "not a nix file"),
("default.nix", "import ./test.nix"),
];
for (path, content) in files {
fs::write(temp_dir.path().join(path), content)
.expect("Failed to write file");
}
// Change to temp dir for file discovery
let original_dir =
std::env::current_dir().expect("Failed to get current dir");
std::env::set_current_dir(temp_dir.path())
.expect("Failed to change directory");
let found_files = fixer.find_nix_files().expect("Failed to find Nix files");
// Should find 3 .nix files (not the .txt file)
assert_eq!(found_files.len(), 3, "Should find exactly 3 .nix files");
// Restore original directory
std::env::set_current_dir(original_dir).expect("Failed to restore directory");
}