use super::*;
use crate::{
atom::{Atom, AtomTable},
term::{
binary::write_binary,
boxed::{
write_bigint, write_closure, write_cons, write_float, write_map, write_reference,
write_tuple,
},
shared_binary::{SharedBinary, write_proc_bin},
},
};
fn common_atoms() -> AtomTable {
AtomTable::with_common_atoms()
}
#[test]
fn exact_equality_compares_immediates_and_distinguishes_numeric_types() {
let mut float_heap = [0_u64; 2];
let float_one = write_float(&mut float_heap, 1.0).unwrap();
assert_eq!(Term::small_int(1), Term::small_int(1));
assert_ne!(Term::small_int(1), Term::small_int(2));
assert_eq!(Term::atom(Atom::OK), Term::atom(Atom::OK));
assert_ne!(Term::atom(Atom::OK), Term::atom(Atom::ERROR));
assert_ne!(Term::small_int(1), float_one);
assert_eq!(Term::pid(1), Term::pid(1));
assert_ne!(Term::NIL, Term::atom(Atom::NIL));
}
#[test]
fn numeric_equality_compares_bigints_by_value() {
let mut small_heap = [0_u64; 4];
let mut same_heap = [0_u64; 4];
let small_bigint = write_bigint(&mut small_heap, false, &[42]).unwrap();
let same_bigint = write_bigint(&mut same_heap, false, &[42]).unwrap();
assert!(numeric_eq(Term::small_int(42), small_bigint));
assert!(numeric_eq(small_bigint, same_bigint));
assert!(!exact_eq(Term::small_int(42), small_bigint));
}
#[test]
fn exact_equality_compares_boxed_terms_structurally() {
let mut left_heap = [0_u64; 3];
let mut right_heap = [0_u64; 3];
let mut different_heap = [0_u64; 3];
let left = write_tuple(&mut left_heap, &[Term::small_int(1), Term::small_int(2)]).unwrap();
let right = write_tuple(&mut right_heap, &[Term::small_int(1), Term::small_int(2)]).unwrap();
let different = write_tuple(
&mut different_heap,
&[Term::small_int(1), Term::small_int(3)],
)
.unwrap();
assert_eq!(left, right);
assert_ne!(left, different);
}
#[test]
fn numeric_equality_coerces_integer_float_pairs() {
let mut one_heap = [0_u64; 2];
let mut one_point_five_heap = [0_u64; 2];
let float_one = write_float(&mut one_heap, 1.0).unwrap();
let float_one_point_five = write_float(&mut one_point_five_heap, 1.5).unwrap();
assert!(numeric_eq(Term::small_int(1), float_one));
assert!(!numeric_eq(Term::small_int(1), float_one_point_five));
assert!(numeric_eq(Term::small_int(1), Term::small_int(1)));
assert!(numeric_eq(Term::atom(Atom::OK), Term::atom(Atom::OK)));
assert!(!numeric_eq(Term::small_int(1), Term::atom(Atom::OK)));
}
#[test]
fn beam_ordering_across_available_types_follows_rank_order() {
let mut ref_heap = [0_u64; 2];
let mut closure_heap = [0_u64; 7];
let mut tuple_heap = [0_u64; 1];
let mut map_heap = [0_u64; 2];
let mut list_heap = [0_u64; 2];
let mut binary_heap = [0_u64; 3];
let terms = [
Term::small_int(1),
Term::atom(Atom::OK),
write_reference(&mut ref_heap, 1).unwrap(),
write_closure(&mut closure_heap, Atom::OK, 0, 0, 1, 0, &[]).unwrap(),
Term::pid(1),
write_tuple(&mut tuple_heap, &[]).unwrap(),
write_map(&mut map_heap, &[], &[]).unwrap(),
Term::NIL,
write_cons(&mut list_heap, Term::small_int(1), Term::NIL).unwrap(),
write_binary(&mut binary_heap, b"a").unwrap(),
];
let atom_table = common_atoms();
for window in terms.windows(2) {
assert_eq!(cmp(window[0], window[1], &atom_table), Ordering::Less);
}
assert_eq!(
cmp(Term::small_int(1), Term::atom(Atom::OK), &atom_table),
Ordering::Less
);
assert_eq!(
cmp(Term::atom(Atom::OK), Term::pid(1), &atom_table),
Ordering::Less
);
}
#[test]
fn beam_ordering_compares_within_types() {
let mut tuple_one_heap = [0_u64; 2];
let mut tuple_two_heap = [0_u64; 3];
let mut tuple_a_heap = [0_u64; 3];
let mut tuple_b_heap = [0_u64; 3];
let tuple_one = write_tuple(&mut tuple_one_heap, &[Term::small_int(1)]).unwrap();
let tuple_two = write_tuple(
&mut tuple_two_heap,
&[Term::small_int(1), Term::small_int(2)],
)
.unwrap();
let tuple_a =
write_tuple(&mut tuple_a_heap, &[Term::small_int(1), Term::small_int(2)]).unwrap();
let tuple_b =
write_tuple(&mut tuple_b_heap, &[Term::small_int(1), Term::small_int(3)]).unwrap();
let atom_table = common_atoms();
assert_eq!(
cmp(Term::small_int(1), Term::small_int(2), &atom_table),
Ordering::Less
);
assert_ne!(
cmp(Term::small_int(2), Term::small_int(1), &atom_table),
Ordering::Less
);
assert_eq!(cmp(tuple_a, tuple_b, &atom_table), Ordering::Less);
assert_eq!(cmp(tuple_one, tuple_two, &atom_table), Ordering::Less);
}
#[test]
fn nested_structural_comparison_recurses_into_tuples_and_lists() {
let mut inner_left_heap = [0_u64; 3];
let mut inner_right_heap = [0_u64; 3];
let mut inner_diff_heap = [0_u64; 3];
let inner_left = write_tuple(
&mut inner_left_heap,
&[Term::small_int(1), Term::small_int(2)],
)
.unwrap();
let inner_right = write_tuple(
&mut inner_right_heap,
&[Term::small_int(1), Term::small_int(2)],
)
.unwrap();
let inner_diff = write_tuple(
&mut inner_diff_heap,
&[Term::small_int(1), Term::small_int(3)],
)
.unwrap();
let mut outer_left_heap = [0_u64; 3];
let mut outer_right_heap = [0_u64; 3];
let mut outer_diff_heap = [0_u64; 3];
let outer_left = write_tuple(&mut outer_left_heap, &[inner_left, Term::small_int(3)]).unwrap();
let outer_right =
write_tuple(&mut outer_right_heap, &[inner_right, Term::small_int(3)]).unwrap();
let outer_diff = write_tuple(&mut outer_diff_heap, &[inner_diff, Term::small_int(3)]).unwrap();
assert_eq!(outer_left, outer_right);
assert_ne!(outer_left, outer_diff);
let mut right_tail_left_heap = [0_u64; 2];
let mut right_tail_right_heap = [0_u64; 2];
let mut right_nested_left_heap = [0_u64; 2];
let mut right_nested_right_heap = [0_u64; 2];
let left_nested_tail =
write_cons(&mut right_tail_left_heap, Term::small_int(3), Term::NIL).unwrap();
let right_nested_tail =
write_cons(&mut right_tail_right_heap, Term::small_int(4), Term::NIL).unwrap();
let left_nested = write_cons(
&mut right_nested_left_heap,
Term::small_int(2),
left_nested_tail,
)
.unwrap();
let right_nested = write_cons(
&mut right_nested_right_heap,
Term::small_int(2),
right_nested_tail,
)
.unwrap();
let mut left_root_tail_heap = [0_u64; 2];
let mut right_root_tail_heap = [0_u64; 2];
let mut left_root_heap = [0_u64; 2];
let mut right_root_heap = [0_u64; 2];
let left_root_tail = write_cons(&mut left_root_tail_heap, left_nested, Term::NIL).unwrap();
let right_root_tail = write_cons(&mut right_root_tail_heap, right_nested, Term::NIL).unwrap();
let left_list = write_cons(&mut left_root_heap, Term::small_int(1), left_root_tail).unwrap();
let right_list = write_cons(&mut right_root_heap, Term::small_int(1), right_root_tail).unwrap();
let atom_table = common_atoms();
assert_eq!(cmp(left_list, right_list, &atom_table), Ordering::Less);
}
#[test]
fn proper_lists_compare_head_then_tail_iteratively() {
let mut left_tail_heap = [0_u64; 2];
let mut right_tail_heap = [0_u64; 2];
let mut left_head_heap = [0_u64; 2];
let mut right_head_heap = [0_u64; 2];
let left_tail = write_cons(&mut left_tail_heap, Term::small_int(2), Term::NIL).unwrap();
let right_tail = write_cons(&mut right_tail_heap, Term::small_int(3), Term::NIL).unwrap();
let left = write_cons(&mut left_head_heap, Term::small_int(1), left_tail).unwrap();
let right = write_cons(&mut right_head_heap, Term::small_int(1), right_tail).unwrap();
let atom_table = common_atoms();
assert_eq!(cmp(left, right, &atom_table), Ordering::Less);
}
#[test]
fn map_comparison_uses_sorted_key_order_then_values() {
let mut left_heap = [0_u64; 6];
let mut right_heap = [0_u64; 6];
let mut different_value_heap = [0_u64; 6];
let left = write_map(
&mut left_heap,
&[Term::small_int(2), Term::small_int(1)],
&[Term::atom(Atom::ERROR), Term::atom(Atom::OK)],
)
.unwrap();
let right = write_map(
&mut right_heap,
&[Term::small_int(1), Term::small_int(2)],
&[Term::atom(Atom::OK), Term::atom(Atom::ERROR)],
)
.unwrap();
let different_value = write_map(
&mut different_value_heap,
&[Term::small_int(1), Term::small_int(2)],
&[Term::atom(Atom::ERROR), Term::atom(Atom::ERROR)],
)
.unwrap();
let atom_table = common_atoms();
assert_eq!(left, right);
assert_eq!(cmp(right, different_value, &atom_table), Ordering::Greater);
}
#[test]
fn exact_equality_covers_boxed_numeric_reference_fun_map_and_binary_terms() {
let mut float_a_heap = [0_u64; 2];
let mut float_b_heap = [0_u64; 2];
let mut bigint_a_heap = [0_u64; 4];
let mut bigint_b_heap = [0_u64; 4];
let mut ref_a_heap = [0_u64; 2];
let mut ref_b_heap = [0_u64; 2];
let mut closure_a_heap = [0_u64; 8];
let mut closure_b_heap = [0_u64; 8];
let mut bin_a_heap = [0_u64; 3];
let mut bin_b_heap = [0_u64; 3];
let mut proc_bin_heap = [0_u64; 3];
let float_a = write_float(&mut float_a_heap, 2.5).unwrap();
let float_b = write_float(&mut float_b_heap, 2.5).unwrap();
let bigint_a = write_bigint(&mut bigint_a_heap, false, &[9]).unwrap();
let bigint_b = write_bigint(&mut bigint_b_heap, false, &[9]).unwrap();
let ref_a = write_reference(&mut ref_a_heap, 42).unwrap();
let ref_b = write_reference(&mut ref_b_heap, 42).unwrap();
let closure_a = write_closure(
&mut closure_a_heap,
Atom::OK,
1,
1,
1,
0,
&[Term::small_int(1)],
)
.unwrap();
let closure_b = write_closure(
&mut closure_b_heap,
Atom::OK,
1,
1,
1,
0,
&[Term::small_int(1)],
)
.unwrap();
let bin_a = write_binary(&mut bin_a_heap, b"ab").unwrap();
let bin_b = write_binary(&mut bin_b_heap, b"ab").unwrap();
let shared_bin = SharedBinary::new(b"ab".to_vec());
let proc_bin = write_proc_bin(&mut proc_bin_heap, &shared_bin).unwrap();
assert_eq!(float_a, float_b);
assert_eq!(bigint_a, bigint_b);
assert_eq!(ref_a, ref_b);
assert_eq!(closure_a, closure_b);
assert_eq!(bin_a, bin_b);
assert_eq!(bin_a, proc_bin);
}
#[test]
fn closure_comparison_includes_generation_and_unique_id_metadata() {
let mut closure_a_heap = [0_u64; 7];
let mut closure_b_heap = [0_u64; 7];
let mut closure_c_heap = [0_u64; 7];
let closure_a = write_closure(&mut closure_a_heap, Atom::OK, 1, 0, 1, 10, &[]).unwrap();
let closure_b = write_closure(&mut closure_b_heap, Atom::OK, 1, 0, 2, 10, &[]).unwrap();
let closure_c = write_closure(&mut closure_c_heap, Atom::OK, 1, 0, 1, 11, &[]).unwrap();
let atom_table = common_atoms();
assert_ne!(cmp(closure_a, closure_b, &atom_table), Ordering::Equal);
assert_ne!(cmp(closure_a, closure_c, &atom_table), Ordering::Equal);
}
#[test]
fn numeric_ordering_compares_bigints_by_value() {
let mut positive_small_heap = [0_u64; 4];
let mut positive_large_heap = [0_u64; 5];
let mut negative_small_heap = [0_u64; 4];
let mut negative_large_heap = [0_u64; 5];
let positive_small = write_bigint(&mut positive_small_heap, false, &[9]).unwrap();
let positive_large = write_bigint(&mut positive_large_heap, false, &[0, 1]).unwrap();
let negative_small = write_bigint(&mut negative_small_heap, true, &[9]).unwrap();
let negative_large = write_bigint(&mut negative_large_heap, true, &[0, 1]).unwrap();
let atom_table = common_atoms();
assert_eq!(
cmp(negative_large, negative_small, &atom_table),
Ordering::Less
);
assert_eq!(
cmp(negative_small, Term::small_int(0), &atom_table),
Ordering::Less
);
assert_eq!(
cmp(Term::small_int(9), positive_small, &atom_table),
Ordering::Equal
);
assert_eq!(
cmp(positive_small, Term::small_int(10), &atom_table),
Ordering::Less
);
assert_eq!(
cmp(positive_small, positive_large, &atom_table),
Ordering::Less
);
assert_eq!(
cmp(negative_small, positive_small, &atom_table),
Ordering::Less
);
}
#[test]
fn edge_cases_cover_empty_tuple_empty_list_and_atom_nil() {
let mut empty_tuple_heap = [0_u64; 1];
let empty_tuple = write_tuple(&mut empty_tuple_heap, &[]).unwrap();
assert_eq!(empty_tuple, empty_tuple);
assert_eq!(Term::NIL, Term::NIL);
assert_ne!(Term::NIL, Term::atom(Atom::NIL));
let atom_table = common_atoms();
assert_eq!(
cmp(Term::atom(Atom::NIL), Term::NIL, &atom_table),
Ordering::Less
);
}
#[test]
fn comparing_long_lists_does_not_stack_overflow() {
const LEN: usize = 10_000;
let mut left_heap = vec![[0_u64; 2]; LEN];
let mut right_heap = vec![[0_u64; 2]; LEN];
let mut left = Term::NIL;
let mut right = Term::NIL;
for index in (0..LEN).rev() {
left = write_cons(&mut left_heap[index], Term::small_int(index as i64), left).unwrap();
right = write_cons(&mut right_heap[index], Term::small_int(index as i64), right).unwrap();
}
let atom_table = common_atoms();
assert_eq!(left, right);
assert_eq!(cmp(left, right, &atom_table), Ordering::Equal);
}
fn ordered_atom_terms(table: &AtomTable) -> (Term, Term, Term) {
(
Term::atom(table.lookup("apple").unwrap()),
Term::atom(table.lookup("mango").unwrap()),
Term::atom(table.lookup("zebra").unwrap()),
)
}
fn intern_atoms(table: &AtomTable, names: &[&str]) {
for name in names {
table.intern(name);
}
}
fn assert_alphabetical_atom_order(table: &AtomTable) {
let (apple, mango, zebra) = ordered_atom_terms(table);
assert_eq!(cmp(apple, mango, table), Ordering::Less);
assert_eq!(cmp(mango, zebra, table), Ordering::Less);
assert_eq!(cmp(apple, zebra, table), Ordering::Less);
assert_eq!(cmp(mango, apple, table), Ordering::Greater);
assert_eq!(cmp(zebra, mango, table), Ordering::Greater);
assert_eq!(cmp(zebra, apple, table), Ordering::Greater);
assert_eq!(cmp(apple, apple, table), Ordering::Equal);
}
#[test]
fn atom_ordering_uses_names_when_interned_reverse_alphabetically() {
let table = AtomTable::new();
intern_atoms(&table, &["zebra", "mango", "apple"]);
assert_alphabetical_atom_order(&table);
}
#[test]
fn atom_ordering_uses_names_when_interned_alphabetically() {
let table = AtomTable::new();
intern_atoms(&table, &["apple", "mango", "zebra"]);
assert_alphabetical_atom_order(&table);
}
#[test]
fn sorting_atom_terms_is_stable_across_intern_sequences() {
let reverse_table = AtomTable::new();
intern_atoms(&reverse_table, &["zebra", "mango", "apple"]);
let (apple_reverse, mango_reverse, zebra_reverse) = ordered_atom_terms(&reverse_table);
let mut reverse_terms = [zebra_reverse, apple_reverse, mango_reverse];
reverse_terms.sort_by(|left, right| cmp(*left, *right, &reverse_table));
let forward_table = AtomTable::new();
intern_atoms(&forward_table, &["apple", "mango", "zebra"]);
let (apple_forward, mango_forward, zebra_forward) = ordered_atom_terms(&forward_table);
let mut forward_terms = [zebra_forward, apple_forward, mango_forward];
forward_terms.sort_by(|left, right| cmp(*left, *right, &forward_table));
let reverse_names: Vec<_> = reverse_terms
.iter()
.filter_map(|term| term.as_atom().and_then(|atom| reverse_table.resolve(atom)))
.collect();
let forward_names: Vec<_> = forward_terms
.iter()
.filter_map(|term| term.as_atom().and_then(|atom| forward_table.resolve(atom)))
.collect();
assert_eq!(reverse_names, ["apple", "mango", "zebra"]);
assert_eq!(forward_names, ["apple", "mango", "zebra"]);
}
#[test]
fn map_with_atom_keys_iterates_in_name_order() {
let table = AtomTable::new();
intern_atoms(&table, &["zebra", "apple"]);
let apple = Term::atom(table.lookup("apple").unwrap());
let zebra = Term::atom(table.lookup("zebra").unwrap());
let mut entries = [(zebra, Term::small_int(1)), (apple, Term::small_int(2))];
entries.sort_by(|(left, _), (right, _)| cmp(*left, *right, &table));
let keys: Vec<_> = entries.iter().map(|(key, _)| *key).collect();
let values: Vec<_> = entries.iter().map(|(_, value)| *value).collect();
let mut heap = [0_u64; 6];
let map_term = write_map(&mut heap, &keys, &values).unwrap();
let map = Map::new(map_term).unwrap();
assert_eq!(map.key(0).and_then(Term::as_atom), apple.as_atom());
assert_eq!(map.key(1).and_then(Term::as_atom), zebra.as_atom());
}