package frostfs import ( "bytes" "context" "errors" "fmt" "io" "testing" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test" "github.com/stretchr/testify/require" ) type readerInitiatorMock struct { parts map[oid.ID][]byte } func (r *readerInitiatorMock) InitFrostFSObjectPayloadReader(_ context.Context, p GetFrostFSParams) (io.ReadCloser, error) { partPayload, ok := r.parts[p.Addr.Object()] if !ok { return nil, errors.New("part not found") } if p.Off+p.Ln == 0 { return io.NopCloser(bytes.NewReader(partPayload)), nil } if p.Off > uint64(len(partPayload)-1) { return nil, fmt.Errorf("invalid offset: %d/%d", p.Off, len(partPayload)) } if p.Off+p.Ln > uint64(len(partPayload)) { return nil, fmt.Errorf("invalid range: %d-%d/%d", p.Off, p.Off+p.Ln, len(partPayload)) } return io.NopCloser(bytes.NewReader(partPayload[p.Off : p.Off+p.Ln])), nil } func prepareDataReader() ([]byte, []PartObj, *readerInitiatorMock) { mockInitReader := &readerInitiatorMock{ parts: map[oid.ID][]byte{ oidtest.ID(): []byte("first part 1"), oidtest.ID(): []byte("second part 2"), oidtest.ID(): []byte("third part 3"), }, } var fullPayload []byte parts := make([]PartObj, 0, len(mockInitReader.parts)) for id, payload := range mockInitReader.parts { parts = append(parts, PartObj{OID: id, Size: uint64(len(payload))}) fullPayload = append(fullPayload, payload...) } return fullPayload, parts, mockInitReader } func TestMultiReader(t *testing.T) { ctx := context.Background() fullPayload, parts, mockInitReader := prepareDataReader() for _, tc := range []struct { name string off uint64 ln uint64 err error }{ { name: "simple read all", }, { name: "simple read with length", ln: uint64(len(fullPayload)), }, { name: "middle of parts", off: parts[0].Size + 2, ln: 4, }, { name: "first and second", off: parts[0].Size - 4, ln: 8, }, { name: "first and third", off: parts[0].Size - 4, ln: parts[1].Size + 8, }, { name: "second part", off: parts[0].Size, ln: parts[1].Size, }, { name: "second and third", off: parts[0].Size, ln: parts[1].Size + parts[2].Size, }, { name: "offset out of range", off: uint64(len(fullPayload) + 1), ln: 1, err: errOffsetIsOutOfRange, }, { name: "zero length", off: parts[1].Size + 1, ln: 0, err: errorZeroRangeLength, }, } { t.Run(tc.name, func(t *testing.T) { multiReader, err := NewMultiObjectReader(ctx, MultiObjectReaderConfig{ Initiator: mockInitReader, Parts: parts, Off: tc.off, Ln: tc.ln, }) require.ErrorIs(t, err, tc.err) if tc.err == nil { off := tc.off ln := tc.ln if off+ln == 0 { ln = uint64(len(fullPayload)) } data, err := io.ReadAll(multiReader) require.NoError(t, err) require.Equal(t, fullPayload[off:off+ln], data) } }) } }