Files
five/hbrt/gobridge_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

532 lines
14 KiB
Go

package hbrt
import (
"fmt"
"reflect"
"strings"
"testing"
)
// ===== Test helper Go functions for type conversion =====
func goIdentityString(s string) string { return s }
func goIdentityInt(n int) int { return n }
func goIdentityInt64(n int64) int64 { return n }
func goIdentityFloat64(f float64) float64 { return f }
func goIdentityBool(b bool) bool { return b }
func goIdentityBytes(b []byte) []byte { return b }
func goReturnSlice() []string { return []string{"a", "b", "c"} }
func goReturnIntSlice() []int { return []int{10, 20, 30} }
func goReturnMap() map[string]interface{} {
return map[string]interface{}{"name": "Charles", "age": 30, "active": true}
}
func goMultiReturn(s string) (string, error) { return strings.ToUpper(s), nil }
func goMultiReturnErr() (string, error) { return "", fmt.Errorf("test error") }
func goSumVariadic(nums ...int) int {
sum := 0
for _, n := range nums {
sum += n
}
return sum
}
func goSwapStrings(a, b string) (string, string) { return b, a }
func goMakeStruct() *testStruct { return &testStruct{Name: "Five", Value: 42} }
type testStruct struct {
Name string
Value int
}
func (ts *testStruct) GetName() string { return ts.Name }
func (ts *testStruct) GetValue() int { return ts.Value }
func (ts *testStruct) SetName(n string) { ts.Name = n }
func (ts *testStruct) SetValue(v int) { ts.Value = v }
func (ts *testStruct) Add(n int) int { return ts.Value + n }
func (ts *testStruct) String() string { return fmt.Sprintf("%s=%d", ts.Name, ts.Value) }
// ===== PRG → Go: Value to Go type conversion =====
func TestPRGToGo_String(t *testing.T) {
v := MakeString("hello five")
results := GoCallFunc(goIdentityString, v)
if len(results) != 1 || results[0].AsString() != "hello five" {
t.Errorf("string: got %v", results)
}
}
func TestPRGToGo_EmptyString(t *testing.T) {
v := MakeString("")
results := GoCallFunc(goIdentityString, v)
if len(results) != 1 || results[0].AsString() != "" {
t.Errorf("empty string: got %v", results)
}
}
func TestPRGToGo_Int(t *testing.T) {
v := MakeInt(42)
results := GoCallFunc(goIdentityInt, v)
if len(results) != 1 || results[0].AsInt() != 42 {
t.Errorf("int: got %v", results)
}
}
func TestPRGToGo_NegativeInt(t *testing.T) {
v := MakeInt(-100)
results := GoCallFunc(goIdentityInt, v)
if len(results) != 1 || results[0].AsInt() != -100 {
t.Errorf("neg int: got %v", results)
}
}
func TestPRGToGo_Int64(t *testing.T) {
v := MakeLong(9999999999)
results := GoCallFunc(goIdentityInt64, v)
if len(results) != 1 || results[0].AsLong() != 9999999999 {
t.Errorf("int64: got %v", results)
}
}
func TestPRGToGo_Float64(t *testing.T) {
v := MakeDouble(3.14159, 0, 5)
results := GoCallFunc(goIdentityFloat64, v)
if len(results) != 1 {
t.Fatalf("float64: no result")
}
diff := results[0].AsDouble() - 3.14159
if diff > 0.00001 || diff < -0.00001 {
t.Errorf("float64: got %v", results[0].AsDouble())
}
}
func TestPRGToGo_BoolTrue(t *testing.T) {
v := MakeBool(true)
results := GoCallFunc(goIdentityBool, v)
if len(results) != 1 || !results[0].AsBool() {
t.Errorf("bool true: got %v", results)
}
}
func TestPRGToGo_BoolFalse(t *testing.T) {
v := MakeBool(false)
results := GoCallFunc(goIdentityBool, v)
if len(results) != 1 || results[0].AsBool() {
t.Errorf("bool false: got %v", results)
}
}
func TestPRGToGo_StringAsBytes(t *testing.T) {
v := MakeString("binary data")
results := GoCallFunc(goIdentityBytes, v)
if len(results) != 1 || results[0].AsString() != "binary data" {
t.Errorf("bytes: got %v", results)
}
}
// ===== Go → PRG: Go return to Value conversion =====
func TestGoToPRG_StringSlice(t *testing.T) {
results := GoCallFunc(goReturnSlice)
if len(results) != 1 {
t.Fatalf("slice: expected 1 result, got %d", len(results))
}
v := results[0]
if !v.IsArray() {
t.Fatalf("slice: expected array, got %v", v)
}
arr := v.AsArray()
if len(arr.Items) != 3 {
t.Fatalf("slice: expected 3 items, got %d", len(arr.Items))
}
if arr.Items[0].AsString() != "a" || arr.Items[1].AsString() != "b" || arr.Items[2].AsString() != "c" {
t.Errorf("slice: got %v %v %v", arr.Items[0], arr.Items[1], arr.Items[2])
}
}
func TestGoToPRG_IntSlice(t *testing.T) {
results := GoCallFunc(goReturnIntSlice)
if len(results) != 1 || !results[0].IsArray() {
t.Fatalf("int slice: expected array")
}
arr := results[0].AsArray()
if len(arr.Items) != 3 {
t.Fatalf("int slice: expected 3 items")
}
if arr.Items[0].AsInt() != 10 || arr.Items[1].AsInt() != 20 || arr.Items[2].AsInt() != 30 {
t.Errorf("int slice: got %d %d %d", arr.Items[0].AsInt(), arr.Items[1].AsInt(), arr.Items[2].AsInt())
}
}
func TestGoToPRG_Map(t *testing.T) {
results := GoCallFunc(goReturnMap)
if len(results) != 1 {
t.Fatalf("map: expected 1 result")
}
v := results[0]
if !v.IsHash() {
t.Fatalf("map: expected hash, got type=%v", reflect.TypeOf(v))
}
h := v.AsHash()
// Check keys exist (order may vary)
found := map[string]bool{}
for i, k := range h.Keys {
key := k.AsString()
found[key] = true
switch key {
case "name":
if h.Values[i].AsString() != "Charles" {
t.Errorf("map name: got %v", h.Values[i])
}
case "age":
if h.Values[i].AsInt() != 30 {
t.Errorf("map age: got %v", h.Values[i])
}
case "active":
if !h.Values[i].AsBool() {
t.Errorf("map active: got %v", h.Values[i])
}
}
}
if !found["name"] || !found["age"] || !found["active"] {
t.Errorf("map: missing keys, found=%v", found)
}
}
// ===== Multi-return =====
func TestGoToPRG_MultiReturn(t *testing.T) {
v := MakeString("hello")
results := GoCallFunc(goMultiReturn, v)
if len(results) != 2 {
t.Fatalf("multi: expected 2 results, got %d", len(results))
}
if results[0].AsString() != "HELLO" {
t.Errorf("multi[0]: got %q", results[0].AsString())
}
// error is nil → should be NIL value
if !results[1].IsNil() {
t.Errorf("multi[1]: expected NIL, got %v", results[1])
}
}
func TestGoToPRG_MultiReturnError(t *testing.T) {
results := GoCallFunc(goMultiReturnErr)
if len(results) != 2 {
t.Fatalf("multi err: expected 2 results, got %d", len(results))
}
if results[0].AsString() != "" {
t.Errorf("multi err[0]: got %q", results[0].AsString())
}
// error is non-nil → should be string
if results[1].IsNil() {
t.Errorf("multi err[1]: expected error string, got NIL")
}
if results[1].AsString() != "test error" {
t.Errorf("multi err[1]: got %q", results[1].AsString())
}
}
func TestGoToPRG_SwapStrings(t *testing.T) {
a := MakeString("first")
b := MakeString("second")
results := GoCallFunc(goSwapStrings, a, b)
if len(results) != 2 {
t.Fatalf("swap: expected 2 results, got %d", len(results))
}
if results[0].AsString() != "second" || results[1].AsString() != "first" {
t.Errorf("swap: got %q, %q", results[0].AsString(), results[1].AsString())
}
}
// ===== Go Object wrapping and method calls =====
func TestGoObject_Wrap(t *testing.T) {
obj := &testStruct{Name: "Five", Value: 42}
v := WrapGo(obj)
if !v.IsPointer() {
t.Fatalf("wrap: expected pointer")
}
if !IsGoObject(v) {
t.Fatalf("wrap: IsGoObject should be true")
}
}
func TestGoObject_WrapNil(t *testing.T) {
v := WrapGo(nil)
if !v.IsNil() {
t.Errorf("wrap nil: expected NIL")
}
}
func TestGoObject_MethodCall(t *testing.T) {
obj := &testStruct{Name: "Five", Value: 42}
v := WrapGo(obj)
// GetName()
results := GoCall(v, "GetName")
if len(results) != 1 || results[0].AsString() != "Five" {
t.Errorf("GetName: got %v", results)
}
// GetValue()
results = GoCall(v, "GetValue")
if len(results) != 1 || results[0].AsInt() != 42 {
t.Errorf("GetValue: got %v", results)
}
}
func TestGoObject_MethodCallWithArg(t *testing.T) {
obj := &testStruct{Name: "Five", Value: 42}
v := WrapGo(obj)
// Add(8) → 50
results := GoCall(v, "Add", MakeInt(8))
if len(results) != 1 || results[0].AsInt() != 50 {
t.Errorf("Add(8): got %v", results)
}
}
func TestGoObject_MethodMutate(t *testing.T) {
obj := &testStruct{Name: "Five", Value: 42}
v := WrapGo(obj)
// SetName("Go")
GoCall(v, "SetName", MakeString("Go"))
if obj.Name != "Go" {
t.Errorf("SetName: name=%q", obj.Name)
}
// SetValue(100)
GoCall(v, "SetValue", MakeInt(100))
if obj.Value != 100 {
t.Errorf("SetValue: value=%d", obj.Value)
}
}
func TestGoObject_MethodNotFound(t *testing.T) {
obj := &testStruct{Name: "Five", Value: 42}
v := WrapGo(obj)
results := GoCall(v, "NonExistent")
if len(results) < 2 {
t.Fatalf("not found: expected error result")
}
errMsg := results[1].AsString()
if !strings.Contains(errMsg, "method not found") {
t.Errorf("not found: got %q", errMsg)
}
}
func TestGoObject_NilReceiver(t *testing.T) {
v := MakeNil()
results := GoCall(v, "Anything")
if len(results) < 2 {
t.Fatalf("nil receiver: expected error")
}
if !strings.Contains(results[1].AsString(), "nil receiver") {
t.Errorf("nil receiver: got %q", results[1].AsString())
}
}
// ===== GoGet/GoSet field access =====
func TestGoObject_FieldGet(t *testing.T) {
obj := &testStruct{Name: "Five", Value: 42}
v := WrapGo(obj)
name := GoGet(v, "Name")
if name.AsString() != "Five" {
t.Errorf("GoGet Name: got %q", name.AsString())
}
val := GoGet(v, "Value")
if val.AsInt() != 42 {
t.Errorf("GoGet Value: got %d", val.AsInt())
}
}
func TestGoObject_FieldSet(t *testing.T) {
obj := &testStruct{Name: "Five", Value: 42}
v := WrapGo(obj)
GoSet(v, "Name", MakeString("Updated"))
if obj.Name != "Updated" {
t.Errorf("GoSet Name: got %q", obj.Name)
}
GoSet(v, "Value", MakeInt(99))
if obj.Value != 99 {
t.Errorf("GoSet Value: got %d", obj.Value)
}
}
func TestGoObject_FieldNotFound(t *testing.T) {
obj := &testStruct{Name: "Five", Value: 42}
v := WrapGo(obj)
result := GoGet(v, "NonExistent")
if !result.IsNil() {
t.Errorf("field not found: expected NIL, got %v", result)
}
}
// ===== GoMultiAssign =====
func TestGoMultiAssign_Basic(t *testing.T) {
vm := NewVM()
th := vm.NewThread()
th.Frame(0, 3)
results := []Value{MakeString("hello"), MakeInt(42), MakeBool(true)}
GoMultiAssign(th, results, 1, 2, 3)
if th.Local(1).AsString() != "hello" {
t.Errorf("multi assign[1]: got %v", th.Local(1))
}
if th.Local(2).AsInt() != 42 {
t.Errorf("multi assign[2]: got %v", th.Local(2))
}
if !th.Local(3).AsBool() {
t.Errorf("multi assign[3]: got %v", th.Local(3))
}
}
func TestGoMultiAssign_FewerResults(t *testing.T) {
vm := NewVM()
th := vm.NewThread()
th.Frame(0, 3)
results := []Value{MakeString("only one")}
GoMultiAssign(th, results, 1, 2, 3)
if th.Local(1).AsString() != "only one" {
t.Errorf("fewer[1]: got %v", th.Local(1))
}
if !th.Local(2).IsNil() {
t.Errorf("fewer[2]: expected NIL, got %v", th.Local(2))
}
if !th.Local(3).IsNil() {
t.Errorf("fewer[3]: expected NIL, got %v", th.Local(3))
}
}
// ===== GoTypeName =====
func TestGoTypeName(t *testing.T) {
obj := &testStruct{Name: "Five", Value: 42}
v := WrapGo(obj)
name := GoTypeName(v)
if name != "*hbrt.testStruct" {
t.Errorf("type name: got %q", name)
}
}
// ===== Edge cases =====
func TestEdge_IntToFloat(t *testing.T) {
// PRG sends int, Go expects float64
v := MakeInt(42)
results := GoCallFunc(goIdentityFloat64, v)
if len(results) != 1 {
t.Fatalf("int→float: no result")
}
if results[0].AsDouble() != 42.0 {
t.Errorf("int→float: got %v", results[0].AsDouble())
}
}
func TestEdge_FloatToInt(t *testing.T) {
// PRG sends float, Go expects int
v := MakeDouble(42.7, 0, 0)
results := GoCallFunc(goIdentityInt, v)
if len(results) != 1 {
t.Fatalf("float→int: no result")
}
if results[0].AsInt() != 42 {
t.Errorf("float→int: got %v", results[0].AsInt())
}
}
func TestEdge_NilToString(t *testing.T) {
v := MakeNil()
results := GoCallFunc(goIdentityString, v)
if len(results) != 1 || results[0].AsString() != "" {
t.Errorf("nil→string: got %v", results)
}
}
func TestEdge_NilToBool(t *testing.T) {
v := MakeNil()
results := GoCallFunc(goIdentityBool, v)
if len(results) != 1 || results[0].AsBool() {
t.Errorf("nil→bool: got %v", results)
}
}
func TestEdge_WrapGoError_Nil(t *testing.T) {
v := WrapGoError(nil)
if !v.IsNil() {
t.Errorf("nil error: expected NIL")
}
}
func TestEdge_WrapGoError_NonNil(t *testing.T) {
v := WrapGoError(fmt.Errorf("something failed"))
if v.IsNil() {
t.Errorf("error: expected non-NIL")
}
if v.AsString() != "something failed" {
t.Errorf("error: got %q", v.AsString())
}
}
// ===== Real Go standard library functions =====
func TestRealGo_StringsToUpper(t *testing.T) {
results := GoCallFunc(strings.ToUpper, MakeString("hello five"))
if len(results) != 1 || results[0].AsString() != "HELLO FIVE" {
t.Errorf("ToUpper: got %v", results)
}
}
func TestRealGo_StringsContains(t *testing.T) {
results := GoCallFunc(strings.Contains, MakeString("hello five"), MakeString("five"))
if len(results) != 1 || !results[0].AsBool() {
t.Errorf("Contains: got %v", results)
}
}
func TestRealGo_StringsSplit(t *testing.T) {
results := GoCallFunc(strings.Split, MakeString("a,b,c"), MakeString(","))
if len(results) != 1 || !results[0].IsArray() {
t.Fatalf("Split: expected array")
}
arr := results[0].AsArray()
if len(arr.Items) != 3 {
t.Fatalf("Split: expected 3 items, got %d", len(arr.Items))
}
if arr.Items[0].AsString() != "a" || arr.Items[1].AsString() != "b" || arr.Items[2].AsString() != "c" {
t.Errorf("Split: got %v", arr.Items)
}
}
func TestRealGo_StringsJoin(t *testing.T) {
// Build a Harbour array, pass to Go strings.Join
items := MakeArrayFrom([]Value{MakeString("x"), MakeString("y"), MakeString("z")})
results := GoCallFunc(strings.Join, items, MakeString("-"))
if len(results) != 1 || results[0].AsString() != "x-y-z" {
t.Errorf("Join: got %v", results)
}
}
func TestRealGo_StringsReplaceAll(t *testing.T) {
results := GoCallFunc(strings.ReplaceAll, MakeString("foo-bar"), MakeString("-"), MakeString("_"))
if len(results) != 1 || results[0].AsString() != "foo_bar" {
t.Errorf("ReplaceAll: got %v", results)
}
}
func TestRealGo_FmtSprintf(t *testing.T) {
results := GoCallFunc(fmt.Sprintf, MakeString("Name: %s, Age: %d"), MakeString("Charles"), MakeInt(30))
if len(results) != 1 || results[0].AsString() != "Name: Charles, Age: 30" {
t.Errorf("Sprintf: got %v", results)
}
}