123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- package buffer
- import (
- "bytes"
- "crypto/rand"
- "fmt"
- "io"
- "reflect"
- "testing"
- "unsafe"
- "github.com/jacobsa/fuse/internal/fusekernel"
- "github.com/kylelemons/godebug/pretty"
- )
- func toByteSlice(p unsafe.Pointer, n int) []byte {
- sh := reflect.SliceHeader{
- Data: uintptr(p),
- Len: n,
- Cap: n,
- }
- return *(*[]byte)(unsafe.Pointer(&sh))
- }
- // fillWithGarbage writes random data to [p, p+n).
- func fillWithGarbage(p unsafe.Pointer, n int) (err error) {
- b := toByteSlice(p, n)
- _, err = io.ReadFull(rand.Reader, b)
- return
- }
- func randBytes(n int) (b []byte, err error) {
- b = make([]byte, n)
- _, err = io.ReadFull(rand.Reader, b)
- return
- }
- // findNonZero finds the offset of the first non-zero byte in [p, p+n). If
- // none, it returns n.
- func findNonZero(p unsafe.Pointer, n int) int {
- b := toByteSlice(p, n)
- for i, x := range b {
- if x != 0 {
- return i
- }
- }
- return n
- }
- func TestMemclr(t *testing.T) {
- // All sizes up to 32 bytes.
- var sizes []int
- for i := 0; i <= 32; i++ {
- sizes = append(sizes, i)
- }
- // And a few hand-chosen sizes.
- sizes = append(sizes, []int{
- 39, 41, 64, 127, 128, 129,
- 1<<20 - 1,
- 1 << 20,
- 1<<20 + 1,
- }...)
- // For each size, fill a buffer with random bytes and then zero it.
- for _, size := range sizes {
- size := size
- t.Run(fmt.Sprintf("size=%d", size), func(t *testing.T) {
- // Generate
- b, err := randBytes(size)
- if err != nil {
- t.Fatalf("randBytes: %v", err)
- }
- // Clear
- var p unsafe.Pointer
- if len(b) != 0 {
- p = unsafe.Pointer(&b[0])
- }
- memclr(p, uintptr(len(b)))
- // Check
- if i := findNonZero(p, len(b)); i != len(b) {
- t.Fatalf("non-zero byte at offset %d", i)
- }
- })
- }
- }
- func TestOutMessageAppend(t *testing.T) {
- var om OutMessage
- om.Reset()
- // Append some payload.
- const wantPayloadStr = "tacoburrito"
- wantPayload := []byte(wantPayloadStr)
- om.Append(wantPayload[:4])
- om.Append(wantPayload[4:])
- // The result should be a zeroed header followed by the desired payload.
- const wantLen = OutMessageHeaderSize + len(wantPayloadStr)
- if got, want := om.Len(), wantLen; got != want {
- t.Errorf("om.Len() = %d, want %d", got, want)
- }
- b := om.Bytes()
- if got, want := len(b), wantLen; got != want {
- t.Fatalf("len(om.Bytes()) = %d, want %d", got, want)
- }
- want := append(
- make([]byte, OutMessageHeaderSize),
- wantPayload...)
- if !bytes.Equal(b, want) {
- t.Error("messages differ")
- }
- }
- func TestOutMessageAppendString(t *testing.T) {
- var om OutMessage
- om.Reset()
- // Append some payload.
- const wantPayload = "tacoburrito"
- om.AppendString(wantPayload[:4])
- om.AppendString(wantPayload[4:])
- // The result should be a zeroed header followed by the desired payload.
- const wantLen = OutMessageHeaderSize + len(wantPayload)
- if got, want := om.Len(), wantLen; got != want {
- t.Errorf("om.Len() = %d, want %d", got, want)
- }
- b := om.Bytes()
- if got, want := len(b), wantLen; got != want {
- t.Fatalf("len(om.Bytes()) = %d, want %d", got, want)
- }
- want := append(
- make([]byte, OutMessageHeaderSize),
- wantPayload...)
- if !bytes.Equal(b, want) {
- t.Error("messages differ")
- }
- }
- func TestOutMessageShrinkTo(t *testing.T) {
- // Set up a buffer with some payload.
- var om OutMessage
- om.Reset()
- om.AppendString("taco")
- om.AppendString("burrito")
- // Shrink it.
- om.ShrinkTo(OutMessageHeaderSize + len("taco"))
- // The result should be a zeroed header followed by "taco".
- const wantLen = OutMessageHeaderSize + len("taco")
- if got, want := om.Len(), wantLen; got != want {
- t.Errorf("om.Len() = %d, want %d", got, want)
- }
- b := om.Bytes()
- if got, want := len(b), wantLen; got != want {
- t.Fatalf("len(om.Bytes()) = %d, want %d", got, want)
- }
- want := append(
- make([]byte, OutMessageHeaderSize),
- "taco"...)
- if !bytes.Equal(b, want) {
- t.Error("messages differ")
- }
- }
- func TestOutMessageHeader(t *testing.T) {
- var om OutMessage
- om.Reset()
- // Fill in the header.
- want := fusekernel.OutHeader{
- Len: 0xdeadbeef,
- Error: -31231917,
- Unique: 0xcafebabeba5eba11,
- }
- h := om.OutHeader()
- if h == nil {
- t.Fatal("OutHeader returned nil")
- }
- *h = want
- // Check that the result is as expected.
- b := om.Bytes()
- if len(b) != int(unsafe.Sizeof(want)) {
- t.Fatalf("unexpected length %d; want %d", len(b), unsafe.Sizeof(want))
- }
- got := *(*fusekernel.OutHeader)(unsafe.Pointer(&b[0]))
- if diff := pretty.Compare(got, want); diff != "" {
- t.Errorf("diff -got +want:\n%s", diff)
- }
- }
- func TestOutMessageReset(t *testing.T) {
- var om OutMessage
- h := om.OutHeader()
- const trials = 10
- for i := 0; i < trials; i++ {
- // Fill the header with garbage.
- err := fillWithGarbage(unsafe.Pointer(h), int(unsafe.Sizeof(*h)))
- if err != nil {
- t.Fatalf("fillWithGarbage: %v", err)
- }
- // Ensure a non-zero payload length.
- if p := om.GrowNoZero(128); p == nil {
- t.Fatal("GrowNoZero failed")
- }
- // Reset.
- om.Reset()
- // Check that the length was updated.
- if got, want := om.Len(), OutMessageHeaderSize; got != want {
- t.Fatalf("om.Len() = %d, want %d", got, want)
- }
- // Check that the header was zeroed.
- if h.Len != 0 {
- t.Fatalf("non-zero Len %v", h.Len)
- }
- if h.Error != 0 {
- t.Fatalf("non-zero Error %v", h.Error)
- }
- if h.Unique != 0 {
- t.Fatalf("non-zero Unique %v", h.Unique)
- }
- }
- }
- func TestOutMessageGrow(t *testing.T) {
- var om OutMessage
- om.Reset()
- // Set up garbage where the payload will soon be.
- const payloadSize = 1234
- {
- p := om.GrowNoZero(payloadSize)
- if p == nil {
- t.Fatal("GrowNoZero failed")
- }
- err := fillWithGarbage(p, payloadSize)
- if err != nil {
- t.Fatalf("fillWithGarbage: %v", err)
- }
- om.ShrinkTo(OutMessageHeaderSize)
- }
- // Call Grow.
- if p := om.Grow(payloadSize); p == nil {
- t.Fatal("Grow failed")
- }
- // Check the resulting length in two ways.
- const wantLen = payloadSize + OutMessageHeaderSize
- if got, want := om.Len(), wantLen; got != want {
- t.Errorf("om.Len() = %d, want %d", got)
- }
- b := om.Bytes()
- if got, want := len(b), wantLen; got != want {
- t.Fatalf("len(om.Len()) = %d, want %d", got)
- }
- // Check that the payload was zeroed.
- for i, x := range b[OutMessageHeaderSize:] {
- if x != 0 {
- t.Fatalf("non-zero byte 0x%02x at payload offset %d", x, i)
- }
- }
- }
- func BenchmarkOutMessageReset(b *testing.B) {
- // A single buffer, which should fit in some level of CPU cache.
- b.Run("Single buffer", func(b *testing.B) {
- var om OutMessage
- for i := 0; i < b.N; i++ {
- om.Reset()
- }
- b.SetBytes(int64(unsafe.Offsetof(om.payload)))
- })
- // Many megabytes worth of buffers, which should defeat the CPU cache.
- b.Run("Many buffers", func(b *testing.B) {
- // The number of messages; intentionally a power of two.
- const numMessages = 128
- var oms [numMessages]OutMessage
- if s := unsafe.Sizeof(oms); s < 128<<20 {
- panic(fmt.Sprintf("Array is too small; total size: %d", s))
- }
- for i := 0; i < b.N; i++ {
- oms[i%numMessages].Reset()
- }
- b.SetBytes(int64(unsafe.Offsetof(oms[0].payload)))
- })
- }
- func BenchmarkOutMessageGrowShrink(b *testing.B) {
- // A single buffer, which should fit in some level of CPU cache.
- b.Run("Single buffer", func(b *testing.B) {
- var om OutMessage
- for i := 0; i < b.N; i++ {
- om.Grow(MaxReadSize)
- om.ShrinkTo(OutMessageHeaderSize)
- }
- b.SetBytes(int64(MaxReadSize))
- })
- // Many megabytes worth of buffers, which should defeat the CPU cache.
- b.Run("Many buffers", func(b *testing.B) {
- // The number of messages; intentionally a power of two.
- const numMessages = 128
- var oms [numMessages]OutMessage
- if s := unsafe.Sizeof(oms); s < 128<<20 {
- panic(fmt.Sprintf("Array is too small; total size: %d", s))
- }
- for i := 0; i < b.N; i++ {
- oms[i%numMessages].Grow(MaxReadSize)
- oms[i%numMessages].ShrinkTo(OutMessageHeaderSize)
- }
- b.SetBytes(int64(MaxReadSize))
- })
- }
|