// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. package hbrt import "testing" // --- Equal --- func TestEqualNilNil(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushNil() th.PushNil() th.Equal() if !th.pop().AsBool() { t.Error("NIL == NIL should be true") } th.EndProc() } func TestEqualNilOther(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushNil() th.PushInt(0) th.Equal() if th.pop().AsBool() { t.Error("NIL == 0 should be false") } th.EndProc() } func TestEqualIntInt(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushInt(42) th.PushInt(42) th.Equal() if !th.pop().AsBool() { t.Error("42 == 42 should be true") } th.PushInt(42) th.PushInt(99) th.Equal() if th.pop().AsBool() { t.Error("42 == 99 should be false") } th.EndProc() } func TestEqualIntDouble(t *testing.T) { th := newTestThread() th.Frame(0, 0) // Cross-type numeric: Int == Double th.PushInt(42) th.PushDouble(42.0, 4, 1) th.Equal() if !th.pop().AsBool() { t.Error("42 == 42.0 should be true") } th.EndProc() } func TestEqualString(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushString("hello") th.PushString("hello") th.Equal() if !th.pop().AsBool() { t.Error(`"hello" == "hello" should be true`) } th.PushString("hello") th.PushString("world") th.Equal() if th.pop().AsBool() { t.Error(`"hello" == "world" should be false`) } th.EndProc() } func TestEqualLogical(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushBool(true) th.PushBool(true) th.Equal() if !th.pop().AsBool() { t.Error(".T. == .T. should be true") } th.PushBool(true) th.PushBool(false) th.Equal() if th.pop().AsBool() { t.Error(".T. == .F. should be false") } th.EndProc() } func TestEqualDate(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.push(MakeDate(2461033)) th.push(MakeDate(2461033)) th.Equal() if !th.pop().AsBool() { t.Error("same date should be equal") } th.push(MakeDate(2461033)) th.push(MakeDate(2461034)) th.Equal() if th.pop().AsBool() { t.Error("different dates should not be equal") } th.EndProc() } func TestEqualTimestamp(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.push(MakeTimestamp(2461033, 43200000)) th.push(MakeTimestamp(2461033, 43200000)) th.Equal() if !th.pop().AsBool() { t.Error("same timestamp should be equal") } // Same date, different time th.push(MakeTimestamp(2461033, 43200000)) th.push(MakeTimestamp(2461033, 43200001)) th.Equal() if th.pop().AsBool() { t.Error("different time should not be equal") } th.EndProc() } func TestEqualArrayIdentity(t *testing.T) { th := newTestThread() th.Frame(0, 0) a := MakeArray(3) th.push(a) th.push(a) // same pointer th.Equal() if !th.pop().AsBool() { t.Error("same array should be equal (pointer identity)") } th.push(a) th.push(MakeArray(3)) // different pointer th.Equal() if th.pop().AsBool() { t.Error("different arrays should not be equal") } th.EndProc() } // --- NotEqual --- func TestNotEqual(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushInt(1) th.PushInt(2) th.NotEqual() if !th.pop().AsBool() { t.Error("1 != 2 should be true") } th.PushInt(5) th.PushInt(5) th.NotEqual() if th.pop().AsBool() { t.Error("5 != 5 should be false") } th.EndProc() } // --- Relational --- func TestLessInt(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushInt(1) th.PushInt(2) th.Less() if !th.pop().AsBool() { t.Error("1 < 2 should be true") } th.PushInt(2) th.PushInt(1) th.Less() if th.pop().AsBool() { t.Error("2 < 1 should be false") } th.PushInt(1) th.PushInt(1) th.Less() if th.pop().AsBool() { t.Error("1 < 1 should be false") } th.EndProc() } func TestLessEqualInt(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushInt(1) th.PushInt(1) th.LessEqual() if !th.pop().AsBool() { t.Error("1 <= 1 should be true") } th.PushInt(2) th.PushInt(1) th.LessEqual() if th.pop().AsBool() { t.Error("2 <= 1 should be false") } th.EndProc() } func TestGreater(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushInt(10) th.PushInt(5) th.Greater() if !th.pop().AsBool() { t.Error("10 > 5 should be true") } th.EndProc() } func TestGreaterEqual(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushInt(5) th.PushInt(5) th.GreaterEqual() if !th.pop().AsBool() { t.Error("5 >= 5 should be true") } th.EndProc() } func TestLessString(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushString("abc") th.PushString("def") th.Less() if !th.pop().AsBool() { t.Error(`"abc" < "def" should be true`) } th.PushString("def") th.PushString("abc") th.Less() if th.pop().AsBool() { t.Error(`"def" < "abc" should be false`) } th.EndProc() } func TestLessDate(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.push(MakeDate(2461033)) th.push(MakeDate(2461034)) th.Less() if !th.pop().AsBool() { t.Error("earlier date < later date should be true") } th.EndProc() } func TestLessTimestamp(t *testing.T) { th := newTestThread() th.Frame(0, 0) // Same day, earlier time th.push(MakeTimestamp(2461033, 10000)) th.push(MakeTimestamp(2461033, 20000)) th.Less() if !th.pop().AsBool() { t.Error("earlier timestamp should be less") } th.EndProc() } func TestLessTypeMismatch(t *testing.T) { th := newTestThread() th.Frame(0, 0) defer func() { r := recover() if r == nil { t.Error("expected panic on type mismatch comparison") } }() th.PushInt(1) th.PushString("hello") th.Less() // should panic } // --- Logical operators --- func TestNot(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushBool(true) th.Not() if th.pop().AsBool() { t.Error("NOT .T. should be .F.") } th.PushBool(false) th.Not() if !th.pop().AsBool() { t.Error("NOT .F. should be .T.") } th.EndProc() } func TestAnd(t *testing.T) { th := newTestThread() th.Frame(0, 0) tests := []struct { a, b, want bool }{ {true, true, true}, {true, false, false}, {false, true, false}, {false, false, false}, } for _, tt := range tests { th.PushBool(tt.a) th.PushBool(tt.b) th.And() if th.pop().AsBool() != tt.want { t.Errorf("%v .AND. %v should be %v", tt.a, tt.b, tt.want) } } th.EndProc() } func TestOr(t *testing.T) { th := newTestThread() th.Frame(0, 0) tests := []struct { a, b, want bool }{ {true, true, true}, {true, false, true}, {false, true, true}, {false, false, false}, } for _, tt := range tests { th.PushBool(tt.a) th.PushBool(tt.b) th.Or() if th.pop().AsBool() != tt.want { t.Errorf("%v .OR. %v should be %v", tt.a, tt.b, tt.want) } } th.EndProc() } // --- PopLogical --- func TestPopLogical(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushBool(true) if !th.PopLogical() { t.Error("PopLogical(.T.) should be true") } th.PushBool(false) if th.PopLogical() { t.Error("PopLogical(.F.) should be false") } th.EndProc() } func TestPopLogicalPanicOnNonBool(t *testing.T) { th := newTestThread() th.Frame(0, 0) defer func() { if r := recover(); r == nil { t.Error("expected panic on PopLogical with non-boolean") } }() th.PushInt(42) th.PopLogical() } // --- EqualIntIs optimization --- func TestEqualIntIs(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushInt(10) if !th.EqualIntIs(10) { t.Error("10 == 10 should be true") } th.PushInt(10) if th.EqualIntIs(20) { t.Error("10 == 20 should be false") } th.PushDouble(10.0, 4, 1) if !th.EqualIntIs(10) { t.Error("10.0 == 10 should be true") } th.EndProc() } // --- Cross-type numeric comparison --- func TestCompareIntVsLong(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushInt(42) th.PushLong(42) th.Equal() if !th.pop().AsBool() { t.Error("Int(42) == Long(42) should be true") } th.EndProc() } func TestCompareIntVsDouble(t *testing.T) { th := newTestThread() th.Frame(0, 0) th.PushInt(5) th.PushDouble(10.0, 4, 1) th.Less() if !th.pop().AsBool() { t.Error("Int(5) < Double(10.0) should be true") } th.EndProc() }