Skip to content

Commit 6136b7b

Browse files
committed
test: add Array bounds behaviour documentation tests
- Document that Array[T] has no bounds checking (matches C semantics) - Verify valid index access for first, last, and middle elements - Confirm nil array access correctly panics - Add safe usage pattern tests (manual length tracking, sentinel values) - Test primitive arrays (ToIntArray) have same behaviour as enum arrays
1 parent ce65fdb commit 6136b7b

1 file changed

Lines changed: 198 additions & 0 deletions

File tree

bindings_test.go

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,204 @@ func TestGeneratorFieldAccessors(t *testing.T) {
710710
})
711711
}
712712

713+
// =============================================================================
714+
// Test: Array Bounds Behaviour Documentation
715+
// =============================================================================
716+
717+
// TestArray_BoundsBehaviour documents and verifies the bounds behaviour of the
718+
// Array[T] type. This is a critical safety documentation test.
719+
//
720+
// IMPORTANT: The Array type has NO bounds checking, matching C behaviour.
721+
// Out-of-bounds access leads to undefined behaviour (memory corruption, crashes,
722+
// or silent data corruption). This is by design to match FFmpeg's C semantics.
723+
//
724+
// Users MUST track array lengths themselves, just as in C code.
725+
func TestArray_BoundsBehaviour(t *testing.T) {
726+
t.Run("valid_indices_work_correctly", func(t *testing.T) {
727+
// Allocate array of 3 elements
728+
arr := AllocAVCodecIDArray(3)
729+
if arr == nil {
730+
t.Fatal("AllocAVCodecIDArray returned nil")
731+
}
732+
defer AVFree(arr.RawPtr())
733+
734+
// Valid indices: 0, 1, 2
735+
arr.Set(0, AVCodecIdH264)
736+
arr.Set(1, AVCodecIdHevc)
737+
arr.Set(2, AVCodecIdAV1)
738+
739+
if arr.Get(0) != AVCodecIdH264 {
740+
t.Errorf("Get(0) = %v, want AVCodecIdH264", arr.Get(0))
741+
}
742+
if arr.Get(1) != AVCodecIdHevc {
743+
t.Errorf("Get(1) = %v, want AVCodecIdHevc", arr.Get(1))
744+
}
745+
if arr.Get(2) != AVCodecIdAV1 {
746+
t.Errorf("Get(2) = %v, want AVCodecIdAV1", arr.Get(2))
747+
}
748+
})
749+
750+
t.Run("documents_no_bounds_checking", func(t *testing.T) {
751+
// This test documents that Array has NO bounds checking.
752+
// We cannot safely test out-of-bounds access as it causes undefined behaviour.
753+
//
754+
// DO NOT uncomment the following - it will cause memory corruption:
755+
// arr := AllocAVCodecIDArray(3)
756+
// arr.Get(100) // UNDEFINED BEHAVIOUR - reads arbitrary memory
757+
// arr.Set(100, AVCodecIdH264) // UNDEFINED BEHAVIOUR - writes arbitrary memory
758+
//
759+
// Users must track array lengths themselves. This matches C semantics where
760+
// arrays are just pointers with no length information.
761+
762+
t.Log("Array[T] has NO bounds checking - matches C semantics")
763+
t.Log("Out-of-bounds access causes undefined behaviour (crash or corruption)")
764+
t.Log("Users MUST track array lengths themselves")
765+
})
766+
767+
t.Run("zero_index_is_valid", func(t *testing.T) {
768+
// Verify index 0 works for any non-empty array
769+
arr := AllocAVCodecIDArray(1)
770+
if arr == nil {
771+
t.Fatal("AllocAVCodecIDArray returned nil")
772+
}
773+
defer AVFree(arr.RawPtr())
774+
775+
arr.Set(0, AVCodecIdMpeg2Video)
776+
if arr.Get(0) != AVCodecIdMpeg2Video {
777+
t.Errorf("Get(0) = %v, want AVCodecIdMpeg2Video", arr.Get(0))
778+
}
779+
})
780+
781+
t.Run("last_valid_index", func(t *testing.T) {
782+
// Verify the last valid index (size-1) works correctly
783+
const size = 5
784+
arr := AllocAVCodecIDArray(size)
785+
if arr == nil {
786+
t.Fatal("AllocAVCodecIDArray returned nil")
787+
}
788+
defer AVFree(arr.RawPtr())
789+
790+
// Set last element (index size-1)
791+
arr.Set(size-1, AVCodecIdVp9)
792+
if arr.Get(size-1) != AVCodecIdVp9 {
793+
t.Errorf("Get(%d) = %v, want AVCodecIdVp9", size-1, arr.Get(size-1))
794+
}
795+
})
796+
797+
t.Run("primitive_arrays_same_behaviour", func(t *testing.T) {
798+
// Verify primitive type arrays also have no bounds checking
799+
// Test with int array using ToIntArray
800+
801+
// Allocate raw memory for 3 ints
802+
ptr := AVMalloc(uint64(3 * intSize))
803+
if ptr == nil {
804+
t.Fatal("AVMalloc returned nil")
805+
}
806+
defer AVFree(ptr)
807+
808+
arr := ToIntArray(ptr)
809+
if arr == nil {
810+
t.Fatal("ToIntArray returned nil")
811+
}
812+
813+
// Valid access
814+
arr.Set(0, 42)
815+
arr.Set(1, 100)
816+
arr.Set(2, -1)
817+
818+
if arr.Get(0) != 42 {
819+
t.Errorf("int array Get(0) = %v, want 42", arr.Get(0))
820+
}
821+
if arr.Get(1) != 100 {
822+
t.Errorf("int array Get(1) = %v, want 100", arr.Get(1))
823+
}
824+
if arr.Get(2) != -1 {
825+
t.Errorf("int array Get(2) = %v, want -1", arr.Get(2))
826+
}
827+
828+
t.Log("Primitive arrays (ToIntArray, ToUint8Array, etc.) also have no bounds checking")
829+
})
830+
831+
t.Run("nil_array_panics", func(t *testing.T) {
832+
// Nil array should panic on access - this IS checked
833+
var arr *Array[AVCodecID] = nil
834+
835+
defer func() {
836+
if r := recover(); r == nil {
837+
t.Error("Expected panic when accessing nil array, but none occurred")
838+
} else {
839+
t.Logf("Nil array access correctly panics: %v", r)
840+
}
841+
}()
842+
843+
// This should panic
844+
_ = arr.Get(0)
845+
})
846+
}
847+
848+
// TestArray_SafeUsagePatterns documents safe patterns for using Array[T]
849+
func TestArray_SafeUsagePatterns(t *testing.T) {
850+
t.Run("track_length_manually", func(t *testing.T) {
851+
// Safe pattern: always track length alongside array
852+
const length = 3
853+
arr := AllocAVCodecIDArray(length)
854+
if arr == nil {
855+
t.Fatal("AllocAVCodecIDArray returned nil")
856+
}
857+
defer AVFree(arr.RawPtr())
858+
859+
// Safe iteration using tracked length
860+
values := []AVCodecID{AVCodecIdH264, AVCodecIdHevc, AVCodecIdAV1}
861+
for i := 0; i < length; i++ {
862+
arr.Set(uintptr(i), values[i])
863+
}
864+
865+
// Safe read using tracked length
866+
for i := 0; i < length; i++ {
867+
if arr.Get(uintptr(i)) != values[i] {
868+
t.Errorf("Get(%d) mismatch", i)
869+
}
870+
}
871+
872+
t.Log("Safe pattern: track array length in a separate variable")
873+
})
874+
875+
t.Run("use_sentinel_values", func(t *testing.T) {
876+
// Safe pattern: use sentinel value to mark end (like null-terminated strings)
877+
// Many FFmpeg APIs use AVCodecIdNone or -1 as sentinel
878+
arr := AllocAVCodecIDArray(4)
879+
if arr == nil {
880+
t.Fatal("AllocAVCodecIDArray returned nil")
881+
}
882+
defer AVFree(arr.RawPtr())
883+
884+
// Store values with sentinel
885+
arr.Set(0, AVCodecIdH264)
886+
arr.Set(1, AVCodecIdHevc)
887+
arr.Set(2, AVCodecIdAV1)
888+
arr.Set(3, AVCodecIdNone) // Sentinel
889+
890+
// Safe iteration using sentinel
891+
count := 0
892+
for i := uintptr(0); ; i++ {
893+
val := arr.Get(i)
894+
if val == AVCodecIdNone {
895+
break
896+
}
897+
count++
898+
if count > 100 { // Safety limit
899+
t.Fatal("Sentinel not found within reasonable range")
900+
}
901+
}
902+
903+
if count != 3 {
904+
t.Errorf("Found %d elements before sentinel, want 3", count)
905+
}
906+
907+
t.Log("Safe pattern: use sentinel value (e.g., AVCodecIdNone) to mark end")
908+
})
909+
}
910+
713911
// TestGeneratorEnumArrayHelpers validates that enum array helpers work correctly
714912
// This tests the AllocXArray generation pattern for enums
715913
func TestGeneratorEnumArrayHelpers(t *testing.T) {

0 commit comments

Comments
 (0)