github.com/scaleoutsean/fusego@v0.0.0-20220224074057-4a6429e46bb8/internal/buffer/out_message_test.go (about)

     1  package buffer
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rand"
     6  	"fmt"
     7  	"io"
     8  	"reflect"
     9  	"testing"
    10  	"unsafe"
    11  
    12  	"github.com/scaleoutsean/fusego/internal/fusekernel"
    13  	"github.com/kylelemons/godebug/pretty"
    14  )
    15  
    16  func toByteSlice(p unsafe.Pointer, n int) []byte {
    17  	sh := reflect.SliceHeader{
    18  		Data: uintptr(p),
    19  		Len:  n,
    20  		Cap:  n,
    21  	}
    22  
    23  	return *(*[]byte)(unsafe.Pointer(&sh))
    24  }
    25  
    26  // fillWithGarbage writes random data to [p, p+n).
    27  func fillWithGarbage(p unsafe.Pointer, n int) error {
    28  	b := toByteSlice(p, n)
    29  	_, err := io.ReadFull(rand.Reader, b)
    30  	return err
    31  }
    32  
    33  func randBytes(n int) ([]byte, error) {
    34  	b := make([]byte, n)
    35  	_, err := io.ReadFull(rand.Reader, b)
    36  	return b, err
    37  }
    38  
    39  // findNonZero finds the offset of the first non-zero byte in [p, p+n). If
    40  // none, it returns n.
    41  func findNonZero(p unsafe.Pointer, n int) int {
    42  	b := toByteSlice(p, n)
    43  	for i, x := range b {
    44  		if x != 0 {
    45  			return i
    46  		}
    47  	}
    48  
    49  	return n
    50  }
    51  
    52  func TestMemclr(t *testing.T) {
    53  	// All sizes up to 32 bytes.
    54  	var sizes []int
    55  	for i := 0; i <= 32; i++ {
    56  		sizes = append(sizes, i)
    57  	}
    58  
    59  	// And a few hand-chosen sizes.
    60  	sizes = append(sizes, []int{
    61  		39, 41, 64, 127, 128, 129,
    62  		1<<20 - 1,
    63  		1 << 20,
    64  		1<<20 + 1,
    65  	}...)
    66  
    67  	// For each size, fill a buffer with random bytes and then zero it.
    68  	for _, size := range sizes {
    69  		size := size
    70  		t.Run(fmt.Sprintf("size=%d", size), func(t *testing.T) {
    71  			// Generate
    72  			b, err := randBytes(size)
    73  			if err != nil {
    74  				t.Fatalf("randBytes: %v", err)
    75  			}
    76  
    77  			// Clear
    78  			var p unsafe.Pointer
    79  			if len(b) != 0 {
    80  				p = unsafe.Pointer(&b[0])
    81  			}
    82  
    83  			memclr(p, uintptr(len(b)))
    84  
    85  			// Check
    86  			if i := findNonZero(p, len(b)); i != len(b) {
    87  				t.Fatalf("non-zero byte at offset %d", i)
    88  			}
    89  		})
    90  	}
    91  }
    92  
    93  func TestOutMessageAppend(t *testing.T) {
    94  	var om OutMessage
    95  	om.Reset()
    96  
    97  	// Append some payload.
    98  	const wantPayloadStr = "tacoburrito"
    99  	wantPayload := []byte(wantPayloadStr)
   100  	om.Append(wantPayload[:4])
   101  	om.Append(wantPayload[4:])
   102  
   103  	// The result should be a zeroed header followed by the desired payload.
   104  	const wantLen = OutMessageHeaderSize + len(wantPayloadStr)
   105  
   106  	if got, want := om.Len(), wantLen; got != want {
   107  		t.Errorf("om.Len() = %d, want %d", got, want)
   108  	}
   109  
   110  	b := om.Bytes()
   111  	if got, want := len(b), wantLen; got != want {
   112  		t.Fatalf("len(om.Bytes()) = %d, want %d", got, want)
   113  	}
   114  
   115  	want := append(
   116  		make([]byte, OutMessageHeaderSize),
   117  		wantPayload...)
   118  
   119  	if !bytes.Equal(b, want) {
   120  		t.Error("messages differ")
   121  	}
   122  }
   123  
   124  func TestOutMessageAppendString(t *testing.T) {
   125  	var om OutMessage
   126  	om.Reset()
   127  
   128  	// Append some payload.
   129  	const wantPayload = "tacoburrito"
   130  	om.AppendString(wantPayload[:4])
   131  	om.AppendString(wantPayload[4:])
   132  
   133  	// The result should be a zeroed header followed by the desired payload.
   134  	const wantLen = OutMessageHeaderSize + len(wantPayload)
   135  
   136  	if got, want := om.Len(), wantLen; got != want {
   137  		t.Errorf("om.Len() = %d, want %d", got, want)
   138  	}
   139  
   140  	b := om.Bytes()
   141  	if got, want := len(b), wantLen; got != want {
   142  		t.Fatalf("len(om.Bytes()) = %d, want %d", got, want)
   143  	}
   144  
   145  	want := append(
   146  		make([]byte, OutMessageHeaderSize),
   147  		wantPayload...)
   148  
   149  	if !bytes.Equal(b, want) {
   150  		t.Error("messages differ")
   151  	}
   152  }
   153  
   154  func TestOutMessageShrinkTo(t *testing.T) {
   155  	// Set up a buffer with some payload.
   156  	var om OutMessage
   157  	om.Reset()
   158  	om.AppendString("taco")
   159  	om.AppendString("burrito")
   160  
   161  	// Shrink it.
   162  	om.ShrinkTo(OutMessageHeaderSize + len("taco"))
   163  
   164  	// The result should be a zeroed header followed by "taco".
   165  	const wantLen = OutMessageHeaderSize + len("taco")
   166  
   167  	if got, want := om.Len(), wantLen; got != want {
   168  		t.Errorf("om.Len() = %d, want %d", got, want)
   169  	}
   170  
   171  	b := om.Bytes()
   172  	if got, want := len(b), wantLen; got != want {
   173  		t.Fatalf("len(om.Bytes()) = %d, want %d", got, want)
   174  	}
   175  
   176  	want := append(
   177  		make([]byte, OutMessageHeaderSize),
   178  		"taco"...)
   179  
   180  	if !bytes.Equal(b, want) {
   181  		t.Error("messages differ")
   182  	}
   183  }
   184  
   185  func TestOutMessageHeader(t *testing.T) {
   186  	var om OutMessage
   187  	om.Reset()
   188  
   189  	// Fill in the header.
   190  	want := fusekernel.OutHeader{
   191  		Len:    0xdeadbeef,
   192  		Error:  -31231917,
   193  		Unique: 0xcafebabeba5eba11,
   194  	}
   195  
   196  	h := om.OutHeader()
   197  	if h == nil {
   198  		t.Fatal("OutHeader returned nil")
   199  	}
   200  
   201  	*h = want
   202  
   203  	// Check that the result is as expected.
   204  	b := om.Bytes()
   205  	if len(b) != int(unsafe.Sizeof(want)) {
   206  		t.Fatalf("unexpected length %d; want %d", len(b), unsafe.Sizeof(want))
   207  	}
   208  
   209  	got := *(*fusekernel.OutHeader)(unsafe.Pointer(&b[0]))
   210  	if diff := pretty.Compare(got, want); diff != "" {
   211  		t.Errorf("diff -got +want:\n%s", diff)
   212  	}
   213  }
   214  
   215  func TestOutMessageReset(t *testing.T) {
   216  	var om OutMessage
   217  	h := om.OutHeader()
   218  
   219  	const trials = 10
   220  	for i := 0; i < trials; i++ {
   221  		// Fill the header with garbage.
   222  		err := fillWithGarbage(unsafe.Pointer(h), int(unsafe.Sizeof(*h)))
   223  		if err != nil {
   224  			t.Fatalf("fillWithGarbage: %v", err)
   225  		}
   226  
   227  		// Ensure a non-zero payload length.
   228  		if p := om.GrowNoZero(128); p == nil {
   229  			t.Fatal("GrowNoZero failed")
   230  		}
   231  
   232  		// Reset.
   233  		om.Reset()
   234  
   235  		// Check that the length was updated.
   236  		if got, want := om.Len(), OutMessageHeaderSize; got != want {
   237  			t.Fatalf("om.Len() = %d, want %d", got, want)
   238  		}
   239  
   240  		// Check that the header was zeroed.
   241  		if h.Len != 0 {
   242  			t.Fatalf("non-zero Len %v", h.Len)
   243  		}
   244  
   245  		if h.Error != 0 {
   246  			t.Fatalf("non-zero Error %v", h.Error)
   247  		}
   248  
   249  		if h.Unique != 0 {
   250  			t.Fatalf("non-zero Unique %v", h.Unique)
   251  		}
   252  	}
   253  }
   254  
   255  func TestOutMessageGrow(t *testing.T) {
   256  	var om OutMessage
   257  	om.Reset()
   258  
   259  	// Set up garbage where the payload will soon be.
   260  	const payloadSize = 1234
   261  	{
   262  		p := om.GrowNoZero(payloadSize)
   263  		if p == nil {
   264  			t.Fatal("GrowNoZero failed")
   265  		}
   266  
   267  		err := fillWithGarbage(p, payloadSize)
   268  		if err != nil {
   269  			t.Fatalf("fillWithGarbage: %v", err)
   270  		}
   271  
   272  		om.ShrinkTo(OutMessageHeaderSize)
   273  	}
   274  
   275  	// Call Grow.
   276  	if p := om.Grow(payloadSize); p == nil {
   277  		t.Fatal("Grow failed")
   278  	}
   279  
   280  	// Check the resulting length in two ways.
   281  	const wantLen = payloadSize + OutMessageHeaderSize
   282  	if got, want := om.Len(), wantLen; got != want {
   283  		t.Errorf("om.Len() = %d, want %d", got, want)
   284  	}
   285  
   286  	b := om.Bytes()
   287  	if got, want := len(b), wantLen; got != want {
   288  		t.Fatalf("len(om.Len()) = %d, want %d", got, want)
   289  	}
   290  
   291  	// Check that the payload was zeroed.
   292  	for i, x := range b[OutMessageHeaderSize:] {
   293  		if x != 0 {
   294  			t.Fatalf("non-zero byte 0x%02x at payload offset %d", x, i)
   295  		}
   296  	}
   297  }
   298  
   299  func BenchmarkOutMessageReset(b *testing.B) {
   300  	// A single buffer, which should fit in some level of CPU cache.
   301  	b.Run("Single buffer", func(b *testing.B) {
   302  		var om OutMessage
   303  		for i := 0; i < b.N; i++ {
   304  			om.Reset()
   305  		}
   306  
   307  		b.SetBytes(int64(unsafe.Offsetof(om.payload)))
   308  	})
   309  
   310  	// Many megabytes worth of buffers, which should defeat the CPU cache.
   311  	b.Run("Many buffers", func(b *testing.B) {
   312  		// The number of messages; intentionally a power of two.
   313  		const numMessages = 128
   314  
   315  		var oms [numMessages]OutMessage
   316  		if s := unsafe.Sizeof(oms); s < 128<<20 {
   317  			panic(fmt.Sprintf("Array is too small; total size: %d", s))
   318  		}
   319  
   320  		for i := 0; i < b.N; i++ {
   321  			oms[i%numMessages].Reset()
   322  		}
   323  
   324  		b.SetBytes(int64(unsafe.Offsetof(oms[0].payload)))
   325  	})
   326  }
   327  
   328  func BenchmarkOutMessageGrowShrink(b *testing.B) {
   329  	// A single buffer, which should fit in some level of CPU cache.
   330  	b.Run("Single buffer", func(b *testing.B) {
   331  		var om OutMessage
   332  		for i := 0; i < b.N; i++ {
   333  			om.Grow(MaxReadSize)
   334  			om.ShrinkTo(OutMessageHeaderSize)
   335  		}
   336  
   337  		b.SetBytes(int64(MaxReadSize))
   338  	})
   339  
   340  	// Many megabytes worth of buffers, which should defeat the CPU cache.
   341  	b.Run("Many buffers", func(b *testing.B) {
   342  		// The number of messages; intentionally a power of two.
   343  		const numMessages = 128
   344  
   345  		var oms [numMessages]OutMessage
   346  		if s := unsafe.Sizeof(oms); s < 128<<20 {
   347  			panic(fmt.Sprintf("Array is too small; total size: %d", s))
   348  		}
   349  
   350  		for i := 0; i < b.N; i++ {
   351  			oms[i%numMessages].Grow(MaxReadSize)
   352  			oms[i%numMessages].ShrinkTo(OutMessageHeaderSize)
   353  		}
   354  
   355  		b.SetBytes(int64(MaxReadSize))
   356  	})
   357  }