use std::sync::{Arc, Mutex};
use sixu::error::RuntimeError;
use sixu::format::*;
use sixu::parser::parse;
use sixu::runtime::{Runtime, RuntimeContext, RuntimeExecutor, StepResult};
struct TestExecutor {
texts: Arc<Mutex<Vec<String>>>,
commands: Arc<Mutex<Vec<String>>>,
counter: Arc<Mutex<i32>>,
finished_called: Arc<Mutex<bool>>,
}
impl TestExecutor {
fn new() -> Self {
Self {
texts: Arc::new(Mutex::new(Vec::new())),
commands: Arc::new(Mutex::new(Vec::new())),
counter: Arc::new(Mutex::new(0)),
finished_called: Arc::new(Mutex::new(false)),
}
}
fn texts(&self) -> Vec<String> {
self.texts.lock().unwrap().clone()
}
fn commands(&self) -> Vec<String> {
self.commands.lock().unwrap().clone()
}
fn eval_condition_str(&self, condition: &str) -> bool {
match condition.trim() {
"true" => true,
"false" => false,
"counter < 3" => *self.counter.lock().unwrap() < 3,
"counter < 5" => *self.counter.lock().unwrap() < 5,
_ => false,
}
}
}
impl RuntimeExecutor for TestExecutor {
fn handle_command(
&mut self,
_ctx: &mut RuntimeContext,
command_line: &ResolvedCommandLine,
) -> sixu::error::Result<bool> {
self.commands
.lock()
.unwrap()
.push(command_line.command.clone());
if command_line.command == "increment" {
let mut counter = self.counter.lock().unwrap();
*counter += 1;
}
Ok(true) }
fn handle_extra_system_call(
&mut self,
_ctx: &mut RuntimeContext,
_systemcall_line: &ResolvedSystemCallLine,
) -> sixu::error::Result<bool> {
Ok(true)
}
fn handle_text(
&mut self,
_ctx: &mut RuntimeContext,
_leading: Option<&str>,
text: Option<&str>,
_tailing: Option<&str>,
) -> sixu::error::Result<bool> {
if let Some(t) = text {
self.texts.lock().unwrap().push(t.to_string());
}
Ok(false) }
fn finished(&mut self, _ctx: &mut RuntimeContext) {
*self.finished_called.lock().unwrap() = true;
}
}
fn run_story(script: &str) -> (Vec<String>, Vec<String>) {
let (_, story) = parse("test", script).unwrap();
let executor = TestExecutor::new();
let mut runtime = Runtime::new(executor);
runtime.add_story(story);
runtime.start("test", Some("entry")).unwrap();
let mut iterations = 0;
loop {
match runtime.step() {
Ok(StepResult::Done) => {
iterations += 1;
if iterations > 100 {
panic!("Too many iterations, possible infinite loop");
}
}
Ok(StepResult::NeedsCondition(condition)) => {
let result = runtime.executor().eval_condition_str(&condition);
runtime.resume_condition(result);
}
Ok(StepResult::NeedsScript(_)) => {
runtime.resume_script(None, true);
}
Ok(StepResult::NeedsStoryFile(_)) => {
unimplemented!("story file loading not supported in this test")
}
Err(RuntimeError::StoryFinished) | Err(RuntimeError::StoryNotStarted) => break,
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
let texts = runtime.executor().texts();
let commands = runtime.executor().commands();
(texts, commands)
}
#[test]
fn test_cond_true_executes_text() {
let script = r#"
::entry {
#[cond("true")]
text_visible
text_after
}
"#;
let (texts, _) = run_story(script);
assert_eq!(texts, vec!["text_visible", "text_after"]);
}
#[test]
fn test_cond_false_skips_text() {
let script = r#"
::entry {
#[cond("false")]
text_hidden
text_after
}
"#;
let (texts, _) = run_story(script);
assert_eq!(texts, vec!["text_after"]);
}
#[test]
fn test_if_alias_works_same_as_cond() {
let script = r#"
::entry {
#[if("true")]
visible
#[if("false")]
hidden
after
}
"#;
let (texts, _) = run_story(script);
assert_eq!(texts, vec!["visible", "after"]);
}
#[test]
fn test_cond_on_block() {
let script = r#"
::entry {
#[cond("true")]
{
block_text
}
#[cond("false")]
{
hidden_block
}
after
}
"#;
let (texts, _) = run_story(script);
assert_eq!(texts, vec!["block_text", "after"]);
}
#[test]
fn test_cond_on_command() {
let script = r#"
::entry {
#[cond("true")]
@visible_cmd arg=1
#[cond("false")]
@hidden_cmd arg=2
@always_cmd arg=3
}
"#;
let (_, commands) = run_story(script);
assert_eq!(commands, vec!["visible_cmd", "always_cmd"]);
}
#[test]
fn test_multiple_attributes_only_last_used() {
let script = r#"
::entry {
#[cond("true")]
#[cond("false")]
should_be_hidden
after
}
"#;
let (texts, _) = run_story(script);
assert_eq!(texts, vec!["after"]);
}
#[test]
fn test_while_loop_with_block() {
let script = r#"
::entry {
#[while("counter < 3")]
{
@increment
}
after_loop
}
"#;
let (texts, commands) = run_story(script);
assert_eq!(commands, vec!["increment", "increment", "increment"]);
assert_eq!(texts, vec!["after_loop"]);
}
#[test]
fn test_while_false_skips_entirely() {
let script = r#"
::entry {
#[while("false")]
{
@never_runs
}
after
}
"#;
let (texts, commands) = run_story(script);
assert_eq!(commands, Vec::<String>::new());
assert_eq!(texts, vec!["after"]);
}
#[test]
fn test_while_on_single_command() {
let script = r#"
::entry {
#[while("counter < 3")]
@increment
"after loop"
}
"#;
let (texts, commands) = run_story(script);
assert_eq!(commands, vec!["increment", "increment", "increment"]);
assert_eq!(texts, vec!["after loop"]);
}
#[test]
fn test_loop_with_break() {
let script = r#"
::entry {
#[loop]
{
@increment
#[cond("counter < 3")]
#continue
#break
}
after_loop
}
"#;
let (texts, commands) = run_story(script);
assert_eq!(commands, vec!["increment", "increment", "increment"]);
assert_eq!(texts, vec!["after_loop"]);
}
#[test]
fn test_loop_break_immediately() {
let script = r#"
::entry {
#[loop]
{
#break
}
"after loop"
}
"#;
let (texts, commands) = run_story(script);
assert_eq!(commands, Vec::<String>::new());
assert_eq!(texts, vec!["after loop"]);
}
#[test]
fn test_continue_skips_rest_of_iteration() {
let script = r#"
::entry {
#[while("counter < 5")]
{
@increment
#[cond("counter < 3")]
#continue
@after_continue
}
done
}
"#;
let (texts, commands) = run_story(script);
assert_eq!(
commands,
vec![
"increment",
"increment",
"increment",
"after_continue",
"increment",
"after_continue",
"increment",
"after_continue"
]
);
assert_eq!(texts, vec!["done"]);
}
#[test]
fn test_cond_on_systemcall() {
let script = r#"
::entry {
text_before
#[cond("false")]
#goto paragraph="other"
text_after
}
"#;
let (texts, _) = run_story(script);
assert_eq!(texts[0], "text_before");
assert_eq!(texts[1], "text_after");
}
#[test]
fn test_nested_cond_in_while() {
let script = r#"
::entry {
#[while("counter < 3")]
{
#[cond("true")]
@increment
}
done
}
"#;
let (texts, commands) = run_story(script);
assert_eq!(commands, vec!["increment", "increment", "increment"]);
assert_eq!(texts, vec!["done"]);
}