use super::*;
use crate::emacs_core::builtins::symbols::{builtin_set, builtin_symbol_value};
use crate::emacs_core::intern::{intern, intern_uninterned};
use crate::emacs_core::{Context, format_eval_result};
use crate::test_utils::{runtime_startup_context, runtime_startup_eval_all};
fn eval_all(src: &str) -> Vec<String> {
let mut ev = Context::new();
let forms = crate::emacs_core::value_reader::read_all(src).expect("parse");
let roots = ev.save_specpdl_roots();
for form in &forms {
ev.push_specpdl_root(*form);
}
let result = forms
.iter()
.map(|form| {
let result = ev.eval_form(*form);
format_eval_result(&result)
})
.collect();
ev.restore_specpdl_roots(roots);
result
}
fn bootstrap_context() -> Context {
runtime_startup_context()
}
fn bootstrap_eval_all(src: &str) -> Vec<String> {
runtime_startup_eval_all(src)
}
#[test]
fn custom_manager_new_is_empty() {
crate::test_utils::init_test_tracing();
let _cm = CustomManager::new();
}
#[test]
fn custom_manager_pdump_round_trip() {
crate::test_utils::init_test_tracing();
let cm = CustomManager::new();
let dump = crate::emacs_core::pdump::convert::dump_custom_manager(&cm);
assert!(dump.auto_buffer_local.is_empty());
assert!(dump.auto_buffer_local_syms.is_empty());
let legacy_dump = crate::emacs_core::pdump::types::DumpCustomManager {
auto_buffer_local_syms: Vec::new(),
auto_buffer_local: vec!["tab-width".to_string(), "fill-column".to_string()],
};
let _restored = crate::emacs_core::pdump::convert::load_custom_manager(&legacy_dump);
}
#[test]
fn defcustom_basic() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(r#"(defcustom my-var 42 "My variable.")"#);
assert_eq!(results[0], "OK my-var");
}
#[test]
fn defcustom_sets_value() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(r#"(defcustom my-var 42 "My variable.") my-var"#);
assert_eq!(results[1], "OK 42");
}
#[test]
fn defcustom_with_type() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(r#"(defcustom my-var 42 "Docs." :type 'integer) my-var"#);
assert_eq!(results[1], "OK 42");
}
#[test]
fn defcustom_with_group() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(r#"(defcustom my-var 10 "Docs." :group 'my-group) my-var"#);
assert_eq!(results[1], "OK 10");
}
#[test]
fn custom_declare_variable_version_string_survives_gc_stress() {
crate::test_utils::init_test_tracing();
let mut ev = bootstrap_context();
ev.gc_stress = true;
let result = ev
.eval_str(
r#"(progn
(custom-declare-variable
'vm-custom-version-gc
nil
"Docs."
:type 'boolean
:version "29.1")
(get 'vm-custom-version-gc 'custom-version))"#,
)
.expect("custom-declare-variable should preserve :version string");
assert_eq!(result, Value::string("29.1"));
}
#[test]
fn defcustom_does_not_override_existing() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(r#"(setq my-var 99) (defcustom my-var 42 "Docs.") my-var"#);
assert_eq!(results[2], "OK 99");
}
#[test]
fn defcustom_marks_special() {
crate::test_utils::init_test_tracing();
let mut ev = bootstrap_context();
let _result = ev.eval_str(r#"(defcustom my-var 42 "Docs.")"#);
assert!(ev.obarray().is_special("my-var"));
}
#[test]
fn defcustom_custom_variable_p() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(
r#"(defcustom my-var 42 "Docs.") (custom-variable-p 'my-var) (custom-variable-p 'other)"#,
);
assert!(
results[1].starts_with("OK ("),
"custom-variable-p should return truthy standard-value, got: {}",
results[1]
);
assert_eq!(results[2], "OK nil");
}
#[test]
fn custom_declare_variable_with_funcall_lambda_default_sets_variable() {
crate::test_utils::init_test_tracing();
let mut ev = bootstrap_context();
ev.set_lexical_binding(true);
let result = ev
.eval_str(
r#"(progn
(custom-declare-variable
'test-three-step-help
(list 'funcall (list 'function (list 'lambda nil 'nil)))
"docstring"
:type 'boolean
:group 'help)
(boundp 'test-three-step-help))"#,
)
.expect("eval");
assert_eq!(
result,
crate::emacs_core::value::Value::T,
"test-three-step-help should be bound after custom-declare-variable"
);
}
#[test]
fn defcustom_loaded_from_real_elc_sets_variable() {
crate::test_utils::init_test_tracing();
let dir = tempfile::tempdir().expect("tempdir");
let src_path = dir.path().join("test_dc.el");
std::fs::write(
&src_path,
r#";;; -*- lexical-binding: t -*-
(defvar-local test-mle-process nil "doc1")
;;;###autoload
(put 'test-mle-process 'risky-local-variable t)
(defcustom test-mle-edge 'window
"Where function `test-mle-fn' should align to.
Multiple lines, just like the bindings.el version."
:type '(choice (const right-margin)
(const right-fringe)
(const window))
:group 'help
:version "30.1")
(defun test-mle-fn () test-mle-edge)
"#,
)
.expect("write source");
let gnu_emacs = "/home/exec/.local/bin/emacs";
if !std::path::Path::new(gnu_emacs).exists() {
eprintln!("skipping: GNU Emacs not available at {gnu_emacs}");
return;
}
let status = std::process::Command::new(gnu_emacs)
.args(["--batch", "--eval"])
.arg(format!("(byte-compile-file \"{}\")", src_path.display()))
.status()
.expect("byte-compile");
assert!(status.success(), "byte-compile failed");
let elc_path = dir.path().join("test_dc.elc");
assert!(elc_path.exists(), ".elc file should exist");
let mut ev = bootstrap_context();
super::super::load::load_file(&mut ev, &elc_path).expect("load .elc fixture");
let result = ev.eval_str("(boundp 'test-mle-edge)").expect("boundp eval");
assert_eq!(
result,
crate::emacs_core::value::Value::T,
"test-mle-edge should be bound after loading .elc"
);
let value = ev.eval_str("test-mle-edge").expect("read test-mle-edge");
assert_eq!(value, crate::emacs_core::value::Value::symbol("window"));
}
#[test]
fn custom_declare_variable_with_bytecode_lambda_default_sets_variable() {
crate::test_utils::init_test_tracing();
let mut ev = bootstrap_context();
ev.set_lexical_binding(true);
let result = ev
.eval_str(
r##"(progn
(custom-declare-variable
'test-bc-edge
(list 'funcall (list 'function (read "#[0 \"\\300\\207\" [window] 1]")))
"docstring"
:type 'symbol
:group 'help)
test-bc-edge)"##,
)
.expect("eval");
assert_eq!(
result,
crate::emacs_core::value::Value::symbol("window"),
"test-bc-edge should be set to 'window via bytecode lambda default"
);
}
#[test]
fn defcustom_quoted_symbol_default_followed_by_defun_using_var() {
crate::test_utils::init_test_tracing();
let mut ev = bootstrap_context();
ev.set_lexical_binding(true);
let results: Vec<_> = ev
.eval_str_each(
r#"(defcustom mode-line-test-edge 'window
"Where function `mode-line-format-right-align' should align to."
:type '(choice (const right-margin)
(const right-fringe)
(const window)))
(defun mode--line-test ()
(let ((edge mode-line-test-edge))
edge))
(list mode-line-test-edge (mode--line-test))"#,
)
.iter()
.map(crate::emacs_core::error::format_eval_result)
.collect();
assert_eq!(results[2], "OK (window window)", "results: {results:?}");
}
#[test]
fn defgroup_basic() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(r#"(defgroup my-group nil "My group.")"#);
assert_eq!(results[0], "OK my-group");
}
#[test]
fn defgroup_registers_group() {
crate::test_utils::init_test_tracing();
let mut ev = bootstrap_context();
let _result = ev.eval_str(r#"(defgroup my-group nil "Docs.")"#);
let doc = ev
.obarray
.get_property("my-group", "group-documentation")
.expect("group-documentation");
assert_eq!(doc.as_utf8_str(), Some("Docs."));
}
#[test]
fn custom_group_p_unavailable_without_custom_library() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(
r#"(defgroup my-group nil "Docs.")
(fboundp 'custom-group-p)
(custom-group-p 'my-group)
(custom-group-p 'other)"#,
);
assert_eq!(results[1], "OK nil");
assert_eq!(results[2], "ERR (void-function (custom-group-p))");
assert_eq!(results[3], "ERR (void-function (custom-group-p))");
}
#[test]
fn defgroup_with_parent_records_parent_group() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(
r#"(defgroup parent-group nil "Parent.")
(defgroup child-group nil "Child." :group 'parent-group)
(get 'parent-group 'custom-group)"#,
);
assert_eq!(results[2], "OK ((child-group custom-group))");
}
#[test]
fn defvar_local_basic() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(r#"(defvar-local my-local 42) my-local"#);
assert_eq!(results[0], "OK my-local");
assert_eq!(results[1], "OK 42");
}
#[test]
fn defvar_local_marks_special() {
crate::test_utils::init_test_tracing();
let mut ev = bootstrap_context();
let _result = ev.eval_str(r#"(defvar-local my-local 42)"#);
assert!(ev.obarray().is_special("my-local"));
}
#[test]
fn defvar_local_marks_buffer_local() {
crate::test_utils::init_test_tracing();
let mut ev = bootstrap_context();
let _result = ev.eval_str(r#"(defvar-local my-local 42)"#);
assert!(ev.obarray().is_buffer_local("my-local"));
let id = crate::emacs_core::intern::intern("my-local");
assert!(ev.obarray().blv(id).map_or(false, |b| b.local_if_set));
}
#[test]
fn defvar_local_does_not_override() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(r#"(setq my-local 99) (defvar-local my-local 42) my-local"#);
assert_eq!(results[2], "OK 99");
}
#[test]
fn defvar_local_with_docstring() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(r#"(defvar-local my-local 42 "Documentation.") my-local"#);
assert_eq!(results[1], "OK 42");
}
#[test]
fn setq_default_basic() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(r#"(defvar x 10) (setq-default x 42) x"#);
assert_eq!(results[2], "OK 42");
}
#[test]
fn setq_default_multiple_pairs() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(r#"(defvar a 1) (defvar b 2) (setq-default a 10 b 20) a"#);
assert_eq!(results[3], "OK 10");
}
#[test]
fn setq_default_returns_last_value() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(r#"(setq-default x 42)"#);
assert_eq!(results[0], "OK 42");
}
#[test]
fn setq_default_follows_alias_resolution() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(
r#"(defvaralias 'vm-setq-default-alias 'vm-setq-default-base)
(setq-default vm-setq-default-alias 3)
(list (default-value 'vm-setq-default-base)
(default-value 'vm-setq-default-alias))"#,
);
assert_eq!(results[2], "OK (3 3)");
}
#[test]
fn setq_default_rejects_constant_symbols() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(
r#"(list
(condition-case err (setq-default nil 1) (error err))
(condition-case err (setq-default :foo 1) (error err)))"#,
);
assert_eq!(
results[0],
"OK ((setting-constant nil) (setting-constant :foo))"
);
}
#[test]
fn setq_default_alias_triggers_variable_watchers_twice() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(
r#"(setq vm-setq-default-watch-events nil)
(fset 'vm-setq-default-watch-rec
(lambda (symbol newval operation where)
(setq vm-setq-default-watch-events
(cons (list symbol newval operation where)
vm-setq-default-watch-events))))
(defvaralias 'vm-setq-default-watch 'vm-setq-default-watch-base)
(add-variable-watcher 'vm-setq-default-watch-base 'vm-setq-default-watch-rec)
(setq-default vm-setq-default-watch 7)
(length vm-setq-default-watch-events)"#,
);
assert_eq!(results[5], "OK 2");
}
#[test]
fn default_value_returns_global() {
crate::test_utils::init_test_tracing();
let results = eval_all(r#"(defvar my-var 42) (default-value 'my-var)"#);
assert_eq!(results[1], "OK 42");
}
#[test]
fn default_value_void_signals_error() {
crate::test_utils::init_test_tracing();
let results = eval_all(r#"(default-value 'nonexistent-var)"#);
assert!(results[0].starts_with("ERR"));
}
#[test]
fn keyword_defaults_and_symbol_values_self_evaluate() {
crate::test_utils::init_test_tracing();
let results = eval_all(
r#"(list (default-value :foo) (default-toplevel-value :foo) (symbol-value :foo))"#,
);
assert_eq!(results[0], "OK (:foo :foo :foo)");
}
#[test]
fn uninterned_keyword_defaults_do_not_self_evaluate() {
crate::test_utils::init_test_tracing();
let results = eval_all(
r#"(let ((s (make-symbol ":vm-k")))
(list (condition-case e (eval s nil) (error (car e)))
(condition-case e (symbol-value s) (error (car e)))
(condition-case e (default-value s) (error (car e)))))"#,
);
assert_eq!(results[0], "OK (void-variable void-variable void-variable)");
}
#[test]
fn uninterned_value_cells_ignore_buffer_local_namesakes() {
crate::test_utils::init_test_tracing();
let mut eval = Context::new();
let canonical = intern("depth-alist");
let uninterned = intern_uninterned("depth-alist");
eval.buffers
.current_buffer_mut()
.expect("current buffer")
.set_buffer_local("depth-alist", Value::fixnum(7));
builtin_set(&mut eval, vec![Value::symbol(uninterned), Value::NIL])
.expect("set should bind uninterned symbol");
assert_eq!(
eval.obarray().symbol_value_id(uninterned).copied(),
Some(Value::NIL)
);
assert_eq!(eval.obarray().symbol_value_id(canonical).copied(), None);
assert_eq!(
eval.buffers
.current_buffer()
.expect("current buffer")
.get_buffer_local("depth-alist"),
Some(Value::fixnum(7))
);
let value = builtin_default_value(&mut eval, vec![Value::symbol(uninterned)])
.expect("default-value should read uninterned symbol");
assert_eq!(value, Value::NIL);
let symbol_value = builtin_symbol_value(&mut eval, vec![Value::symbol(uninterned)])
.expect("symbol-value should read uninterned symbol");
assert_eq!(symbol_value, Value::NIL);
}
#[test]
fn set_default_sets_global() {
crate::test_utils::init_test_tracing();
let results = eval_all(r#"(set-default 'my-var 99) (default-value 'my-var)"#);
assert_eq!(results[1], "OK 99");
}
#[test]
fn set_default_preserves_current_buffer_local_binding() {
crate::test_utils::init_test_tracing();
let mut eval = Context::new();
let current = eval.buffers.current_buffer_id().expect("current buffer");
eval.set_buffer_local_binding_by_id(
current,
crate::emacs_core::intern::intern("vm-set-default-local"),
Value::fixnum(7),
)
.expect("buffer-local binding");
builtin_set_default(
&mut eval,
vec![Value::symbol("vm-set-default-local"), Value::fixnum(99)],
)
.expect("set-default");
assert_eq!(
eval.buffers
.current_buffer()
.expect("current buffer")
.buffer_local_value("vm-set-default-local"),
Some(Value::fixnum(7))
);
assert_eq!(
builtin_default_value(&mut eval, vec![Value::symbol("vm-set-default-local")])
.expect("default-value"),
Value::fixnum(99)
);
assert_eq!(
builtin_symbol_value(&mut eval, vec![Value::symbol("vm-set-default-local")])
.expect("symbol-value"),
Value::fixnum(7)
);
}
#[test]
fn set_default_and_default_value_follow_alias_resolution() {
crate::test_utils::init_test_tracing();
let results = eval_all(
r#"(defvaralias 'vm-set-default-alias 'vm-set-default-base)
(set-default 'vm-set-default-alias 5)
(list (default-value 'vm-set-default-base)
(default-value 'vm-set-default-alias))"#,
);
assert_eq!(results[2], "OK (5 5)");
}
#[test]
fn default_value_alias_void_uses_original_symbol_in_error_payload() {
crate::test_utils::init_test_tracing();
let results = eval_all(
r#"(defvaralias 'vm-default-alias-unbound 'vm-default-base-unbound)
(condition-case err
(default-value 'vm-default-alias-unbound)
(error err))"#,
);
assert_eq!(results[1], "OK (void-variable vm-default-alias-unbound)");
}
#[test]
fn set_default_rejects_constant_symbols() {
crate::test_utils::init_test_tracing();
let results = eval_all(
r#"(list
(condition-case err (set-default nil 1) (error err))
(condition-case err (set-default t 1) (error err))
(condition-case err (set-default :foo 1) (error err)))"#,
);
assert_eq!(
results[0],
"OK ((setting-constant nil) (setting-constant t) (setting-constant :foo))"
);
}
#[test]
fn set_default_triggers_variable_watchers() {
crate::test_utils::init_test_tracing();
let results = eval_all(
r#"(fset 'vm-set-default-watch-rec
(lambda (symbol newval operation where)
(setq vm-set-default-watch-last
(list symbol newval operation where))))
(add-variable-watcher 'vm-set-default-watch-target 'vm-set-default-watch-rec)
(set-default 'vm-set-default-watch-target 42)
vm-set-default-watch-last"#,
);
assert_eq!(results[3], "OK (vm-set-default-watch-target 42 set nil)");
}
#[test]
fn set_default_alias_triggers_variable_watchers_twice() {
crate::test_utils::init_test_tracing();
let results = eval_all(
r#"(setq vm-set-default-alias-watch-events nil)
(fset 'vm-set-default-alias-watch-rec
(lambda (symbol newval operation where)
(setq vm-set-default-alias-watch-events
(cons (list symbol newval operation where)
vm-set-default-alias-watch-events))))
(defvaralias 'vm-set-default-alias-watch 'vm-set-default-alias-base)
(add-variable-watcher 'vm-set-default-alias-base 'vm-set-default-alias-watch-rec)
(set-default 'vm-set-default-alias-watch 9)
(length vm-set-default-alias-watch-events)"#,
);
assert_eq!(results[5], "OK 2");
}
#[test]
fn set_default_toplevel_alias_triggers_variable_watchers_twice() {
crate::test_utils::init_test_tracing();
let results = eval_all(
r#"(setq vm-set-default-top-watch-events nil)
(fset 'vm-set-default-top-watch-rec
(lambda (symbol newval operation where)
(setq vm-set-default-top-watch-events
(cons (list symbol newval operation where)
vm-set-default-top-watch-events))))
(defvaralias 'vm-set-default-top-watch 'vm-set-default-top-base)
(add-variable-watcher 'vm-set-default-top-base 'vm-set-default-top-watch-rec)
(set-default-toplevel-value 'vm-set-default-top-watch 7)
(length vm-set-default-top-watch-events)"#,
);
assert_eq!(results[5], "OK 2");
}
#[test]
fn make_variable_buffer_local_works() {
crate::test_utils::init_test_tracing();
let results = eval_all(r#"(make-variable-buffer-local 'my-var)"#);
assert_eq!(results[0], "OK my-var");
}
#[test]
fn make_variable_buffer_local_binds_unbound_symbol_to_nil_like_gnu() {
crate::test_utils::init_test_tracing();
let result = bootstrap_eval_all(
r#"(progn
(makunbound 'vm-mvbl-unbound)
(make-variable-buffer-local 'vm-mvbl-unbound)
(list (boundp 'vm-mvbl-unbound)
(default-value 'vm-mvbl-unbound)
(with-temp-buffer
(local-variable-p 'vm-mvbl-unbound))))"#,
);
assert_eq!(result[0], "OK (t nil nil)");
}
#[test]
fn make_variable_buffer_local_resolves_alias_for_auto_local_assignment() {
crate::test_utils::init_test_tracing();
let result = bootstrap_eval_all(
r#"(setq vm-mvbl-base 1)
(defvaralias 'vm-mvbl-alias 'vm-mvbl-base)
(make-variable-buffer-local 'vm-mvbl-alias)
(with-temp-buffer
(setq vm-mvbl-alias 7)
(list (local-variable-p 'vm-mvbl-alias)
(local-variable-p 'vm-mvbl-base)
vm-mvbl-alias
vm-mvbl-base
(default-value 'vm-mvbl-base)))"#,
);
assert_eq!(result[3], "OK (t t 7 7 1)");
}
#[test]
fn make_variable_buffer_local_constant_and_keyword_payloads_match_oracle() {
crate::test_utils::init_test_tracing();
let result = eval_all(
r#"(list
(condition-case err (make-variable-buffer-local nil) (error err))
(condition-case err (make-variable-buffer-local t) (error err))
(condition-case err (make-variable-buffer-local :vm-mvbl-k) (error err))
(condition-case err (make-variable-buffer-local 1) (error err)))"#,
);
assert_eq!(
result[0],
"OK ((setting-constant nil) (setting-constant t) (setting-constant :vm-mvbl-k) (wrong-type-argument symbolp 1))"
);
}
#[test]
fn make_local_variable_in_buffer() {
crate::test_utils::init_test_tracing();
let results = eval_all(
r#"(defvar my-var 42)
(get-buffer-create "test-buf")
(set-buffer "test-buf")
(make-local-variable 'my-var)
(local-variable-p 'my-var)"#,
);
assert_eq!(results[4], "OK t");
}
#[test]
fn make_local_variable_resolves_alias_bindings() {
crate::test_utils::init_test_tracing();
let result = bootstrap_eval_all(
r#"(setq vm-mlv-base 4)
(defvaralias 'vm-mlv-alias 'vm-mlv-base)
(with-temp-buffer
(make-local-variable 'vm-mlv-alias)
(list (local-variable-p 'vm-mlv-alias)
(local-variable-p 'vm-mlv-base)
(symbol-value 'vm-mlv-alias)
(symbol-value 'vm-mlv-base)
(default-value 'vm-mlv-base)))"#,
);
assert_eq!(result[2], "OK (t t 4 4 4)");
}
#[test]
fn make_local_variable_preserves_existing_buffer_local_binding() {
crate::test_utils::init_test_tracing();
let result = bootstrap_eval_all(
r#"(progn
(setq vm-mlv-preserve-global 1)
(with-temp-buffer
(set (make-local-variable 'vm-mlv-preserve-global) 9)
(make-local-variable 'vm-mlv-preserve-global)
(list vm-mlv-preserve-global
(default-value 'vm-mlv-preserve-global))))"#,
);
assert_eq!(result[0], "OK (9 1)");
}
#[test]
fn make_local_variable_captures_dynamic_value_in_new_local_binding() {
crate::test_utils::init_test_tracing();
let result = eval_all(
r#"(let ((buf (get-buffer-create "vm-mlv-buf")))
(let ((vm-mlv-cross 5))
(set-buffer buf)
(make-local-variable 'vm-mlv-cross))
(set-buffer buf)
(condition-case err vm-mlv-cross (error err)))"#,
);
assert_eq!(result[0], "OK 5");
}
#[test]
fn make_local_variable_on_void_symbol_creates_local_void_binding() {
crate::test_utils::init_test_tracing();
let result = bootstrap_eval_all(
r#"(with-temp-buffer
(makunbound 'vm-mlv-void)
(make-local-variable 'vm-mlv-void)
(list (local-variable-p 'vm-mlv-void (current-buffer))
(buffer-local-boundp 'vm-mlv-void (current-buffer))
(condition-case err (symbol-value 'vm-mlv-void) (error (car err)))
(condition-case err
(buffer-local-value 'vm-mlv-void (current-buffer))
(error (car err)))
(not (null (memq 'vm-mlv-void (buffer-local-variables))))
(assoc 'vm-mlv-void (buffer-local-variables))))"#,
);
assert_eq!(result[0], "OK (t nil void-variable void-variable t nil)");
}
#[test]
fn make_local_variable_ignores_lexical_bindings_like_gnu() {
crate::test_utils::init_test_tracing();
let result = bootstrap_eval_all(
r#"(let ((lexical-binding t))
(eval
'(progn
(setq vm-mlv-lex-global 'global)
(with-temp-buffer
(let ((vm-mlv-lex-global 'lex))
(make-local-variable 'vm-mlv-lex-global)
(list vm-mlv-lex-global
(symbol-value 'vm-mlv-lex-global)
(buffer-local-value 'vm-mlv-lex-global (current-buffer))
(local-variable-p 'vm-mlv-lex-global (current-buffer))
(buffer-local-boundp 'vm-mlv-lex-global (current-buffer))
(default-value 'vm-mlv-lex-global)))))
t))"#,
);
assert_eq!(result[0], "OK (lex global global t t global)");
}
#[test]
fn make_local_variable_after_compiled_preread_matches_gnu() {
crate::test_utils::init_test_tracing();
let result = bootstrap_eval_all(
r#"(let* ((fn (byte-compile
'(lambda ()
(unless delay-mode-hooks nil)
(make-local-variable 'delay-mode-hooks)
(let ((delay-mode-hooks t)) nil)
(list (local-variable-p 'delay-mode-hooks)
(assq 'delay-mode-hooks (buffer-local-variables))
delay-mode-hooks
(default-value 'delay-mode-hooks))))))
(with-temp-buffer
(funcall fn)))"#,
);
assert_eq!(result[0], "OK (t (delay-mode-hooks) nil nil)");
}
#[test]
fn make_local_variable_constant_and_keyword_payloads_match_oracle() {
crate::test_utils::init_test_tracing();
let result = bootstrap_eval_all(
r#"(list
(condition-case err (with-temp-buffer (make-local-variable nil)) (error err))
(condition-case err (with-temp-buffer (make-local-variable t)) (error err))
(condition-case err (with-temp-buffer (make-local-variable :vm-k)) (error err))
(condition-case err (with-temp-buffer (make-local-variable 1)) (error err)))"#,
);
assert_eq!(
result[0],
"OK ((setting-constant nil) (setting-constant t) (setting-constant :vm-k) (wrong-type-argument symbolp 1))"
);
}
#[test]
fn local_variable_p_returns_nil_when_not_local() {
crate::test_utils::init_test_tracing();
let results = eval_all(
r#"(get-buffer-create "test-buf")
(set-buffer "test-buf")
(local-variable-p 'nonexistent)"#,
);
assert_eq!(results[2], "OK nil");
}
#[test]
fn local_variable_p_reports_builtin_buffer_locals() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(
r#"(with-temp-buffer
(list (local-variable-p 'major-mode)
(local-variable-p 'mode-name)
(local-variable-p 'buffer-undo-list)))"#,
);
assert_eq!(results[0], "OK (t t t)");
}
#[test]
fn local_variable_p_enforces_buffer_and_symbol_contracts() {
crate::test_utils::init_test_tracing();
let results = eval_all(
r#"(list
(condition-case err (local-variable-p 'x) (error err))
(condition-case err (local-variable-p 'x nil) (error err))
(condition-case err (local-variable-p 'x (current-buffer)) (error err))
(condition-case err (local-variable-p 'x 1) (error err))
(condition-case err (local-variable-p 1 (current-buffer)) (error err))
(condition-case err (local-variable-p :vm-k (current-buffer)) (error err))
(condition-case err (local-variable-p nil (current-buffer)) (error err))
(condition-case err (local-variable-p t (current-buffer)) (error err))
(condition-case err (local-variable-p 'x (current-buffer) nil) (error err)))"#,
);
assert_eq!(
results[0],
"OK (nil nil nil (wrong-type-argument bufferp 1) (wrong-type-argument symbolp 1) nil nil nil (wrong-number-of-arguments local-variable-p 3))"
);
}
#[test]
fn local_and_buffer_local_predicates_follow_alias_resolution() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(
r#"(defvaralias 'vm-local-p-alias 'vm-local-p-base)
(let ((buf (get-buffer-create "vm-local-p-buf")))
(set-buffer buf)
(setq-local vm-local-p-alias 8)
(list (local-variable-p 'vm-local-p-alias buf)
(local-variable-p 'vm-local-p-base buf)
(buffer-local-boundp 'vm-local-p-alias buf)
(buffer-local-boundp 'vm-local-p-base buf)))"#,
);
assert_eq!(results[1], "OK (t t t t)");
}
#[test]
fn buffer_local_bound_p_matches_emacs_shape() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(
r#"(defvar neomacs-buffer-local-boundp-global 1)
(let ((buf (get-buffer-create "test-buf")))
(buffer-local-boundp 'neomacs-buffer-local-boundp-global buf))
(let ((buf (get-buffer-create "test-buf")))
(buffer-local-boundp 'neomacs-buffer-local-boundp-missing buf))
(let ((buf (get-buffer-create "test-buf-local")))
(set-buffer buf)
(make-local-variable 'neomacs-buffer-local-boundp-local)
(setq neomacs-buffer-local-boundp-local 7)
(buffer-local-boundp 'neomacs-buffer-local-boundp-local buf))
(let ((buf (get-buffer-create "dead-buf")))
(kill-buffer buf)
(buffer-local-boundp 'neomacs-buffer-local-boundp-global buf))
(condition-case err (buffer-local-boundp 1 (current-buffer)) (error (car err)))
(condition-case err (buffer-local-boundp 'x nil) (error (car err)))
(condition-case err (buffer-local-boundp 'x (current-buffer) nil)
(error (car err)))"#,
);
assert_eq!(results[1], "OK t");
assert_eq!(results[2], "OK nil");
assert_eq!(results[3], "OK t");
assert_eq!(results[4], r#"ERR (error ("No such buffer"))"#);
assert_eq!(results[5], "OK wrong-type-argument");
assert_eq!(results[6], "OK wrong-type-argument");
assert_eq!(results[7], "OK wrong-number-of-arguments");
}
#[test]
fn buffer_local_variables_include_default_entries() {
crate::test_utils::init_test_tracing();
let results = eval_all(
r#"(get-buffer-create "test-buf")
(set-buffer "test-buf")
(let ((locals (buffer-local-variables)))
(and (listp locals)
(assq 'buffer-read-only locals)))"#,
);
assert_eq!(results[2], "OK (buffer-read-only)");
}
#[test]
fn buffer_local_variables_argument_validation() {
crate::test_utils::init_test_tracing();
let results = eval_all(
r#"(condition-case err (buffer-local-variables 1) (error err))
(condition-case err (buffer-local-variables "test-buf") (error err))
(condition-case err (buffer-local-variables nil nil) (error err))"#,
);
assert_eq!(results[0], "OK (wrong-type-argument bufferp 1)");
assert_eq!(results[1], "OK (wrong-type-argument bufferp \"test-buf\")");
assert_eq!(
results[2],
"OK (wrong-number-of-arguments buffer-local-variables 2)"
);
}
#[test]
fn kill_local_variable_removes_binding() {
crate::test_utils::init_test_tracing();
let results = eval_all(
r#"(defvar my-var 42)
(get-buffer-create "test-buf")
(set-buffer "test-buf")
(make-local-variable 'my-var)
(local-variable-p 'my-var)
(kill-local-variable 'my-var)
(local-variable-p 'my-var)"#,
);
assert_eq!(results[4], "OK t");
assert_eq!(results[6], "OK nil");
}
#[test]
fn kill_local_variable_resolves_alias_bindings() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(
r#"(defvaralias 'vm-klv-alias 'vm-klv-base)
(with-temp-buffer
(set (make-local-variable 'vm-klv-alias) 3)
(kill-local-variable 'vm-klv-alias)
(list (local-variable-p 'vm-klv-alias)
(local-variable-p 'vm-klv-base)
(condition-case err
(symbol-value 'vm-klv-alias)
(error (car err)))))"#,
);
assert_eq!(results[1], "OK (nil nil void-variable)");
}
#[test]
fn kill_local_variable_accepts_keywords_like_oracle() {
crate::test_utils::init_test_tracing();
let result = bootstrap_eval_all(
r#"(list
(condition-case err (with-temp-buffer (kill-local-variable nil)) (error err))
(condition-case err (with-temp-buffer (kill-local-variable t)) (error err))
(condition-case err (with-temp-buffer (kill-local-variable :vm-k)) (error err))
(condition-case err (with-temp-buffer (kill-local-variable 1)) (error err)))"#,
);
assert_eq!(
result[0],
"OK (nil t :vm-k (wrong-type-argument symbolp 1))"
);
}
#[test]
fn kill_local_variable_triggers_makunbound_watcher_with_buffer_where() {
crate::test_utils::init_test_tracing();
let result = bootstrap_eval_all(
r#"(progn
(setq vm-klv-a-events nil)
(fset 'vm-klv-a-rec
(lambda (symbol newval operation where)
(setq vm-klv-a-events
(cons (list symbol newval operation (bufferp where) (buffer-live-p where))
vm-klv-a-events))))
(defvaralias 'vm-klv-a-alias 'vm-klv-a-base)
(add-variable-watcher 'vm-klv-a-base 'vm-klv-a-rec)
(with-temp-buffer
(set (make-local-variable 'vm-klv-a-alias) 7)
(kill-local-variable 'vm-klv-a-alias))
vm-klv-a-events)"#,
);
assert_eq!(
result[0],
"OK ((vm-klv-a-base nil makunbound t t) (vm-klv-a-base 7 set t t))"
);
}
#[test]
fn custom_set_variables_basic() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(
r#"(defvar my-var 1)
(custom-set-variables '(my-var 42))
(default-value 'my-var)"#,
);
assert_eq!(results[2], "OK 42");
}
#[test]
fn custom_set_variables_ignores_unknown_variable() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(
r#"(custom-set-variables '(my-var 42))
(condition-case err (default-value 'my-var) (error err))"#,
);
assert_eq!(results[1], "OK (void-variable my-var)");
}
#[test]
fn custom_set_faces_returns_nil() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(r#"(custom-set-faces '(default ((t (:height 120)))))"#);
assert_eq!(results[0], "OK nil");
}
#[test]
fn custom_set_faces_non_list_spec_errors() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(r#"(condition-case err (custom-set-faces 1) (error err))"#);
assert_eq!(results[0], r#"OK (error "Incompatible Custom theme spec")"#);
}
#[test]
fn custom_set_faces_requires_symbol_face_name() {
crate::test_utils::init_test_tracing();
let results =
bootstrap_eval_all(r#"(condition-case err (custom-set-faces '(1 2)) (error err))"#);
assert_eq!(results[0], "OK (wrong-type-argument symbolp 1)");
}
#[test]
fn custom_set_variables_errors_for_non_list_spec() {
crate::test_utils::init_test_tracing();
let results =
bootstrap_eval_all(r#"(condition-case err (custom-set-variables 1) (error err))"#);
assert_eq!(results[0], "OK (wrong-type-argument listp 1)");
}
#[test]
fn custom_set_variables_errors_for_non_symbol_variable_name() {
crate::test_utils::init_test_tracing();
let results =
bootstrap_eval_all(r#"(condition-case err (custom-set-variables '(1 2)) (error err))"#);
assert_eq!(results[0], "OK (wrong-type-argument symbolp 1)");
}
#[test]
fn defcustom_then_setq_default() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(
r#"(defcustom my-opt 10 "Opt." :type 'integer)
(setq-default my-opt 20)
my-opt"#,
);
assert_eq!(results[2], "OK 20");
}
#[test]
fn defvar_local_then_buffer_local_check() {
crate::test_utils::init_test_tracing();
let mut ev = bootstrap_context();
let _ = ev.eval_str(
r#"(defvar-local my-local-var 99)
(make-variable-buffer-local 'other-var)"#,
);
assert!(ev.obarray().is_buffer_local("my-local-var"));
let id_local = crate::emacs_core::intern::intern("my-local-var");
let id_other = crate::emacs_core::intern::intern("other-var");
assert!(ev.obarray().blv(id_local).map_or(false, |b| b.local_if_set));
assert!(ev.obarray().blv(id_other).map_or(false, |b| b.local_if_set));
}
#[test]
fn defcustom_keyword_args_ignored_gracefully() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(
r#"(defcustom my-var 5 "Docs." :type 'integer :group 'editing :initialize 'custom-initialize-default) my-var"#,
);
assert_eq!(results[1], "OK 5");
}
#[test]
fn defgroup_multiple_groups() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(
r#"(defgroup g1 nil "Group 1.")
(defgroup g2 nil "Group 2.")
(list (get 'g1 'group-documentation)
(get 'g2 'group-documentation))"#,
);
assert_eq!(results[2], "OK (\"Group 1.\" \"Group 2.\")");
}
#[test]
fn setq_default_works_on_new_variable() {
crate::test_utils::init_test_tracing();
let results = bootstrap_eval_all(r#"(setq-default new-var 100) new-var"#);
assert_eq!(results[1], "OK 100");
}