wa-lang.org/wazero@v1.0.2/internal/wasm/memory_test.go (about) 1 package wasm 2 3 import ( 4 "context" 5 "math" 6 "strings" 7 "testing" 8 9 "wa-lang.org/wazero/api" 10 "wa-lang.org/wazero/internal/testing/require" 11 ) 12 13 func TestMemoryPageConsts(t *testing.T) { 14 require.Equal(t, MemoryPageSize, uint32(1)<<MemoryPageSizeInBits) 15 require.Equal(t, MemoryPageSize, uint32(1<<16)) 16 require.Equal(t, MemoryLimitPages, uint32(1<<16)) 17 } 18 19 func TestMemoryPagesToBytesNum(t *testing.T) { 20 for _, numPage := range []uint32{0, 1, 5, 10} { 21 require.Equal(t, uint64(numPage*MemoryPageSize), MemoryPagesToBytesNum(numPage)) 22 } 23 } 24 25 func TestMemoryBytesNumToPages(t *testing.T) { 26 for _, numbytes := range []uint32{0, MemoryPageSize * 1, MemoryPageSize * 10} { 27 require.Equal(t, numbytes/MemoryPageSize, memoryBytesNumToPages(uint64(numbytes))) 28 } 29 } 30 31 func TestMemoryInstance_Grow_Size(t *testing.T) { 32 tests := []struct { 33 name string 34 ctx context.Context 35 capEqualsMax bool 36 }{ 37 {name: "nil context"}, 38 {name: "context", ctx: testCtx}, 39 {name: "nil context, capEqualsMax", capEqualsMax: true}, 40 {name: "context, capEqualsMax", ctx: testCtx, capEqualsMax: true}, 41 } 42 43 for _, tt := range tests { 44 tc := tt 45 46 t.Run(tc.name, func(t *testing.T) { 47 ctx := tc.ctx 48 max := uint32(10) 49 maxBytes := MemoryPagesToBytesNum(max) 50 var m *MemoryInstance 51 if tc.capEqualsMax { 52 m = &MemoryInstance{Cap: max, Max: max, Buffer: make([]byte, 0, maxBytes)} 53 } else { 54 m = &MemoryInstance{Max: max, Buffer: make([]byte, 0)} 55 } 56 57 res, ok := m.Grow(ctx, 5) 58 require.True(t, ok) 59 require.Equal(t, uint32(0), res) 60 require.Equal(t, uint32(5), m.PageSize(ctx)) 61 62 // Zero page grow is well-defined, should return the current page correctly. 63 res, ok = m.Grow(ctx, 0) 64 require.True(t, ok) 65 require.Equal(t, uint32(5), res) 66 require.Equal(t, uint32(5), m.PageSize(ctx)) 67 68 res, ok = m.Grow(ctx, 4) 69 require.True(t, ok) 70 require.Equal(t, uint32(5), res) 71 require.Equal(t, uint32(9), m.PageSize(ctx)) 72 73 // At this point, the page size equal 9, 74 // so trying to grow two pages should result in failure. 75 _, ok = m.Grow(ctx, 2) 76 require.False(t, ok) 77 require.Equal(t, uint32(9), m.PageSize(ctx)) 78 79 // But growing one page is still permitted. 80 res, ok = m.Grow(ctx, 1) 81 require.True(t, ok) 82 require.Equal(t, uint32(9), res) 83 84 // Ensure that the current page size equals the max. 85 require.Equal(t, max, m.PageSize(ctx)) 86 87 if tc.capEqualsMax { // Ensure the capacity isn't more than max. 88 require.Equal(t, maxBytes, uint64(cap(m.Buffer))) 89 } else { // Slice doubles, so it should have a higher capacity than max. 90 require.True(t, maxBytes < uint64(cap(m.Buffer))) 91 } 92 }) 93 } 94 } 95 96 func TestMemoryInstance_ReadByte(t *testing.T) { 97 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 98 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 0, 0, 0, 16}, Min: 1} 99 v, ok := mem.ReadByte(ctx, 7) 100 require.True(t, ok) 101 require.Equal(t, byte(16), v) 102 103 _, ok = mem.ReadByte(ctx, 8) 104 require.False(t, ok) 105 106 _, ok = mem.ReadByte(ctx, 9) 107 require.False(t, ok) 108 } 109 } 110 111 func TestPagesToUnitOfBytes(t *testing.T) { 112 tests := []struct { 113 name string 114 pages uint32 115 expected string 116 }{ 117 { 118 name: "zero", 119 pages: 0, 120 expected: "0 Ki", 121 }, 122 { 123 name: "one", 124 pages: 1, 125 expected: "64 Ki", 126 }, 127 { 128 name: "megs", 129 pages: 100, 130 expected: "6 Mi", 131 }, 132 { 133 name: "max memory", 134 pages: MemoryLimitPages, 135 expected: "4 Gi", 136 }, 137 { 138 name: "max uint32", 139 pages: math.MaxUint32, 140 expected: "3 Ti", 141 }, 142 } 143 144 for _, tt := range tests { 145 tc := tt 146 147 t.Run(tc.name, func(t *testing.T) { 148 require.Equal(t, tc.expected, PagesToUnitOfBytes(tc.pages)) 149 }) 150 } 151 } 152 153 func TestMemoryInstance_HasSize(t *testing.T) { 154 memory := &MemoryInstance{Buffer: make([]byte, MemoryPageSize)} 155 156 tests := []struct { 157 name string 158 offset uint32 159 sizeInBytes uint64 160 expected bool 161 }{ 162 { 163 name: "simple valid arguments", 164 offset: 0, // arbitrary valid offset 165 sizeInBytes: 8, // arbitrary valid size 166 expected: true, 167 }, 168 { 169 name: "maximum valid sizeInBytes", 170 offset: memory.Size(testCtx) - 8, 171 sizeInBytes: 8, 172 expected: true, 173 }, 174 { 175 name: "sizeInBytes exceeds the valid size by 1", 176 offset: 100, // arbitrary valid offset 177 sizeInBytes: uint64(memory.Size(testCtx) - 99), 178 expected: false, 179 }, 180 { 181 name: "offset exceeds the memory size", 182 offset: memory.Size(testCtx), 183 sizeInBytes: 1, // arbitrary size 184 expected: false, 185 }, 186 { 187 name: "offset + sizeInBytes overflows in uint32", 188 offset: math.MaxUint32 - 1, // invalid too large offset 189 sizeInBytes: 4, // if there's overflow, offset + sizeInBytes is 3, and it may pass the check 190 expected: false, 191 }, 192 { 193 name: "address.wast:200", 194 offset: 4294967295, 195 sizeInBytes: 1, 196 expected: false, 197 }, 198 } 199 200 for _, tt := range tests { 201 tc := tt 202 203 t.Run(tc.name, func(t *testing.T) { 204 require.Equal(t, tc.expected, memory.hasSize(tc.offset, uint32(tc.sizeInBytes))) 205 }) 206 } 207 } 208 209 func TestMemoryInstance_ReadUint16Le(t *testing.T) { 210 tests := []struct { 211 name string 212 memory []byte 213 offset uint32 214 expected uint16 215 expectedOk bool 216 }{ 217 { 218 name: "valid offset with an endian-insensitive v", 219 memory: []byte{0xff, 0xff}, 220 offset: 0, // arbitrary valid offset. 221 expected: math.MaxUint16, 222 expectedOk: true, 223 }, 224 { 225 name: "valid offset with an endian-sensitive v", 226 memory: []byte{0xfe, 0xff}, 227 offset: 0, // arbitrary valid offset. 228 expected: math.MaxUint16 - 1, 229 expectedOk: true, 230 }, 231 { 232 name: "maximum boundary valid offset", 233 offset: 1, 234 memory: []byte{0x00, 0x1, 0x00}, 235 expected: 1, // arbitrary valid v 236 expectedOk: true, 237 }, 238 { 239 name: "offset exceeds the maximum valid offset by 1", 240 memory: []byte{0xff, 0xff}, 241 offset: 1, 242 }, 243 } 244 245 for _, tt := range tests { 246 tc := tt 247 248 t.Run(tc.name, func(t *testing.T) { 249 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 250 memory := &MemoryInstance{Buffer: tc.memory} 251 252 v, ok := memory.ReadUint16Le(ctx, tc.offset) 253 require.Equal(t, tc.expectedOk, ok) 254 require.Equal(t, tc.expected, v) 255 } 256 }) 257 } 258 } 259 260 func TestMemoryInstance_ReadUint32Le(t *testing.T) { 261 tests := []struct { 262 name string 263 memory []byte 264 offset uint32 265 expected uint32 266 expectedOk bool 267 }{ 268 { 269 name: "valid offset with an endian-insensitive v", 270 memory: []byte{0xff, 0xff, 0xff, 0xff}, 271 offset: 0, // arbitrary valid offset. 272 expected: math.MaxUint32, 273 expectedOk: true, 274 }, 275 { 276 name: "valid offset with an endian-sensitive v", 277 memory: []byte{0xfe, 0xff, 0xff, 0xff}, 278 offset: 0, // arbitrary valid offset. 279 expected: math.MaxUint32 - 1, 280 expectedOk: true, 281 }, 282 { 283 name: "maximum boundary valid offset", 284 offset: 1, 285 memory: []byte{0x00, 0x1, 0x00, 0x00, 0x00}, 286 expected: 1, // arbitrary valid v 287 expectedOk: true, 288 }, 289 { 290 name: "offset exceeds the maximum valid offset by 1", 291 memory: []byte{0xff, 0xff, 0xff, 0xff}, 292 offset: 1, 293 }, 294 } 295 296 for _, tt := range tests { 297 tc := tt 298 299 t.Run(tc.name, func(t *testing.T) { 300 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 301 memory := &MemoryInstance{Buffer: tc.memory} 302 303 v, ok := memory.ReadUint32Le(ctx, tc.offset) 304 require.Equal(t, tc.expectedOk, ok) 305 require.Equal(t, tc.expected, v) 306 } 307 }) 308 } 309 } 310 311 func TestMemoryInstance_ReadUint64Le(t *testing.T) { 312 tests := []struct { 313 name string 314 memory []byte 315 offset uint32 316 expected uint64 317 expectedOk bool 318 }{ 319 { 320 name: "valid offset with an endian-insensitive v", 321 memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 322 offset: 0, // arbitrary valid offset. 323 expected: math.MaxUint64, 324 expectedOk: true, 325 }, 326 { 327 name: "valid offset with an endian-sensitive v", 328 memory: []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 329 offset: 0, // arbitrary valid offset. 330 expected: math.MaxUint64 - 1, 331 expectedOk: true, 332 }, 333 { 334 name: "maximum boundary valid offset", 335 offset: 1, 336 memory: []byte{0x00, 0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 337 expected: 1, // arbitrary valid v 338 expectedOk: true, 339 }, 340 { 341 name: "offset exceeds the maximum valid offset by 1", 342 memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 343 offset: 1, 344 }, 345 } 346 347 for _, tt := range tests { 348 tc := tt 349 350 t.Run(tc.name, func(t *testing.T) { 351 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 352 memory := &MemoryInstance{Buffer: tc.memory} 353 354 v, ok := memory.ReadUint64Le(ctx, tc.offset) 355 require.Equal(t, tc.expectedOk, ok) 356 require.Equal(t, tc.expected, v) 357 } 358 }) 359 } 360 } 361 362 func TestMemoryInstance_ReadFloat32Le(t *testing.T) { 363 tests := []struct { 364 name string 365 memory []byte 366 offset uint32 367 expected float32 368 expectedOk bool 369 }{ 370 { 371 name: "valid offset with an endian-insensitive v", 372 memory: []byte{0xff, 0x00, 0x00, 0xff}, 373 offset: 0, // arbitrary valid offset. 374 expected: math.Float32frombits(uint32(0xff0000ff)), 375 expectedOk: true, 376 }, 377 { 378 name: "valid offset with an endian-sensitive v", 379 memory: []byte{0xfe, 0x00, 0x00, 0xff}, 380 offset: 0, // arbitrary valid offset. 381 expected: math.Float32frombits(uint32(0xff0000fe)), 382 expectedOk: true, 383 }, 384 { 385 name: "maximum boundary valid offset", 386 offset: 1, 387 memory: []byte{0x00, 0xcd, 0xcc, 0xcc, 0x3d}, 388 expected: 0.1, // arbitrary valid v 389 expectedOk: true, 390 }, 391 { 392 name: "offset exceeds the maximum valid offset by 1", 393 memory: []byte{0xff, 0xff, 0xff, 0xff}, 394 offset: 1, 395 }, 396 } 397 398 for _, tt := range tests { 399 tc := tt 400 401 t.Run(tc.name, func(t *testing.T) { 402 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 403 memory := &MemoryInstance{Buffer: tc.memory} 404 405 v, ok := memory.ReadFloat32Le(ctx, tc.offset) 406 require.Equal(t, tc.expectedOk, ok) 407 require.Equal(t, tc.expected, v) 408 } 409 }) 410 } 411 } 412 413 func TestMemoryInstance_ReadFloat64Le(t *testing.T) { 414 tests := []struct { 415 name string 416 memory []byte 417 offset uint32 418 expected float64 419 expectedOk bool 420 }{ 421 { 422 name: "valid offset with an endian-insensitive v", 423 memory: []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff}, 424 offset: 0, // arbitrary valid offset. 425 expected: math.Float64frombits(uint64(0xff000000000000ff)), 426 expectedOk: true, 427 }, 428 { 429 name: "valid offset with an endian-sensitive v", 430 memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f}, 431 offset: 0, // arbitrary valid offset. 432 expected: math.MaxFloat64, // arbitrary valid v 433 expectedOk: true, 434 }, 435 { 436 name: "maximum boundary valid offset", 437 offset: 1, 438 memory: []byte{0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f}, 439 expected: math.MaxFloat64, // arbitrary valid v 440 expectedOk: true, 441 }, 442 { 443 name: "offset exceeds the maximum valid offset by 1", 444 memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 445 offset: 1, 446 }, 447 } 448 449 for _, tt := range tests { 450 tc := tt 451 452 t.Run(tc.name, func(t *testing.T) { 453 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 454 memory := &MemoryInstance{Buffer: tc.memory} 455 456 v, ok := memory.ReadFloat64Le(ctx, tc.offset) 457 require.Equal(t, tc.expectedOk, ok) 458 require.Equal(t, tc.expected, v) 459 } 460 }) 461 } 462 } 463 464 func TestMemoryInstance_Read(t *testing.T) { 465 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 466 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1} 467 468 buf, ok := mem.Read(ctx, 4, 4) 469 require.True(t, ok) 470 require.Equal(t, []byte{16, 0, 0, 0}, buf) 471 472 // Test write-through 473 buf[3] = 4 474 require.Equal(t, []byte{16, 0, 0, 4}, buf) 475 require.Equal(t, []byte{0, 0, 0, 0, 16, 0, 0, 4}, mem.Buffer) 476 477 _, ok = mem.Read(ctx, 5, 4) 478 require.False(t, ok) 479 480 _, ok = mem.Read(ctx, 9, 4) 481 require.False(t, ok) 482 } 483 } 484 485 func TestMemoryInstance_WriteUint16Le(t *testing.T) { 486 memory := &MemoryInstance{Buffer: make([]byte, 100)} 487 488 tests := []struct { 489 name string 490 offset uint32 491 v uint16 492 expectedOk bool 493 expectedBytes []byte 494 }{ 495 { 496 name: "valid offset with an endian-insensitive v", 497 offset: 0, // arbitrary valid offset. 498 v: math.MaxUint16, 499 expectedOk: true, 500 expectedBytes: []byte{0xff, 0xff}, 501 }, 502 { 503 name: "valid offset with an endian-sensitive v", 504 offset: 0, // arbitrary valid offset. 505 v: math.MaxUint16 - 1, 506 expectedOk: true, 507 expectedBytes: []byte{0xfe, 0xff}, 508 }, 509 { 510 name: "maximum boundary valid offset", 511 offset: memory.Size(testCtx) - 2, // 2 is the size of uint16 512 v: 1, // arbitrary valid v 513 expectedOk: true, 514 expectedBytes: []byte{0x1, 0x00}, 515 }, 516 { 517 name: "offset exceeds the maximum valid offset by 1", 518 offset: memory.Size(testCtx) - 2 + 1, // 2 is the size of uint16 519 v: 1, // arbitrary valid v 520 expectedBytes: []byte{0xff, 0xff}, 521 }, 522 } 523 524 for _, tt := range tests { 525 tc := tt 526 527 t.Run(tc.name, func(t *testing.T) { 528 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 529 require.Equal(t, tc.expectedOk, memory.WriteUint16Le(ctx, tc.offset, tc.v)) 530 if tc.expectedOk { 531 require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+2]) // 2 is the size of uint16 532 } 533 } 534 }) 535 } 536 } 537 538 func TestMemoryInstance_WriteUint32Le(t *testing.T) { 539 memory := &MemoryInstance{Buffer: make([]byte, 100)} 540 541 tests := []struct { 542 name string 543 offset uint32 544 v uint32 545 expectedOk bool 546 expectedBytes []byte 547 }{ 548 { 549 name: "valid offset with an endian-insensitive v", 550 offset: 0, // arbitrary valid offset. 551 v: math.MaxUint32, 552 expectedOk: true, 553 expectedBytes: []byte{0xff, 0xff, 0xff, 0xff}, 554 }, 555 { 556 name: "valid offset with an endian-sensitive v", 557 offset: 0, // arbitrary valid offset. 558 v: math.MaxUint32 - 1, 559 expectedOk: true, 560 expectedBytes: []byte{0xfe, 0xff, 0xff, 0xff}, 561 }, 562 { 563 name: "maximum boundary valid offset", 564 offset: memory.Size(testCtx) - 4, // 4 is the size of uint32 565 v: 1, // arbitrary valid v 566 expectedOk: true, 567 expectedBytes: []byte{0x1, 0x00, 0x00, 0x00}, 568 }, 569 { 570 name: "offset exceeds the maximum valid offset by 1", 571 offset: memory.Size(testCtx) - 4 + 1, // 4 is the size of uint32 572 v: 1, // arbitrary valid v 573 expectedBytes: []byte{0xff, 0xff, 0xff, 0xff}, 574 }, 575 } 576 577 for _, tt := range tests { 578 tc := tt 579 580 t.Run(tc.name, func(t *testing.T) { 581 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 582 require.Equal(t, tc.expectedOk, memory.WriteUint32Le(ctx, tc.offset, tc.v)) 583 if tc.expectedOk { 584 require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of uint32 585 } 586 } 587 }) 588 } 589 } 590 591 func TestMemoryInstance_WriteUint64Le(t *testing.T) { 592 memory := &MemoryInstance{Buffer: make([]byte, 100)} 593 tests := []struct { 594 name string 595 offset uint32 596 v uint64 597 expectedOk bool 598 expectedBytes []byte 599 }{ 600 { 601 name: "valid offset with an endian-insensitive v", 602 offset: 0, // arbitrary valid offset. 603 v: math.MaxUint64, 604 expectedOk: true, 605 expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 606 }, 607 { 608 name: "valid offset with an endian-sensitive v", 609 offset: 0, // arbitrary valid offset. 610 v: math.MaxUint64 - 1, 611 expectedOk: true, 612 expectedBytes: []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 613 }, 614 { 615 name: "maximum boundary valid offset", 616 offset: memory.Size(testCtx) - 8, // 8 is the size of uint64 617 v: 1, // arbitrary valid v 618 expectedOk: true, 619 expectedBytes: []byte{0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 620 }, 621 { 622 name: "offset exceeds the maximum valid offset by 1", 623 offset: memory.Size(testCtx) - 8 + 1, // 8 is the size of uint64 624 v: 1, // arbitrary valid v 625 expectedOk: false, 626 }, 627 } 628 629 for _, tt := range tests { 630 tc := tt 631 632 t.Run(tc.name, func(t *testing.T) { 633 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 634 require.Equal(t, tc.expectedOk, memory.WriteUint64Le(ctx, tc.offset, tc.v)) 635 if tc.expectedOk { 636 require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of uint64 637 } 638 } 639 }) 640 } 641 } 642 643 func TestMemoryInstance_WriteFloat32Le(t *testing.T) { 644 memory := &MemoryInstance{Buffer: make([]byte, 100)} 645 646 tests := []struct { 647 name string 648 offset uint32 649 v float32 650 expectedOk bool 651 expectedBytes []byte 652 }{ 653 { 654 name: "valid offset with an endian-insensitive v", 655 offset: 0, // arbitrary valid offset. 656 v: math.Float32frombits(uint32(0xff0000ff)), 657 expectedOk: true, 658 expectedBytes: []byte{0xff, 0x00, 0x00, 0xff}, 659 }, 660 { 661 name: "valid offset with an endian-sensitive v", 662 offset: 0, // arbitrary valid offset. 663 v: math.Float32frombits(uint32(0xff0000fe)), // arbitrary valid v 664 expectedOk: true, 665 expectedBytes: []byte{0xfe, 0x00, 0x00, 0xff}, 666 }, 667 { 668 name: "maximum boundary valid offset", 669 offset: memory.Size(testCtx) - 4, // 4 is the size of float32 670 v: 0.1, // arbitrary valid v 671 expectedOk: true, 672 expectedBytes: []byte{0xcd, 0xcc, 0xcc, 0x3d}, 673 }, 674 { 675 name: "offset exceeds the maximum valid offset by 1", 676 offset: memory.Size(testCtx) - 4 + 1, // 4 is the size of float32 677 v: math.MaxFloat32, // arbitrary valid v 678 expectedBytes: []byte{0xff, 0xff, 0xff, 0xff}, 679 }, 680 } 681 682 for _, tt := range tests { 683 tc := tt 684 685 t.Run(tc.name, func(t *testing.T) { 686 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 687 require.Equal(t, tc.expectedOk, memory.WriteFloat32Le(ctx, tc.offset, tc.v)) 688 if tc.expectedOk { 689 require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of float32 690 } 691 } 692 }) 693 } 694 } 695 696 func TestMemoryInstance_WriteFloat64Le(t *testing.T) { 697 memory := &MemoryInstance{Buffer: make([]byte, 100)} 698 tests := []struct { 699 name string 700 offset uint32 701 v float64 702 expectedOk bool 703 expectedBytes []byte 704 }{ 705 { 706 name: "valid offset with an endian-insensitive v", 707 offset: 0, // arbitrary valid offset. 708 v: math.Float64frombits(uint64(0xff000000000000ff)), 709 expectedOk: true, 710 expectedBytes: []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff}, 711 }, 712 { 713 name: "valid offset with an endian-sensitive v", 714 offset: 0, // arbitrary valid offset. 715 v: math.MaxFloat64, // arbitrary valid v 716 expectedOk: true, 717 expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f}, 718 }, 719 { 720 name: "maximum boundary valid offset", 721 offset: memory.Size(testCtx) - 8, // 8 is the size of float64 722 v: math.MaxFloat64, // arbitrary valid v 723 expectedOk: true, 724 expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f}, 725 }, 726 { 727 name: "offset exceeds the maximum valid offset by 1", 728 offset: memory.Size(testCtx) - 8 + 1, // 8 is the size of float64 729 v: math.MaxFloat64, // arbitrary valid v 730 expectedOk: false, 731 }, 732 } 733 734 for _, tt := range tests { 735 tc := tt 736 737 t.Run(tc.name, func(t *testing.T) { 738 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 739 require.Equal(t, tc.expectedOk, memory.WriteFloat64Le(ctx, tc.offset, tc.v)) 740 if tc.expectedOk { 741 require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of float64 742 } 743 } 744 }) 745 } 746 } 747 748 func TestMemoryInstance_Write(t *testing.T) { 749 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 750 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1} 751 752 buf := []byte{16, 0, 0, 4} 753 require.True(t, mem.Write(ctx, 4, buf)) 754 require.Equal(t, []byte{0, 0, 0, 0, 16, 0, 0, 4}, mem.Buffer) 755 756 // Test it isn't write-through 757 buf[3] = 0 758 require.Equal(t, []byte{16, 0, 0, 0}, buf) 759 require.Equal(t, []byte{0, 0, 0, 0, 16, 0, 0, 4}, mem.Buffer) 760 761 ok := mem.Write(ctx, 5, buf) 762 require.False(t, ok) 763 764 ok = mem.Write(ctx, 9, buf) 765 require.False(t, ok) 766 } 767 } 768 769 func TestMemoryInstance_WriteString(t *testing.T) { 770 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 771 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1} 772 773 s := "bear" 774 require.True(t, mem.WriteString(ctx, 4, s)) 775 require.Equal(t, []byte{0, 0, 0, 0, 'b', 'e', 'a', 'r'}, mem.Buffer) 776 777 ok := mem.WriteString(ctx, 5, s) 778 require.False(t, ok) 779 780 ok = mem.WriteString(ctx, 9, s) 781 require.False(t, ok) 782 } 783 } 784 785 func BenchmarkWriteString(b *testing.B) { 786 tests := []string{ 787 "", 788 "bear", 789 "hello world", 790 strings.Repeat("hello ", 10), 791 } 792 // nolint intentionally testing interface access 793 var mem api.Memory 794 mem = &MemoryInstance{Buffer: make([]byte, 1000), Min: 1} 795 for _, tt := range tests { 796 b.Run("", func(b *testing.B) { 797 b.Run("Write", func(b *testing.B) { 798 for i := 0; i < b.N; i++ { 799 if !mem.Write(testCtx, 0, []byte(tt)) { 800 b.Fail() 801 } 802 } 803 }) 804 b.Run("WriteString", func(b *testing.B) { 805 for i := 0; i < b.N; i++ { 806 if !mem.WriteString(testCtx, 0, tt) { 807 b.Fail() 808 } 809 } 810 }) 811 }) 812 } 813 }