use std::{collections::BTreeSet, fs, path::Path, process::Command};
use rust_split::{SplitOutput, explode, split_bin, split_mod};
fn compile(out: &SplitOutput, module: bool, check_private: bool) {
let temp = tempfile::tempdir().unwrap();
let dir = temp.path();
let mut paths = BTreeSet::new();
for file in &out.files {
assert!(paths.insert(&file.path), "duplicate path: {}", file.path);
let path = dir.join(&file.path);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(path, &file.contents).unwrap();
}
if module {
fs::write(dir.join("lib.rs"), "pub mod foo;\n").unwrap();
}
rustc(
dir,
&[
"--crate-type=lib",
"--crate-name=checked",
"lib.rs",
"-o",
"libchecked.rlib",
],
);
let prefix = if module { "checked::foo" } else { "checked" };
fs::write(
dir.join("main.rs"),
format!("fn main() {{ assert_eq!({prefix}::run(), \"7\"); }}"),
)
.unwrap();
let exe = format!("harness{}", std::env::consts::EXE_SUFFIX);
rustc(
dir,
&["main.rs", "--extern", "checked=libchecked.rlib", "-o", &exe],
);
assert!(Command::new(dir.join(exe)).status().unwrap().success());
if check_private {
fs::write(
dir.join("outside.rs"),
format!("fn main() {{ let _ = {prefix}::State::new(); }}"),
)
.unwrap();
let denied = Command::new("rustc")
.current_dir(dir)
.args([
"--edition=2024",
"outside.rs",
"--extern",
"checked=libchecked.rlib",
])
.output()
.unwrap();
assert!(!denied.status.success());
assert!(String::from_utf8_lossy(&denied.stderr).contains("E0624"));
}
}
fn rustc(dir: &Path, args: &[&str]) {
let result = Command::new("rustc")
.current_dir(dir)
.arg("--edition=2024")
.args(args)
.output()
.unwrap();
assert!(
result.status.success(),
"{}",
String::from_utf8_lossy(&result.stderr)
);
}
fn both(src: &str, ceiling: usize, mut check: impl FnMut(&SplitOutput, bool)) {
let exploded = explode(src).unwrap();
assert_eq!(
exploded
.chunks
.iter()
.map(|c| c.text.as_str())
.collect::<String>(),
src
);
for module in [false, true] {
let out = if module {
split_mod(&exploded, ceiling, "foo")
} else {
split_bin(&exploded, ceiling, "lib")
};
assert!(out.still_oversized.is_empty(), "{:?}", out.still_oversized);
for _ in 0..5 {
let again = if module {
split_mod(&exploded, ceiling, "foo")
} else {
split_bin(&exploded, ceiling, "lib")
};
assert_eq!(out.files, again.files);
}
check(&out, module);
}
}
#[test]
fn retained_owners_keep_bound_where_clause_and_default_imports() {
for (decl, constructor, read) in [
(
"pub struct State<T: Copy + Debug = Byte> { value: T }",
"Self { value }",
"state.value",
),
(
"pub struct State<T = Byte> where T: Copy + Debug { value: T }",
"Self { value }",
"state.value",
),
(
"pub enum State<T: Copy + Debug = Byte> { Value(T) }",
"Self::Value(value)",
"match state { State::Value(value) => value }",
),
(
"pub union State<T: Copy + Debug = Byte> { value: T }",
"Self { value }",
"unsafe { state.value }",
),
] {
let src = format!(
"use std::fmt::Debug;\nuse std::primitive::u8 as Byte;\n{decl}\n\
impl<T: Copy + Debug> State<T> {{\n\
pub fn new(value: T) -> Self {{ let _: &dyn Debug = &value; {constructor} }}\n\
}}\npub fn run() -> String {{ let state = State::new(7u8); format!(\"{{}}\", {read}) }}\n"
);
both(&src, 100, |out, module| {
let root = &out.files[0].contents;
assert!(root.contains(decl));
assert!(root.contains("use std::fmt::Debug;"));
assert!(root.contains("use std::primitive::u8 as Byte;"));
compile(out, module, false);
});
}
}
#[test]
fn owner_local_methods_preserve_scope_and_visibility_comments() {
for restriction in ["pub(self)", "pub(in self)", "pub(/* owner scope */ self)"] {
let src = format!(
"pub struct State;\nimpl State {{ {restriction} fn new() -> Self {{ Self }} }}\n\
pub fn run() -> String {{ let _ = State::new(); 7.to_string() }}\n"
);
both(&src, 100, |out, module| {
assert!(out.files[0].contents.contains("pub struct State;"));
assert!(out.files[0].contents.contains(restriction));
if restriction.contains("/*") {
assert!(
out.files
.iter()
.any(|f| f.contents.contains("/* owner scope */"))
);
}
compile(out, module, true);
});
}
}
const MACRO_OWNER: &str = "macro_rules! field { () => { u8 }; }\n\
struct State { value: field!() }\n\
impl State { fn new() -> Self { Self { value: 7 } } }\n\
pub fn run() -> String { State::new().value.to_string() }\n";
#[test]
fn retained_owner_keeps_its_local_type_macro_in_both_topologies() {
both(MACRO_OWNER, 100, |out, module| {
let root = &out.files[0].contents;
assert!(root.contains("struct State { value: field!() }"));
assert!(root.find("macro_rules! field").unwrap() < root.find("struct State").unwrap());
compile(out, module, false);
});
}
#[test]
fn retained_owner_local_impl_keeps_its_lexical_dependencies() {
let src = "macro_rules! value { () => { 7 }; }\n\
pub struct State;\n\
impl State { pub(self) fn new() -> Self { assert_eq!(value!(), 7); Self } }\n\
pub fn run() -> String { let _ = State::new(); 7.to_string() }\n";
both(src, 100, |out, module| compile(out, module, true));
}
#[test]
fn local_macro_dependencies_and_redefinitions_keep_original_textual_consumers() {
let src = "macro_rules! scalar { () => { u8 }; }\n\
macro_rules! field { () => { scalar!() }; }\n\
fn before() -> usize { std::mem::size_of::<field!()>() }\n\
struct State { value: field!() }\n\
impl State { fn new() -> Self { Self { value: 7 } } }\n\
macro_rules! scalar { () => { u64 }; }\n\
fn after() -> usize { std::mem::size_of::<field!()>() }\n\
pub fn run() -> String {\n\
assert_eq!(before(), 1);\n\
assert_eq!(after(), 8);\n\
State::new().value.to_string()\n\
}\n";
both(src, 100, |out, module| compile(out, module, false));
}
#[test]
fn recursive_owner_keeps_its_lexical_macro() {
let inner = MACRO_OWNER.replace("pub fn run()", "pub fn execute()");
let padding = "let _ = 0;\n".repeat(20);
let src = format!(
"mod inner {{\n{inner}\nfn pad_a() {{\n{padding}}}\nfn pad_b() {{\n{padding}}}\n}}\n\
pub fn run() -> String {{ inner::execute() }}\n"
);
both(&src, 40, |out, module| {
let root = out
.files
.iter()
.find(|f| f.path.ends_with("inner/mod.rs"))
.unwrap();
assert!(root.contents.contains("struct State { value: field!() }"));
assert!(root.contents.contains("macro_rules! field"));
compile(out, module, false);
});
}
#[test]
fn recursive_groups_avoid_retained_types_and_fixed_module_names() {
let padding = "let _ = 0;\n".repeat(20);
let src = format!(
"mod inner {{\n#![allow(non_camel_case_types)]\n\
struct g00;\nimpl g00 {{}}\n\
struct g00_;\nimpl g00_ {{}}\n\
mod g01 {{}}\n\
fn pad_a() {{\n{padding}}}\nfn pad_b() {{\n{padding}}}\n\
pub fn execute() -> String {{ 7.to_string() }}\n}}\n\
pub fn run() -> String {{ inner::execute() }}\n"
);
both(&src, 40, |out, module| {
let root = out
.files
.iter()
.find(|f| f.path.ends_with("inner/mod.rs"))
.unwrap();
assert!(root.contents.contains("struct g00;"));
assert!(root.contents.contains("struct g00_;"));
assert!(root.contents.contains("mod g00__;"));
compile(out, module, false);
});
}
#[test]
fn oversized_lexical_dependencies_stay_in_order_and_report_parent_overflow() {
let source = MACRO_OWNER.replace(
"{ u8 }",
&format!("{{\n{}u8\n}}", "// macro body\n".repeat(35)),
);
let exploded = explode(&source).unwrap();
for module in [false, true] {
let out = if module {
split_mod(&exploded, 30, "foo")
} else {
split_bin(&exploded, 30, "lib")
};
assert!(!out.still_oversized.is_empty());
assert!(out.files[0].loc >= 30);
let root = &out.files[0].contents;
assert!(root.find("macro_rules! field").unwrap() < root.find("struct State").unwrap());
let mut indices = rust_split::plan_split(&exploded, 25).root_items;
let plan = rust_split::plan_split(&exploded, 25);
indices.extend(plan.header);
indices.extend(plan.mods);
indices.extend(plan.oversized.iter().map(|o| o.chunk_index));
indices.extend(
plan.parts
.iter()
.flat_map(|p| p.chunk_indices.iter().copied()),
);
indices.sort_unstable();
assert_eq!(indices, (0..exploded.chunks.len()).collect::<Vec<_>>());
compile(&out, module, false);
}
}
#[test]
fn retained_aliases_reserve_generated_names_with_chained_collisions() {
for banner_on_impl in [false, true] {
let banner = "\n// ---- Worker ----\n\n";
let src = format!(
"#![allow(non_camel_case_types)]\n\
macro_rules! field {{ () => {{ u8 }}; }}\n\
type worker = field!();\ntype worker_ = field!();\n\
struct State {{ value: field!() }}\n{}\
impl State {{ fn new() -> Self {{ Self {{ value: 7 }} }} }}\n{}\
pub fn run() -> String {{ let x: worker = State::new().value; let y: worker_ = x; y.to_string() }}\n",
if banner_on_impl { banner } else { "" },
if banner_on_impl { "" } else { banner }
);
both(&src, 100, |out, module| {
let root = &out.files[0].contents;
assert!(root.contains("type worker = field!();"));
assert!(root.contains("type worker_ = field!();"));
assert!(root.contains("mod worker__;"));
compile(out, module, false);
});
}
}
#[test]
fn retained_traits_reserve_generated_module_names() {
let src = "#![allow(non_camel_case_types)]\n\
macro_rules! field { () => { u8 }; }\n\
struct State { value: field!() }\n\
trait worker { fn get(&self) -> field!(); }\n\
impl worker for State { fn get(&self) -> u8 { self.value } }\n\n\
// ---- Worker ----\n\n\
impl State { fn new() -> Self { Self { value: 7 } } }\n\
pub fn run() -> String { State::new().get().to_string() }\n";
both(src, 100, |out, module| {
assert!(out.files[0].contents.contains("trait worker"));
assert!(out.files[0].contents.contains("mod worker_;"));
compile(out, module, false);
});
}