use std::fs;
use std::process::Command;
use rust_split::{Exploded, SplitOutput, explode, plan_split, split_bin, split_mod};
fn text_at<'a>(out: &'a SplitOutput, path: &str) -> &'a str {
&out.files.iter().find(|f| f.path == path).unwrap().contents
}
fn assert_plan_partition(exploded: &Exploded, ceiling: usize) {
let plan = plan_split(exploded, ceiling);
for _ in 0..20 {
assert_eq!(plan, plan_split(exploded, ceiling));
}
let mut indices = plan.header.clone();
indices.extend(&plan.root_items);
indices.extend(&plan.mods);
indices.extend(plan.oversized.iter().map(|o| o.chunk_index));
for part in &plan.parts {
assert!(part.loc < ceiling, "{part:?}");
indices.extend(&part.chunk_indices);
}
indices.sort_unstable();
assert_eq!(indices, (0..exploded.chunks.len()).collect::<Vec<_>>());
}
#[test]
fn banners_bound_cross_references_even_through_a_shared_helper() {
let src = "fn shared() {}\n\n\
// ---- Parsing ----\n\n\
fn parse() { shared(); emit(); }\n\n\
fn parse_more() { parse(); }\n\n\
// ---- Output ----\n\n\
fn emit() { shared(); parse(); }\n\n\
fn emit_more() { emit(); }\n\n\
// ---- Parsing ----\n\n\
fn parse_again() { parse(); }\n";
let exploded = explode(src).unwrap();
assert_eq!(
exploded
.chunks
.iter()
.map(|c| c.text.as_str())
.collect::<String>(),
src
);
let plan = plan_split(&exploded, 1000);
assert_eq!(plan.parts.len(), 4, "helper + three explicit sections");
let out = split_mod(&exploded, 1000, "mod");
assert!(text_at(&out, "parsing.rs").contains("fn parse_more"));
assert!(!text_at(&out, "parsing.rs").contains("fn emit"));
assert!(text_at(&out, "output.rs").contains("fn emit_more"));
assert!(text_at(&out, "parsing_2.rs").contains("fn parse_again"));
for ceiling in [1, 5, 10, 30, 1000] {
assert_plan_partition(&exploded, ceiling);
}
}
const SHARED: &str = "use std::collections::BTreeMap;\n\n\
/// Shared across phases.\n\
#[derive(Default)]\n\
pub struct State<T> {\n values: BTreeMap<u8, T>,\n}\n\n\
pub enum Mode { Ready }\n\n\
union Bits { value: u32 }\n\n\
// ---- Collection ----\n\n\
impl<T> self::State<T> {\n\
pub fn new(value: T) -> Self {\n\
Self { values: BTreeMap::from([(0, value)]) }\n\
}\n\
}\n\n\
// ---- Reading ----\n\n\
impl<T> State<T> {\n\
fn read(&self) -> &T {\n\
self.values.get(&0).unwrap()\n\
}\n\
}\n\n\
impl Mode { fn ready() -> Self { Self::Ready } }\n\n\
impl Bits { fn new() -> Self { Self { value: 1 } } }\n\n\
impl std::fmt::Display for State<u8> {\n\
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\
write!(f, \"{}\", self.read())\n\
}\n\
}\n\n\
pub fn run() -> String {\n\
let _ = Mode::ready();\n\
let _ = Bits::new();\n\
State::new(7u8).to_string()\n\
}\n";
fn compile_and_run(out: &SplitOutput, module: bool) {
let dir = tempfile::tempdir().unwrap();
for file in &out.files {
let path = dir.path().join(&file.path);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(path, &file.contents).unwrap();
}
let entry = if module {
"mod foo;\nfn main() { assert_eq!(foo::run(), \"7\"); }\n"
} else {
"include!(\"lib.rs\");\nfn main() { assert_eq!(run(), \"7\"); }\n"
};
fs::write(dir.path().join("main.rs"), entry).unwrap();
let exe = dir
.path()
.join(format!("check{}", std::env::consts::EXE_SUFFIX));
let compiled = Command::new("rustc")
.current_dir(dir.path())
.args(["--edition=2024", "main.rs", "-o"])
.arg(&exe)
.output()
.unwrap();
assert!(
compiled.status.success(),
"{}",
String::from_utf8_lossy(&compiled.stderr)
);
assert!(Command::new(exe).status().unwrap().success());
}
#[test]
fn shared_generic_types_keep_comments_attributes_imports_and_private_fields() {
let exploded = explode(SHARED).unwrap();
for ceiling in [1, 10, 30, 1000] {
assert_plan_partition(&exploded, ceiling);
}
for (module, stem) in [(true, "foo"), (false, "lib")] {
let out = if module {
split_mod(&exploded, 100, stem)
} else {
split_bin(&exploded, 100, stem)
};
assert!(out.still_oversized.is_empty(), "{:?}", out.still_oversized);
let root = text_at(&out, &format!("{stem}.rs"));
for expected in [
"/// Shared across phases.\n#[derive(Default)]\npub struct State<T> {\n values:",
"pub enum Mode { Ready }",
"union Bits { value: u32 }",
"use std::collections::BTreeMap;",
] {
assert!(root.contains(expected), "missing {expected}:\n{root}");
}
for file in out.files.iter().skip(1) {
assert!(!file.contents.contains("struct State"));
assert!(!file.contents.contains("enum Mode"));
assert!(!file.contents.contains("union Bits"));
}
compile_and_run(&out, module);
}
}
#[test]
fn oversized_impls_still_anchor_their_type_and_preserve_unmet_budgets() {
let src = "struct State { value: usize }\n\nimpl State {\n\
fn value(&self) -> usize {\n self.value\n }\n}\n";
let exploded = explode(src).unwrap();
let plan = plan_split(&exploded, 4);
assert_eq!(plan.root_items.len(), 1);
assert_eq!(plan.oversized.len(), 1);
let out = split_mod(&exploded, 7, "foo");
assert!(!out.still_oversized.is_empty());
for chunk in &exploded.chunks {
assert!(text_at(&out, "foo.rs").contains(chunk.text.trim()));
}
}
#[test]
fn trait_only_and_foreign_path_impls_do_not_claim_a_local_type() {
let src = "struct Local;\n\n\
impl std::fmt::Display for Local {\n\
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\
write!(f, \"local\")\n\
}\n\
}\n\n\
mod other { pub struct Local; }\n\n\
impl other::Local { fn new() -> Self { Self } }\n";
let exploded = explode(src).unwrap();
let plan = plan_split(&exploded, 1000);
assert!(plan.root_items.is_empty());
assert_plan_partition(&exploded, 1000);
}
#[test]
fn recursively_split_modules_keep_types_in_their_own_parent() {
let padding = " let _ = 0;\n".repeat(30);
let inner = SHARED.replace("pub fn run()", "pub fn execute()");
let src = format!(
"mod inner {{\n{inner}\nfn padding() {{\n{padding}}}\n}}\n\npub fn run() -> String {{ inner::execute() }}\n"
);
let out = split_mod(&explode(&src).unwrap(), 60, "foo");
assert!(out.still_oversized.is_empty(), "{:?}", out.still_oversized);
let owner = text_at(&out, "foo/inner/mod.rs");
assert!(owner.contains("pub struct State<T> {\n values:"));
assert!(owner.contains("union Bits { value: u32 }"));
compile_and_run(&out, true);
}
#[test]
fn parent_type_names_are_reserved_for_generated_modules() {
let src = "#![allow(non_camel_case_types)]\n\n\
struct worker;\n\nstruct worker_;\n\n\
// ---- Worker ----\n\n\
impl worker { fn value() -> u8 { 7 } }\n\n\
impl worker_ { fn value() -> u8 { worker::value() } }\n\n\
pub fn run() -> String { worker_::value().to_string() }\n";
let out = split_mod(&explode(src).unwrap(), 100, "foo");
assert!(text_at(&out, "foo.rs").contains("mod worker__;"));
compile_and_run(&out, true);
}
#[test]
fn many_shared_types_report_an_oversized_parent_without_moving_or_losing_them() {
let src: String = (0..20)
.map(|i| {
format!(
"/// Shared owner {i}.\nstruct State{i} {{ value: u8 }}\n\nimpl State{i} {{}}\n\n"
)
})
.collect();
let exploded = explode(&src).unwrap();
let out = split_mod(&exploded, 30, "mod");
assert!(out.still_oversized.iter().any(|s| s.starts_with("mod.rs ")));
for row in exploded.manifest.rows.iter().filter(|r| r.kind == "struct") {
assert!(text_at(&out, "mod.rs").contains(&exploded.chunks[row.chunk_index].text));
}
assert_plan_partition(&exploded, 30);
}