#![cfg(not(target_arch = "wasm32"))]
use runmat_builtins::Value;
use runmat_core::{ExecutionStreamKind, RunError, RunMatSession};
use runmat_gc::gc_test_context;
use std::path::{Path, PathBuf};
struct CwdGuard {
original: PathBuf,
}
impl Drop for CwdGuard {
fn drop(&mut self) {
let _ = std::env::set_current_dir(&self.original);
}
}
fn push_cwd(path: &Path) -> CwdGuard {
let original = std::env::current_dir().expect("current dir");
std::env::set_current_dir(path).expect("set current dir");
CwdGuard { original }
}
fn isolate_plot_test() -> runmat_runtime::builtins::plotting::PlotTestLockGuard {
let guard = runmat_runtime::builtins::plotting::lock_plot_test_context();
runmat_runtime::builtins::plotting::reset_plot_state();
guard
}
#[test]
fn pcode_command_generates_p_file_and_run_prefers_it() {
let _fs_guard = runmat_filesystem::provider_override_lock();
let temp = tempfile::TempDir::new().expect("tempdir");
std::fs::write(temp.path().join("worker.m"), "pcodeValue = 10;\n").expect("write source");
let _cwd = push_cwd(temp.path());
let mut engine = gc_test_context(RunMatSession::new).unwrap();
let result = runmat_core::execute_text_request_for_testing(&mut engine, "pcode worker;")
.expect("pcode command should execute");
assert!(result.error.is_none());
assert!(temp.path().join("worker.p").is_file());
std::fs::write(temp.path().join("worker.m"), "pcodeValue = 20;\n")
.expect("rewrite source after pcode");
let result = runmat_core::execute_text_request_for_testing(&mut engine, "run worker;")
.expect("run command should execute P-code");
assert!(result.error.is_none());
let readback = runmat_core::execute_text_request_for_testing(&mut engine, "pcodeValue")
.expect("read P-code assigned variable");
assert_eq!(
readback.value.as_ref().map(|value| value.to_string()),
Some("10".to_string())
);
let result = runmat_core::execute_text_request_for_testing(&mut engine, "pcodeValue = 0;")
.expect("reset value");
assert!(result.error.is_none());
let result = runmat_core::execute_text_request_for_testing(&mut engine, "run worker.m;")
.expect("explicit .m run should still execute P-code sibling");
assert!(result.error.is_none());
let readback = runmat_core::execute_text_request_for_testing(&mut engine, "pcodeValue")
.expect("read explicit .m P-code assigned variable");
assert_eq!(
readback.value.as_ref().map(|value| value.to_string()),
Some("10".to_string())
);
}
#[test]
fn run_invalid_pcode_reports_pcode_identifier() {
let _fs_guard = runmat_filesystem::provider_override_lock();
let temp = tempfile::TempDir::new().expect("tempdir");
std::fs::write(temp.path().join("bad.p"), "not runmat pcode").expect("write invalid pcode");
let _cwd = push_cwd(temp.path());
let mut engine = gc_test_context(RunMatSession::new).unwrap();
let result = runmat_core::execute_text_request_for_testing(&mut engine, "run bad;")
.expect("run should report runtime error in response");
assert_eq!(
result.error.as_ref().and_then(|err| err.identifier()),
Some("RunMat:pcode:InvalidPcode")
);
}
#[test]
fn close_all_command_form_executes_without_lowering_all_as_a_builtin() {
let _plot_guard = isolate_plot_test();
let mut engine = gc_test_context(RunMatSession::new).unwrap();
runmat_core::execute_text_request_for_testing(&mut engine, "figure(1);").unwrap();
runmat_core::execute_text_request_for_testing(&mut engine, "figure(2);").unwrap();
let result = runmat_core::execute_text_request_for_testing(&mut engine, "close all;").unwrap();
assert!(result.error.is_none());
}
#[test]
fn clear_command_clears_workspace_state() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
runmat_core::execute_text_request_for_testing(&mut engine, "x = 1;").unwrap();
let result = runmat_core::execute_text_request_for_testing(&mut engine, "clear;").unwrap();
assert!(result.error.is_none());
assert!(result.workspace.full);
assert!(result.workspace.values.is_empty());
let err = runmat_core::execute_text_request_for_testing(&mut engine, "x").unwrap_err();
let RunError::Semantic(err) = err else {
panic!("expected semantic undefined-variable error");
};
assert_eq!(err.identifier.as_deref(), Some("RunMat:UndefinedVariable"));
}
#[test]
fn clear_all_command_form_clears_workspace_state() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
runmat_core::execute_text_request_for_testing(&mut engine, "y = 42;").unwrap();
let result = runmat_core::execute_text_request_for_testing(&mut engine, "clear all;").unwrap();
assert!(result.error.is_none());
assert!(result.workspace.full);
assert!(result.workspace.values.is_empty());
let err = runmat_core::execute_text_request_for_testing(&mut engine, "y").unwrap_err();
let RunError::Semantic(err) = err else {
panic!("expected semantic undefined-variable error");
};
assert_eq!(err.identifier.as_deref(), Some("RunMat:UndefinedVariable"));
}
#[test]
fn clear_named_variable_removes_only_that_binding() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
runmat_core::execute_text_request_for_testing(&mut engine, "x = 1; y = 2;").unwrap();
let result = runmat_core::execute_text_request_for_testing(&mut engine, "clear x;").unwrap();
assert!(result.error.is_none());
assert!(result.workspace.full);
assert_eq!(result.workspace.values.len(), 1);
assert_eq!(result.workspace.values[0].name, "y");
let err = runmat_core::execute_text_request_for_testing(&mut engine, "x").unwrap_err();
let RunError::Semantic(err) = err else {
panic!("expected semantic undefined-variable error");
};
assert_eq!(err.identifier.as_deref(), Some("RunMat:UndefinedVariable"));
let y_value = runmat_core::execute_text_request_for_testing(&mut engine, "y").unwrap();
assert!(y_value.error.is_none());
assert_eq!(
y_value.value.as_ref().map(|v| v.to_string()),
Some("2".to_string())
);
}
#[test]
fn clear_multiple_named_variables_accepts_multiple_inputs() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
runmat_core::execute_text_request_for_testing(&mut engine, "a = 1; b = 2; c = 3;").unwrap();
let result = runmat_core::execute_text_request_for_testing(&mut engine, "clear a b;").unwrap();
assert!(result.error.is_none());
assert!(result.workspace.full);
assert_eq!(result.workspace.values.len(), 1);
assert_eq!(result.workspace.values[0].name, "c");
}
#[test]
fn clear_followed_by_assignments_shows_vars_in_workspace() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
let result = runmat_core::execute_text_request_for_testing(
&mut engine,
"clear();\nXRange = -2:0.02:2;\nYRange = -2:0.02:2;\ndisp(YRange);",
)
.unwrap();
assert!(result.error.is_none());
let names: Vec<&str> = result
.workspace
.values
.iter()
.map(|e| e.name.as_str())
.collect();
assert!(
names.contains(&"XRange"),
"XRange should be in workspace after clear(); XRange = ...; got: {names:?}"
);
assert!(
names.contains(&"YRange"),
"YRange should be in workspace after clear(); YRange = ...; got: {names:?}"
);
}
#[test]
fn untaken_branch_assignments_do_not_overwrite_existing_values() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
runmat_core::execute_text_request_for_testing(&mut engine, "untaken = 1;").unwrap();
let result = runmat_core::execute_text_request_for_testing(
&mut engine,
"if 0; untaken = 2; else; taken = 3; end;",
)
.unwrap();
assert!(result.error.is_none());
let taken = runmat_core::execute_text_request_for_testing(&mut engine, "taken").unwrap();
assert_eq!(
taken.value.as_ref().map(|v| v.to_string()),
Some("3".to_string())
);
let untaken = runmat_core::execute_text_request_for_testing(&mut engine, "untaken").unwrap();
assert_eq!(
untaken.value.as_ref().map(|v| v.to_string()),
Some("1".to_string()),
"untaken branch should not overwrite prior workspace value"
);
}
#[test]
fn jit_fallback_preserves_workspace_tracking_after_preflight_failure() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
runmat_core::execute_text_request_for_testing(&mut engine, "textValue = \"hello\";").unwrap();
for _ in 0..12 {
let result =
runmat_core::execute_text_request_for_testing(&mut engine, "jitFallbackTarget = 1;")
.unwrap();
assert!(result.error.is_none());
}
let clear_result =
runmat_core::execute_text_request_for_testing(&mut engine, "clear jitFallbackTarget;")
.unwrap();
assert!(clear_result.error.is_none());
let result =
runmat_core::execute_text_request_for_testing(&mut engine, "jitFallbackTarget = 2;")
.unwrap();
assert!(result.error.is_none());
let names: Vec<&str> = result
.workspace
.values
.iter()
.map(|entry| entry.name.as_str())
.collect();
assert!(
names.contains(&"jitFallbackTarget"),
"interpreter fallback after JIT preflight failure should still publish assignments; got: {names:?}"
);
}
#[test]
fn clearvars_command_clears_workspace_state() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
runmat_core::execute_text_request_for_testing(&mut engine, "x = 1; y = 2;").unwrap();
let result = runmat_core::execute_text_request_for_testing(&mut engine, "clearvars;").unwrap();
assert!(result.error.is_none());
assert!(result.workspace.full);
assert!(result.workspace.values.is_empty());
let err = runmat_core::execute_text_request_for_testing(&mut engine, "x").unwrap_err();
let RunError::Semantic(err) = err else {
panic!("expected semantic undefined-variable error");
};
assert_eq!(err.identifier.as_deref(), Some("RunMat:UndefinedVariable"));
}
#[test]
fn clearvars_named_variable_removes_only_that_binding() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
runmat_core::execute_text_request_for_testing(&mut engine, "x = 1; y = 2;").unwrap();
let result =
runmat_core::execute_text_request_for_testing(&mut engine, "clearvars x;").unwrap();
assert!(result.error.is_none());
assert!(result.workspace.full);
assert_eq!(result.workspace.values.len(), 1);
assert_eq!(result.workspace.values[0].name, "y");
let err = runmat_core::execute_text_request_for_testing(&mut engine, "x").unwrap_err();
let RunError::Semantic(err) = err else {
panic!("expected semantic undefined-variable error");
};
assert_eq!(err.identifier.as_deref(), Some("RunMat:UndefinedVariable"));
}
#[test]
fn clearvars_multiple_named_variables_accepts_multiple_inputs() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
runmat_core::execute_text_request_for_testing(&mut engine, "a = 1; b = 2; c = 3;").unwrap();
let result =
runmat_core::execute_text_request_for_testing(&mut engine, "clearvars a b;").unwrap();
assert!(result.error.is_none());
assert!(result.workspace.full);
assert_eq!(result.workspace.values.len(), 1);
assert_eq!(result.workspace.values[0].name, "c");
}
#[test]
fn clearvars_except_keeps_named_bindings() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
runmat_core::execute_text_request_for_testing(&mut engine, "drop1 = 1; keep = 2; drop2 = 3;")
.unwrap();
let result =
runmat_core::execute_text_request_for_testing(&mut engine, "clearvars -except keep;")
.unwrap();
assert!(result.error.is_none());
assert!(result.workspace.full);
assert_eq!(result.workspace.values.len(), 1);
assert_eq!(result.workspace.values[0].name, "keep");
let value = runmat_core::execute_text_request_for_testing(&mut engine, "keep").unwrap();
assert!(value.error.is_none());
assert_eq!(
value.value.as_ref().map(|v| v.to_string()),
Some("2".to_string())
);
}
#[test]
fn clearvars_selected_names_with_except_preserves_exclusions() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
runmat_core::execute_text_request_for_testing(
&mut engine,
"a = 1; b = 2; c = 3; untouched = 4;",
)
.unwrap();
let result =
runmat_core::execute_text_request_for_testing(&mut engine, "clearvars a b c -except b;")
.unwrap();
assert!(result.error.is_none());
assert!(result.workspace.full);
let names: Vec<&str> = result
.workspace
.values
.iter()
.map(|entry| entry.name.as_str())
.collect();
assert!(!names.contains(&"a"), "a should have been cleared");
assert!(names.contains(&"b"), "b should have been preserved");
assert!(!names.contains(&"c"), "c should have been cleared");
assert!(
names.contains(&"untouched"),
"variables outside the selected clear set should remain"
);
}
#[test]
fn clearvars_function_call_selected_names_with_except_preserves_exclusions() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
runmat_core::execute_text_request_for_testing(
&mut engine,
"a = 1; b = 2; c = 3; untouched = 4;",
)
.unwrap();
let result = runmat_core::execute_text_request_for_testing(
&mut engine,
"clearvars('a', 'b', 'c', '-except', 'b');",
)
.unwrap();
assert!(result.error.is_none());
assert!(result.workspace.full);
let names: Vec<&str> = result
.workspace
.values
.iter()
.map(|entry| entry.name.as_str())
.collect();
assert!(!names.contains(&"a"), "a should have been cleared");
assert!(names.contains(&"b"), "b should have been preserved");
assert!(!names.contains(&"c"), "c should have been cleared");
assert!(
names.contains(&"untouched"),
"variables outside the selected clear set should remain"
);
}
#[test]
fn clearvars_named_variable_is_undefined_later_in_same_execution() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
let result =
runmat_core::execute_text_request_for_testing(&mut engine, "x = 7; clearvars x; disp(x);")
.unwrap();
assert_eq!(
result.error.as_ref().and_then(|err| err.identifier()),
Some("RunMat:UndefinedVariable")
);
let err = runmat_core::execute_text_request_for_testing(&mut engine, "x").unwrap_err();
let RunError::Semantic(err) = err else {
panic!("expected semantic undefined-variable error");
};
assert_eq!(err.identifier.as_deref(), Some("RunMat:UndefinedVariable"));
}
#[test]
fn clear_named_variable_is_undefined_later_in_same_execution() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
let result =
runmat_core::execute_text_request_for_testing(&mut engine, "x = 7; clear x; disp(x);")
.unwrap();
assert_eq!(
result.error.as_ref().and_then(|err| err.identifier()),
Some("RunMat:UndefinedVariable")
);
let err = runmat_core::execute_text_request_for_testing(&mut engine, "x").unwrap_err();
let RunError::Semantic(err) = err else {
panic!("expected semantic undefined-variable error");
};
assert_eq!(err.identifier.as_deref(), Some("RunMat:UndefinedVariable"));
}
#[test]
fn clearvars_repro_with_clc_and_close_all_executes() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
let result = runmat_core::execute_text_request_for_testing(
&mut engine,
"clearvars; clc; close all\nx = 5;\ndisp(x);",
)
.unwrap();
assert!(result.error.is_none());
assert!(
result
.streams
.iter()
.any(|entry| entry.stream == ExecutionStreamKind::ClearScreen),
"expected clc to emit a clear-screen control event"
);
}
#[test]
fn clc_emits_clear_screen_control_stream() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
let result = runmat_core::execute_text_request_for_testing(&mut engine, "clc;").unwrap();
assert!(result.error.is_none());
assert!(
result
.streams
.iter()
.any(|entry| entry.stream == ExecutionStreamKind::ClearScreen),
"expected clear-screen control event in execution streams"
);
}
#[test]
fn plotting_command_gap_repros_execute() {
let _plot_guard = isolate_plot_test();
let mut engine = gc_test_context(RunMatSession::new).unwrap();
let result = runmat_core::execute_text_request_for_testing(
&mut engine,
"figure; plot([1 2], [3 4]); axis off; axis on; axis manual; axis ij; axis xy; drawnow;",
)
.unwrap();
if let Some(error) = result.error {
assert_eq!(error.identifier(), Some("RunMat:plot:EngineError"));
}
}
#[test]
fn debugger_compat_commands_execute_through_core() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
let result = runmat_core::execute_text_request_for_testing(
&mut engine,
"keyboard; mlock; locked = mislocked; munlock; unlocked = mislocked; dbclear all; status = dbstatus; info = getcallinfo;",
)
.unwrap();
assert!(
result.error.is_none(),
"unexpected debugger compatibility error: {:?}",
result.error
);
let locked = runmat_core::execute_text_request_for_testing(&mut engine, "locked").unwrap();
assert_eq!(locked.value.as_ref(), Some(&Value::Bool(true)));
let unlocked = runmat_core::execute_text_request_for_testing(&mut engine, "unlocked").unwrap();
assert_eq!(unlocked.value.as_ref(), Some(&Value::Bool(false)));
let status = runmat_core::execute_text_request_for_testing(&mut engine, "status").unwrap();
assert!(matches!(status.value.as_ref(), Some(Value::Cell(cell)) if cell.data.is_empty()));
let info = runmat_core::execute_text_request_for_testing(&mut engine, "info").unwrap();
assert!(matches!(info.value.as_ref(), Some(Value::Struct(_))));
runmat_vm::reset_thread_state_for_tests();
let reset_locked =
runmat_core::execute_text_request_for_testing(&mut engine, "resetLocked = mislocked;")
.unwrap();
assert!(
reset_locked.error.is_none(),
"mislocked after reset failed: {:?}",
reset_locked.error
);
let reset_locked =
runmat_core::execute_text_request_for_testing(&mut engine, "resetLocked").unwrap();
assert_eq!(reset_locked.value.as_ref(), Some(&Value::Bool(false)));
}
#[test]
fn dbstack_reports_user_function_frame_through_core() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
let define = runmat_core::execute_text_request_for_testing(
&mut engine,
"function st = stack_probe(); st = dbstack; end",
)
.unwrap();
assert!(
define.error.is_none(),
"function definition failed: {:?}",
define.error
);
let result =
runmat_core::execute_text_request_for_testing(&mut engine, "frames = stack_probe();")
.unwrap();
assert!(
result.error.is_none(),
"stack probe failed: {:?}",
result.error
);
let frames = runmat_core::execute_text_request_for_testing(&mut engine, "frames").unwrap();
let Some(Value::Cell(cell)) = frames.value.as_ref() else {
panic!("dbstack should return a cell row of frame structs");
};
assert!(
!cell.data.is_empty(),
"dbstack should include at least one frame"
);
let Value::Struct(first) = &cell.data[0] else {
panic!("first frame should be a struct");
};
assert_eq!(
first.fields.get("name"),
Some(&Value::String("stack_probe".to_string()))
);
let Some(Value::Num(line)) = first.fields.get("line") else {
panic!("first frame should include a numeric line");
};
assert!(
*line >= 1.0,
"VM-backed dbstack frame should report a source line, got {line}"
);
}
#[test]
fn figure_name_property_pairs_execute_through_core() {
let _plot_guard = isolate_plot_test();
let mut engine = gc_test_context(RunMatSession::new).unwrap();
let result = runmat_core::execute_text_request_for_testing(
&mut engine,
"f = figure('Name', 'demo', 'NumberTitle', 'off'); assert(strcmp(get(f, 'Name'), 'demo')); assert(~get(f, 'NumberTitle'));",
)
.unwrap();
assert!(
result.error.is_none(),
"unexpected execution error: {:?}",
result.error
);
}
#[test]
fn warning_command_form_executes() {
let mut engine = gc_test_context(RunMatSession::new).unwrap();
let result = runmat_core::execute_text_request_for_testing(
&mut engine,
"warning off all; warning on all;",
)
.unwrap();
assert!(result.error.is_none());
}