Files
five/hbrt/thread_test.go
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- 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>
2026-03-31 09:41:50 +09:00

277 lines
5.2 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
package hbrt
import "testing"
func newTestThread() *Thread {
vm := NewVM()
return vm.NewThread()
}
// --- Stack operations ---
func TestStackPushPop(t *testing.T) {
th := newTestThread()
th.push(MakeInt(10))
th.push(MakeInt(20))
th.push(MakeInt(30))
if th.sp != 3 {
t.Fatalf("sp = %d, want 3", th.sp)
}
v := th.pop()
if v.AsInt() != 30 {
t.Errorf("pop = %d, want 30", v.AsInt())
}
v = th.pop()
if v.AsInt() != 20 {
t.Errorf("pop = %d, want 20", v.AsInt())
}
v = th.pop()
if v.AsInt() != 10 {
t.Errorf("pop = %d, want 10", v.AsInt())
}
}
func TestStackPeek(t *testing.T) {
th := newTestThread()
th.push(MakeInt(42))
v := th.peek()
if v.AsInt() != 42 {
t.Errorf("peek = %d, want 42", v.AsInt())
}
if th.sp != 1 {
t.Error("peek should not change sp")
}
}
func TestStackDup(t *testing.T) {
th := newTestThread()
th.PushInt(99)
th.Dup()
if th.sp != 2 {
t.Fatalf("sp = %d, want 2", th.sp)
}
a := th.pop()
b := th.pop()
if a.AsInt() != 99 || b.AsInt() != 99 {
t.Error("Dup should duplicate top")
}
}
// --- Frame and locals ---
func TestFrameLocals(t *testing.T) {
th := newTestThread()
// Simulate: FUNCTION Foo(a, b) with LOCAL c
th.push(MakeInt(10)) // arg a
th.push(MakeInt(20)) // arg b
th.PendingParams2(2) // tell Frame how many args are on stack
th.Frame(2, 1) // 2 params, 1 local
// Param a = local 1
if th.Local(1).AsInt() != 10 {
t.Errorf("local 1 = %d, want 10", th.Local(1).AsInt())
}
// Param b = local 2
if th.Local(2).AsInt() != 20 {
t.Errorf("local 2 = %d, want 20", th.Local(2).AsInt())
}
// Local c = local 3 (NIL)
if !th.Local(3).IsNil() {
t.Error("local 3 should be NIL")
}
// Set local 3
th.SetLocal(3, MakeString("hello"))
if th.Local(3).AsString() != "hello" {
t.Error("local 3 should be 'hello'")
}
th.EndProc()
}
func TestLocalSetInt(t *testing.T) {
th := newTestThread()
th.Frame(0, 2)
th.LocalSetInt(1, 42)
th.LocalSetInt(2, -99)
if th.Local(1).AsInt() != 42 {
t.Errorf("local 1 = %d, want 42", th.Local(1).AsInt())
}
if th.Local(2).AsInt() != -99 {
t.Errorf("local 2 = %d, want -99", th.Local(2).AsInt())
}
th.EndProc()
}
func TestPushPopLocal(t *testing.T) {
th := newTestThread()
th.Frame(0, 2)
th.LocalSetInt(1, 100)
th.PushLocal(1)
if th.peek().AsInt() != 100 {
t.Error("PushLocal should push local value")
}
th.PushInt(200)
th.PopLocal(2)
if th.Local(2).AsInt() != 200 {
t.Error("PopLocal should set local from stack")
}
th.EndProc()
}
// --- Return value ---
func TestRetValue(t *testing.T) {
th := newTestThread()
th.Frame(0, 0)
th.PushInt(42)
th.RetValue()
if th.GetRetValue().AsInt() != 42 {
t.Errorf("RetValue = %d, want 42", th.GetRetValue().AsInt())
}
th.EndProc()
}
func TestRetInt(t *testing.T) {
th := newTestThread()
th.Frame(0, 0)
th.RetInt(999)
if th.GetRetValue().AsLong() != 999 {
t.Errorf("RetInt = %d, want 999", th.GetRetValue().AsLong())
}
th.EndProc()
}
// --- Function call ---
func TestFunctionCall(t *testing.T) {
vm := NewVM()
// Register a simple function: FUNCTION Double(n) → n * 2
mod := NewModule("TEST",
Sym("DOUBLE", FsPublic|FsLocal, func(th *Thread) {
th.Frame(1, 0)
defer th.EndProc()
n := th.Local(1).AsNumInt()
th.RetInt(n * 2)
}),
)
vm.RegisterModule(mod)
th := vm.NewThread()
th.Frame(0, 0)
// Call: Double(21)
th.PushSymbol(mod.At(0))
th.PushNil()
th.PushInt(21)
th.Function(1)
result := th.pop()
if result.AsLong() != 42 {
t.Errorf("Double(21) = %d, want 42", result.AsLong())
}
th.EndProc()
}
func TestNestedFunctionCall(t *testing.T) {
vm := NewVM()
// FUNCTION Add(a, b) → a + b (simplified)
addSym := Sym("ADD", FsPublic|FsLocal, func(th *Thread) {
th.Frame(2, 0)
defer th.EndProc()
a := th.Local(1).AsNumInt()
b := th.Local(2).AsNumInt()
th.RetInt(a + b)
})
// FUNCTION Main() → Add(10, Add(20, 30))
mainSym := Sym("MAIN", FsPublic|FsLocal|FsFirst, func(th *Thread) {
th.Frame(0, 0)
defer th.EndProc()
// Inner call: Add(20, 30)
th.PushSymbol(vm.FindSymbol("ADD"))
th.PushNil()
th.PushInt(20)
th.PushInt(30)
th.Function(2) // → 50 on stack
// Outer call: Add(10, <result>)
innerResult := th.pop()
th.PushSymbol(vm.FindSymbol("ADD"))
th.PushNil()
th.PushInt(10)
th.PushValue(innerResult)
th.Function(2) // → 60 on stack
th.RetValue()
})
mod := NewModule("TEST", addSym, mainSym)
vm.RegisterModule(mod)
result := vm.Run("MAIN")
if result.AsLong() != 60 {
t.Errorf("Main() = %d, want 60", result.AsLong())
}
}
// --- Static variables ---
func TestStaticVariables(t *testing.T) {
th := newTestThread()
statics := []Value{MakeInt(0), MakeString("hello")}
th.RegisterStatics("MOD1", statics)
th.Frame(0, 0)
th.PushStatic("MOD1", 1)
if th.pop().AsInt() != 0 {
t.Error("static 1 should be 0")
}
th.PushInt(42)
th.PopStatic("MOD1", 1)
th.PushStatic("MOD1", 1)
if th.pop().AsInt() != 42 {
t.Error("static 1 should be 42 after PopStatic")
}
th.EndProc()
}
// --- Panic recovery ---
func TestStackUnderflowPanic(t *testing.T) {
th := newTestThread()
defer func() {
r := recover()
if r == nil {
t.Error("expected panic on stack underflow")
}
}()
th.pop() // should panic
}