diff --git a/object/slicer/slicer.go b/object/slicer/slicer.go index 768072186..086f6b4ef 100644 --- a/object/slicer/slicer.go +++ b/object/slicer/slicer.go @@ -173,24 +173,26 @@ func slice(ctx context.Context, ow ObjectWriter, header object.Object, data io.R for { n, err = data.Read(bChunk) - if err != nil { - if !errors.Is(err, io.EOF) { - return rootID, fmt.Errorf("read payload chunk: %w", err) + if n > 0 { + if _, err = writer.Write(bChunk[:n]); err != nil { + return oid.ID{}, err } - // no more data to read + continue + } - if err = writer.Close(); err != nil { - return rootID, fmt.Errorf("writer close: %w", err) + if err != nil { + if !errors.Is(err, io.EOF) { + return rootID, fmt.Errorf("read payload chunk: %w", err) } - - rootID = writer.ID() - break } - if _, err = writer.Write(bChunk[:n]); err != nil { - return oid.ID{}, err + if err = writer.Close(); err != nil { + return rootID, fmt.Errorf("writer close: %w", err) } + + rootID = writer.ID() + break } return rootID, nil diff --git a/object/slicer/slicer_test.go b/object/slicer/slicer_test.go index b4e520cb5..fd61291ca 100644 --- a/object/slicer/slicer_test.go +++ b/object/slicer/slicer_test.go @@ -269,6 +269,23 @@ func testSlicer(t *testing.T, size, sizeLimit uint64) { } } +// eofOnLastChunkReader is a special reader for tests. It returns io.EOF with the last data portion. +type eofOnLastChunkReader struct { + Payload []byte + point int +} + +func (l *eofOnLastChunkReader) Read(p []byte) (int, error) { + n := copy(p, l.Payload[l.point:]) + l.point += n + + if l.point == len(l.Payload) { + return n, io.EOF + } + + return n, nil +} + func testSlicerByHeaderType(t *testing.T, checker *slicedObjectChecker, in input, opts slicer.Options) { ctx := context.Background() @@ -296,6 +313,20 @@ func testSlicerByHeaderType(t *testing.T, checker *slicedObjectChecker, in input checker.chainCollector.verify(checker.input, rootID) }) + t.Run("slicer.Put, io.EOF in last chunk", func(t *testing.T) { + checker.chainCollector = newChainCollector(t) + + var hdr object.Object + hdr.SetSessionToken(opts.Session()) + hdr.SetContainerID(in.container) + hdr.SetOwnerID(&in.owner) + hdr.SetAttributes(in.attributes...) + + rootID, err := slicer.Put(ctx, checker, hdr, checker.input.signer, &eofOnLastChunkReader{Payload: in.payload}, opts) + require.NoError(t, err) + checker.chainCollector.verify(checker.input, rootID) + }) + t.Run("Slicer.InitPut", func(t *testing.T) { checker.chainCollector = newChainCollector(t)