use std::fs;
use std::path::Path;
use std::process::Command;
use rust_split::{SplitOutput, explode, split_bin};
const MAX_LOC: usize = 500;
fn fixture_dir() -> std::path::PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/split")
}
fn fixture_src() -> String {
fs::read_to_string(fixture_dir().join("toolchain_lib.rs")).unwrap()
}
fn split_fixture() -> SplitOutput {
let src = fixture_src();
let exploded = explode(&src).unwrap();
split_bin(&exploded, MAX_LOC, "lib")
}
fn part_paths(out: &SplitOutput) -> Vec<&str> {
out.files
.iter()
.map(|f| f.path.as_str())
.filter(|p| !matches!(*p, "lib.rs" | "tests.rs"))
.collect()
}
#[test]
fn fixture_explodes_losslessly() {
let src = fixture_src();
let exploded = explode(&src).unwrap();
let joined: String = exploded.chunks.iter().map(|c| c.text.as_str()).collect();
assert_eq!(joined, src, "explode must stay lossless");
assert_eq!(
explode(&src).unwrap(),
exploded,
"explode must stay deterministic"
);
}
#[test]
fn public_reexports_and_public_items_stay_pub() {
let out = split_fixture();
let root = out.files.iter().find(|f| f.path == "lib.rs").unwrap();
assert!(
root.contents.contains("mod helpers;"),
"spine must keep the source `mod helpers;`:\n{}",
root.contents
);
assert!(
root.contents.contains("pub use helpers::HelperConfig;"),
"spine must keep the source `pub use` verbatim:\n{}",
root.contents
);
assert!(
root.contents.contains("pub struct Platform"),
"the shared public type must stay in its owning module:\n{}",
root.contents
);
for path in part_paths(&out) {
let part = out.files.iter().find(|f| f.path == path).unwrap();
let parsed = syn::parse_file(&part.contents).unwrap();
let has_public = parsed.items.iter().any(|item| {
let vis = match item {
syn::Item::Fn(i) => &i.vis,
syn::Item::Struct(i) => &i.vis,
syn::Item::Enum(i) => &i.vis,
_ => return false,
};
matches!(vis, syn::Visibility::Public(_))
});
if has_public {
let module = path.trim_end_matches(".rs");
assert!(
root.contents.contains(&format!("pub use {module}::*;")),
"public items in {path} must be re-exported publicly:\n{}",
root.contents
);
}
}
}
#[test]
fn cfg_test_gate_keeps_its_braced_tests_module() {
let out = split_fixture();
let root = out.files.iter().find(|f| f.path == "lib.rs").unwrap();
assert!(
root.contents.contains("#[cfg(test)]\nmod tests {"),
"the tests module must keep its braced gate:\n{}",
root.contents
);
assert!(root.contents.contains("use super::*;"));
assert!(!out.files.iter().any(|f| f.path == "tests.rs"));
assert!(
!root.contents.contains("use tests::*"),
"an inline module is never glob re-exported"
);
}
#[test]
fn packing_yields_multiple_balanced_files_not_one_near_the_ceiling() {
let out = split_fixture();
assert!(out.still_oversized.is_empty(), "{:?}", out.still_oversized);
for f in &out.files {
assert!(f.loc < MAX_LOC, "{} is over the ceiling: {}", f.path, f.loc);
}
let parts = part_paths(&out);
assert!(
parts.len() >= 3,
"an interconnected ~500-LOC domain must spread over >= 3 cohesive \
parts, got {parts:?}"
);
for f in &out.files {
if parts.contains(&f.path.as_str()) {
assert!(
f.loc <= 260,
"{} grazes the ceiling ({} LOC) — the ceiling is not a packing \
target; parts should sit near half of it",
f.path,
f.loc
);
}
}
}
#[test]
fn split_output_compiles_and_the_public_api_serves_a_dependent() {
let out = split_fixture();
let temp = tempfile::tempdir().unwrap();
let dir = temp.path();
for f in &out.files {
let path = dir.join(&f.path);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(path, &f.contents).unwrap();
}
fs::copy(fixture_dir().join("helpers.rs"), dir.join("helpers.rs")).unwrap();
let rlib = dir.join("libtoolchain_fixture.rlib");
run_rustc(
dir,
&[
"--edition",
"2024",
"--crate-type",
"lib",
"--crate-name",
"toolchain_fixture",
"lib.rs",
"-o",
rlib.to_str().unwrap(),
],
);
let harness = dir.join("fixture_tests");
run_rustc(
dir,
&[
"--edition",
"2024",
"--test",
"--crate-name",
"toolchain_fixture_tests",
"lib.rs",
"-o",
harness.to_str().unwrap(),
],
);
let tests = Command::new(&harness).output().unwrap();
assert!(
tests.status.success(),
"moved tests must still pass:\n{}",
String::from_utf8_lossy(&tests.stdout)
);
let consumer_src = r#"use toolchain_fixture::{
Constraint, HelperConfig, Platform, RegisteredToolchain, Registry,
ToolchainRequirement, ToolchainType, resolve,
};
fn main() {
let mut registry = Registry::new();
registry.register_toolchain(RegisteredToolchain {
toolchain_type: ToolchainType::new("//cc:toolchain_type"),
label: "//cc:gcc".to_owned(),
target_compatible_with: vec![Constraint::new("os:linux")],
exec_compatible_with: vec![],
});
registry.register_execution_platform(Platform::with(&["os:linux"]));
let resolution = resolve(
®istry,
&[ToolchainRequirement::mandatory("//cc:toolchain_type")],
&Platform::with(&["os:linux"]),
&HelperConfig::permissive(),
)
.expect("public API resolves");
assert_eq!(resolution.selected.len(), 1);
}
"#;
fs::write(dir.join("consumer.rs"), consumer_src).unwrap();
let consumer = dir.join("consumer");
run_rustc(
dir,
&[
"--edition",
"2024",
"consumer.rs",
"--extern",
&format!("toolchain_fixture={}", rlib.to_str().unwrap()),
"-o",
consumer.to_str().unwrap(),
],
);
let status = Command::new(&consumer).status().unwrap();
assert!(status.success(), "consumer must run against the split lib");
}
#[test]
fn split_module_output_compiles_with_rebased_paths() {
let temp = tempfile::tempdir().unwrap();
let dir = temp.path();
fs::create_dir_all(dir.join("docs")).unwrap();
fs::write(dir.join("docs/note.md"), "note\n").unwrap();
fs::write(
dir.join("lib.rs"),
"pub mod sibling;\npub mod top;\npub mod deep;\n",
)
.unwrap();
fs::write(
dir.join("sibling.rs"),
"pub struct Thing;\n\npub fn alpha() -> i32 {\n 1\n}\n",
)
.unwrap();
fs::write(dir.join("top.rs"), "pub struct Absolute;\n").unwrap();
let god_file = r#"use super::sibling::Thing;
use super::sibling::alpha;
use self::local::X;
use crate::top::Absolute;
pub mod local {
pub struct X;
}
pub fn helper() -> i32 {
alpha()
}
pub fn uses_thing(t: Thing, x: X) -> i32 {
let _ = (t, x);
helper()
}
pub fn note() -> &'static str {
include_str!("docs/note.md")
}
pub fn marker() -> i32 {
let _: Option<Absolute> = None;
2
}
#[cfg(test)]
mod tests {
use super::helper;
#[test]
fn smoke() {
assert_eq!(helper(), 1);
}
}
"#;
let exploded = explode(god_file).unwrap();
let out = rust_split::split_mod(&exploded, MAX_LOC, "deep");
for f in &out.files {
let path = dir.join(&f.path);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(path, &f.contents).unwrap();
}
assert!(
out.files.iter().any(|f| f.path.starts_with("deep/")),
"the file module's parts go in deep/: {:?}",
out.files.iter().map(|f| &f.path).collect::<Vec<_>>()
);
run_rustc(
dir,
&[
"--edition",
"2024",
"--crate-type",
"lib",
"--crate-name",
"nested_fixture",
"lib.rs",
"-o",
dir.join("libnested_fixture.rlib").to_str().unwrap(),
],
);
let harness = dir.join("nested_tests");
run_rustc(
dir,
&[
"--edition",
"2024",
"--test",
"--crate-name",
"nested_fixture_tests",
"lib.rs",
"-o",
harness.to_str().unwrap(),
],
);
let tests = Command::new(&harness).output().unwrap();
assert!(
tests.status.success(),
"the extracted tests module must still resolve `use super::helper`:\n{}",
String::from_utf8_lossy(&tests.stdout)
);
}
fn run_rustc(dir: &Path, args: &[&str]) {
let output = Command::new("rustc")
.current_dir(dir)
.args(args)
.output()
.expect("rustc must be runnable (it ships with cargo)");
assert!(
output.status.success(),
"rustc {:?} failed:\n{}",
args,
String::from_utf8_lossy(&output.stderr)
);
}