use std::fs;
use std::path::Path;
use anyhow::{Context as _, Result};
use crate::discovery::LibModuleDecl;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ScaffoldOutcome {
AlreadyExists,
Created,
}
#[inline]
pub fn ensure_tests_main(
tests_main_path: &Path,
crate_lib_name: Option<&str>,
lib_modules: &[LibModuleDecl],
use_lib_aggregation: bool,
) -> Result<ScaffoldOutcome> {
if tests_main_path.exists() {
return Ok(ScaffoldOutcome::AlreadyExists);
}
if let Some(parent) = tests_main_path.parent() {
fs::create_dir_all(parent).with_context(|| format!("creating {}", parent.display()))?;
}
let content = render_template(crate_lib_name, lib_modules, use_lib_aggregation);
fs::write(tests_main_path, content)
.with_context(|| format!("writing {}", tests_main_path.display()))?;
Ok(ScaffoldOutcome::Created)
}
fn render_template(
crate_lib_name: Option<&str>,
lib_modules: &[LibModuleDecl],
use_lib_aggregation: bool,
) -> String {
if use_lib_aggregation {
return render_lib_aggregation_template();
}
if lib_modules.is_empty() {
return render_crate_use_template(crate_lib_name);
}
render_path_include_template(lib_modules)
}
fn render_lib_aggregation_template() -> String {
"\
//! Rudzio test runner entry. Generated by rudzio-migrate.
//!
//! `src/lib.rs` is pulled in below as `mod __lib;` so the test
//! binary recompiles the whole lib with `cfg(test)` active and
//! sees every `#[rudzio::suite]` block \u{2014} including ones gated
//! by `#[cfg(test)]` and ones that reference items defined at
//! the lib's crate root.
//!
//! `pub use __lib::*;` mirrors the lib's public surface at this
//! binary's crate root, so `crate::X` paths inside the lib still
//! resolve to the equivalent items here. The one caveat:
//! `pub use ::*` only re-exports `pub` items \u{2014} if any
//! `use crate::Foo` inside the lib reaches a `pub(crate) Foo`,
//! rewrite that one site as `use self::Foo` (in lib.rs) or
//! `use super::Foo` (in submodules). Both are semantically
//! equivalent inside the lib and unaffected by the inclusion.
#![allow(
unreachable_pub,
dead_code,
unused_crate_dependencies,
reason = \"test binary is a recompile of the lib; visibility, usage, and dep-reach lints don't apply here\"
)]
#[path = \"../src/lib.rs\"]
mod __lib;
pub use __lib::*;
#[rudzio::main]
fn main() {}
"
.to_owned()
}
fn render_path_include_template(lib_modules: &[LibModuleDecl]) -> String {
let mut includes = String::new();
for module in lib_modules {
for attr in &module.attrs {
includes.push_str(attr);
includes.push('\n');
}
includes.push_str("#[path = \"../");
includes.push_str(&module.rel_path);
includes.push_str("\"]\nmod ");
includes.push_str(&module.ident);
includes.push_str(";\n\n");
}
format!(
"\
//! Rudzio test runner entry. Generated by rudzio-migrate.
//!
//! Each `mod X;` declared at the crate root of `src/lib.rs` is
//! pulled in below via `#[path]`. That recompiles the files INTO
//! this test binary with `cfg(test)` active, so `#[rudzio::suite]`
//! blocks inside `#[cfg(test)] mod tests {{ ... }}` register their
//! `linkme::distributed_slice` entries into this binary's slice.
//! The runner then finds them.
//!
//! Because the includes mirror the lib's module tree, any
//! `use crate::<mod>::...` paths inside the included files resolve
//! to the equivalent module in this binary.
// Every lib `pub mod X;` is `mod X;` here — items that are `pub`
// inside those files aren't reachable outside this test binary,
// which trips `unreachable_pub`; and items only used by the
// non-test code paths look unused when only the `#[cfg(test)]`
// modules run. Both lints are legitimate in a normal lib but
// meaningless for a test-binary recompile.
#![allow(
unreachable_pub,
dead_code,
unused_crate_dependencies,
reason = \"test binary is a recompile of the lib; visibility, usage, and dep-reach lints don't apply here\"
)]
{includes}#[rudzio::main]
fn main() {{}}
"
)
}
fn render_crate_use_template(crate_lib_name: Option<&str>) -> String {
let crate_use = crate_lib_name.map_or_else(String::new, |name| format!("use {name} as _;\n\n"));
format!(
"\
//! Rudzio test runner entry. Generated by rudzio-migrate.
//!
//! `#[rudzio::suite(...)]` blocks at the lib's non-test scope reach
//! this binary via the `use <crate> as _;` below. Suite blocks
//! gated by `#[cfg(test)]` inside the lib don't — they require
//! `#[path]` aggregation here with the lib's `crate::<mod>::...`
//! paths mirrored, which the tool emits automatically only when a
//! discoverable `src/lib.rs` is present.
#![allow(
unused_crate_dependencies,
reason = \"test binary has a different dep set than the lib; unused-crate warnings don't apply here\"
)]
{crate_use}#[rudzio::main]
fn main() {{}}
"
)
}