Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ crossterm = "0.29"
ignore = "0.4"
lsp-server = "0.7.9"
lsp-types = "0.97"
nix = { version = "0.29", features = ["poll"] }
owo-colors = "4"
regex = "1.11.1"
serde = { version = "1.0.209", features = [ "derive" ] }
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,7 @@ fn main() {
Some("automatic") => Mode::Automatic,
Some("quiet") => Mode::Quiet,
Some("interactive") => Mode::Interactive,
_ => unreachable!()
_ => unreachable!(),
}
};

Expand Down Expand Up @@ -967,6 +967,7 @@ fn main() {
);
std::process::exit(0);
}
Ok((_, Outcome::Failed(_) | Outcome::Throw(_))) => std::process::exit(1),
Ok((_, _)) => std::process::exit(0),
Err(error) => {
eprintln!("{}", problem::concise_runner_error(&error, &Terminal));
Expand Down
49 changes: 48 additions & 1 deletion src/runner/checks/driver.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};

use crate::runner::driver::{
draw, edit, is_list_forma, Automatic, Console, Driver, Event, Interaction, Mock, UserInput,
draw, edit, is_list_forma, Automatic, Console, Driver, Event, Interaction, Mock, Standing,
UserInput,
};
use crate::value::{Numeric, Value};

Expand Down Expand Up @@ -160,6 +161,51 @@ fn default_enter_completes_with_produced() {
);
}

#[test]
fn overrule_fail_enter_propagates() {
// At a failed sign-off the default is the failure itself: a bare Enter
// settles Fail and never silently lifts it.
let mut it = Interaction::overrule(Standing::Fail);
assert_eq!(
it.handle(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)),
Some(UserInput::Fail(String::new()))
);
}

#[test]
fn overrule_fail_menu_o_overrides() {
// Override is reachable only deliberately — from the menu — and settles as
// Override, which the runner lifts to a rollup-severing Done.
let mut it = Interaction::overrule(Standing::Fail);
it.handle(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
assert_eq!(
it.handle(KeyEvent::new(KeyCode::Char('o'), KeyModifiers::NONE)),
Some(UserInput::Override)
);
}

#[test]
fn overrule_skip_enter_propagates() {
// An all-skipped scope defaults to Skip, not Done.
let mut it = Interaction::overrule(Standing::Skip);
assert_eq!(
it.handle(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)),
Some(UserInput::Skip)
);
}

