- 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>
232 lines
4.1 KiB
Go
232 lines
4.1 KiB
Go
package hbrtl
|
|
|
|
import (
|
|
"five/hbrt"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestChannelSendReceive(t *testing.T) {
|
|
_, th := setupVM()
|
|
|
|
// Create channel
|
|
th.PendingParams2(0)
|
|
ChannelFunc(th)
|
|
ch := th.GetRetValue()
|
|
if ch.AsChannel() == nil {
|
|
t.Fatal("CHANNEL() did not return channel")
|
|
}
|
|
|
|
// Send in goroutine
|
|
go func() {
|
|
_, th2 := setupVM()
|
|
th2.PushValue(ch)
|
|
th2.PushString("hello from goroutine")
|
|
th2.PendingParams2(2)
|
|
ChSend(th2)
|
|
}()
|
|
|
|
// Receive
|
|
th.PushValue(ch)
|
|
th.PendingParams2(1)
|
|
ChReceive(th)
|
|
result := th.GetRetValue().AsString()
|
|
if result != "hello from goroutine" {
|
|
t.Errorf("CHRECEIVE = %q, want 'hello from goroutine'", result)
|
|
}
|
|
}
|
|
|
|
func TestChannelBuffered(t *testing.T) {
|
|
_, th := setupVM()
|
|
|
|
// Buffered channel (size 3)
|
|
th.PushInt(3)
|
|
th.PendingParams2(1)
|
|
ChannelFunc(th)
|
|
ch := th.GetRetValue()
|
|
|
|
// Send 3 items without blocking
|
|
for i := 1; i <= 3; i++ {
|
|
th.PushValue(ch)
|
|
th.PushInt(i)
|
|
th.PendingParams2(2)
|
|
ChSend(th)
|
|
}
|
|
|
|
// Receive all 3
|
|
for i := 1; i <= 3; i++ {
|
|
th.PushValue(ch)
|
|
th.PendingParams2(1)
|
|
ChReceive(th)
|
|
r := th.GetRetValue().AsInt()
|
|
if r != i {
|
|
t.Errorf("CHRECEIVE[%d] = %d, want %d", i, r, i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWaitGroup(t *testing.T) {
|
|
_, th := setupVM()
|
|
|
|
// Create WaitGroup(3)
|
|
th.PushInt(3)
|
|
th.PendingParams2(1)
|
|
WaitGroupFunc(th)
|
|
wg := th.GetRetValue()
|
|
if wg.AsWaitGroup() == nil {
|
|
t.Fatal("WAITGROUP() did not return WaitGroup")
|
|
}
|
|
|
|
var counter int64
|
|
|
|
// Launch 3 goroutines
|
|
for i := 0; i < 3; i++ {
|
|
go func() {
|
|
atomic.AddInt64(&counter, 1)
|
|
_, th2 := setupVM()
|
|
th2.PushValue(wg)
|
|
th2.PendingParams2(1)
|
|
WgDone(th2)
|
|
}()
|
|
}
|
|
|
|
// Wait
|
|
th.PushValue(wg)
|
|
th.PendingParams2(1)
|
|
WgWait(th)
|
|
|
|
if atomic.LoadInt64(&counter) != 3 {
|
|
t.Errorf("counter = %d, want 3", counter)
|
|
}
|
|
}
|
|
|
|
func TestMutex(t *testing.T) {
|
|
_, th := setupVM()
|
|
|
|
// Create mutex
|
|
th.PendingParams2(0)
|
|
MutexFunc(th)
|
|
mtx := th.GetRetValue()
|
|
if mtx.AsMutex() == nil {
|
|
t.Fatal("MUTEX() did not return Mutex")
|
|
}
|
|
|
|
var counter int64
|
|
wg := hbrt.MakeWaitGroup(10)
|
|
|
|
for i := 0; i < 10; i++ {
|
|
go func() {
|
|
_, th2 := setupVM()
|
|
// Lock
|
|
th2.PushValue(mtx)
|
|
th2.PendingParams2(1)
|
|
LockFunc(th2)
|
|
|
|
atomic.AddInt64(&counter, 1)
|
|
|
|
// Unlock
|
|
th2.PushValue(mtx)
|
|
th2.PendingParams2(1)
|
|
UnlockFunc(th2)
|
|
|
|
wg.AsWaitGroup().WG.Done()
|
|
}()
|
|
}
|
|
|
|
wg.AsWaitGroup().WG.Wait()
|
|
if atomic.LoadInt64(&counter) != 10 {
|
|
t.Errorf("mutex counter = %d, want 10", counter)
|
|
}
|
|
}
|
|
|
|
func TestSleep(t *testing.T) {
|
|
_, th := setupVM()
|
|
start := time.Now()
|
|
|
|
// Sleep 0.1 seconds
|
|
th.PushDouble(0.1, 3, 1)
|
|
th.PendingParams2(1)
|
|
SleepFunc(th)
|
|
|
|
elapsed := time.Since(start)
|
|
if elapsed < 80*time.Millisecond {
|
|
t.Errorf("SLEEP(0.1) took only %v", elapsed)
|
|
}
|
|
if elapsed > 300*time.Millisecond {
|
|
t.Errorf("SLEEP(0.1) took too long: %v", elapsed)
|
|
}
|
|
}
|
|
|
|
func TestGoFuncWithBlock(t *testing.T) {
|
|
vm, th := setupVM()
|
|
_ = vm
|
|
|
|
ch := hbrt.MakeChannel(1)
|
|
|
|
// Create a block that sends to channel
|
|
blk := hbrt.MakeBlock(func(t2 *hbrt.Thread) {
|
|
t2.Frame(1, 0)
|
|
defer t2.EndProc()
|
|
// param 1 is the channel
|
|
chVal := t2.Local(1)
|
|
c := chVal.AsChannel()
|
|
if c != nil {
|
|
c.Send(hbrt.MakeString("from block goroutine"))
|
|
}
|
|
t2.RetNil()
|
|
}, 0)
|
|
|
|
// GO(block, ch)
|
|
th.PushValue(blk)
|
|
th.PushValue(ch)
|
|
th.PendingParams2(2)
|
|
GoFunc(th)
|
|
|
|
// Receive result
|
|
result := ch.AsChannel().Receive()
|
|
if result.AsString() != "from block goroutine" {
|
|
t.Errorf("GO block result = %q", result.AsString())
|
|
}
|
|
}
|
|
|
|
func TestProducerConsumer(t *testing.T) {
|
|
// Classic producer-consumer pattern
|
|
ch := hbrt.MakeChannel(5)
|
|
wg := hbrt.MakeWaitGroup(1)
|
|
|
|
// Producer: send 10 items
|
|
go func() {
|
|
for i := 1; i <= 10; i++ {
|
|
ch.AsChannel().Send(hbrt.MakeInt(i))
|
|
}
|
|
ch.AsChannel().Close()
|
|
}()
|
|
|
|
// Consumer: receive until closed
|
|
go func() {
|
|
sum := 0
|
|
for {
|
|
v, ok := ch.AsChannel().TryReceive()
|
|
if !ok {
|
|
// Channel might not have data yet, try blocking
|
|
time.Sleep(time.Millisecond)
|
|
continue
|
|
}
|
|
if v.IsNil() {
|
|
break
|
|
}
|
|
sum += v.AsInt()
|
|
if sum >= 55 { // 1+2+...+10 = 55
|
|
break
|
|
}
|
|
}
|
|
if sum != 55 {
|
|
t.Errorf("consumer sum = %d, want 55", sum)
|
|
}
|
|
wg.AsWaitGroup().WG.Done()
|
|
}()
|
|
|
|
wg.AsWaitGroup().WG.Wait()
|
|
}
|