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 }