#![cfg(unix)]
use super::{
STALE_WASM_CARGO_CONFIG, migrate_dart_placeholder_test, migrate_kotlin_build_gradle,
migrate_poly_toml_drop_snippet_hook, migrate_swift_placeholder_test,
migrate_wasm_cargo_config_allow_multiple_definition,
};
use std::path::Path;
const SWIFT_RELATIVE: &str = "packages/swift/Tests/EvilTests/EvilTests.swift";
const SWIFT_PLACEHOLDER: &str = "import XCTest\n\nfinal class EvilTests: XCTestCase {\n func testPlaceholder() {\n XCTAssertTrue(true)\n }\n}\n";
const SWIFT_REPLACEMENT: &str = "import XCTest\n\nfinal class EvilTests: XCTestCase {\n func testRoundTrip() {\n XCTAssertEqual(greet(), \"hi\")\n }\n}\n";
const DART_RELATIVE: &str = "packages/dart/test/evil_test.dart";
const DART_PLACEHOLDER: &str = "void main() {\n test('placeholder', () {\n expect(1 + 1, equals(2));\n });\n}\n";
const DART_REPLACEMENT: &str = "void main() {\n it('greets', () {\n check(greet(), equals('hi'));\n });\n}\n";
const KOTLIN_RELATIVE: &str = "packages/kotlin/build.gradle.kts";
const STALE_BUILD_GRADLE: &str =
"mavenPublishing {\n configure(\n KotlinJvm(\n sourcesJar = true,\n )\n )\n}\n";
const POLY_RELATIVE: &str = "poly.toml";
const STALE_POLY_TOML: &str =
"[hooks.pre-commit.commands.alef-snippets]\nrun = \"alef snippets check --strict --cache off\"\nworkspace = true\n";
const CARGO_RELATIVE: &str = ".cargo/config.toml";
fn symlink(target: &Path, link: &Path) {
std::os::unix::fs::symlink(target, link).expect("symlink");
}
fn seed(path: &Path, content: &str) {
std::fs::create_dir_all(path.parent().expect("seeded path has a parent")).expect("create parent directory");
std::fs::write(path, content).expect("seed file");
}
fn assert_untouched(outside: &Path, name: &str, content: &str) {
let entries: Vec<_> = std::fs::read_dir(outside)
.expect("read outside directory")
.map(|entry| entry.expect("directory entry").file_name())
.collect();
assert_eq!(
entries.len(),
1,
"migration left residue outside the project: {entries:?}"
);
assert_eq!(
std::fs::read_to_string(outside.join(name)).expect("outside file"),
content,
"migration rewrote a file outside the project"
);
}
#[test]
fn swift_placeholder_migration_refuses_a_symlinked_ancestor_that_leaves_the_project() {
let temporary = tempfile::tempdir().expect("temporary directory");
let base = temporary.path().join("base");
let outside = temporary.path().join("outside");
std::fs::create_dir_all(base.join("packages/swift/Tests")).expect("Tests directory");
seed(&outside.join("EvilTests.swift"), SWIFT_PLACEHOLDER);
symlink(&outside, &base.join("packages/swift/Tests/EvilTests"));
let error = migrate_swift_placeholder_test(&base, Path::new(SWIFT_RELATIVE), SWIFT_REPLACEMENT)
.expect_err("a symlinked module directory must be refused");
assert!(error.to_string().contains("not contained"), "{error}");
assert_untouched(&outside, "EvilTests.swift", SWIFT_PLACEHOLDER);
}
#[test]
fn dart_placeholder_migration_refuses_a_symlinked_ancestor_that_leaves_the_project() {
let temporary = tempfile::tempdir().expect("temporary directory");
let base = temporary.path().join("base");
let outside = temporary.path().join("outside");
std::fs::create_dir_all(base.join("packages/dart")).expect("dart directory");
seed(&outside.join("evil_test.dart"), DART_PLACEHOLDER);
symlink(&outside, &base.join("packages/dart/test"));
let error = migrate_dart_placeholder_test(&base, Path::new(DART_RELATIVE), DART_REPLACEMENT)
.expect_err("a symlinked test directory must be refused");
assert!(error.to_string().contains("not contained"), "{error}");
assert_untouched(&outside, "evil_test.dart", DART_PLACEHOLDER);
}
#[test]
fn cargo_config_migration_refuses_a_symlinked_dot_cargo_directory() {
let temporary = tempfile::tempdir().expect("temporary directory");
let base = temporary.path().join("base");
let outside = temporary.path().join("outside");
std::fs::create_dir_all(&base).expect("base directory");
seed(&outside.join("config.toml"), STALE_WASM_CARGO_CONFIG);
symlink(&outside, &base.join(".cargo"));
let error = migrate_wasm_cargo_config_allow_multiple_definition(&base)
.expect_err("a symlinked .cargo directory must be refused");
assert!(error.to_string().contains("not contained"), "{error}");
assert_untouched(&outside, "config.toml", STALE_WASM_CARGO_CONFIG);
}
#[test]
fn poly_toml_migration_refuses_a_symlinked_leaf_pointing_outside_the_project() {
let temporary = tempfile::tempdir().expect("temporary directory");
let base = temporary.path().join("base");
let outside = temporary.path().join("outside");
std::fs::create_dir_all(&base).expect("base directory");
seed(&outside.join(POLY_RELATIVE), STALE_POLY_TOML);
symlink(&outside.join(POLY_RELATIVE), &base.join(POLY_RELATIVE));
let error = migrate_poly_toml_drop_snippet_hook(&base).expect_err("a poly.toml symlinked outside must be refused");
assert!(error.to_string().contains("not contained"), "{error}");
assert_untouched(&outside, POLY_RELATIVE, STALE_POLY_TOML);
}
#[test]
fn kotlin_build_gradle_migration_refuses_a_symlinked_ancestor_that_leaves_the_project() {
let temporary = tempfile::tempdir().expect("temporary directory");
let base = temporary.path().join("base");
let outside = temporary.path().join("outside");
std::fs::create_dir_all(base.join("packages")).expect("packages directory");
seed(&outside.join("build.gradle.kts"), STALE_BUILD_GRADLE);
symlink(&outside, &base.join("packages/kotlin"));
let error = migrate_kotlin_build_gradle(&base).expect_err("a symlinked packages/kotlin directory must be refused");
assert!(error.to_string().contains("not contained"), "{error}");
assert_untouched(&outside, "build.gradle.kts", STALE_BUILD_GRADLE);
}
#[test]
fn swift_placeholder_migration_still_repairs_an_ordinary_directory() {
let temporary = tempfile::tempdir().expect("temporary directory");
let base = temporary.path().join("base");
seed(&base.join(SWIFT_RELATIVE), SWIFT_PLACEHOLDER);
let changed = migrate_swift_placeholder_test(&base, Path::new(SWIFT_RELATIVE), SWIFT_REPLACEMENT)
.expect("an ordinary directory must still be repaired");
assert!(changed, "the vacuous placeholder must be reported as repaired");
assert_eq!(
std::fs::read_to_string(base.join(SWIFT_RELATIVE)).expect("repaired file"),
SWIFT_REPLACEMENT
);
}
#[test]
fn dart_placeholder_migration_still_repairs_an_ordinary_directory() {
let temporary = tempfile::tempdir().expect("temporary directory");
let base = temporary.path().join("base");
seed(&base.join(DART_RELATIVE), DART_PLACEHOLDER);
let changed = migrate_dart_placeholder_test(&base, Path::new(DART_RELATIVE), DART_REPLACEMENT)
.expect("an ordinary directory must still be repaired");
assert!(changed, "the vacuous placeholder must be reported as repaired");
assert_eq!(
std::fs::read_to_string(base.join(DART_RELATIVE)).expect("repaired file"),
DART_REPLACEMENT
);
}
#[test]
fn cargo_config_migration_still_repairs_an_ordinary_directory() {
let temporary = tempfile::tempdir().expect("temporary directory");
let base = temporary.path().join("base");
seed(&base.join(CARGO_RELATIVE), STALE_WASM_CARGO_CONFIG);
let changed = migrate_wasm_cargo_config_allow_multiple_definition(&base)
.expect("an ordinary .cargo directory must still be repaired");
assert!(changed, "the stale wasm32 rustflags must be reported as repaired");
let repaired = std::fs::read_to_string(base.join(CARGO_RELATIVE)).expect("repaired file");
assert!(
repaired.contains("--allow-multiple-definition"),
"the repair did not reach the file: {repaired}"
);
}
#[test]
fn poly_toml_migration_still_repairs_an_ordinary_repo_root() {
let temporary = tempfile::tempdir().expect("temporary directory");
let base = temporary.path().join("base");
seed(&base.join(POLY_RELATIVE), STALE_POLY_TOML);
let changed = migrate_poly_toml_drop_snippet_hook(&base).expect("an ordinary repo root must still be repaired");
assert!(changed, "the retracted hook must be reported as removed");
let repaired = std::fs::read_to_string(base.join(POLY_RELATIVE)).expect("repaired file");
assert!(!repaired.contains("alef-snippets"), "the hook survived: {repaired}");
}
#[test]
fn kotlin_build_gradle_migration_still_repairs_an_ordinary_directory() {
let temporary = tempfile::tempdir().expect("temporary directory");
let base = temporary.path().join("base");
seed(&base.join(KOTLIN_RELATIVE), STALE_BUILD_GRADLE);
let changed = migrate_kotlin_build_gradle(&base).expect("an ordinary directory must still be repaired");
assert!(changed, "the missing trailing comma must be reported as repaired");
let repaired = std::fs::read_to_string(base.join(KOTLIN_RELATIVE)).expect("repaired file");
assert!(
repaired.contains(" ),\n )\n"),
"the trailing comma was not added: {repaired}"
);
}
#[test]
fn symlinked_ancestor_that_stays_inside_the_project_is_still_repaired() {
let temporary = tempfile::tempdir().expect("temporary directory");
let base = temporary.path().join("base");
let real = base.join("real-tests");
std::fs::create_dir_all(&real).expect("real tests directory");
std::fs::create_dir_all(base.join("packages/swift/Tests")).expect("Tests directory");
symlink(&real, &base.join("packages/swift/Tests/EvilTests"));
std::fs::write(real.join("EvilTests.swift"), SWIFT_PLACEHOLDER).expect("seed file");
let changed = migrate_swift_placeholder_test(&base, Path::new(SWIFT_RELATIVE), SWIFT_REPLACEMENT)
.expect("a symlink that stays inside the project is not an escape");
assert!(changed, "the vacuous placeholder must be reported as repaired");
assert_eq!(
std::fs::read_to_string(real.join("EvilTests.swift")).expect("repaired file"),
SWIFT_REPLACEMENT
);
}
#[test]
fn base_directory_reached_through_a_symlink_is_still_repaired() {
let temporary = tempfile::tempdir().expect("temporary directory");
let real_root = temporary.path().join("real-root");
seed(&real_root.join("base").join(SWIFT_RELATIVE), SWIFT_PLACEHOLDER);
let linked_root = temporary.path().join("linked-root");
symlink(&real_root, &linked_root);
let base = linked_root.join("base");
let changed = migrate_swift_placeholder_test(&base, Path::new(SWIFT_RELATIVE), SWIFT_REPLACEMENT)
.expect("a symlinked base_dir must not be treated as an escape");
assert!(changed, "the vacuous placeholder must be reported as repaired");
assert_eq!(
std::fs::read_to_string(real_root.join("base").join(SWIFT_RELATIVE)).expect("repaired file"),
SWIFT_REPLACEMENT
);
}