Files
five/hbrtl/goroutine_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

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()
}