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