use super::*;
#[test]
fn relocated_modules_preserve_logical_names_and_nested_source_directories()
-> Result<(), Box<dyn Error>> {
let root = temporary_root("relocated-modules")?;
fs::create_dir_all(root.join("src/physical"))?;
fs::write(
root.join("src/lib.rs"),
r#"#[allow(dead_code)] #[path = "physical/owner.rs"] mod logical;"#,
)?;
fs::write(root.join("src/logical.rs"), "invalid Rust source")?;
fs::write(
root.join("src/physical/owner.rs"),
r#"
mod inputs;
#[path = "answer.rs"] mod outputs;
pub struct Owner;
trait StateMachine {}
impl StateMachine for Owner {}
"#,
)?;
fs::write(
root.join("src/physical/inputs.rs"),
r#"
use super::Owner;
use super::outputs::Outcome;
trait StepInputOf<T> { type Outcome; }
pub struct Change;
impl StepInputOf<Owner> for Change { type Outcome = Outcome; }
"#,
)?;
fs::write(
root.join("src/physical/answer.rs"),
"pub enum Outcome { Applied, Refused }",
)?;
let architecture = read_crate(&root)?;
let machine = architecture.state_machine_qualified("crate::logical::Owner")?;
expect_test::expect![[r#"
● state machine ▸ step input ◆ outcome
● Owner
└── ▸ Change
└── ◆ Outcome
├── Applied
└── Refused
"#]]
.assert_eq(&machine.render(crate::DiagramTarget::TextTree));
fs::remove_dir_all(root)?;
Ok(())
}
#[test]
fn path_attributes_follow_source_files_and_inline_module_directories() -> Result<(), Box<dyn Error>>
{
let root = temporary_root("inline-module-paths")?;
for directory in ["outer/nested", "redirected", "inline", "folder"] {
fs::create_dir_all(root.join("src").join(directory))?;
}
for (path, source) in [
(
"lib.rs",
r#"
mod outer;
mod inline { #[path = "actual.rs"] mod child; }
#[path = "folder/mod.rs"] mod grouped;
"#,
),
(
"outer.rs",
r#"
#[path = "sibling.rs"] mod sibling;
mod nested { #[path = "actual.rs"] mod child; }
#[path = "redirected"] mod moved { mod child; }
"#,
),
("sibling.rs", "pub struct Sibling;"),
("outer/nested/actual.rs", "pub struct Nested;"),
("redirected/child.rs", "pub struct Redirected;"),
("inline/actual.rs", "pub struct Inline;"),
("folder/mod.rs", "mod child;"),
("folder/child.rs", "pub struct Grouped;"),
] {
fs::write(root.join("src").join(path), source)?;
}
let mut model = SourceModel::new();
model.read_module_file(&root.join("src/lib.rs"), Vec::new(), &root.join("src"))?;
assert_eq!(
model
.declarations
.keys()
.map(|path| qualified(path))
.collect::<Vec<_>>(),
[
"crate::grouped::child::Grouped",
"crate::inline::child::Inline",
"crate::outer::moved::child::Redirected",
"crate::outer::nested::child::Nested",
"crate::outer::sibling::Sibling",
]
);
fs::remove_dir_all(root)?;
Ok(())
}
#[test]
fn invalid_or_missing_explicit_paths_never_fall_back_to_conventional_sources()
-> Result<(), Box<dyn Error>> {
let root = temporary_root("invalid-module-paths")?;
fs::write(root.join("src/owner.rs"), "pub struct Owner;")?;
for attribute in [
"#[path]",
"#[path(\"owner.rs\")]",
"#[path = 3]",
"#[path = concat!(\"owner\", \".rs\")]",
] {
fs::write(root.join("src/lib.rs"), format!("{attribute} mod owner;"))?;
assert!(
error_message(read_crate(&root)).contains("path attribute must be a string literal")
);
}
fs::write(
root.join("src/lib.rs"),
r#"#[path = "owner.rs"] #[path = "other.rs"] mod owner;"#,
)?;
assert!(error_message(read_crate(&root)).contains("more than one path attribute"));
fs::write(
root.join("src/lib.rs"),
r#"#[path = "missing.rs"] mod owner;"#,
)?;
let missing = error_message(read_crate(&root));
assert!(missing.contains("could not resolve"));
assert!(missing.contains("missing.rs"));
fs::remove_dir_all(root)?;
Ok(())
}
#[test]
fn one_source_can_define_distinct_modules_but_recursive_inclusion_is_refused()
-> Result<(), Box<dyn Error>> {
let root = temporary_root("shared-module-source")?;
fs::write(
root.join("src/lib.rs"),
r#"#[path = "shared.rs"] mod first; #[path = "shared.rs"] mod second;"#,
)?;
fs::write(root.join("src/shared.rs"), "pub struct Value;")?;
let mut model = SourceModel::new();
model.read_module_file(&root.join("src/lib.rs"), Vec::new(), &root.join("src"))?;
assert_eq!(
model
.declarations
.keys()
.map(|path| qualified(path))
.collect::<Vec<_>>(),
["crate::first::Value", "crate::second::Value"]
);
fs::write(
root.join("src/shared.rs"),
r#"#[path = "shared.rs"] mod again;"#,
)?;
assert!(error_message(read_crate(&root)).contains("recursive module source"));
fs::remove_dir_all(root)?;
Ok(())
}