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

379 lines
8.6 KiB
Go

package hbrtl
import (
"five/hbrt"
"os"
"path/filepath"
"strings"
"testing"
)
// === hb_jsonEncode ===
func TestJsonEncode_String(t *testing.T) {
_, th := setupVM()
th.PushString("hello")
th.PendingParams2(1)
HbJsonEncode(th)
if r := th.GetRetValue().AsString(); r != `"hello"` {
t.Errorf("encode string = %q, want %q", r, `"hello"`)
}
}
func TestJsonEncode_Number(t *testing.T) {
_, th := setupVM()
th.PushInt(42)
th.PendingParams2(1)
HbJsonEncode(th)
if r := th.GetRetValue().AsString(); r != "42" {
t.Errorf("encode int = %q, want %q", r, "42")
}
}
func TestJsonEncode_Bool(t *testing.T) {
_, th := setupVM()
th.PushBool(true)
th.PendingParams2(1)
HbJsonEncode(th)
if r := th.GetRetValue().AsString(); r != "true" {
t.Errorf("encode bool = %q", r)
}
}
func TestJsonEncode_Nil(t *testing.T) {
_, th := setupVM()
th.PushNil()
th.PendingParams2(1)
HbJsonEncode(th)
if r := th.GetRetValue().AsString(); r != "null" {
t.Errorf("encode nil = %q", r)
}
}
func TestJsonEncode_Array(t *testing.T) {
_, th := setupVM()
arr := hbrt.MakeArrayFrom([]hbrt.Value{
hbrt.MakeInt(1), hbrt.MakeString("two"), hbrt.MakeBool(false),
})
th.PushValue(arr)
th.PendingParams2(1)
HbJsonEncode(th)
r := th.GetRetValue().AsString()
if r != `[1,"two",false]` {
t.Errorf("encode array = %q", r)
}
}
func TestJsonEncode_Hash(t *testing.T) {
_, th := setupVM()
h := &hbrt.HbHash{
Keys: []hbrt.Value{hbrt.MakeString("name"), hbrt.MakeString("age")},
Values: []hbrt.Value{hbrt.MakeString("Five"), hbrt.MakeInt(1)},
}
th.PushValue(hbrt.MakeHashFrom(h))
th.PendingParams2(1)
HbJsonEncode(th)
r := th.GetRetValue().AsString()
if !strings.Contains(r, `"name":"Five"`) || !strings.Contains(r, `"age":1`) {
t.Errorf("encode hash = %q", r)
}
}
func TestJsonEncode_Pretty(t *testing.T) {
_, th := setupVM()
h := &hbrt.HbHash{
Keys: []hbrt.Value{hbrt.MakeString("x")},
Values: []hbrt.Value{hbrt.MakeInt(1)},
}
th.PushValue(hbrt.MakeHashFrom(h))
th.PushBool(true) // pretty
th.PendingParams2(2)
HbJsonEncode(th)
r := th.GetRetValue().AsString()
if !strings.Contains(r, "\n") {
t.Errorf("pretty should have newlines: %q", r)
}
}
// === hb_jsonDecode ===
func TestJsonDecode_Object(t *testing.T) {
_, th := setupVM()
th.PushString(`{"name":"Five","version":2}`)
th.PendingParams2(1)
HbJsonDecode(th)
r := th.GetRetValue()
if !r.IsHash() {
t.Fatalf("decode object should be hash, got %v", r)
}
}
func TestJsonDecode_Array(t *testing.T) {
_, th := setupVM()
th.PushString(`[1,2,3]`)
th.PendingParams2(1)
HbJsonDecode(th)
r := th.GetRetValue()
if !r.IsArray() {
t.Fatalf("decode array should be array")
}
arr := r.AsArray()
if len(arr.Items) != 3 {
t.Errorf("array len = %d, want 3", len(arr.Items))
}
}
func TestJsonDecode_Nested(t *testing.T) {
_, th := setupVM()
th.PushString(`{"users":[{"name":"Kim"},{"name":"Lee"}]}`)
th.PendingParams2(1)
HbJsonDecode(th)
r := th.GetRetValue()
if !r.IsHash() {
t.Fatal("should be hash")
}
}
func TestJsonDecode_Invalid(t *testing.T) {
_, th := setupVM()
th.PushString(`{broken}`)
th.PendingParams2(1)
HbJsonDecode(th)
if !th.GetRetValue().IsNil() {
t.Error("invalid JSON should return NIL")
}
}
// === JsonPretty ===
func TestJsonPretty(t *testing.T) {
_, th := setupVM()
h := &hbrt.HbHash{
Keys: []hbrt.Value{hbrt.MakeString("a"), hbrt.MakeString("b")},
Values: []hbrt.Value{hbrt.MakeInt(1), hbrt.MakeInt(2)},
}
th.PushValue(hbrt.MakeHashFrom(h))
th.PendingParams2(1)
JsonPretty(th)
r := th.GetRetValue().AsString()
if !strings.Contains(r, "\n") || !strings.Contains(r, " ") {
t.Errorf("pretty = %q", r)
}
}
// === JsonPath ===
func TestJsonPath_Simple(t *testing.T) {
_, th := setupVM()
data := makeTestHash()
th.PushValue(data)
th.PushString("$.name")
th.PendingParams2(2)
JsonPath(th)
if r := th.GetRetValue().AsString(); r != "Five" {
t.Errorf("path $.name = %q, want 'Five'", r)
}
}
func TestJsonPath_Nested(t *testing.T) {
_, th := setupVM()
// {"user":{"name":"Charles"}}
inner := &hbrt.HbHash{
Keys: []hbrt.Value{hbrt.MakeString("name")},
Values: []hbrt.Value{hbrt.MakeString("Charles")},
}
outer := &hbrt.HbHash{
Keys: []hbrt.Value{hbrt.MakeString("user")},
Values: []hbrt.Value{hbrt.MakeHashFrom(inner)},
}
th.PushValue(hbrt.MakeHashFrom(outer))
th.PushString("$.user.name")
th.PendingParams2(2)
JsonPath(th)
if r := th.GetRetValue().AsString(); r != "Charles" {
t.Errorf("path $.user.name = %q", r)
}
}
func TestJsonPath_Array(t *testing.T) {
_, th := setupVM()
arr := hbrt.MakeArrayFrom([]hbrt.Value{
hbrt.MakeInt(100), hbrt.MakeInt(200), hbrt.MakeInt(300),
})
h := &hbrt.HbHash{
Keys: []hbrt.Value{hbrt.MakeString("scores")},
Values: []hbrt.Value{arr},
}
th.PushValue(hbrt.MakeHashFrom(h))
th.PushString("$.scores[1]")
th.PendingParams2(2)
JsonPath(th)
if r := th.GetRetValue().AsNumInt(); r != 200 {
t.Errorf("path $.scores[1] = %d, want 200", r)
}
}
func TestJsonPath_Missing(t *testing.T) {
_, th := setupVM()
th.PushValue(makeTestHash())
th.PushString("$.nonexistent.deep.path")
th.PendingParams2(2)
JsonPath(th)
if !th.GetRetValue().IsNil() {
t.Error("missing path should return NIL")
}
}
// === JsonMerge ===
func TestJsonMerge(t *testing.T) {
_, th := setupVM()
a := &hbrt.HbHash{
Keys: []hbrt.Value{hbrt.MakeString("x"), hbrt.MakeString("y")},
Values: []hbrt.Value{hbrt.MakeInt(1), hbrt.MakeInt(2)},
}
b := &hbrt.HbHash{
Keys: []hbrt.Value{hbrt.MakeString("y"), hbrt.MakeString("z")},
Values: []hbrt.Value{hbrt.MakeInt(99), hbrt.MakeInt(3)},
}
th.PushValue(hbrt.MakeHashFrom(a))
th.PushValue(hbrt.MakeHashFrom(b))
th.PendingParams2(2)
JsonMerge(th)
r := th.GetRetValue()
if !r.IsHash() {
t.Fatal("merge should return hash")
}
h := r.AsHash()
if len(h.Keys) != 3 {
t.Errorf("merged keys = %d, want 3", len(h.Keys))
}
}
// === JsonValid ===
func TestJsonValid(t *testing.T) {
_, th := setupVM()
th.PushString(`{"valid":true}`)
th.PendingParams2(1)
JsonValid(th)
if !th.GetRetValue().AsBool() {
t.Error("valid JSON should return true")
}
th.PushString(`{broken`)
th.PendingParams2(1)
JsonValid(th)
if th.GetRetValue().AsBool() {
t.Error("invalid JSON should return false")
}
}
// === JsonType ===
func TestJsonType(t *testing.T) {
_, th := setupVM()
cases := map[string]string{
`{"a":1}`: "object",
`[1,2]`: "array",
`"hello"`: "string",
`42`: "number",
`true`: "boolean",
`null`: "null",
``: "invalid",
}
for input, expected := range cases {
th.PushString(input)
th.PendingParams2(1)
JsonType(th)
if r := th.GetRetValue().AsString(); r != expected {
t.Errorf("JsonType(%q) = %q, want %q", input, r, expected)
}
}
}
// === JsonTo / JsonFrom (file roundtrip) ===
func TestJsonFileRoundtrip(t *testing.T) {
_, th := setupVM()
dir := t.TempDir()
fpath := filepath.Join(dir, "test.json")
// Write
h := &hbrt.HbHash{
Keys: []hbrt.Value{hbrt.MakeString("msg"), hbrt.MakeString("num")},
Values: []hbrt.Value{hbrt.MakeString("hello"), hbrt.MakeInt(42)},
}
th.PushValue(hbrt.MakeHashFrom(h))
th.PushString(fpath)
th.PendingParams2(2)
JsonTo(th)
if !th.GetRetValue().AsBool() {
t.Fatal("JsonTo failed")
}
// Verify file exists
data, err := os.ReadFile(fpath)
if err != nil {
t.Fatal("file not created")
}
if !strings.Contains(string(data), "hello") {
t.Errorf("file content: %s", string(data))
}
// Read back
th.PushString(fpath)
th.PendingParams2(1)
JsonFrom(th)
r := th.GetRetValue()
if !r.IsHash() {
t.Fatal("JsonFrom should return hash")
}
}
// === Encode/Decode roundtrip ===
func TestJsonRoundtrip(t *testing.T) {
_, th := setupVM()
// Complex nested structure
inner := hbrt.MakeArrayFrom([]hbrt.Value{
hbrt.MakeInt(1), hbrt.MakeString("two"), hbrt.MakeBool(true), hbrt.MakeNil(),
})
h := &hbrt.HbHash{
Keys: []hbrt.Value{hbrt.MakeString("arr"), hbrt.MakeString("val")},
Values: []hbrt.Value{inner, hbrt.MakeDouble(3.14, 0, 0)},
}
// Encode
th.PushValue(hbrt.MakeHashFrom(h))
th.PendingParams2(1)
HbJsonEncode(th)
encoded := th.GetRetValue().AsString()
// Decode
th.PushString(encoded)
th.PendingParams2(1)
HbJsonDecode(th)
decoded := th.GetRetValue()
// Re-encode and compare
th.PushValue(decoded)
th.PendingParams2(1)
HbJsonEncode(th)
reEncoded := th.GetRetValue().AsString()
if encoded != reEncoded {
t.Errorf("roundtrip mismatch:\n original: %s\n roundtrip: %s", encoded, reEncoded)
}
}
// helper
func makeTestHash() hbrt.Value {
h := &hbrt.HbHash{
Keys: []hbrt.Value{hbrt.MakeString("name"), hbrt.MakeString("version")},
Values: []hbrt.Value{hbrt.MakeString("Five"), hbrt.MakeInt(1)},
}
return hbrt.MakeHashFrom(h)
}