use std::sync::Arc;
use bash_interop::rig::{Driving, ExitStatus, Failure, Layout, Message, Rig, Shell, field};
use crate::{ENTRY, Keeping, behind, provisioned, report, running, script};
use bash_interop::scratch::{Scripts, bash};
#[tokio::test]
async fn a_shell_that_speaks_once_and_leaves_loses_nothing() {
for round in 0..10 {
let ran = script("bash -c 'BC_INSTR KEEP say REC quick'\n").await;
assert_eq!(
behind(&ran.shells, "REC"),
[["quick"]],
"round {round}{}",
report(&ran.shells)
);
assert_eq!(
ran.shells.len(),
2,
"the script's shell and the one it started"
);
}
}
#[tokio::test]
async fn a_fork_that_speaks_is_a_shell_of_its_own_and_parts_on_its_own() {
let ran = script(
r#"
BC_INSTR KEEP say REC parent "$BASHPID"
( BC_INSTR KEEP say REC fork "$BASHPID" )
sleep 0.2
BC_INSTR KEEP say REC still-here
"#,
)
.await;
assert_eq!(ran.subject, ExitStatus::Code(0));
assert_eq!(
ran.shells.len(),
2,
"{}",
report(&ran.shells)
);
let (parent, fork) = (&ran.shells[0], &ran.shells[1]);
assert_ne!(parent.shell.pid, fork.shell.pid);
assert_eq!(
fork.shell.subshell, 1,
"what bash says of it"
);
assert_eq!(
fork.kept.len(),
1,
"one word, on its own pipe"
);
assert_eq!(
parent.kept.len(),
2,
"and the parent's own stay the parent's"
);
let (fork_parted, parent_parted) = (
fork.parted.expect("the fork exited"),
parent.parted.expect("the subject exited"),
);
assert!(
fork_parted < parent_parted,
"the fork went first, by its own pipe's end of input"
);
assert!(
fork_parted < parent.kept[1].stamp.sent_at,
"and before the parent's last word"
);
}
struct Twice;
impl Rig for Twice {
type Reaction = Vec<Message>;
fn bash(&self, _at: &Layout) -> String {
String::new()
}
async fn joined(&self, _at: &Layout, _shell: Arc<Shell>) -> Result<Vec<Message>, Failure> {
Ok(Vec::new())
}
}
impl Driving for Twice {}
#[tokio::test]
async fn two_labels_in_one_process_are_two_shells() {
let scripts = Scripts::of(&[(
ENTRY,
r#"
BC_INSTR ONE say REC one
BC_INSTR TWO say REC two
BC_INSTR ONE say REC one-again
"#,
)]);
let ran = Twice
.run(
&bash(scripts.at(ENTRY)),
provisioned(&Twice),
)
.await
.unwrap()
.whole()
.unwrap();
assert_eq!(
ran.shells.len(),
2,
"{}",
report(&ran.shells)
);
assert_eq!(
ran.shells[0].shell.pid, ran.shells[1].shell.pid,
"one process"
);
assert_eq!(
behind(&ran.shells[..1], "REC"),
[["one"], ["one-again"]]
);
assert_eq!(
behind(&ran.shells[1..], "REC"),
[["two"]]
);
}
#[tokio::test]
async fn a_label_nobody_joined_is_an_error_by_absence() {
let ran = running(&[(
ENTRY,
r#"
complaint="$(BC_INSTR NOPE say REC lost 2>&1)"
BC_INSTR KEEP say REC "returned $?" "$complaint"
"#,
)])
.await;
assert_eq!(
ran.subject,
ExitStatus::Code(0),
"the subject carries on"
);
let said = behind(&ran.shells, "REC");
assert_eq!(said.len(), 1, "{}", report(&ran.shells));
assert_eq!(said[0][0], "returned 125");
assert!(
said[0][1].contains("label NOPE is not joined"),
"which label: {:?}",
said[0][1]
);
assert!(
said[0][1].contains(&format!("{ENTRY}:2")),
"and where: {:?}",
said[0][1]
);
}
#[tokio::test]
async fn an_account_of_any_size_arrives_whole() {
let euros = "€".repeat(7000);
let scripts = Scripts::of(&[(
ENTRY,
&format!(
r#"
bash -c ": {euros}; BC_INSTR KEEP say REC done"
"#
),
)]);
let ran = Keeping
.run(
&bash(scripts.at(ENTRY)),
provisioned(&Keeping),
)
.await
.unwrap()
.whole()
.unwrap();
assert_eq!(
behind(&ran.shells, "REC"),
[["done"]],
"{}",
report(&ran.shells)
);
let child = ran
.shells
.iter()
.find(|at| at.shell.bash.invocation.command.is_some())
.unwrap();
assert_eq!(
child.shell.bash.invocation.command.as_deref(),
Some(format!(": {euros}; BC_INSTR KEEP say REC done").as_str()),
"21 KB, across six frames"
);
}
#[tokio::test]
async fn many_shells_announce_at_once() {
let pad = "x".repeat(6000);
let scripts = Scripts::of(&[(
ENTRY,
&format!(
r#"
for i in $(seq 16); do
bash -c ": $i {pad}; BC_INSTR KEEP say REC $i" &
done
wait
"#
),
)]);
let ran = Keeping
.run(
&bash(scripts.at(ENTRY)),
provisioned(&Keeping),
)
.await
.unwrap()
.whole()
.unwrap();
assert_eq!(
ran.shells.len(),
17,
"the script and sixteen children{}",
report(&ran.shells)
);
for at in ran
.shells
.iter()
.filter(|at| at.shell.bash.invocation.command.is_some())
{
let said = &at.kept[0].words;
let (i, command) = (
&said[1],
at.shell.bash.invocation.command.as_deref().unwrap(),
);
assert_eq!(
command,
format!(": {i} {pad}; BC_INSTR KEEP say REC {i}"),
"shell {i}'s own"
);
}
}
struct Bringing;
impl Rig for Bringing {
type Reaction = Vec<Message>;
fn bash(&self, _at: &Layout) -> String {
String::new()
}
async fn joined(&self, _at: &Layout, _shell: Arc<Shell>) -> Result<Vec<Message>, Failure> {
Ok(Vec::new())
}
}
impl Driving for Bringing {}
#[tokio::test]
async fn the_words_a_join_brings_are_on_the_shell() {
let scripts = Scripts::of(&[(
ENTRY,
r#"
BC_INSTR KEEP say REC subject
( BC_INSTR KEEP say REC fork )
"#,
)]);
let ran = Bringing
.run(
&bash(scripts.at(ENTRY)),
provisioned(&Bringing),
)
.await
.unwrap()
.whole()
.unwrap();
assert_eq!(
ran.shells.len(),
2,
"{}",
report(&ran.shells)
);
for at in &ran.shells {
assert_eq!(
at.shell.brought,
["role", "worker", "note", "two words"],
"pid {}",
at.shell.pid
);
assert_eq!(
field(&at.shell.brought, "role"),
Some("worker")
);
}
}
impl crate::Joins for Twice {
fn joining(&self, at: &Layout) -> String {
let dir = bash_strings::emit_scalar(at.text());
format!(
r#"
BC_JOIN ONE {dir}
BC_JOIN TWO {dir}
"#
)
}
}
impl crate::Joins for Bringing {
fn joining(&self, at: &Layout) -> String {
let dir = bash_strings::emit_scalar(at.text());
format!(
r#"
BC_JOIN KEEP {dir} role worker note 'two words'
"#
)
}
}