#![allow(clippy::unwrap_used, clippy::panic)]
use brink_compiler::{AnalysisOptions, Dialect};
use brink_format::Value;
use brink_runtime::{DotNetRng, FallbackHandler, FlowInstance, RuntimeError, link};
const SRC: &str = "#@private\n\
VAR secret = 5\n\
== start ==\n\
Hi\n\
-> DONE\n\
== hidden ==\n\
#@private\n\
Secret content\n\
-> DONE\n\
=== function reveal() ===\n\
~ return secret\n\
=== function hidden_fn() ===\n\
#@private\n\
~ return 99\n";
fn compiled() -> brink_format::StoryData {
let options = AnalysisOptions {
dialect: Dialect::Brink,
..AnalysisOptions::default()
};
brink_compiler::compile_with_options("story.ink", |_p| Ok(SRC.to_owned()), options)
.unwrap()
.data
}
fn container_id(data: &brink_format::StoryData, name: &str) -> brink_format::DefinitionId {
let name_id = data
.name_table
.iter()
.position(|n| n == name)
.unwrap_or_else(|| panic!("no name table entry for {name:?}"));
data.containers
.iter()
.find(|c| c.name.is_some_and(|n| n.0 as usize == name_id))
.unwrap_or_else(|| panic!("no container named {name:?}"))
.id
}
#[test]
fn flow_instance_choose_path_string_refuses_private_def() {
let data = compiled();
let (program, _line_tables) = link(&data).unwrap();
let (mut flow, mut world) = FlowInstance::new_at_root(&program);
assert!(
flow.visibility_enforced(),
"enforcement is on by default, matching Story"
);
assert!(matches!(
flow.choose_path_string(&program, &mut world, "hidden"),
Err(RuntimeError::PrivateAccess { .. })
));
assert!(matches!(
flow.choose_path_string_with_args(&program, &mut world, "hidden", &[]),
Err(RuntimeError::PrivateAccess { .. })
));
flow.choose_path_string(&program, &mut world, "start")
.unwrap();
}
#[test]
fn flow_instance_begin_function_eval_refuses_private_def() {
let data = compiled();
let (program, line_tables) = link(&data).unwrap();
let (mut flow, mut world) = FlowInstance::new_at_root(&program);
let container_idx = program.find_address("hidden_fn").unwrap().0;
let err = flow
.begin_function_eval::<DotNetRng>(
&program,
&line_tables,
&mut world,
&FallbackHandler,
container_idx,
&[],
None,
)
.unwrap_err();
assert!(matches!(err, RuntimeError::PrivateAccess { .. }));
assert!(!flow.is_evaluating_function());
let resume_err = flow
.resume_function_eval::<DotNetRng>(
&program,
&line_tables,
&mut world,
&FallbackHandler,
None,
)
.unwrap_err();
assert!(matches!(resume_err, RuntimeError::NotEvaluatingFunction));
let public_idx = program.find_address("reveal").unwrap().0;
let outcome = flow
.begin_function_eval::<DotNetRng>(
&program,
&line_tables,
&mut world,
&FallbackHandler,
public_idx,
&[],
None,
)
.unwrap();
assert!(matches!(
outcome,
brink_runtime::FunctionEval::Returned(Value::Int(5))
));
}
#[test]
fn flow_instance_begin_function_value_eval_refuses_private_def() {
let data = compiled();
let hidden_fn_id = container_id(&data, "hidden_fn");
let (program, line_tables) = link(&data).unwrap();
let (mut flow, mut world) = FlowInstance::new_at_root(&program);
let callee = Value::FnRef(hidden_fn_id);
let err = flow
.begin_function_value_eval::<DotNetRng>(
&program,
&line_tables,
&mut world,
&FallbackHandler,
&callee,
&[],
None,
)
.unwrap_err();
assert!(matches!(err, RuntimeError::PrivateAccess { .. }));
assert!(!flow.is_evaluating_function());
}
#[test]
fn flow_instance_dev_override_allows_private_access() {
let data = compiled();
let (program, line_tables) = link(&data).unwrap();
let (mut flow, mut world) = FlowInstance::new_at_root(&program);
flow.set_visibility_enforcement(false);
assert!(!flow.visibility_enforced());
flow.choose_path_string(&program, &mut world, "hidden")
.unwrap();
let (mut flow2, mut world2) = FlowInstance::new_at_root(&program);
flow2.set_visibility_enforcement(false);
let container_idx = program.find_address("hidden_fn").unwrap().0;
let outcome = flow2
.begin_function_eval::<DotNetRng>(
&program,
&line_tables,
&mut world2,
&FallbackHandler,
container_idx,
&[],
None,
)
.unwrap();
assert!(matches!(
outcome,
brink_runtime::FunctionEval::Returned(Value::Int(99))
));
}