package layer 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.Reader, error) { partPayload, ok := r.parts[p.oid] if !ok { return nil, errors.New("part not found") } if p.off+p.ln == 0 { return 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 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{ layer: 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) } }) } }