#[test]
fn override_inert_without_a_failure() {
// A Skip standing has nothing to override, so the menu's `o` is greyed and
// does nothing.
let mut it = Interaction::overrule(Standing::Skip);
it.handle(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
assert_eq!(
it.handle(KeyEvent::new(KeyCode::Char('o'), KeyModifiers::NONE)),
None
);
}

#[test]
fn esc_edit_typed_enter_returns_literali() {
// Editing is opt-in: Esc -> Edit (the first menu item) opens the buffer
Expand Down Expand Up @@ -511,6 +557,7 @@ fn list_prompt() -> Interaction {
field: edit(String::new(), Value::Literali(String::new()), true),
menu: None,
reason: None,
standing: Standing::Done,
}
}

Expand Down
26 changes: 13 additions & 13 deletions src/runner/checks/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::value;
#[test]
fn variable_lookup() {
let library = Library::core();
let context = Context::native();
let context = Context::native(false);
let op = Operation::Variable(Identifier::new("missing"));
let mut env = Environment::new();
match evaluate(&library, &context, &mut env, &op) {
Expand All @@ -30,7 +30,7 @@ fn variable_lookup() {
#[test]
fn number_evaluates_to_quanticle() {
let library = Library::core();
let context = Context::native();
let context = Context::native(false);
let op = Operation::Number(LangNumeric::Integral(42));
let mut env = Environment::new();
let v = evaluate(&library, &context, &mut env, &op).expect("evaluated");
Expand All @@ -40,7 +40,7 @@ fn number_evaluates_to_quanticle() {
#[test]
fn string_interpolation() {
let library = Library::core();
let context = Context::native();
let context = Context::native(false);
let mut env = Environment::new();
env.extend(
"name".to_string(),
Expand Down Expand Up @@ -68,7 +68,7 @@ fn string_interpolation() {
#[test]
fn multiline_joins_with_newlines() {
let library = Library::core();
let context = Context::native();
let context = Context::native(false);
let op = Operation::Multiline(None, vec!["foo", "bar", "baz"]);
let mut env = Environment::new();
let v = evaluate(&library, &context, &mut env, &op).expect("evaluated");
Expand All @@ -78,7 +78,7 @@ fn multiline_joins_with_newlines() {
#[test]
fn tablet_entries_evaluate() {
let library = Library::core();
let context = Context::native();
let context = Context::native(false);
let op = Operation::Tablet(vec![
Entry {
label: "name",
Expand Down Expand Up @@ -109,7 +109,7 @@ fn tablet_entries_evaluate() {
#[test]
fn list_elements_evaluate() {
let library = Library::core();
let context = Context::native();
let context = Context::native(false);
let op = Operation::List(vec![
Operation::Number(LangNumeric::Integral(1)),
Operation::Number(LangNumeric::Integral(4)),
Expand All @@ -130,7 +130,7 @@ fn list_elements_evaluate() {
#[test]
fn bind_extends_env_for_subsequent_lookup() {
let library = Library::core();
let context = Context::native();
let context = Context::native(false);
let names = [Identifier::new("greeting")];
let bind = Operation::Bind {
names: &names,
Expand All @@ -150,7 +150,7 @@ fn bind_extends_env_for_subsequent_lookup() {
#[test]
fn sequence_evaluation() {
let library = Library::core();
let context = Context::native();
let context = Context::native(false);
let seq = Operation::Sequence(vec![
Operation::Number(LangNumeric::Integral(1)),
Operation::Number(LangNumeric::Integral(2)),
Expand All @@ -173,7 +173,7 @@ fn sequence_evaluation() {
#[test]
fn multi_name_bind_destructures_parametriq() {
let library = Library::core();
let context = Context::native();
let context = Context::native(false);
let mut env = Environment::new();
env.extend(
"triple".to_string(),
Expand Down Expand Up @@ -212,7 +212,7 @@ fn multi_name_bind_destructures_parametriq() {
#[test]
fn multi_name_bind_wrong_arity_errors() {
let library = Library::core();
let context = Context::native();
let context = Context::native(false);
let mut env = Environment::new();
env.extend(
"pair".to_string(),
Expand Down Expand Up @@ -243,7 +243,7 @@ fn multi_name_bind_wrong_arity_errors() {
#[test]
fn multi_name_bind_against_scalar_errors_as_not_tuple() {
let library = Library::core();
let context = Context::native();
let context = Context::native(false);
let mut env = Environment::new();
env.extend(
"scalar".to_string(),
Expand All @@ -266,7 +266,7 @@ fn multi_name_bind_against_scalar_errors_as_not_tuple() {
#[test]
fn execute_dispatches_resolved_builtin() {
let library = Library::core();
let context = Context::native();
let context = Context::native(false);
let id = library
.resolve("seq")
.expect("seq registered");
Expand All @@ -292,7 +292,7 @@ fn execute_dispatches_resolved_builtin() {
#[test]
fn execute_unresolved_function_errors() {
let library = Library::core();
let context = Context::native();
let context = Context::native(false);
let op = Operation::Execute(Executable {
target: ExecutableRef::Unresolved(Identifier::new("click")),
arguments: Vec::new(),
Expand Down
76 changes: 71 additions & 5 deletions src/runner/checks/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1254,9 +1254,10 @@ check :
}

#[test]
fn automatic_failing_exec_fails_step_and_continues() {
// A non-zero exec exit settles its step Fail; the walk continues to the
// sibling below rather than aborting the run.
fn automatic_failing_exec_fails_run_and_continues() {
// A non-zero exec exit settles its step Fail and, with no user present to
// overrule it, rolls the autonomous run up to Failed; the walk still
// continues to the sibling below rather than aborting at the failure.
let source = r#"
% technique v1

Expand Down Expand Up @@ -1286,8 +1287,8 @@ check :
.run(Environment::new())
.expect("run");
match outcome {
Outcome::Done(_) => {}
other => panic!("expected Done, got {:?}", other),
Outcome::Failed(_) => {}
other => panic!("expected Failed, got {:?}", other),
}

let pfftt = fixture.pfftt_contents();
Expand All @@ -1310,6 +1311,71 @@ check :
.any(|r| r.path == "/check:/2"));
}

const ONE_FAILED_STEP: &str = r#"
% technique v1

check :

1. A step the user fails
"#;

#[test]
fn interactive_override_severs_the_rollup_to_done() {
// The user fails the step, then deliberately Overrides the procedure's
// sign-off: the override settles it Done, severing the rollup so the failed
// child does not propagate. Only an interactive run can do this.
let source = ONE_FAILED_STEP.trim_ascii();
let document = parsing::parse(Path::new("Test.tq"), source).expect("parsed");
let mut program = translate(&document).expect("translated");
resolve(&mut program).expect("resolve");

let mut fixture = StoreFixture::new("interactive-override");
let mut runner = Runner::new(
&program,
fixture.take_appender(),
HashMap::new(),
Mock::with_answers([UserInput::Fail("not done".to_string()), UserInput::Override]),
Library::stub(),
);
let outcome = runner
.run(Environment::new())
.expect("run");
match outcome {
Outcome::Done(_) => {}
other => panic!("expected Done after override, got {:?}", other),
}
}

#[test]
fn interactive_accepting_a_failure_propagates() {
// Without an Override, the failed step stands: the procedure rolls up to
// Failed even with the user at the controls.
let source = ONE_FAILED_STEP.trim_ascii();
let document = parsing::parse(Path::new("Test.tq"), source).expect("parsed");
let mut program = translate(&document).expect("translated");
resolve(&mut program).expect("resolve");

let mut fixture = StoreFixture::new("interactive-propagate");
let mut runner = Runner::new(
&program,
fixture.take_appender(),
HashMap::new(),
// The step's Fail, then the sign-off accepts the standing failure.
Mock::with_answers([
UserInput::Fail("not done".to_string()),
UserInput::Fail(String::new()),
]),
Library::stub(),
);
let outcome = runner
.run(Environment::new())
.expect("run");
match outcome {
Outcome::Failed(_) => {}
other => panic!("expected Failed, got {:?}", other),
}
}

#[test]
fn description_instruction_records_under_step_zero() {
// An exec in the procedure description runs in the anonymous step-0
Expand Down
Loading