// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. package hbrt import ( "math" "testing" "unsafe" ) func TestValueSize(t *testing.T) { if size := unsafe.Sizeof(Value{}); size != 24 { t.Errorf("sizeof(Value) = %d, want 24", size) } } // --- Nil --- func TestNil(t *testing.T) { v := MakeNil() if !v.IsNil() { t.Error("MakeNil().IsNil() should be true") } if v.Type() != tNil { t.Errorf("MakeNil().Type() = %d, want %d", v.Type(), tNil) } if v.IsNumeric() || v.IsString() || v.IsLogical() { t.Error("Nil should not match other types") } } // --- Logical --- func TestLogical(t *testing.T) { vt := MakeBool(true) vf := MakeBool(false) if !vt.IsLogical() { t.Error("MakeBool(true).IsLogical() should be true") } if !vt.AsBool() { t.Error("MakeBool(true).AsBool() should be true") } if vf.AsBool() { t.Error("MakeBool(false).AsBool() should be false") } } // --- Integer --- func TestInt(t *testing.T) { tests := []int{0, 1, -1, 42, -42, math.MaxInt32, math.MinInt32} for _, n := range tests { v := MakeInt(n) if !v.IsInt() { t.Errorf("MakeInt(%d).IsInt() should be true", n) } if !v.IsNumeric() { t.Errorf("MakeInt(%d).IsNumeric() should be true", n) } if !v.IsNumInt() { t.Errorf("MakeInt(%d).IsNumInt() should be true", n) } if v.AsInt() != n { t.Errorf("MakeInt(%d).AsInt() = %d", n, v.AsInt()) } if v.AsNumInt() != int64(n) { t.Errorf("MakeInt(%d).AsNumInt() = %d", n, v.AsNumInt()) } } } // --- Long --- func TestLong(t *testing.T) { tests := []int64{0, 1, -1, math.MaxInt64, math.MinInt64, 9223372036854775807, -9223372036854775808} for _, n := range tests { v := MakeLong(n) if !v.IsLong() { t.Errorf("MakeLong(%d).IsLong() should be true", n) } if !v.IsNumeric() { t.Errorf("MakeLong(%d).IsNumeric() should be true", n) } if v.AsLong() != n { t.Errorf("MakeLong(%d).AsLong() = %d", n, v.AsLong()) } } } // --- Double --- func TestDouble(t *testing.T) { tests := []struct { val float64 length uint16 decimal uint16 }{ {0.0, 1, 0}, {3.14, 4, 2}, {-123.456, 7, 3}, {math.MaxFloat64, 255, 255}, {math.SmallestNonzeroFloat64, 255, 255}, } for _, tt := range tests { v := MakeDouble(tt.val, tt.length, tt.decimal) if !v.IsDouble() { t.Errorf("MakeDouble(%g).IsDouble() should be true", tt.val) } if !v.IsNumeric() { t.Errorf("MakeDouble(%g).IsNumeric() should be true", tt.val) } if v.AsDouble() != tt.val { t.Errorf("MakeDouble(%g).AsDouble() = %g", tt.val, v.AsDouble()) } if v.Length() != tt.length { t.Errorf("MakeDouble(%g).Length() = %d, want %d", tt.val, v.Length(), tt.length) } if v.Decimal() != tt.decimal { t.Errorf("MakeDouble(%g).Decimal() = %d, want %d", tt.val, v.Decimal(), tt.decimal) } } } // --- Date --- func TestDate(t *testing.T) { // Julian day for 2026-03-27 ≈ 2461033 julian := int64(2461033) v := MakeDate(julian) if !v.IsDate() { t.Error("MakeDate().IsDate() should be true") } if !v.IsDateTime() { t.Error("MakeDate().IsDateTime() should be true") } if v.AsJulian() != julian { t.Errorf("MakeDate().AsJulian() = %d, want %d", v.AsJulian(), julian) } if v.AsTimeMs() != 0 { t.Error("Date should have 0 timeMs") } } // --- Timestamp --- func TestTimestamp(t *testing.T) { julian := int64(2461033) timeMs := int32(43200000) // 12:00:00.000 v := MakeTimestamp(julian, timeMs) if !v.IsTimestamp() { t.Error("MakeTimestamp().IsTimestamp() should be true") } if !v.IsDateTime() { t.Error("MakeTimestamp().IsDateTime() should be true") } if v.AsJulian() != julian { t.Errorf("AsJulian() = %d, want %d", v.AsJulian(), julian) } if v.AsTimeMs() != timeMs { t.Errorf("AsTimeMs() = %d, want %d", v.AsTimeMs(), timeMs) } } // --- String --- func TestString(t *testing.T) { tests := []string{"", "Hello", "Hello, World!", "한글 테스트", "こんにちは"} for _, s := range tests { v := MakeString(s) if !v.IsString() { t.Errorf("MakeString(%q).IsString() should be true", s) } if v.AsString() != s { t.Errorf("MakeString(%q).AsString() = %q", s, v.AsString()) } if v.StringLen() != len(s) { t.Errorf("MakeString(%q).StringLen() = %d, want %d", s, v.StringLen(), len(s)) } } } // --- Array --- func TestArray(t *testing.T) { v := MakeArray(3) if !v.IsArray() { t.Error("MakeArray().IsArray() should be true") } arr := v.AsArray() if arr == nil { t.Fatal("AsArray() should not be nil") } if len(arr.Items) != 3 { t.Errorf("array len = %d, want 3", len(arr.Items)) } // All items should be nil initially for i, item := range arr.Items { if !item.IsNil() { t.Errorf("arr[%d] should be Nil, got type %d", i, item.Type()) } } } func TestArrayFrom(t *testing.T) { items := []Value{MakeInt(1), MakeString("two"), MakeBool(true)} v := MakeArrayFrom(items) arr := v.AsArray() if len(arr.Items) != 3 { t.Fatalf("len = %d, want 3", len(arr.Items)) } if arr.Items[0].AsInt() != 1 { t.Error("arr[0] should be 1") } if arr.Items[1].AsString() != "two" { t.Error("arr[1] should be 'two'") } if !arr.Items[2].AsBool() { t.Error("arr[2] should be true") } } // --- Object --- func TestObject(t *testing.T) { v := MakeObject(1, 5) if !v.IsObject() { t.Error("MakeObject().IsObject() should be true") } if !v.IsArray() { t.Error("Object.IsArray() should be true (object is array with class)") } arr := v.AsArray() if arr.Class != 1 { t.Errorf("Class = %d, want 1", arr.Class) } } // --- Hash --- func TestHash(t *testing.T) { v := MakeHash() if !v.IsHash() { t.Error("MakeHash().IsHash() should be true") } hh := v.AsHash() if hh == nil { t.Fatal("AsHash() should not be nil") } if len(hh.Keys) != 0 { t.Error("new hash should be empty") } } // --- Block --- func TestBlock(t *testing.T) { called := false v := MakeBlock(func(t *Thread) { called = true }, 0) if !v.IsBlock() { t.Error("MakeBlock().IsBlock() should be true") } blk := v.AsBlock() if blk == nil { t.Fatal("AsBlock() should not be nil") } blk.Fn(nil) // call it if !called { t.Error("block function should have been called") } } // --- NumInt auto-promotion --- func TestMakeNumInt(t *testing.T) { // Within int32 range → Int v1 := MakeNumInt(42) if !v1.IsInt() { t.Error("42 should be Int") } // Beyond int32 range → Long v2 := MakeNumInt(int64(math.MaxInt32) + 1) if !v2.IsLong() { t.Error("MaxInt32+1 should be Long") } v3 := MakeNumInt(int64(math.MinInt32) - 1) if !v3.IsLong() { t.Error("MinInt32-1 should be Long") } } // --- AsNumDouble --- func TestAsNumDouble(t *testing.T) { if MakeInt(42).AsNumDouble() != 42.0 { t.Error("Int(42).AsNumDouble() should be 42.0") } if MakeLong(1000000000000).AsNumDouble() != 1e12 { t.Error("Long(1e12).AsNumDouble() should be 1e12") } if MakeDouble(3.14, 4, 2).AsNumDouble() != 3.14 { t.Error("Double(3.14).AsNumDouble() should be 3.14") } if MakeNil().AsNumDouble() != 0 { t.Error("Nil.AsNumDouble() should be 0") } } // --- Stringer --- func TestStringer(t *testing.T) { tests := []struct { v Value want string }{ {MakeNil(), "NIL"}, {MakeBool(true), ".T."}, {MakeBool(false), ".F."}, {MakeInt(42), "42"}, {MakeLong(-999), "-999"}, {MakeString("hello"), `"hello"`}, } for _, tt := range tests { got := tt.v.String() if got != tt.want { t.Errorf("String() = %q, want %q", got, tt.want) } } } // --- Benchmarks --- func BenchmarkValueMakeInt(b *testing.B) { for i := 0; i < b.N; i++ { _ = MakeInt(i) } } func BenchmarkInterfaceMakeInt(b *testing.B) { for i := 0; i < b.N; i++ { var v interface{} = int64(i) _ = v } } func BenchmarkValueAddInt(b *testing.B) { a := MakeInt(100) for i := 0; i < b.N; i++ { // Simulate: read int, add, write back r := a.AsNumInt() + int64(i) a = MakeNumInt(r) } } func BenchmarkInterfaceAddInt(b *testing.B) { var a interface{} = int64(100) for i := 0; i < b.N; i++ { r := a.(int64) + int64(i) a = r } } func BenchmarkValueTypeCheck(b *testing.B) { v := MakeInt(42) for i := 0; i < b.N; i++ { _ = v.IsNumeric() } } func BenchmarkInterfaceTypeCheck(b *testing.B) { var v interface{} = int64(42) for i := 0; i < b.N; i++ { _, _ = v.(int64) } }