use std::{
fs::{metadata, set_permissions},
io::Write,
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
process::Command,
str::FromStr,
};
use radicle::{
git::Oid,
git::RefString,
node::{Event, NodeId},
prelude::RepoId,
storage::RefUpdate,
};
use subplotlib::steplibrary::datadir::Datadir;
use subplotlib::steplibrary::runcmd::Runcmd;
#[derive(Debug, Default)]
struct SubplotContext {}
impl ContextElement for SubplotContext {}
#[step]
#[context(SubplotContext)]
#[context(Datadir)]
#[context(Runcmd)]
fn setup_node(context: &ScenarioContext, config: SubplotDataFile, adapter: SubplotDataFile) {
let target_path = bindir();
println!("check CI broker binaries are in {}", target_path.display());
assert!(target_path.join("cib").exists());
assert!(target_path.join("cibtool").exists());
assert!(target_path.join("synthetic-events").exists());
context.with_mut(
|context: &mut Runcmd| {
context.prepend_to_path(target_path);
Ok(())
},
false,
)?;
if let Some(parent) = config.name().parent() {
if parent != Path::new("") {
println!(
"create directory for configuration file: {:?}",
parent.display()
);
context.with_mut(
|context: &mut Datadir| {
context.create_dir_all(parent)?;
Ok(())
},
false,
)?;
}
}
println!("write configuration file {}", config.name().display());
context.with_mut(
|context: &mut Datadir| {
context
.open_write(config.name())?
.write_all(config.data())?;
Ok(())
},
false,
)?;
if let Some(parent) = adapter.name().parent() {
if parent != Path::new("") {
println!("create directory for adapter: {}", parent.display());
context.with_mut(
|context: &mut Datadir| {
context.create_dir_all(parent)?;
Ok(())
},
false,
)?;
}
}
context.with_mut(
|context: &mut Datadir| {
const EXECUTABLE: u32 = 0o755;
println!("create env file");
let home = context.canonicalise_filename(".")?;
let home_str = home.display().to_string();
let rad_home = context.canonicalise_filename(".radicle")?;
let rad_home_str = rad_home.display().to_string();
let envs = &[
("HOME", home_str.as_str()),
("RAD_HOME", rad_home_str.as_str()),
("RAD_PASSPHRASE", "secret"),
("RAD_SOCKET", "synt.sock"),
];
{
let mut file = context.open_write("env")?;
for (k, v) in envs.iter() {
file.write_all(format!("export {k}='{v}'\n").as_bytes())?;
}
}
println!("create env.sh script");
{
const SCRIPT: &str = r#"#!/bin/sh
echo "env.sh starts"
if [ -e env ]; then . ./env; fi
exec "$@"
"#;
let mut file = context.open_write("env.sh")?;
file.write_all(SCRIPT.as_bytes())?;
}
println!("make env.sh executable");
let filename = context.canonicalise_filename("env.sh")?;
let meta = metadata(&filename)?;
let mut perm = meta.permissions();
perm.set_mode(EXECUTABLE);
set_permissions(&filename, perm)?;
let filename = Path::new("adapter.sh");
println!(
"write adapter file {} from {}",
filename.display(),
adapter.name().display()
);
context.open_write(filename)?.write_all(adapter.data())?;
println!("make {} executable", filename.display());
let filename = context.canonicalise_filename("adapter.sh")?;
let meta = metadata(&filename)?;
let mut perm = meta.permissions();
perm.set_mode(EXECUTABLE);
set_permissions(&filename, perm)?;
Ok(())
},
false,
)?;
context.with_mut(
|context: &mut Datadir| {
let home = context.canonicalise_filename(".")?;
let rad_home = context.canonicalise_filename(".radicle")?;
rad_in(
&["auth", "--alias=brokertest"],
&[
("RAD_HOME", &rad_home.display().to_string()),
("RAD_PASSPHRASE", "secret"),
("RAD_SOCKET", "synt.sock"),
],
&home,
)?;
Ok(())
},
false,
)?;
}
#[step]
#[context(SubplotContext)]
#[context(Datadir)]
#[context(Runcmd)]
fn create_repo(context: &ScenarioContext, name: &str) {
context.with_mut(
|context: &mut Datadir| {
let home = context.canonicalise_filename(".")?;
let home_str = home.display().to_string();
let rad_home = context.canonicalise_filename(".radicle")?;
let rad_home_str = rad_home.display().to_string();
let envs = &[
("HOME", home_str.as_str()),
("RAD_HOME", rad_home_str.as_str()),
("RAD_PASSPHRASE", "secret"),
("RAD_SOCKET", "synt.sock"),
];
git_in(
&["config", "--global", "user.email", "radicle@example.com"],
envs,
&home,
)?;
git_in(
&["config", "--global", "user.name", "TestyMcTestFace"],
envs,
&home,
)?;
git_in(&["init", "-b", "main", name], envs, &home)?;
{
let filename = Path::new(name).join("file.dat");
let mut file = context.open_write(filename)?;
file.write_all(b"hello, world")?;
}
let repodir = context.canonicalise_filename(name)?;
git_in(&["add", "."], envs, &repodir)?;
git_in(&["commit", "-am", "test"], envs, &repodir)?;
rad_in(
&[
"init",
"--name",
name,
"--description=test",
"--default-branch=main",
"--private",
"--no-confirm",
"--no-seed",
],
envs,
&repodir,
)?;
Ok(())
},
false,
)?;
}
fn rad_in(args: &[&str], envs: &[(&str, &str)], cwd: &Path) -> Result<(), std::io::Error> {
run_in("rad", args, envs, cwd)
}
fn git_in(args: &[&str], envs: &[(&str, &str)], cwd: &Path) -> Result<(), std::io::Error> {
run_in("git", args, envs, cwd)
}
fn run_in(
argv0: &str,
args: &[&str],
envs: &[(&str, &str)],
cwd: &Path,
) -> Result<(), std::io::Error> {
println!("running command {argv0} {args:?}");
println!("envs: {envs:?}");
println!("cwd: {cwd:?}; exists? {}", cwd.exists());
let output = Command::new(argv0)
.args(args)
.envs(envs.iter().copied())
.current_dir(cwd)
.output()?;
println!("{argv0} exit code: {:?}", output.status.code());
println!(
"{argv0}: stdout:\n{}\n====================",
String::from_utf8_lossy(&output.stdout)
);
println!(
"{argv0}: stderr:\n{}\n=====================",
String::from_utf8_lossy(&output.stderr)
);
if !output.status.success() {
panic!("command failed");
}
Ok(())
}
#[step]
#[context(SubplotContext)]
#[context(Datadir)]
#[context(Runcmd)]
fn add_event_file(context: &ScenarioContext, repodir: &Path) {
context.with_mut(
|datadir: &mut Datadir| {
let rad_home = datadir.canonicalise_filename(".radicle")?;
println!("rad_home: {rad_home:#?}");
let nid = nid(&rad_home)?;
println!("nid: {nid:#?}");
let repodir = datadir.canonicalise_filename(repodir)?;
let rid = rid(&rad_home, &repodir)?;
println!("rid: {rid:#?}");
let head = head(&rad_home, &repodir)?;
let node_event = Event::RefsFetched {
remote: nid,
rid,
updated: vec![RefUpdate::Updated {
name: RefString::try_from(
format!("refs/namespaces/{nid}/refs/heads/main").as_str(),
)?,
old: head,
new: head,
}],
};
println!("node_event: {node_event:#?}");
let node_event = serde_json::to_string(&node_event)?;
let event_json = Path::new("event.json");
let filename = datadir.canonicalise_filename(event_json)?;
assert!(!filename.exists());
let mut file = datadir.open_write(event_json)?;
file.write_all(node_event.as_bytes())?;
Ok(())
},
false,
)?;
}
fn nid(rad_home: &Path) -> Result<NodeId, Box<dyn std::error::Error>> {
let output = Command::new("rad")
.arg("self")
.arg("--nid")
.env("RAD_HOME", rad_home.display().to_string().as_str())
.output()?;
if !output.status.success() {
panic!("rad self --nid failed");
}
Ok(NodeId::from_str(
String::from_utf8_lossy(&output.stdout).to_string().trim(),
)?)
}
fn rid(rad_home: &Path, repo: &Path) -> Result<RepoId, Box<dyn std::error::Error>> {
let output = Command::new("rad")
.arg(".")
.env("RAD_HOME", rad_home.display().to_string().as_str())
.current_dir(repo)
.output()?;
if !output.status.success() {
panic!("rad . failed");
}
Ok(RepoId::from_str(
String::from_utf8_lossy(&output.stdout).to_string().trim(),
)?)
}
fn head(rad_home: &Path, repo: &Path) -> Result<Oid, Box<dyn std::error::Error>> {
let output = Command::new("git")
.arg("rev-parse")
.arg("HEAD")
.env("RAD_HOME", rad_home.display().to_string().as_str())
.current_dir(repo)
.output()?;
if !output.status.success() {
panic!("git rev-parse HEAD failed");
}
Ok(Oid::from_str(
String::from_utf8_lossy(&output.stdout).to_string().trim(),
)?)
}
fn bindir() -> PathBuf {
let path = if let Ok(target) = std::env::var("CARGO_TARGET_DIR") {
Path::new(&target).join("debug")
} else {
PathBuf::from("target/debug")
};
path.canonicalize().unwrap()
}
#[step]
#[context(SubplotContext)]
#[context(Runcmd)]
fn stdout_has_one_line(runcmd: &Runcmd) {
let linecount = runcmd.stdout_as_string().lines().count();
if linecount != 1 {
throw!(format!("stdout had {linecount} lines, expected 1"));
}
}
#[step]
#[context(SubplotContext)]
#[context(Runcmd)]
fn stdout_has_n_lines_containing(runcmd: &Runcmd, n: usize, text: &str) {
let linecount = runcmd
.stdout_as_string()
.lines()
.filter(|line| line.contains(text))
.count();
if linecount != n {
throw!(format!("stdout had {linecount} lines, expected {n}"));
}
}
#[step]
#[context(SubplotContext)]
#[context(Runcmd)]
fn stdout_is_empty(runcmd: &Runcmd) {
let stdout = runcmd.stdout_as_string();
if !stdout.is_empty() {
throw!(format!(
"expected stdout to be empty, is actually {stdout:?}"
));
}
}