diff --git a/bool.go b/bool.go index 38d97f5..8de7c78 100644 --- a/bool.go +++ b/bool.go @@ -31,3 +31,7 @@ func False() Bool { func (b Bool) Bool() bool { return C.PyObject_IsTrue(b.obj) != 0 } + +func (b Bool) Not() Bool { + return MakeBool(!b.Bool()) +} diff --git a/bool_test.go b/bool_test.go new file mode 100644 index 0000000..5005a4f --- /dev/null +++ b/bool_test.go @@ -0,0 +1,36 @@ +package gp + +import ( + "testing" +) + +func TestBool(t *testing.T) { + // Test MakeBool + b1 := MakeBool(true) + if !b1.Bool() { + t.Error("MakeBool(true) should return true") + } + + b2 := MakeBool(false) + if b2.Bool() { + t.Error("MakeBool(false) should return false") + } + + // Test True and False + if !True().Bool() { + t.Error("True() should return true") + } + + if False().Bool() { + t.Error("False() should return false") + } + + // Test Not method + if True().Not().Bool() { + t.Error("True().Not() should return false") + } + + if !False().Not().Bool() { + t.Error("False().Not() should return true") + } +} diff --git a/bytes_test.go b/bytes_test.go new file mode 100644 index 0000000..2dd861d --- /dev/null +++ b/bytes_test.go @@ -0,0 +1,51 @@ +package gp + +import ( + "bytes" + "testing" +) + +func TestBytesCreation(t *testing.T) { + // Test BytesFromStr + b1 := BytesFromStr("hello") + if string(b1.Bytes()) != "hello" { + t.Errorf("BytesFromStr: expected 'hello', got '%s'", string(b1.Bytes())) + } + + // Test MakeBytes + data := []byte("world") + b2 := MakeBytes(data) + if !bytes.Equal(b2.Bytes(), data) { + t.Errorf("MakeBytes: expected '%v', got '%v'", data, b2.Bytes()) + } +} + +func TestBytesDecode(t *testing.T) { + // Test UTF-8 decode + b := BytesFromStr("你好") + if !bytes.Equal(b.Bytes(), []byte("你好")) { + t.Errorf("BytesFromStr: expected '你好', got '%s'", string(b.Bytes())) + } + s := b.Decode("utf-8") + if s.String() != "你好" { + t.Errorf("Decode: expected '你好', got '%s'", s.String()) + } + + // Test ASCII decode + b2 := BytesFromStr("hello") + s2 := b2.Decode("ascii") + if s2.String() != "hello" { + t.Errorf("Decode: expected 'hello', got '%s'", s2.String()) + } +} + +func TestBytesConversion(t *testing.T) { + original := []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f} // "Hello" in hex + b := MakeBytes(original) + + // Test conversion back to []byte + result := b.Bytes() + if !bytes.Equal(result, original) { + t.Errorf("Bytes conversion: expected %v, got %v", original, result) + } +} diff --git a/complex_test.go b/complex_test.go new file mode 100644 index 0000000..af35698 --- /dev/null +++ b/complex_test.go @@ -0,0 +1,88 @@ +package gp + +import ( + "testing" +) + +func TestComplex(t *testing.T) { + tests := []struct { + name string + input complex128 + wantReal float64 + wantImag float64 + }{ + { + name: "zero complex", + input: complex(0, 0), + wantReal: 0, + wantImag: 0, + }, + { + name: "positive real and imaginary", + input: complex(3.14, 2.718), + wantReal: 3.14, + wantImag: 2.718, + }, + { + name: "negative real and imaginary", + input: complex(-1.5, -2.5), + wantReal: -1.5, + wantImag: -2.5, + }, + { + name: "mixed signs", + input: complex(-1.23, 4.56), + wantReal: -1.23, + wantImag: 4.56, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := MakeComplex(tt.input) + + // Test Real() method + if got := c.Real(); got != tt.wantReal { + t.Errorf("Complex.Real() = %v, want %v", got, tt.wantReal) + } + + // Test Imag() method + if got := c.Imag(); got != tt.wantImag { + t.Errorf("Complex.Imag() = %v, want %v", got, tt.wantImag) + } + + // Test Complex128() method + if got := c.Complex128(); got != tt.input { + t.Errorf("Complex.Complex128() = %v, want %v", got, tt.input) + } + }) + } +} + +func TestComplexZeroValue(t *testing.T) { + // Create a proper zero complex number instead of using zero-value struct + c := MakeComplex(complex(0, 0)) + + // Test that zero complex behaves correctly + if got := c.Real(); got != 0 { + t.Errorf("Zero Complex.Real() = %v, want 0", got) + } + if got := c.Imag(); got != 0 { + t.Errorf("Zero Complex.Imag() = %v, want 0", got) + } + if got := c.Complex128(); got != 0 { + t.Errorf("Zero Complex.Complex128() = %v, want 0", got) + } +} + +func TestComplexNilHandling(t *testing.T) { + var c Complex // zero-value struct with nil pointer + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for nil pointer access, but got none") + } + }() + + // This should panic + _ = c.Real() +} diff --git a/dict.go b/dict.go index d659091..76260d4 100644 --- a/dict.go +++ b/dict.go @@ -17,10 +17,6 @@ func newDict(obj *PyObject) Dict { return Dict{newObject(obj)} } -func NewDict(obj *PyObject) Dict { - return newDict(obj) -} - func DictFromPairs(pairs ...any) Dict { if len(pairs)%2 != 0 { panic("DictFromPairs requires an even number of arguments") diff --git a/dict_test.go b/dict_test.go new file mode 100644 index 0000000..e9228bf --- /dev/null +++ b/dict_test.go @@ -0,0 +1,174 @@ +package gp + +import ( + "testing" +) + +func TestDictFromPairs(t *testing.T) { + // Add panic test case + t.Run("odd number of arguments", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("DictFromPairs() with odd number of arguments should panic") + } else if r != "DictFromPairs requires an even number of arguments" { + t.Errorf("Expected panic message 'DictFromPairs requires an even number of arguments', got '%v'", r) + } + }() + + DictFromPairs("key1", "value1", "key2") // Should panic + }) + + tests := []struct { + name string + pairs []any + wantKeys []any + wantVals []any + }{ + { + name: "string keys and values", + pairs: []any{"key1", "value1", "key2", "value2"}, + wantKeys: []any{"key1", "key2"}, + wantVals: []any{"value1", "value2"}, + }, + { + name: "mixed types", + pairs: []any{"key1", 42, "key2", 3.14}, + wantKeys: []any{"key1", "key2"}, + wantVals: []any{42, 3.14}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dict := DictFromPairs(tt.pairs...) + + // Verify each key-value pair + for i := 0; i < len(tt.wantKeys); i++ { + key := From(tt.wantKeys[i]) + val := dict.Get(key) + if !ObjectsAreEqual(val, From(tt.wantVals[i])) { + t.Errorf("DictFromPairs() got value %v for key %v, want %v", + val, tt.wantKeys[i], tt.wantVals[i]) + } + } + }) + } +} + +func TestMakeDict(t *testing.T) { + tests := []struct { + name string + m map[any]any + }{ + { + name: "string map", + m: map[any]any{ + "key1": "value1", + "key2": "value2", + }, + }, + { + name: "mixed types map", + m: map[any]any{ + "int": 42, + "float": 3.14, + "string": "hello", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dict := MakeDict(tt.m) + + // Verify each key-value pair + for k, v := range tt.m { + key := From(k) + got := dict.Get(key) + if !ObjectsAreEqual(got, From(v)) { + t.Errorf("MakeDict() got value %v for key %v, want %v", got, k, v) + } + } + }) + } +} + +func TestDictSetGet(t *testing.T) { + dict := DictFromPairs() + + // Test Set and Get + key := From("test_key") + value := From("test_value") + dict.Set(key, value) + + got := dict.Get(key) + if !ObjectsAreEqual(got, value) { + t.Errorf("Dict.Get() got %v, want %v", got, value) + } +} + +func TestDictSetGetString(t *testing.T) { + dict := DictFromPairs() + + // Test SetString and GetString + value := From("test_value") + dict.SetString("test_key", value) + + got := dict.GetString("test_key") + if !ObjectsAreEqual(got, value) { + t.Errorf("Dict.GetString() got %v, want %v", got, value) + } +} + +func TestDictDel(t *testing.T) { + dict := DictFromPairs("test_key", "test_value") + key := From("test_key") + + // Verify key exists + got := dict.Get(key) + if !ObjectsAreEqual(got, From("test_value")) { + t.Errorf("Before deletion, got %v, want %v", got, "test_value") + } + + // Delete the key + dict.Del(key) + + // After deletion, Get should return nil object + got = dict.Get(key) + if got.Obj() != nil { + t.Errorf("After deletion, got %v, want nil", got) + } +} + +func TestDictForEach(t *testing.T) { + dict := DictFromPairs( + "key1", "value1", + "key2", "value2", + "key3", "value3", + ) + + count := 0 + expectedPairs := map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + } + + dict.ForEach(func(key, value Object) { + count++ + k := key.String() + v := value.String() + if expectedVal, ok := expectedPairs[k]; !ok || expectedVal != v { + t.Errorf("ForEach() unexpected pair: %v: %v", k, v) + } + }) + + if count != len(expectedPairs) { + t.Errorf("ForEach() visited %d pairs, want %d", count, len(expectedPairs)) + } +} + +// Helper function to compare Python objects +func ObjectsAreEqual(obj1, obj2 Object) bool { + return obj1.String() == obj2.String() +} diff --git a/float.go b/float.go index f8a12bc..ffca39b 100644 --- a/float.go +++ b/float.go @@ -5,6 +5,8 @@ package gp */ import "C" +// Float represents a Python float object. It provides methods to convert between +// Go float types and Python float objects, as well as checking numeric properties. type Float struct { Object } @@ -21,6 +23,10 @@ func (f Float) Float64() float64 { return float64(C.PyFloat_AsDouble(f.obj)) } +func (f Float) Float32() float32 { + return float32(C.PyFloat_AsDouble(f.obj)) +} + func (f Float) IsInteger() Bool { fn := Cast[Func](f.Attr("is_integer")) return Cast[Bool](fn.callNoArgs()) diff --git a/float_test.go b/float_test.go new file mode 100644 index 0000000..32cca35 --- /dev/null +++ b/float_test.go @@ -0,0 +1,54 @@ +package gp + +import ( + "testing" +) + +func TestFloat(t *testing.T) { + t.Run("MakeFloat and conversions", func(t *testing.T) { + // Test creating float and converting back + f := MakeFloat(3.14159) + + // Test Float64 conversion + if got := f.Float64(); got != 3.14159 { + t.Errorf("Float64() = %v, want %v", got, 3.14159) + } + + // Test Float32 conversion + if got := f.Float32(); float64(got) != float64(float32(3.14159)) { + t.Errorf("Float32() = %v, want %v", got, float32(3.14159)) + } + }) + + t.Run("IsInteger", func(t *testing.T) { + // Test integer float + intFloat := MakeFloat(5.0) + + if !intFloat.IsInteger().Bool() { + t.Errorf("IsInteger() for 5.0 = false, want true") + } + + // Test non-integer float + fracFloat := MakeFloat(5.5) + + if fracFloat.IsInteger().Bool() { + t.Errorf("IsInteger() for 5.5 = true, want false") + } + }) + + t.Run("Zero and special values", func(t *testing.T) { + // Test zero + zero := MakeFloat(0.0) + + if got := zero.Float64(); got != 0.0 { + t.Errorf("Float64() = %v, want 0.0", got) + } + + // Test very large number + large := MakeFloat(1e308) + + if got := large.Float64(); got != 1e308 { + t.Errorf("Float64() = %v, want 1e308", got) + } + }) +} diff --git a/function_test.go b/function_test.go index 4db5b8f..c1c7d0d 100644 --- a/function_test.go +++ b/function_test.go @@ -35,9 +35,6 @@ func (t *TestStruct) TestMethod() int { } func TestAddType(t *testing.T) { - Initialize() - defer Finalize() - m := MainModule() // test add type @@ -124,9 +121,6 @@ func (i *InitTestStruct) Init(val int) { } func TestAddTypeWithInit(t *testing.T) { - Initialize() - defer Finalize() - m := MainModule() typ := AddType[InitTestStruct](m, (*InitTestStruct).Init, "InitTestStruct", "Test init struct") diff --git a/kw_test.go b/kw_test.go new file mode 100644 index 0000000..a5f6dbd --- /dev/null +++ b/kw_test.go @@ -0,0 +1,72 @@ +package gp + +import ( + "reflect" + "testing" +) + +func TestSplitArgs(t *testing.T) { + tests := []struct { + name string + args []any + wantTup Tuple + wantKw KwArgs + }{ + { + name: "empty args", + args: []any{}, + wantTup: MakeTuple(), + wantKw: nil, + }, + { + name: "only positional args", + args: []any{1, "two", 3.0}, + wantTup: MakeTuple(1, "two", 3.0), + wantKw: nil, + }, + { + name: "with kwargs", + args: []any{1, "two", KwArgs{"a": 1, "b": "test"}}, + wantTup: MakeTuple(1, "two"), + wantKw: KwArgs{"a": 1, "b": "test"}, + }, + { + name: "only kwargs", + args: []any{KwArgs{"x": 10, "y": 20}}, + wantTup: MakeTuple(), + wantKw: KwArgs{"x": 10, "y": 20}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotTup, gotKw := splitArgs(tt.args...) + + if !reflect.DeepEqual(gotTup, tt.wantTup) { + t.Errorf("splitArgs() tuple = %v, want %v", gotTup, tt.wantTup) + } + + if !reflect.DeepEqual(gotKw, tt.wantKw) { + t.Errorf("splitArgs() kwargs = %v, want %v", gotKw, tt.wantKw) + } + }) + } +} + +func TestKwArgs(t *testing.T) { + kw := KwArgs{ + "name": "test", + "age": 42, + } + + // Test type assertion + if _, ok := interface{}(kw).(KwArgs); !ok { + t.Error("KwArgs failed type assertion") + } + + // Test map operations + kw["new"] = "value" + if v, ok := kw["new"]; !ok || v != "value" { + t.Error("KwArgs map operations failed") + } +} diff --git a/list_test.go b/list_test.go new file mode 100644 index 0000000..aa515a3 --- /dev/null +++ b/list_test.go @@ -0,0 +1,98 @@ +package gp + +import ( + "testing" +) + +func TestMakeList(t *testing.T) { + tests := []struct { + name string + args []any + wantLen int + wantVals []any + }{ + { + name: "empty list", + args: []any{}, + wantLen: 0, + wantVals: []any{}, + }, + { + name: "integers", + args: []any{1, 2, 3}, + wantLen: 3, + wantVals: []any{1, 2, 3}, + }, + { + name: "mixed types", + args: []any{1, "hello", 3.14}, + wantLen: 3, + wantVals: []any{1, "hello", 3.14}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + list := MakeList(tt.args...) + + if got := list.Len(); got != tt.wantLen { + t.Errorf("MakeList() len = %v, want %v", got, tt.wantLen) + } + + for i, want := range tt.wantVals { + got := list.GetItem(i).String() + if got != From(want).String() { + t.Errorf("MakeList() item[%d] = %v, want %v", i, got, want) + } + } + }) + } +} + +func TestList_SetItem(t *testing.T) { + list := MakeList(1, 2, 3) + list.SetItem(1, From("test")) + + // Get the raw value without quotes for comparison + got := list.GetItem(1).String() + + if got != "test" { + t.Errorf("List.SetItem() = %v, want %v", got, "test") + } +} + +func TestList_Append(t *testing.T) { + list := MakeList(1, 2) + initialLen := list.Len() + + list.Append(From(3)) + + if got := list.Len(); got != initialLen+1 { + t.Errorf("List.Append() length = %v, want %v", got, initialLen+1) + } + + if got := list.GetItem(2).String(); got != From(3).String() { + t.Errorf("List.Append() last item = %v, want %v", got, From(3).String()) + } +} + +func TestList_Len(t *testing.T) { + tests := []struct { + name string + args []any + want int + }{ + {"empty list", []any{}, 0}, + {"single item", []any{1}, 1}, + {"multiple items", []any{1, 2, 3}, 3}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + list := MakeList(tt.args...) + if got := list.Len(); got != tt.want { + t.Errorf("List.Len() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/long.go b/long.go index fcd94cd..414b90a 100644 --- a/long.go +++ b/long.go @@ -18,18 +18,6 @@ func MakeLong(i int64) Long { return newLong(C.PyLong_FromLongLong(C.longlong(i))) } -func (l Long) Int64() int64 { - return int64(C.PyLong_AsLongLong(l.obj)) -} - -func (l Long) Uint64() uint64 { - return uint64(C.PyLong_AsUnsignedLongLong(l.obj)) -} - -func (l Long) AsFloat64() float64 { - return float64(C.PyLong_AsDouble(l.obj)) -} - func LongFromFloat64(v float64) Long { return newLong(C.PyLong_FromDouble(C.double(v))) } @@ -45,12 +33,27 @@ func LongFromUnicode(u Object, base int) Long { return newLong(C.PyLong_FromUnicodeObject(u.Obj(), C.int(base))) } -func (l Long) AsUint64() uint64 { +func (l Long) Int() int { + return int(l.Int64()) +} + +func (l Long) Int64() int64 { + return int64(C.PyLong_AsLongLong(l.obj)) +} + +func (l Long) Uint() uint { + return uint(l.Uint64()) +} + +func (l Long) Uint64() uint64 { return uint64(C.PyLong_AsUnsignedLongLong(l.obj)) } -func (l Long) AsUintptr() uintptr { - return uintptr(C.PyLong_AsLong(l.obj)) +func (l Long) Uintptr() uintptr { + return uintptr(l.Int64()) +} +func (l Long) Float64() float64 { + return float64(C.PyLong_AsDouble(l.obj)) } func LongFromUintptr(v uintptr) Long { diff --git a/long_test.go b/long_test.go new file mode 100644 index 0000000..d78cf6e --- /dev/null +++ b/long_test.go @@ -0,0 +1,146 @@ +package gp + +import ( + "math" + "testing" +) + +func TestLongCreation(t *testing.T) { + tests := []struct { + name string + input int64 + expected int64 + }{ + {"zero", 0, 0}, + {"positive", 42, 42}, + {"negative", -42, -42}, + {"max_int64", math.MaxInt64, math.MaxInt64}, + {"min_int64", math.MinInt64, math.MinInt64}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := MakeLong(tt.input) + if got := l.Int64(); got != tt.expected { + t.Errorf("MakeLong(%d) = %d; want %d", tt.input, got, tt.expected) + } + }) + } +} + +func TestLongFromFloat64(t *testing.T) { + tests := []struct { + name string + input float64 + expected int64 + }{ + {"integer_float", 42.0, 42}, + {"truncated_float", 42.9, 42}, + {"negative_float", -42.9, -42}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := LongFromFloat64(tt.input) + if got := l.Int64(); got != tt.expected { + t.Errorf("LongFromFloat64(%f) = %d; want %d", tt.input, got, tt.expected) + } + }) + } +} + +func TestLongFromString(t *testing.T) { + tests := []struct { + name string + input string + base int + expected int64 + }{ + {"decimal", "42", 10, 42}, + {"hex", "2A", 16, 42}, + {"binary", "101010", 2, 42}, + {"octal", "52", 8, 42}, + {"negative", "-42", 10, -42}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := LongFromString(tt.input, tt.base) + if got := l.Int64(); got != tt.expected { + t.Errorf("LongFromString(%q, %d) = %d; want %d", tt.input, tt.base, got, tt.expected) + } + }) + } +} + +func TestLongConversions(t *testing.T) { + l := MakeLong(42) + + t.Run("Int", func(t *testing.T) { + if got := l.Int(); got != 42 { + t.Errorf("Int() = %d; want 42", got) + } + }) + + t.Run("Uint", func(t *testing.T) { + if got := l.Uint(); got != 42 { + t.Errorf("Uint() = %d; want 42", got) + } + }) + + t.Run("Uint64", func(t *testing.T) { + if got := l.Uint64(); got != 42 { + t.Errorf("Uint64() = %d; want 42", got) + } + }) + + t.Run("Float64", func(t *testing.T) { + if got := l.Float64(); got != 42.0 { + t.Errorf("Float64() = %f; want 42.0", got) + } + }) +} + +func TestLongFromUintptr(t *testing.T) { + tests := []struct { + name string + input uintptr + expected int64 + }{ + {"zero", 0, 0}, + {"positive", 42, 42}, + {"large_number", 1 << 30, 1 << 30}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := LongFromUintptr(tt.input) + if got := l.Int64(); got != tt.expected { + t.Errorf("LongFromUintptr(%d) = %d; want %d", tt.input, got, tt.expected) + } + }) + } +} + +func TestLongFromUnicode(t *testing.T) { + tests := []struct { + name string + input string + base int + expected int64 + }{ + {"unicode_decimal", "42", 10, 42}, + {"unicode_hex", "2A", 16, 42}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create Unicode object from string + u := MakeStr(tt.input) + l := LongFromUnicode(u.Object, tt.base) + if got := l.Int64(); got != tt.expected { + t.Errorf("LongFromUnicode(%q, %d) = %d; want %d", tt.input, tt.base, got, tt.expected) + } + }) + } +} diff --git a/math/math_test.go b/math/math_test.go new file mode 100644 index 0000000..ee4bcfb --- /dev/null +++ b/math/math_test.go @@ -0,0 +1,32 @@ +package math + +import ( + "testing" + + gp "github.com/cpunion/go-python" +) + +func TestSqrt(t *testing.T) { + // Initialize Python + gp.Initialize() + defer gp.Finalize() + + tests := []struct { + input float64 + expected float64 + }{ + {16.0, 4.0}, + {25.0, 5.0}, + {0.0, 0.0}, + {100.0, 10.0}, + } + + for _, test := range tests { + input := gp.MakeFloat(test.input) + result := Sqrt(input) + + if result.Float64() != test.expected { + t.Errorf("Sqrt(%f) = %f; want %f", test.input, result.Float64(), test.expected) + } + } +} diff --git a/object_test.go b/object_test.go index 66e6ff7..0fb370b 100644 --- a/object_test.go +++ b/object_test.go @@ -6,9 +6,6 @@ import ( ) func TestObjectCreation(t *testing.T) { - Initialize() - defer Finalize() - // Test From() with different Go types tests := []struct { name string @@ -60,9 +57,6 @@ func TestObjectCreation(t *testing.T) { } func TestObjectAttributes(t *testing.T) { - Initialize() - defer Finalize() - // Test attributes using Python's built-in object type builtins := ImportModule("builtins") obj := builtins.AttrFunc("object").Call() @@ -107,9 +101,6 @@ class TestClass: } func TestDictOperations(t *testing.T) { - Initialize() - defer Finalize() - // Test dictionary operations pyDict := MakeDict(nil) pyDict.Set(MakeStr("key1"), From(42)) @@ -132,9 +123,6 @@ func TestDictOperations(t *testing.T) { } func TestObjectConversion(t *testing.T) { - Initialize() - defer Finalize() - type Person struct { Name string Age int @@ -176,9 +164,6 @@ func TestObjectConversion(t *testing.T) { } func TestObjectString(t *testing.T) { - Initialize() - defer Finalize() - tests := []struct { name string input interface{} diff --git a/python_test.go b/python_test.go new file mode 100644 index 0000000..ff0ef48 --- /dev/null +++ b/python_test.go @@ -0,0 +1,13 @@ +package gp + +import ( + "os" + "testing" +) + +func TestMain(m *testing.M) { + Initialize() + code := m.Run() + Finalize() + os.Exit(code) +} diff --git a/tuple_test.go b/tuple_test.go new file mode 100644 index 0000000..14e8d8d --- /dev/null +++ b/tuple_test.go @@ -0,0 +1,173 @@ +package gp + +import ( + "testing" +) + +func TestTupleCreation(t *testing.T) { + // Test empty tuple + empty := MakeTupleWithLen(0) + if empty.Len() != 0 { + t.Errorf("Expected empty tuple length 0, got %d", empty.Len()) + } + + // Test tuple with values + tuple := MakeTuple(42, "hello", 3.14) + if tuple.Len() != 3 { + t.Errorf("Expected tuple length 3, got %d", tuple.Len()) + } +} + +func TestTupleGetSet(t *testing.T) { + tuple := MakeTupleWithLen(2) + + // Test setting and getting values + tuple.Set(0, From(123)) + tuple.Set(1, From("test")) + + if val := tuple.Get(0).AsLong().Int64(); val != 123 { + t.Errorf("Expected 123, got %d", val) + } + if val := tuple.Get(1).AsStr().String(); val != "test" { + t.Errorf("Expected 'test', got %s", val) + } +} + +func TestTupleSlice(t *testing.T) { + tuple := MakeTuple(1, 2, 3, 4, 5) + + // Test slicing + slice := tuple.Slice(1, 4) + if slice.Len() != 3 { + t.Errorf("Expected slice length 3, got %d", slice.Len()) + } + + expected := []int64{2, 3, 4} + for i := 0; i < slice.Len(); i++ { + if val := slice.Get(i).AsLong().Int64(); val != expected[i] { + t.Errorf("At index %d: expected %d, got %d", i, expected[i], val) + } + } +} + +func TestTupleParseArgs(t *testing.T) { + tuple := MakeTuple(42, "hello", 3.14, true) + + var ( + intVal int + strVal string + floatVal float64 + boolVal bool + extraVal int // This shouldn't get set + ) + + // Test successful parsing + success := tuple.ParseArgs(&intVal, &strVal, &floatVal, &boolVal) + if !success { + t.Error("ParseArgs failed unexpectedly") + } + + if intVal != 42 { + t.Errorf("Expected int 42, got %d", intVal) + } + if strVal != "hello" { + t.Errorf("Expected string 'hello', got %s", strVal) + } + if floatVal != 3.14 { + t.Errorf("Expected float 3.14, got %f", floatVal) + } + if !boolVal { + t.Errorf("Expected bool true, got false") + } + + // Test parsing with too many arguments + success = tuple.ParseArgs(&intVal, &strVal, &floatVal, &boolVal, &extraVal) + if success { + t.Error("ParseArgs should have failed with too many arguments") + } + + // Test parsing with invalid type + var invalidPtr *testing.T + success = tuple.ParseArgs(&invalidPtr) + if success { + t.Error("ParseArgs should have failed with invalid type") + } +} + +func TestTupleParseArgsTypes(t *testing.T) { + // Test all supported numeric types + tuple := MakeTuple(42, 42, 42, 42, 42, 42, 42, 42, 42, 42) + + var ( + intVal int + int8Val int8 + int16Val int16 + int32Val int32 + int64Val int64 + uintVal uint + uint8Val uint8 + uint16Val uint16 + uint32Val uint32 + uint64Val uint64 + ) + + success := tuple.ParseArgs( + &intVal, &int8Val, &int16Val, &int32Val, &int64Val, + &uintVal, &uint8Val, &uint16Val, &uint32Val, &uint64Val, + ) + + if !success { + t.Error("ParseArgs failed for numeric types") + } + + // Test floating point types + floatTuple := MakeTuple(3.14, 3.14) + var float32Val float32 + var float64Val float64 + + success = floatTuple.ParseArgs(&float32Val, &float64Val) + if !success { + t.Error("ParseArgs failed for floating point types") + } + + // Test complex types + complexTuple := MakeTuple(complex(1, 2), complex(3, 4)) + var complex64Val complex64 + var complex128Val complex128 + + success = complexTuple.ParseArgs(&complex64Val, &complex128Val) + if !success { + t.Error("ParseArgs failed for complex types") + } + + // Test string and bytes + strTuple := MakeTuple("hello") + var strVal string + var bytesVal []byte + var objVal Object + var pyObj *PyObject + + success = strTuple.ParseArgs(&strVal) + if !success || strVal != "hello" { + t.Error("ParseArgs failed for string type") + } + + success = strTuple.ParseArgs(&bytesVal) + if !success || string(bytesVal) != "hello" { + t.Error("ParseArgs failed for bytes type") + } + + success = strTuple.ParseArgs(&objVal) + if !success || !objVal.IsStr() { + t.Error("ParseArgs failed for object type") + } + + success = strTuple.ParseArgs(&pyObj) + if !success || pyObj == nil { + t.Error("ParseArgs failed for PyObject type") + } + str := FromPy(pyObj) + if !str.IsStr() || str.String() != "hello" { + t.Error("FromPy returned non-string object") + } +} diff --git a/unicode.go b/unicode.go index 4b08f67..dcb890d 100644 --- a/unicode.go +++ b/unicode.go @@ -4,10 +4,7 @@ package gp #include */ import "C" -import ( - "reflect" - "unsafe" -) +import "unsafe" type Str struct { Object @@ -18,9 +15,8 @@ func newStr(obj *PyObject) Str { } func MakeStr(s string) Str { - hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) - ptr := (*C.char)(unsafe.Pointer(hdr.Data)) - length := C.long(hdr.Len) + ptr := (*C.char)(unsafe.Pointer(unsafe.StringData(s))) + length := C.long(len(s)) return newStr(C.PyUnicode_FromStringAndSize(ptr, length)) } @@ -31,6 +27,10 @@ func (s Str) String() string { } func (s Str) Len() int { + return int(C.PyUnicode_GetLength(s.obj)) +} + +func (s Str) ByteLen() int { var l C.long C.PyUnicode_AsUTF8AndSize(s.obj, &l) return int(l) diff --git a/unicode_test.go b/unicode_test.go new file mode 100644 index 0000000..c47a4c8 --- /dev/null +++ b/unicode_test.go @@ -0,0 +1,103 @@ +package gp + +import ( + "testing" +) + +func TestMakeStr(t *testing.T) { + tests := []struct { + name string + input string + expected string + length int + byteCount int + }{ + { + name: "empty string", + input: "", + expected: "", + length: 0, + byteCount: 0, + }, + { + name: "ascii string", + input: "hello", + expected: "hello", + length: 5, + byteCount: 5, // ASCII字符每个占1字节 + }, + { + name: "unicode string", + input: "你好世界", + expected: "你好世界", + length: 4, + byteCount: 12, // 中文字符每个占3字节 + }, + { + name: "mixed string", + input: "hello世界", + expected: "hello世界", + length: 7, + byteCount: 11, // 5个ASCII字符(5字节) + 2个中文字符(6字节) + }, + { + name: "special unicode", + input: "π∑€", + expected: "π∑€", + length: 3, + byteCount: 8, // π(2字节) + ∑(3字节) + €(3字节) = 8字节 + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pyStr := MakeStr(tt.input) + + // Test String() method + if got := pyStr.String(); got != tt.expected { + t.Errorf("MakeStr(%q).String() = %q, want %q", tt.input, got, tt.expected) + } + + // Test Len() method + if got := pyStr.Len(); got != tt.length { + t.Errorf("MakeStr(%q).Len() = %d, want %d", tt.input, got, tt.length) + } + + // Test ByteLen() method + if got := pyStr.ByteLen(); got != tt.byteCount { + t.Errorf("MakeStr(%q).ByteLen() = %d, want %d", tt.input, got, tt.byteCount) + } + }) + } +} + +func TestStrEncode(t *testing.T) { + tests := []struct { + name string + input string + encoding string + }{ + { + name: "utf-8 encoding", + input: "hello世界", + encoding: "utf-8", + }, + { + name: "ascii encoding", + input: "hello", + encoding: "ascii", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pyStr := MakeStr(tt.input) + encoded := pyStr.Encode(tt.encoding) + decoded := encoded.Decode(tt.encoding) + + if got := decoded.String(); got != tt.input { + t.Errorf("String encode/decode roundtrip failed: got %q, want %q", got, tt.input) + } + }) + } +}