use crate::core::hash::{self, CommentStyle};
use crate::core::version::to_r_version;
use std::collections::HashMap;
use std::fmt::Write as FmtWrite;
pub(super) fn render_env_block(env: &HashMap<String, String>) -> String {
if env.is_empty() {
return String::new();
}
let mut keys: Vec<&String> = env.keys().collect();
keys.sort();
let mut out = String::new();
let _ = writeln!(out, "# Suite-level environment defaults from [e2e.env]. Each entry");
let _ = writeln!(out, "# uses setdefault semantics: only applied when not already set.");
for key in keys {
let value = &env[key];
let escaped = value.replace('\\', "\\\\").replace('"', "\\\"");
let _ = writeln!(out, "if (Sys.getenv(\"{key}\", unset = \"\") == \"\") {{");
let _ = writeln!(out, " args <- list(\"{escaped}\")");
let _ = writeln!(out, " names(args) <- \"{key}\"");
let _ = writeln!(out, " do.call(Sys.setenv, args)");
let _ = writeln!(out, "}}");
}
let _ = writeln!(out);
out
}
pub(super) fn render_description(
pkg_name: &str,
pkg_version: &str,
dep_mode: crate::e2e::config::DependencyMode,
) -> String {
let dep_line = match dep_mode {
crate::e2e::config::DependencyMode::Registry => {
let r_version = to_r_version(pkg_version);
format!("Imports: {pkg_name} ({r_version})\n")
}
crate::e2e::config::DependencyMode::Local => String::new(),
};
format!(
r#"Package: e2e.r
Title: E2E Tests for {pkg_name}
Version: 0.1.0
Description: End-to-end test suite.
{dep_line}Suggests: testthat (>= 3.0.0)
Config/testthat/edition: 3
"#
)
}
pub(super) fn render_setup_fixtures(test_documents_path: &str, env: &HashMap<String, String>) -> String {
let mut out = String::new();
out.push_str(&hash::header(CommentStyle::Hash));
let _ = writeln!(out);
let env_block = render_env_block(env);
if !env_block.is_empty() {
out.push_str(&env_block);
}
let _ = writeln!(
out,
"# Resolve fixture paths against the repo's `test_documents/` directory."
);
let _ = writeln!(
out,
"# testthat sources setup-*.R with the working directory at tests/,"
);
let _ = writeln!(
out,
"# so test_documents lives three directories up: tests/ -> e2e/r/ -> e2e/ -> repo root."
);
let _ = writeln!(
out,
"# Each `test_that()` block has its working directory reset back to tests/, so"
);
let _ = writeln!(
out,
"# fixture lookups must be performed via this helper rather than relying on `setwd`."
);
let _ = writeln!(
out,
".alef_test_documents <- normalizePath(\"{test_documents_path}\", mustWork = FALSE)"
);
let _ = writeln!(out, ".resolve_fixture <- function(path) {{");
let _ = writeln!(out, " if (dir.exists(.alef_test_documents)) {{");
let _ = writeln!(out, " file.path(.alef_test_documents, path)");
let _ = writeln!(out, " }} else {{");
let _ = writeln!(out, " path");
let _ = writeln!(out, " }}");
let _ = writeln!(out, "}}");
let _ = writeln!(out);
let _ = writeln!(
out,
".alef_format_value <- function(x) {{
if (is.list(x)) {{
for (variant in names(x)) {{
v <- x[[variant]]
if (is.list(v) && !is.null(v[[\"format\"]]) && is.character(v[[\"format\"]])) {{
return(v[[\"format\"]])
}}
}}
if (!is.null(x[[\"format\"]]) && is.character(x[[\"format\"]])) {{
return(x[[\"format\"]])
}}
if (!is.null(x[[\"format_type\"]])) {{
return(x[[\"format_type\"]])
}}
}}
x
}}"
);
out
}
pub(super) fn render_test_runner(
pkg_name: &str,
pkg_path: &str,
dep_mode: crate::e2e::config::DependencyMode,
) -> String {
let mut out = String::new();
out.push_str(&hash::header(CommentStyle::Hash));
let _ = writeln!(out, "library(testthat)");
match dep_mode {
crate::e2e::config::DependencyMode::Registry => {
let _ = writeln!(out, "library({})", pkg_name);
}
crate::e2e::config::DependencyMode::Local => {
let _ = writeln!(out, "devtools::load_all(\"{pkg_path}\")");
}
}
let _ = writeln!(out);
let _ = writeln!(out, "testthat::set_max_fails(Inf)");
let _ = writeln!(
out,
".script_dir <- tryCatch(dirname(normalizePath(sys.frame(1)$ofile)), error = function(e) getwd())"
);
let _ = writeln!(out, "test_dir(file.path(.script_dir, \"tests\"))");
out
}
pub(super) fn render_install_r(pkg_name: &str, pkg_version: &str, github_repo: &str) -> String {
let github_repo = github_repo.trim_end_matches('/');
let mut out = String::new();
let _ = writeln!(out, "# alef-generated installer for registry-mode R test_app.");
let _ = writeln!(out, "# Installs the configured R package from GitHub releases.");
let _ = writeln!(out, "# Requires `R` on PATH.");
let _ = writeln!(out);
let _ = writeln!(out, "# Version override: pass as commandArgs()[6] to test an");
let _ = writeln!(out, "# arbitrary tag; defaults to the alef-pinned version from");
let _ = writeln!(out, "# [crates.e2e.registry.packages.r].version.");
let _ = writeln!(out, "args <- commandArgs(trailingOnly = TRUE)");
let _ = writeln!(out, "VERSION <- if (length(args) > 0) args[1] else \"{pkg_version}\"");
let _ = writeln!(out);
let _ = writeln!(out, "# Construct the GitHub release tarball URL.");
let _ = writeln!(out, "url <- sprintf(");
let _ = writeln!(out, " \"{github_repo}/releases/download/v%s/{pkg_name}_%s.tar.gz\",");
let _ = writeln!(out, " VERSION,");
let _ = writeln!(out, " VERSION");
let _ = writeln!(out, ")");
let _ = writeln!(out);
let _ = writeln!(
out,
"# Install from the release tarball without requiring devtools or remotes."
);
let _ = writeln!(out, "tryCatch({{",);
let _ = writeln!(
out,
" install.packages(url, repos = NULL, type = \"source\", quiet = TRUE)"
);
let _ = writeln!(out, " message(paste(\"Successfully installed {pkg_name}\", VERSION))");
let _ = writeln!(out, "}}, error = function(e) {{");
let _ = writeln!(out, " message(paste(\"Error installing {pkg_name} from\", url))");
let _ = writeln!(out, " message(conditionMessage(e))");
let _ = writeln!(out, " quit(status = 1)");
let _ = writeln!(out, "}})");
out
}
#[cfg(test)]
mod description_tests {
use super::render_description;
use crate::e2e::config::DependencyMode;
#[test]
fn render_description_registry_release_uses_plain_version() {
let out = render_description("mypkg", "1.2.3", DependencyMode::Registry);
assert!(out.contains("Imports: mypkg (1.2.3)"), "got: {out}");
}
#[test]
fn render_description_registry_prerelease_uses_r_version_form() {
let out = render_description("mypkg", "3.6.0-rc.1", DependencyMode::Registry);
assert!(
out.contains("Imports: mypkg (3.6.0.9001)"),
"pre-release must use CRAN dev-pin form, got: {out}"
);
assert!(
!out.contains("3.6.0-rc.1"),
"raw semver dash form must not appear in DESCRIPTION, got: {out}"
);
}
#[test]
fn render_description_local_omits_imports_line() {
let out = render_description("mypkg", "3.6.0-rc.1", DependencyMode::Local);
assert!(
!out.contains("Imports:"),
"local mode must not emit Imports line, got: {out}"
);
}
}
#[cfg(test)]
mod env_tests {
use super::{render_env_block, render_setup_fixtures};
use std::collections::HashMap;
#[test]
fn render_env_block_emits_setdefault_with_sorted_keys() {
let mut env = HashMap::new();
env.insert("E2E_ALLOW_PRIVATE_NETWORK".to_string(), "true".to_string());
env.insert("ALEF_FOO".to_string(), "bar".to_string());
let block = render_env_block(&env);
assert!(
block.contains("if (Sys.getenv(\"ALEF_FOO\", unset = \"\") == \"\") {"),
"got: {block}"
);
assert!(
block.contains("if (Sys.getenv(\"E2E_ALLOW_PRIVATE_NETWORK\", unset = \"\") == \"\") {"),
"got: {block}"
);
assert!(block.contains("names(args) <- \"ALEF_FOO\""), "got: {block}");
let alef_pos = block.find("ALEF_FOO").unwrap();
let e2e_pos = block.find("E2E_ALLOW_PRIVATE_NETWORK").unwrap();
assert!(alef_pos < e2e_pos, "keys must be sorted alphabetically; got: {block}");
}
#[test]
fn render_env_block_empty_when_no_env_configured() {
let env = HashMap::new();
assert_eq!(render_env_block(&env), "");
}
#[test]
fn render_setup_fixtures_includes_env_block_when_env_configured() {
let mut env = HashMap::new();
env.insert("E2E_ALLOW_PRIVATE_NETWORK".to_string(), "true".to_string());
let out = render_setup_fixtures("../../../test_documents", &env);
assert!(
out.contains("if (Sys.getenv(\"E2E_ALLOW_PRIVATE_NETWORK\", unset = \"\") == \"\")"),
"got: {out}"
);
}
#[test]
fn render_setup_fixtures_omits_env_block_when_env_empty() {
let out = render_setup_fixtures("../../../test_documents", &HashMap::new());
assert!(
!out.contains("Suite-level environment defaults"),
"no env block when env empty; got: {out}"
);
}
}