- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline - Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch - RTL: 351 Harbour-compatible functions - RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization - Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec) - HB_FUNC API: Full Harbour C API compatible Go bridge - Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT - Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST - Macro Compiler: Runtime AST parsing and evaluation - Debugger: TUI debugger with source display, breakpoints, stepping - FRB: Native + Pcode dual mode runtime binary - Tests: 13 packages ALL PASS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
454 lines
8.0 KiB
Go
454 lines
8.0 KiB
Go
// 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()
|
|
}
|