package hbrt import ( "fmt" "math" "math/rand" "reflect" "strings" "sync" "testing" "time" ) // ===== Stress test Go functions ===== func goSumInts(a, b, c, d, e int) int { return a + b + c + d + e } func goConcatMany(a, b, c, d, e, f string) string { return a + b + c + d + e + f } func goReturnLargeSlice(n int) []int { s := make([]int, n) for i := range s { s[i] = i } return s } func goReturnLargeMap(n int) map[string]int { m := make(map[string]int, n) for i := 0; i < n; i++ { m[fmt.Sprintf("key_%d", i)] = i } return m } func goNestedSlice() [][]int { return [][]int{{1, 2}, {3, 4}, {5, 6}} } func goReturnNil() *testStruct { return nil } func goAcceptNil(s *testStruct) string { if s == nil { return "nil" } return s.Name } type chainObj struct{ Val int } func (c *chainObj) Add(n int) *chainObj { return &chainObj{Val: c.Val + n} } func (c *chainObj) Mul(n int) *chainObj { return &chainObj{Val: c.Val * n} } func (c *chainObj) Result() int { return c.Val } func goNewChain(n int) *chainObj { return &chainObj{Val: n} } // =================================================================== // 1. VOLUME: Thousands of calls // =================================================================== func TestStress_HighVolume_StringCalls(t *testing.T) { for i := 0; i < 10000; i++ { v := MakeString(fmt.Sprintf("hello_%d", i)) results := GoCallFunc(strings.ToUpper, v) expected := fmt.Sprintf("HELLO_%d", i) if results[0].AsString() != expected { t.Fatalf("iter %d: got %q want %q", i, results[0].AsString(), expected) } } t.Log("10,000 string calls OK") } func TestStress_HighVolume_IntCalls(t *testing.T) { for i := 0; i < 10000; i++ { results := GoCallFunc(goSumInts, MakeInt(i), MakeInt(i*2), MakeInt(i*3), MakeInt(i*4), MakeInt(i*5)) expected := i * 15 // i+2i+3i+4i+5i if results[0].AsInt() != expected { t.Fatalf("iter %d: got %d want %d", i, results[0].AsInt(), expected) } } t.Log("10,000 int calls OK") } func TestStress_HighVolume_FloatCalls(t *testing.T) { for i := 0; i < 10000; i++ { v := MakeDouble(float64(i)*0.1, 0, 0) results := GoCallFunc(math.Sqrt, v) expected := math.Sqrt(float64(i) * 0.1) diff := results[0].AsDouble() - expected if diff > 0.0001 || diff < -0.0001 { t.Fatalf("iter %d: got %f want %f", i, results[0].AsDouble(), expected) } } t.Log("10,000 float calls OK") } func TestStress_HighVolume_BoolCalls(t *testing.T) { for i := 0; i < 10000; i++ { s := fmt.Sprintf("item_%d", i) search := fmt.Sprintf("_%d", i) results := GoCallFunc(strings.Contains, MakeString(s), MakeString(search)) if !results[0].AsBool() { t.Fatalf("iter %d: expected true", i) } } t.Log("10,000 bool calls OK") } // =================================================================== // 2. LARGE DATA: big arrays, maps, strings // =================================================================== func TestStress_LargeString(t *testing.T) { // 1MB string big := strings.Repeat("abcdefghij", 100000) v := MakeString(big) results := GoCallFunc(strings.ToUpper, v) got := results[0].AsString() if len(got) != 1000000 { t.Fatalf("large string: len=%d want 1000000", len(got)) } if got[:10] != "ABCDEFGHIJ" { t.Fatalf("large string: prefix=%q", got[:10]) } t.Logf("1MB string roundtrip OK (len=%d)", len(got)) } func TestStress_LargeArray(t *testing.T) { // 10,000 element array Go→PRG results := GoCallFunc(goReturnLargeSlice, MakeInt(10000)) if !results[0].IsArray() { t.Fatalf("large array: not array") } arr := results[0].AsArray() if len(arr.Items) != 10000 { t.Fatalf("large array: len=%d want 10000", len(arr.Items)) } if arr.Items[0].AsInt() != 0 || arr.Items[9999].AsInt() != 9999 { t.Fatalf("large array: first=%d last=%d", arr.Items[0].AsInt(), arr.Items[9999].AsInt()) } // PRG→Go roundtrip: send array back to Go strings.Join strItems := make([]Value, 100) for i := range strItems { strItems[i] = MakeString(fmt.Sprintf("%d", i)) } arrVal := MakeArrayFrom(strItems) joinResults := GoCallFunc(strings.Join, arrVal, MakeString(",")) joined := joinResults[0].AsString() parts := strings.Split(joined, ",") if len(parts) != 100 { t.Fatalf("array roundtrip: len=%d", len(parts)) } t.Log("10,000 element array + 100 element roundtrip OK") } func TestStress_LargeMap(t *testing.T) { results := GoCallFunc(goReturnLargeMap, MakeInt(1000)) if !results[0].IsHash() { t.Fatalf("large map: not hash") } h := results[0].AsHash() if len(h.Keys) != 1000 { t.Fatalf("large map: len=%d want 1000", len(h.Keys)) } t.Logf("1,000 entry map OK") } // =================================================================== // 3. TYPE BOUNDARY: edge values // =================================================================== func TestStress_IntBoundary(t *testing.T) { cases := []int{0, 1, -1, 127, -128, 255, 32767, -32768, 65535, 2147483647, -2147483648, 100000000} for _, n := range cases { results := GoCallFunc(goIdentityInt, MakeInt(n)) if results[0].AsInt() != n { t.Errorf("boundary int %d: got %d", n, results[0].AsInt()) } } t.Log("Int boundary values OK") } func TestStress_Int64Boundary(t *testing.T) { cases := []int64{0, 1, -1, math.MaxInt32, math.MinInt32, math.MaxInt32 + 1, math.MinInt32 - 1, 999999999999, -999999999999} for _, n := range cases { results := GoCallFunc(goIdentityInt64, MakeLong(n)) if results[0].AsLong() != n { t.Errorf("boundary int64 %d: got %d", n, results[0].AsLong()) } } t.Log("Int64 boundary values OK") } func TestStress_FloatBoundary(t *testing.T) { cases := []float64{0, 0.1, -0.1, math.SmallestNonzeroFloat64, math.MaxFloat64 / 2, -math.MaxFloat64 / 2, math.Pi, math.E, math.Phi} for _, f := range cases { results := GoCallFunc(goIdentityFloat64, MakeDouble(f, 0, 0)) diff := math.Abs(results[0].AsDouble() - f) if diff > 1e-10 { t.Errorf("boundary float %g: got %g", f, results[0].AsDouble()) } } t.Log("Float boundary values OK") } func TestStress_StringBoundary(t *testing.T) { cases := []string{ "", // empty " ", // single space "\t\n\r", // whitespace "a", // single char strings.Repeat("x", 65536), // 64KB "Hello 世界 🌍", // unicode "line1\nline2\nline3", // newlines `"quoted"`, // quotes "null\x00byte", // null byte } for i, s := range cases { results := GoCallFunc(goIdentityString, MakeString(s)) if results[0].AsString() != s { t.Errorf("boundary string[%d] len=%d: mismatch", i, len(s)) } } t.Log("String boundary values OK (empty, unicode, 64KB, null bytes)") } // =================================================================== // 4. CONCURRENT: goroutine safety // =================================================================== func TestStress_ConcurrentCalls(t *testing.T) { var wg sync.WaitGroup errors := make(chan string, 100) for g := 0; g < 50; g++ { wg.Add(1) go func(id int) { defer wg.Done() for i := 0; i < 200; i++ { s := fmt.Sprintf("goroutine_%d_iter_%d", id, i) results := GoCallFunc(strings.ToUpper, MakeString(s)) expected := strings.ToUpper(s) if results[0].AsString() != expected { errors <- fmt.Sprintf("g%d i%d: %q != %q", id, i, results[0].AsString(), expected) return } } }(g) } wg.Wait() close(errors) for e := range errors { t.Fatal(e) } t.Log("50 goroutines × 200 calls = 10,000 concurrent calls OK") } func TestStress_ConcurrentObjectMethods(t *testing.T) { var wg sync.WaitGroup errors := make(chan string, 100) for g := 0; g < 20; g++ { wg.Add(1) go func(id int) { defer wg.Done() obj := &testStruct{Name: fmt.Sprintf("obj_%d", id), Value: id} v := WrapGo(obj) for i := 0; i < 500; i++ { results := GoCall(v, "Add", MakeInt(i)) expected := id + i if results[0].AsInt() != expected { errors <- fmt.Sprintf("g%d i%d: %d != %d", id, i, results[0].AsInt(), expected) return } } }(g) } wg.Wait() close(errors) for e := range errors { t.Fatal(e) } t.Log("20 goroutines × 500 method calls = 10,000 concurrent object calls OK") } // =================================================================== // 5. OBJECT LIFECYCLE: create, use, chain, stress // =================================================================== func TestStress_ObjectChain(t *testing.T) { // Chain: obj.Add(5).Mul(3).Add(10).Result() = (0+5)*3+10 = 25 obj := WrapGo(goNewChain(0)) r := GoCall(obj, "Add", MakeInt(5)) obj = r[0] r = GoCall(obj, "Mul", MakeInt(3)) obj = r[0] r = GoCall(obj, "Add", MakeInt(10)) obj = r[0] r = GoCall(obj, "Result") if r[0].AsInt() != 25 { t.Fatalf("chain: got %d want 25", r[0].AsInt()) } t.Log("Object method chain OK: (0+5)*3+10 = 25") } func TestStress_ManyObjects(t *testing.T) { // Create 1000 objects, call methods on each objects := make([]Value, 1000) for i := 0; i < 1000; i++ { objects[i] = WrapGo(&testStruct{Name: fmt.Sprintf("obj_%d", i), Value: i}) } for i, obj := range objects { r := GoCall(obj, "GetValue") if r[0].AsInt() != i { t.Fatalf("object %d: GetValue=%d", i, r[0].AsInt()) } r = GoCall(obj, "GetName") expected := fmt.Sprintf("obj_%d", i) if r[0].AsString() != expected { t.Fatalf("object %d: GetName=%q", i, r[0].AsString()) } } t.Log("1,000 objects created and verified OK") } func TestStress_ObjectNilSafety(t *testing.T) { // Call method on nil-wrapped object results := GoCallFunc(goReturnNil) nilObj := results[0] if !nilObj.IsNil() { t.Fatalf("expected NIL from nil pointer") } // GoCall on NIL should not panic r := GoCall(nilObj, "GetName") if len(r) < 2 { t.Fatalf("expected error result") } t.Log("Nil object safety OK") } // =================================================================== // 6. TYPE COERCION MATRIX: every PRG→Go combination // =================================================================== func goTakeString(s string) string { return s } func goTakeInt(n int) int { return n } func goTakeInt64(n int64) int64 { return n } func goTakeFloat64(f float64) float64 { return f } func goTakeBool(b bool) bool { return b } func goTakeInterface(v interface{}) string { return fmt.Sprintf("%v", v) } func TestStress_CoercionMatrix(t *testing.T) { // Test: every Harbour type sent to every Go type values := []struct { name string v Value }{ {"NIL", MakeNil()}, {"String", MakeString("hello")}, {"Int", MakeInt(42)}, {"Long", MakeLong(9999999999)}, {"Double", MakeDouble(3.14, 0, 0)}, {"Bool.T", MakeBool(true)}, {"Bool.F", MakeBool(false)}, } targets := []struct { name string fn interface{} }{ {"→string", goTakeString}, {"→int", goTakeInt}, {"→int64", goTakeInt64}, {"→float64", goTakeFloat64}, {"→bool", goTakeBool}, {"→interface{}", goTakeInterface}, } passed := 0 for _, v := range values { for _, target := range targets { func() { defer func() { if r := recover(); r != nil { // Some coercions may panic — that's OK, we just track them t.Logf(" %s %s: panic (expected for incompatible)", v.name, target.name) } }() results := GoCallFunc(target.fn, v.v) if len(results) > 0 { passed++ } }() } } t.Logf("Coercion matrix: %d/42 combinations succeeded", passed) } // =================================================================== // 7. PERFORMANCE BENCHMARK // =================================================================== func TestStress_Performance(t *testing.T) { n := 100000 // Benchmark: direct Go call start := time.Now() for i := 0; i < n; i++ { _ = strings.ToUpper("hello") } directTime := time.Since(start) // Benchmark: via GoCallFunc bridge start = time.Now() v := MakeString("hello") for i := 0; i < n; i++ { GoCallFunc(strings.ToUpper, v) } bridgeTime := time.Since(start) ratio := float64(bridgeTime) / float64(directTime) t.Logf("Performance: direct=%v bridge=%v ratio=%.1fx", directTime, bridgeTime, ratio) t.Logf("Bridge throughput: %.0f calls/sec", float64(n)/bridgeTime.Seconds()) // Benchmark: object method call obj := WrapGo(&testStruct{Name: "test", Value: 42}) arg := MakeInt(1) start = time.Now() for i := 0; i < n; i++ { GoCall(obj, "Add", arg) } methodTime := time.Since(start) t.Logf("Method call: %v (%.0f calls/sec)", methodTime, float64(n)/methodTime.Seconds()) } // =================================================================== // 8. RANDOM FUZZ: random types and values // =================================================================== func TestStress_RandomFuzz(t *testing.T) { rng := rand.New(rand.NewSource(42)) funcs := []interface{}{ strings.ToUpper, strings.ToLower, strings.TrimSpace, } for i := 0; i < 5000; i++ { s := randomString(rng, rng.Intn(100)) fn := funcs[rng.Intn(len(funcs))] v := MakeString(s) results := GoCallFunc(fn, v) if len(results) == 0 { t.Fatalf("fuzz iter %d: no results", i) } // Verify against direct call expected := reflect.ValueOf(fn).Call([]reflect.Value{reflect.ValueOf(s)}) if results[0].AsString() != expected[0].String() { t.Fatalf("fuzz iter %d: %q → got %q want %q", i, s, results[0].AsString(), expected[0].String()) } } t.Log("5,000 random fuzz calls OK") } func randomString(rng *rand.Rand, length int) string { chars := "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ\t\n" b := make([]byte, length) for i := range b { b[i] = chars[rng.Intn(len(chars))] } return string(b) }