Skip to content

Commit

Permalink
test: add proto value test cases (#368)
Browse files Browse the repository at this point in the history
* test: add case nil value for slices

* test: add test for slice data liveness
  • Loading branch information
muktihari authored Aug 20, 2024
1 parent 938fc97 commit ad64194
Showing 1 changed file with 163 additions and 0 deletions.
163 changes: 163 additions & 0 deletions proto/value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ package proto
import (
"fmt"
"math"
"runtime"
"testing"
"time"
"unsafe"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -276,6 +278,13 @@ func TestString(t *testing.T) {
t.Fatalf("expected: %v, got: %v", input, v.String())
}
})
t.Run("empty string", func(t *testing.T) {
value := String("")
result := value.String()
if result != "" {
t.Fatalf("expected nil, got: %v", result)
}
})
t.Run("invalid", func(t *testing.T) {
v := Value{}
if v.String() != basetype.StringInvalid {
Expand Down Expand Up @@ -305,6 +314,13 @@ func TestSliceBool(t *testing.T) {
t.Fatal(diff)
}
})
t.Run("nil value", func(t *testing.T) {
value := SliceBool([]bool(nil))
result := value.SliceBool()
if result != nil {
t.Fatalf("expected nil, got: %v", result)
}
})
t.Run("invalid", func(t *testing.T) {
value := Value{}
result := value.SliceBool()
Expand Down Expand Up @@ -335,6 +351,13 @@ func TestSliceInt8(t *testing.T) {
t.Fatal(diff)
}
})
t.Run("nil value", func(t *testing.T) {
value := SliceInt8([]int8(nil))
result := value.SliceInt8()
if result != nil {
t.Fatalf("expected nil, got: %v", result)
}
})
t.Run("invalid", func(t *testing.T) {
value := Value{}
result := value.SliceInt8()
Expand Down Expand Up @@ -365,6 +388,13 @@ func TestSliceUint8(t *testing.T) {
t.Fatal(diff)
}
})
t.Run("nil value", func(t *testing.T) {
value := SliceUint8([]uint8(nil))
result := value.SliceUint8()
if result != nil {
t.Fatalf("expected nil, got: %v", result)
}
})
t.Run("invalid", func(t *testing.T) {
value := Value{}
result := value.SliceUint8()
Expand Down Expand Up @@ -395,6 +425,13 @@ func TestSliceInt16(t *testing.T) {
t.Fatal(diff)
}
})
t.Run("nil value", func(t *testing.T) {
value := SliceInt16([]int16(nil))
result := value.SliceInt16()
if result != nil {
t.Fatalf("expected nil, got: %v", result)
}
})
t.Run("invalid", func(t *testing.T) {
value := Value{}
result := value.SliceInt16()
Expand Down Expand Up @@ -425,6 +462,13 @@ func TestSliceUint16(t *testing.T) {
t.Fatal(diff)
}
})
t.Run("nil value", func(t *testing.T) {
value := SliceUint16([]uint16(nil))
result := value.SliceUint16()
if result != nil {
t.Fatalf("expected nil, got: %v", result)
}
})
t.Run("invalid", func(t *testing.T) {
value := Value{}
result := value.SliceUint16()
Expand Down Expand Up @@ -455,6 +499,13 @@ func TestSliceInt32(t *testing.T) {
t.Fatal(diff)
}
})
t.Run("nil value", func(t *testing.T) {
value := SliceInt32([]int32(nil))
result := value.SliceInt32()
if result != nil {
t.Fatalf("expected nil, got: %v", result)
}
})
t.Run("invalid", func(t *testing.T) {
value := Value{}
result := value.SliceInt32()
Expand Down Expand Up @@ -485,6 +536,13 @@ func TestSliceUint32(t *testing.T) {
t.Fatal(diff)
}
})
t.Run("nil value", func(t *testing.T) {
value := SliceUint32([]uint32(nil))
result := value.SliceUint32()
if result != nil {
t.Fatalf("expected nil, got: %v", result)
}
})
t.Run("invalid", func(t *testing.T) {
value := Value{}
result := value.SliceUint32()
Expand Down Expand Up @@ -515,6 +573,13 @@ func TestSliceInt64(t *testing.T) {
t.Fatal(diff)
}
})
t.Run("nil value", func(t *testing.T) {
value := SliceInt64([]int64(nil))
result := value.SliceInt64()
if result != nil {
t.Fatalf("expected nil, got: %v", result)
}
})
t.Run("invalid", func(t *testing.T) {
value := Value{}
result := value.SliceInt64()
Expand Down Expand Up @@ -545,6 +610,13 @@ func TestSliceUint64(t *testing.T) {
t.Fatal(diff)
}
})
t.Run("nil value", func(t *testing.T) {
value := SliceUint64([]uint64(nil))
result := value.SliceUint64()
if result != nil {
t.Fatalf("expected nil, got: %v", result)
}
})
t.Run("invalid", func(t *testing.T) {
value := Value{}
result := value.SliceUint64()
Expand Down Expand Up @@ -575,6 +647,13 @@ func TestSliceFloat32(t *testing.T) {
t.Fatal(diff)
}
})
t.Run("nil value", func(t *testing.T) {
value := SliceFloat32([]float32(nil))
result := value.SliceFloat32()
if result != nil {
t.Fatalf("expected nil, got: %v", result)
}
})
t.Run("invalid", func(t *testing.T) {
value := Value{}
result := value.SliceFloat32()
Expand Down Expand Up @@ -605,6 +684,13 @@ func TestSliceFloat64(t *testing.T) {
t.Fatal(diff)
}
})
t.Run("nil value", func(t *testing.T) {
value := SliceFloat64([]float64(nil))
result := value.SliceFloat64()
if result != nil {
t.Fatalf("expected nil, got: %v", result)
}
})
t.Run("invalid", func(t *testing.T) {
value := Value{}
result := value.SliceFloat64()
Expand Down Expand Up @@ -635,6 +721,13 @@ func TestSliceString(t *testing.T) {
t.Fatal(diff)
}
})
t.Run("nil value", func(t *testing.T) {
value := SliceString([]string(nil))
result := value.SliceString()
if result != nil {
t.Fatalf("expected nil, got: %v", result)
}
})
t.Run("invalid", func(t *testing.T) {
value := Value{}
result := value.SliceString()
Expand Down Expand Up @@ -922,6 +1015,76 @@ func TestLen(t *testing.T) {
}
}

func TestSliceDataLiveness(t *testing.T) {
// NOTE: This may not be necessary; however, it gives us more
// confidence that an unsafe.Pointer inside a Value will never be
// garbage-collected as long as the Value is still reachable.

// compile time type-assertion, this test only valid when ptr is an unsafe.Pointer.
var _ unsafe.Pointer = Value{}.ptr

const n = 1 << 16
const timeout = 100 * time.Millisecond

var makeslice = func(n int) []uint64 {
vs := make([]uint64, n)
for i := range vs {
vs[i] = uint64(i)
}
return vs
}

var (
// z must be garbage-collected on first GC() phase.
z = makeslice(n)
zptr = unsafe.SliceData(z)
zcollected = make(chan struct{})

vals = makeslice(n)
ptr = unsafe.SliceData(vals)
uptr = uintptr(unsafe.Pointer(ptr))
value = SliceUint64(vals)
collected = make(chan struct{})

expected = append(vals[:0:0], vals...)
)

runtime.SetFinalizer(zptr, func(_ *uint64) { close(zcollected) })
runtime.SetFinalizer(ptr, func(_ *uint64) { close(collected) })

// Make sure `vals` stays alive.
runtime.GC()
runtime.GC()

<-zcollected // Must be garbage-collected.

select {
case <-collected:
t.Fatalf("object at address 0x%x has been garbage-collected", uptr)
case <-time.After(timeout):
}

retrieved := value.SliceUint64()
if diff := cmp.Diff(retrieved, expected); diff != "" {
t.Fatal(diff)
}

collected = make(chan struct{})
runtime.SetFinalizer(ptr, nil)
runtime.SetFinalizer(ptr, func(_ *uint64) { close(collected) })

// Make sure `vals` has been garbage-collected
// as `value` is no longer reachable.
runtime.GC()
runtime.GC()

select {
case <-collected:
case <-time.After(timeout):
t.Errorf("ptr finalizer never be invoked after %s, object at 0x%x is still alive", timeout, uptr)
}
}

func BenchmarkValueSliceBool(b *testing.B) {
s := []bool{true, false}

Expand Down

0 comments on commit ad64194

Please sign in to comment.