This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch main
in repository ego.
View the commit online.
commit 6c474798586a19db304a27be8ba162b5cd2a6c9b
Author: [email protected] <[email protected]>
AuthorDate: Tue Mar 31 10:45:32 2026 -0600
feat(efl): add Eina_Value Go wrapper with generic scalar API
Add efl.Value type wrapping Eina_Value with NewValue[T] and
ValueGet[T] generic constructors/getters for all scalar types.
Includes Kind(), String(), Free(), WrapValue(), and Ptr().
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
efl/value.go | 517 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
efl/value_test.go | 388 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 905 insertions(+)
diff --git a/efl/value.go b/efl/value.go
new file mode 100644
index 0000000..4f1257a
--- /dev/null
+++ b/efl/value.go
@@ -0,0 +1,517 @@
+package efl
+
+/*
+#cgo pkg-config: eina
+
+#include <Eina.h>
+#include <stdlib.h>
+
+// _ego_value_new allocates a new Eina_Value of the given type.
+static Eina_Value *_ego_value_new(const Eina_Value_Type *type) {
+ return eina_value_new(type);
+}
+
+// _ego_value_free releases an Eina_Value allocated with eina_value_new.
+static void _ego_value_free(Eina_Value *v) {
+ eina_value_free(v);
+}
+
+// _ego_value_type_get returns the type descriptor of v.
+static const Eina_Value_Type *_ego_value_type_get(const Eina_Value *v) {
+ return eina_value_type_get(v);
+}
+
+// _ego_value_pset sets the scalar value of v by passing ptr (pointer to the
+// actual value). eina_value_pset is a static inline function; this thin
+// wrapper makes it reachable from cgo.
+static Eina_Bool _ego_value_pset(Eina_Value *v, const void *ptr) {
+ return eina_value_pset(v, ptr);
+}
+
+// _ego_value_pget reads the scalar value of v into the memory pointed to by
+// ptr. eina_value_pget is a static inline function; this thin wrapper makes
+// it reachable from cgo.
+static Eina_Bool _ego_value_pget(const Eina_Value *v, void *ptr) {
+ return eina_value_pget(v, ptr);
+}
+
+// _ego_value_to_string converts v to a human-readable C string. The caller
+// is responsible for freeing the returned pointer with free().
+static char *_ego_value_to_string(const Eina_Value *v) {
+ return eina_value_to_string(v);
+}
+
+// _ego_value_set_string sets the STRING value of v to s. The string type
+// requires pset(&s) — a pointer to the char pointer — not pset(s).
+static Eina_Bool _ego_value_set_string(Eina_Value *v, const char *s) {
+ return eina_value_pset(v, &s);
+}
+
+// _ego_value_get_string retrieves the STRING value of v. Returns the
+// internal string pointer owned by v; the caller must not free it.
+static const char *_ego_value_get_string(const Eina_Value *v) {
+ const char *s = NULL;
+ eina_value_pget(v, &s);
+ return s;
+}
+*/
+import "C"
+
+import (
+ "errors"
+ "fmt"
+ "unsafe"
+)
+
+// ValueKind identifies the broad category of scalar value stored in a Value.
+type ValueKind int
+
+const (
+ ValueKindUnknown ValueKind = iota
+ ValueKindInt
+ ValueKindUint
+ ValueKindInt64
+ ValueKindUint64
+ ValueKindInt16
+ ValueKindUint16
+ ValueKindFloat32
+ ValueKindFloat64
+ ValueKindString
+ ValueKindBool
+ ValueKindByte
+)
+
+// ScalarType is the constraint for types that can be stored in a Value.
+type ScalarType interface {
+ ~int | ~uint | ~float64 | ~float32 | ~string | ~bool |
+ ~int64 | ~uint64 | ~int32 | ~uint32 | ~int16 | ~uint16 | ~byte
+}
+
+// Value is a Go wrapper around a C Eina_Value scalar.
+// When owned is true, Free releases the underlying C allocation.
+type Value struct {
+ ptr *C.Eina_Value
+ owned bool
+}
+
+// NewValue creates a new Eina_Value of the type inferred from T and sets it to
+// val. The returned Value is owned and must be released by calling Free when no
+// longer needed.
+func NewValue[T ScalarType](val T) (*Value, error) {
+ v := &Value{owned: true}
+
+ switch any(val).(type) {
+ case int, int32:
+ v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_INT)
+ if v.ptr == nil {
+ return nil, errors.New("efl: eina_value_new(INT) returned nil")
+ }
+ cv := C.int(scalarToInt(val))
+ if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ C._ego_value_free(v.ptr)
+ return nil, fmt.Errorf("efl: eina_value_pset(INT) failed")
+ }
+
+ case uint, uint32:
+ v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_UINT)
+ if v.ptr == nil {
+ return nil, errors.New("efl: eina_value_new(UINT) returned nil")
+ }
+ cv := C.uint(scalarToUint(val))
+ if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ C._ego_value_free(v.ptr)
+ return nil, fmt.Errorf("efl: eina_value_pset(UINT) failed")
+ }
+
+ case int64:
+ v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_INT64)
+ if v.ptr == nil {
+ return nil, errors.New("efl: eina_value_new(INT64) returned nil")
+ }
+ cv := C.int64_t(scalarToInt64(val))
+ if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ C._ego_value_free(v.ptr)
+ return nil, fmt.Errorf("efl: eina_value_pset(INT64) failed")
+ }
+
+ case uint64:
+ v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_UINT64)
+ if v.ptr == nil {
+ return nil, errors.New("efl: eina_value_new(UINT64) returned nil")
+ }
+ cv := C.uint64_t(scalarToUint64(val))
+ if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ C._ego_value_free(v.ptr)
+ return nil, fmt.Errorf("efl: eina_value_pset(UINT64) failed")
+ }
+
+ case int16:
+ v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_SHORT)
+ if v.ptr == nil {
+ return nil, errors.New("efl: eina_value_new(SHORT) returned nil")
+ }
+ cv := C.short(scalarToInt16(val))
+ if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ C._ego_value_free(v.ptr)
+ return nil, fmt.Errorf("efl: eina_value_pset(SHORT) failed")
+ }
+
+ case uint16:
+ v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_USHORT)
+ if v.ptr == nil {
+ return nil, errors.New("efl: eina_value_new(USHORT) returned nil")
+ }
+ cv := C.ushort(scalarToUint16(val))
+ if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ C._ego_value_free(v.ptr)
+ return nil, fmt.Errorf("efl: eina_value_pset(USHORT) failed")
+ }
+
+ case float32:
+ v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_FLOAT)
+ if v.ptr == nil {
+ return nil, errors.New("efl: eina_value_new(FLOAT) returned nil")
+ }
+ cv := C.float(scalarToFloat32(val))
+ if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ C._ego_value_free(v.ptr)
+ return nil, fmt.Errorf("efl: eina_value_pset(FLOAT) failed")
+ }
+
+ case float64:
+ v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_DOUBLE)
+ if v.ptr == nil {
+ return nil, errors.New("efl: eina_value_new(DOUBLE) returned nil")
+ }
+ cv := C.double(scalarToFloat64(val))
+ if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ C._ego_value_free(v.ptr)
+ return nil, fmt.Errorf("efl: eina_value_pset(DOUBLE) failed")
+ }
+
+ case string:
+ v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_STRING)
+ if v.ptr == nil {
+ return nil, errors.New("efl: eina_value_new(STRING) returned nil")
+ }
+ cs := C.CString(scalarToString(val))
+ defer C.free(unsafe.Pointer(cs))
+ if C._ego_value_set_string(v.ptr, cs) == 0 {
+ C._ego_value_free(v.ptr)
+ return nil, fmt.Errorf("efl: eina_value_pset(STRING) failed")
+ }
+
+ case bool:
+ // EINA_VALUE_TYPE_BOOL maps to an unsigned char (0/1).
+ v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_BOOL)
+ if v.ptr == nil {
+ return nil, errors.New("efl: eina_value_new(BOOL) returned nil")
+ }
+ var cv C.uchar
+ if scalarToBool(val) {
+ cv = 1
+ }
+ if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ C._ego_value_free(v.ptr)
+ return nil, fmt.Errorf("efl: eina_value_pset(BOOL) failed")
+ }
+
+ case byte:
+ // byte == uint8. EINA_VALUE_TYPE_CHAR (signed char) has a distinct type
+ // pointer from EINA_VALUE_TYPE_BOOL/UCHAR, so Kind() can identify byte
+ // values unambiguously. The bit pattern round-trips correctly via a
+ // signed char; the cast to C.char reinterprets high-bit values as
+ // negative, but the byte value is recovered on the read side with the
+ // same cast back to byte.
+ v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_CHAR)
+ if v.ptr == nil {
+ return nil, errors.New("efl: eina_value_new(CHAR) returned nil")
+ }
+ cv := C.char(scalarToByte(val))
+ if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ C._ego_value_free(v.ptr)
+ return nil, fmt.Errorf("efl: eina_value_pset(CHAR) failed")
+ }
+
+ default:
+ return nil, fmt.Errorf("efl: unsupported scalar type %T", val)
+ }
+
+ return v, nil
+}
+
+// ValueGet retrieves the value stored in v as type T.
+// Returns an error if T does not match the stored type or if v is nil.
+func ValueGet[T ScalarType](v *Value) (T, error) {
+ var zero T
+ if v == nil || v.ptr == nil {
+ return zero, errors.New("efl: ValueGet called on nil Value")
+ }
+
+ switch any(zero).(type) {
+ case int, int32:
+ var cv C.int
+ if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ return zero, errors.New("efl: eina_value_pget(INT) failed")
+ }
+ return fromInt[T](int(cv)), nil
+
+ case uint, uint32:
+ var cv C.uint
+ if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ return zero, errors.New("efl: eina_value_pget(UINT) failed")
+ }
+ return fromUint[T](uint(cv)), nil
+
+ case int64:
+ var cv C.int64_t
+ if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ return zero, errors.New("efl: eina_value_pget(INT64) failed")
+ }
+ return fromInt64[T](int64(cv)), nil
+
+ case uint64:
+ var cv C.uint64_t
+ if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ return zero, errors.New("efl: eina_value_pget(UINT64) failed")
+ }
+ return fromUint64[T](uint64(cv)), nil
+
+ case int16:
+ var cv C.short
+ if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ return zero, errors.New("efl: eina_value_pget(SHORT) failed")
+ }
+ return fromInt16[T](int16(cv)), nil
+
+ case uint16:
+ var cv C.ushort
+ if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ return zero, errors.New("efl: eina_value_pget(USHORT) failed")
+ }
+ return fromUint16[T](uint16(cv)), nil
+
+ case float32:
+ var cv C.float
+ if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ return zero, errors.New("efl: eina_value_pget(FLOAT) failed")
+ }
+ return fromFloat32[T](float32(cv)), nil
+
+ case float64:
+ var cv C.double
+ if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ return zero, errors.New("efl: eina_value_pget(DOUBLE) failed")
+ }
+ return fromFloat64[T](float64(cv)), nil
+
+ case string:
+ cs := C._ego_value_get_string(v.ptr)
+ if cs == nil {
+ return zero, errors.New("efl: eina_value_pget(STRING) returned nil")
+ }
+ return fromString[T](C.GoString(cs)), nil
+
+ case bool:
+ var cv C.uchar
+ if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ return zero, errors.New("efl: eina_value_pget(BOOL) failed")
+ }
+ return fromBool[T](cv != 0), nil
+
+ case byte:
+ var cv C.char
+ if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+ return zero, errors.New("efl: eina_value_pget(CHAR) failed")
+ }
+ return fromByte[T](byte(cv)), nil
+ }
+
+ return zero, fmt.Errorf("efl: unsupported scalar type %T", zero)
+}
+
+// WrapValue creates a non-owning Go wrapper around an existing C Eina_Value
+// pointer. The caller retains responsibility for the lifetime of the C object.
+// ptr must not be nil.
+func WrapValue(ptr unsafe.Pointer) *Value {
+ return &Value{
+ ptr: (*C.Eina_Value)(ptr),
+ owned: false,
+ }
+}
+
+// Ptr returns the raw C pointer as an unsafe.Pointer so that callers can pass
+// the value to other C APIs without importing the cgo types directly.
+func (v *Value) Ptr() unsafe.Pointer {
+ if v == nil {
+ return nil
+ }
+ return unsafe.Pointer(v.ptr)
+}
+
+// Free releases the underlying C Eina_Value if this wrapper owns it. It is
+// safe to call Free on a nil *Value or to call it more than once; repeated
+// calls after the first are no-ops.
+func (v *Value) Free() {
+ if v == nil || v.ptr == nil || !v.owned {
+ return
+ }
+ C._ego_value_free(v.ptr)
+ v.ptr = nil
+}
+
+// IsNil reports whether the underlying C pointer is nil.
+func (v *Value) IsNil() bool {
+ return v == nil || v.ptr == nil
+}
+
+// Kind returns the ValueKind that describes the stored scalar type.
+// Returns ValueKindUnknown for unrecognised or compound types.
+func (v *Value) Kind() ValueKind {
+ if v == nil || v.ptr == nil {
+ return ValueKindUnknown
+ }
+ t := C._ego_value_type_get(v.ptr)
+ switch t {
+ case C.EINA_VALUE_TYPE_INT:
+ return ValueKindInt
+ case C.EINA_VALUE_TYPE_UINT:
+ return ValueKindUint
+ case C.EINA_VALUE_TYPE_INT64:
+ return ValueKindInt64
+ case C.EINA_VALUE_TYPE_UINT64:
+ return ValueKindUint64
+ case C.EINA_VALUE_TYPE_SHORT:
+ return ValueKindInt16
+ case C.EINA_VALUE_TYPE_USHORT:
+ return ValueKindUint16
+ case C.EINA_VALUE_TYPE_FLOAT:
+ return ValueKindFloat32
+ case C.EINA_VALUE_TYPE_DOUBLE:
+ return ValueKindFloat64
+ case C.EINA_VALUE_TYPE_STRING, C.EINA_VALUE_TYPE_STRINGSHARE:
+ return ValueKindString
+ case C.EINA_VALUE_TYPE_BOOL: // same pointer as UCHAR; represents bool
+ return ValueKindBool
+ case C.EINA_VALUE_TYPE_CHAR:
+ return ValueKindByte
+ default:
+ return ValueKindUnknown
+ }
+}
+
+// String implements fmt.Stringer by delegating to eina_value_to_string, which
+// produces a human-readable representation of the stored value. The returned
+// string is owned by Go after the call. Returns "<nil>" for a nil Value.
+func (v *Value) String() string {
+ if v == nil || v.ptr == nil {
+ return "<nil>"
+ }
+ cs := C._ego_value_to_string(v.ptr)
+ if cs == nil {
+ return ""
+ }
+ defer C.free(unsafe.Pointer(cs))
+ return C.GoString(cs)
+}
+
+// ---------------------------------------------------------------------------
+// Generic conversion helpers
+//
+// These bridge the gap between the type-switched concrete C types and the
+// generic T parameter. Because the calling switch statement has already
+// matched the concrete underlying type of T, the assertion from the
+// intermediate concrete type to T is always valid at runtime.
+// ---------------------------------------------------------------------------
+
+// scalarToInt extracts the int representation of v. v must have underlying
+// type int or int32 — the caller is responsible for the precondition.
+func scalarToInt[T ScalarType](v T) int {
+ switch u := any(v).(type) {
+ case int:
+ return u
+ case int32:
+ return int(u)
+ default:
+ return 0
+ }
+}
+
+func scalarToUint[T ScalarType](v T) uint {
+ switch u := any(v).(type) {
+ case uint:
+ return u
+ case uint32:
+ return uint(u)
+ default:
+ return 0
+ }
+}
+
+func scalarToInt64[T ScalarType](v T) int64 {
+ return any(v).(int64) //nolint:forcetypeassert
+}
+
+func scalarToUint64[T ScalarType](v T) uint64 {
+ return any(v).(uint64) //nolint:forcetypeassert
+}
+
+func scalarToInt16[T ScalarType](v T) int16 {
+ return any(v).(int16) //nolint:forcetypeassert
+}
+
+func scalarToUint16[T ScalarType](v T) uint16 {
+ return any(v).(uint16) //nolint:forcetypeassert
+}
+
+func scalarToFloat32[T ScalarType](v T) float32 {
+ return any(v).(float32) //nolint:forcetypeassert
+}
+
+func scalarToFloat64[T ScalarType](v T) float64 {
+ return any(v).(float64) //nolint:forcetypeassert
+}
+
+func scalarToString[T ScalarType](v T) string {
+ return any(v).(string) //nolint:forcetypeassert
+}
+
+func scalarToBool[T ScalarType](v T) bool {
+ return any(v).(bool) //nolint:forcetypeassert
+}
+
+func scalarToByte[T ScalarType](v T) byte {
+ return any(v).(byte) //nolint:forcetypeassert
+}
+
+// fromInt converts a plain int back to T. T must have underlying type int or
+// int32; the calling switch guarantees this.
+func fromInt[T ScalarType](n int) T {
+ switch any(*new(T)).(type) {
+ case int32:
+ return any(int32(n)).(T) //nolint:forcetypeassert
+ default:
+ return any(n).(T) //nolint:forcetypeassert
+ }
+}
+
+func fromUint[T ScalarType](n uint) T {
+ switch any(*new(T)).(type) {
+ case uint32:
+ return any(uint32(n)).(T) //nolint:forcetypeassert
+ default:
+ return any(n).(T) //nolint:forcetypeassert
+ }
+}
+
+func fromInt64[T ScalarType](n int64) T { return any(n).(T) } //nolint:forcetypeassert
+func fromUint64[T ScalarType](n uint64) T { return any(n).(T) } //nolint:forcetypeassert
+func fromInt16[T ScalarType](n int16) T { return any(n).(T) } //nolint:forcetypeassert
+func fromUint16[T ScalarType](n uint16) T { return any(n).(T) } //nolint:forcetypeassert
+func fromFloat32[T ScalarType](n float32) T {
+ return any(n).(T) //nolint:forcetypeassert
+}
+func fromFloat64[T ScalarType](n float64) T { return any(n).(T) } //nolint:forcetypeassert
+func fromString[T ScalarType](s string) T { return any(s).(T) } //nolint:forcetypeassert
+func fromBool[T ScalarType](b bool) T { return any(b).(T) } //nolint:forcetypeassert
+func fromByte[T ScalarType](b byte) T { return any(b).(T) } //nolint:forcetypeassert
diff --git a/efl/value_test.go b/efl/value_test.go
new file mode 100644
index 0000000..2bf4b0e
--- /dev/null
+++ b/efl/value_test.go
@@ -0,0 +1,388 @@
+package efl
+
+import (
+ "math"
+ "strings"
+ "testing"
+)
+
+// TestNewValueIntRoundTrip verifies that an int value survives a NewValue /
+// ValueGet round-trip with the correct numeric value.
+func TestNewValueIntRoundTrip(t *testing.T) {
+ v, err := NewValue[int](42)
+ if err != nil {
+ t.Fatalf("NewValue[int]: %v", err)
+ }
+ defer v.Free()
+
+ got, err := ValueGet[int](v)
+ if err != nil {
+ t.Fatalf("ValueGet[int]: %v", err)
+ }
+ if got != 42 {
+ t.Errorf("want 42, got %d", got)
+ }
+}
+
+// TestNewValueStringRoundTrip verifies string storage and retrieval.
+func TestNewValueStringRoundTrip(t *testing.T) {
+ const want = "hello, eina"
+ v, err := NewValue[string](want)
+ if err != nil {
+ t.Fatalf("NewValue[string]: %v", err)
+ }
+ defer v.Free()
+
+ got, err := ValueGet[string](v)
+ if err != nil {
+ t.Fatalf("ValueGet[string]: %v", err)
+ }
+ if got != want {
+ t.Errorf("want %q, got %q", want, got)
+ }
+}
+
+// TestNewValueFloat64RoundTrip verifies float64 storage and retrieval.
+func TestNewValueFloat64RoundTrip(t *testing.T) {
+ const want = 3.14159265358979
+ v, err := NewValue[float64](want)
+ if err != nil {
+ t.Fatalf("NewValue[float64]: %v", err)
+ }
+ defer v.Free()
+
+ got, err := ValueGet[float64](v)
+ if err != nil {
+ t.Fatalf("ValueGet[float64]: %v", err)
+ }
+ if math.Abs(got-want) > 1e-10 {
+ t.Errorf("want ~%.15f, got %.15f", want, got)
+ }
+}
+
+// TestNewValueBoolRoundTrip verifies bool storage and retrieval for both true
+// and false.
+func TestNewValueBoolRoundTrip(t *testing.T) {
+ for _, want := range []bool{true, false} {
+ v, err := NewValue[bool](want)
+ if err != nil {
+ t.Fatalf("NewValue[bool](%v): %v", want, err)
+ }
+ got, err := ValueGet[bool](v)
+ v.Free()
+ if err != nil {
+ t.Fatalf("ValueGet[bool](%v): %v", want, err)
+ }
+ if got != want {
+ t.Errorf("want %v, got %v", want, got)
+ }
+ }
+}
+
+// TestNewValueInt64RoundTrip verifies int64 storage and retrieval using a
+// value that exceeds 32-bit range.
+func TestNewValueInt64RoundTrip(t *testing.T) {
+ const want = int64(math.MaxInt64)
+ v, err := NewValue[int64](want)
+ if err != nil {
+ t.Fatalf("NewValue[int64]: %v", err)
+ }
+ defer v.Free()
+
+ got, err := ValueGet[int64](v)
+ if err != nil {
+ t.Fatalf("ValueGet[int64]: %v", err)
+ }
+ if got != want {
+ t.Errorf("want %d, got %d", want, got)
+ }
+}
+
+// TestNewValueUint64RoundTrip verifies uint64 storage and retrieval.
+func TestNewValueUint64RoundTrip(t *testing.T) {
+ const want = uint64(math.MaxUint64)
+ v, err := NewValue[uint64](want)
+ if err != nil {
+ t.Fatalf("NewValue[uint64]: %v", err)
+ }
+ defer v.Free()
+
+ got, err := ValueGet[uint64](v)
+ if err != nil {
+ t.Fatalf("ValueGet[uint64]: %v", err)
+ }
+ if got != want {
+ t.Errorf("want %d, got %d", want, got)
+ }
+}
+
+// TestNewValueFloat32RoundTrip verifies float32 storage and retrieval.
+func TestNewValueFloat32RoundTrip(t *testing.T) {
+ const want = float32(1.5)
+ v, err := NewValue[float32](want)
+ if err != nil {
+ t.Fatalf("NewValue[float32]: %v", err)
+ }
+ defer v.Free()
+
+ got, err := ValueGet[float32](v)
+ if err != nil {
+ t.Fatalf("ValueGet[float32]: %v", err)
+ }
+ if got != want {
+ t.Errorf("want %v, got %v", want, got)
+ }
+}
+
+// TestNewValueByteRoundTrip verifies byte (uint8) storage and retrieval.
+func TestNewValueByteRoundTrip(t *testing.T) {
+ const want = byte(0xAB)
+ v, err := NewValue[byte](want)
+ if err != nil {
+ t.Fatalf("NewValue[byte]: %v", err)
+ }
+ defer v.Free()
+
+ got, err := ValueGet[byte](v)
+ if err != nil {
+ t.Fatalf("ValueGet[byte]: %v", err)
+ }
+ if got != want {
+ t.Errorf("want 0x%02X, got 0x%02X", want, got)
+ }
+}
+
+// TestNewValueInt16RoundTrip verifies int16 storage and retrieval.
+func TestNewValueInt16RoundTrip(t *testing.T) {
+ const want = int16(-1000)
+ v, err := NewValue[int16](want)
+ if err != nil {
+ t.Fatalf("NewValue[int16]: %v", err)
+ }
+ defer v.Free()
+
+ got, err := ValueGet[int16](v)
+ if err != nil {
+ t.Fatalf("ValueGet[int16]: %v", err)
+ }
+ if got != want {
+ t.Errorf("want %d, got %d", want, got)
+ }
+}
+
+// TestNewValueUint16RoundTrip verifies uint16 storage and retrieval.
+func TestNewValueUint16RoundTrip(t *testing.T) {
+ const want = uint16(60000)
+ v, err := NewValue[uint16](want)
+ if err != nil {
+ t.Fatalf("NewValue[uint16]: %v", err)
+ }
+ defer v.Free()
+
+ got, err := ValueGet[uint16](v)
+ if err != nil {
+ t.Fatalf("ValueGet[uint16]: %v", err)
+ }
+ if got != want {
+ t.Errorf("want %d, got %d", want, got)
+ }
+}
+
+// TestValueKind verifies that Kind returns the correct ValueKind for each
+// supported scalar type.
+func TestValueKind(t *testing.T) {
+ tests := []struct {
+ name string
+ kind ValueKind
+ make func() *Value
+ }{
+ {"int", ValueKindInt, func() *Value {
+ v, _ := NewValue[int](0)
+ return v
+ }},
+ {"uint", ValueKindUint, func() *Value {
+ v, _ := NewValue[uint](0)
+ return v
+ }},
+ {"int64", ValueKindInt64, func() *Value {
+ v, _ := NewValue[int64](0)
+ return v
+ }},
+ {"uint64", ValueKindUint64, func() *Value {
+ v, _ := NewValue[uint64](0)
+ return v
+ }},
+ {"int16", ValueKindInt16, func() *Value {
+ v, _ := NewValue[int16](0)
+ return v
+ }},
+ {"uint16", ValueKindUint16, func() *Value {
+ v, _ := NewValue[uint16](0)
+ return v
+ }},
+ {"float32", ValueKindFloat32, func() *Value {
+ v, _ := NewValue[float32](0)
+ return v
+ }},
+ {"float64", ValueKindFloat64, func() *Value {
+ v, _ := NewValue[float64](0)
+ return v
+ }},
+ {"string", ValueKindString, func() *Value {
+ v, _ := NewValue[string]("")
+ return v
+ }},
+ {"bool", ValueKindBool, func() *Value {
+ v, _ := NewValue[bool](false)
+ return v
+ }},
+ {"byte", ValueKindByte, func() *Value {
+ v, _ := NewValue[byte](0)
+ return v
+ }},
+ }
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ v := tc.make()
+ if v == nil {
+ t.Fatal("NewValue returned nil")
+ }
+ defer v.Free()
+ if got := v.Kind(); got != tc.kind {
+ t.Errorf("want Kind %d, got %d", tc.kind, got)
+ }
+ })
+ }
+}
+
+// TestValueString verifies that String() produces a non-empty, readable
+// representation for common types.
+func TestValueString(t *testing.T) {
+ tests := []struct {
+ name string
+ contains string
+ make func() *Value
+ }{
+ {"int 99", "99", func() *Value {
+ v, _ := NewValue[int](99)
+ return v
+ }},
+ {"string hello", "hello", func() *Value {
+ v, _ := NewValue[string]("hello")
+ return v
+ }},
+ {"float64 2.5", "2.5", func() *Value {
+ v, _ := NewValue[float64](2.5)
+ return v
+ }},
+ {"bool true", "1", func() *Value { // eina renders bool as 0/1
+ v, _ := NewValue[bool](true)
+ return v
+ }},
+ }
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ v := tc.make()
+ if v == nil {
+ t.Fatal("NewValue returned nil")
+ }
+ defer v.Free()
+ s := v.String()
+ if !strings.Contains(s, tc.contains) {
+ t.Errorf("String() = %q, want it to contain %q", s, tc.contains)
+ }
+ })
+ }
+}
+
+// TestValueStringNil verifies that String() on a nil *Value returns "<nil>"
+// without panicking.
+func TestValueStringNil(t *testing.T) {
+ var v *Value
+ if got := v.String(); got != "<nil>" {
+ t.Errorf("want <nil>, got %q", got)
+ }
+}
+
+// TestValueFreeOwned verifies that Free releases an owned Value and that a
+// second call is a safe no-op (double-free guard).
+func TestValueFreeOwned(t *testing.T) {
+ v, err := NewValue[int](7)
+ if err != nil {
+ t.Fatalf("NewValue[int]: %v", err)
+ }
+ v.Free() // first Free: should release the C allocation
+ v.Free() // second Free: must not panic or double-free
+}
+
+// TestValueFreeNil verifies that calling Free on a nil *Value is a safe no-op.
+func TestValueFreeNil(t *testing.T) {
+ var v *Value
+ v.Free() // must not panic
+}
+
+// TestValueIsNilFalse verifies that IsNil returns false for a live Value.
+func TestValueIsNilFalse(t *testing.T) {
+ v, err := NewValue[int](1)
+ if err != nil {
+ t.Fatalf("NewValue[int]: %v", err)
+ }
+ defer v.Free()
+ if v.IsNil() {
+ t.Error("expected IsNil() == false for a live Value")
+ }
+}
+
+// TestValueIsNilTrue verifies that IsNil returns true for a nil *Value and for
+// a Value whose internal pointer has been cleared by Free.
+func TestValueIsNilTrue(t *testing.T) {
+ var v *Value
+ if !v.IsNil() {
+ t.Error("expected IsNil() == true for nil *Value")
+ }
+
+ v2, err := NewValue[int](0)
+ if err != nil {
+ t.Fatalf("NewValue[int]: %v", err)
+ }
+ v2.Free()
+ if !v2.IsNil() {
+ t.Error("expected IsNil() == true after Free()")
+ }
+}
+
+// TestValueGetNil verifies that ValueGet on a nil Value returns an error
+// rather than panicking.
+func TestValueGetNil(t *testing.T) {
+ var v *Value
+ _, err := ValueGet[int](v)
+ if err == nil {
+ t.Error("expected error from ValueGet on nil Value")
+ }
+}
+
+// TestWrapValue verifies that a non-owned wrapper over an existing Eina_Value
+// pointer can read back the stored value and that Free on it does not touch
+// the underlying allocation.
+func TestWrapValue(t *testing.T) {
+ owned, err := NewValue[int](123)
+ if err != nil {
+ t.Fatalf("NewValue[int]: %v", err)
+ }
+ defer owned.Free()
+
+ wrapped := WrapValue(owned.Ptr())
+ if wrapped.IsNil() {
+ t.Fatal("WrapValue returned a nil wrapper")
+ }
+ // Free on a non-owned wrapper must be a no-op.
+ wrapped.Free()
+ // The original owned value should still be readable.
+ got, err := ValueGet[int](owned)
+ if err != nil {
+ t.Fatalf("ValueGet after WrapValue.Free: %v", err)
+ }
+ if got != 123 {
+ t.Errorf("want 123, got %d", got)
+ }
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.