@@ -7,6 +7,7 @@ package iox_test
77import (
88 "bytes"
99 "errors"
10+ "fmt"
1011 "io"
1112 "testing"
1213
@@ -457,22 +458,21 @@ func TestCopyPolicy_ReaderFromFastPath_More_Returns(t *testing.T) {
457458 t .Fatalf ("want More from ReaderFrom fast path: n=%d err=%v" , n , err )
458459 }
459460}
460- func TestCopyNPolicy_Short_NoUnexpectedEOF_NoErrShort (t * testing.T ) {
461+ func TestCopyNPolicy_Short_ReturnsUnexpectedEOF (t * testing.T ) {
461462 var dst bytes.Buffer
462463 // src returns fewer than N without error → UnexpectedEOF
463464 src := bytes .NewBufferString ("ab" )
464465 n , err := iox .CopyNPolicy (& dst , src , 3 , iox.YieldPolicy {})
465- // Current CopyNPolicy returns underlying result directly (no UnexpectedEOF mapping)
466- if err != nil || n != 2 {
467- t .Fatalf ("want (2,nil) got n=%d err=%v" , n , err )
466+ if err != io .ErrUnexpectedEOF || n != 2 {
467+ t .Fatalf ("want (2,ErrUnexpectedEOF) got n=%d err=%v" , n , err )
468468 }
469469}
470- func TestCopyNPolicy_ShortEOF_NoUnexpectedEOF (t * testing.T ) {
470+ func TestCopyNPolicy_ShortEOF_ReturnsUnexpectedEOF (t * testing.T ) {
471471 var dst bytes.Buffer
472472 src := bytes .NewBufferString ("a" )
473473 n , err := iox .CopyNPolicy (& dst , src , 2 , iox.YieldPolicy {})
474- if err != nil || n != 1 {
475- t .Fatalf ("want (1,nil ) got n=%d err=%v" , n , err )
474+ if err != io . ErrUnexpectedEOF || n != 1 {
475+ t .Fatalf ("want (1,ErrUnexpectedEOF ) got n=%d err=%v" , n , err )
476476 }
477477}
478478func TestCopyNBufferPolicy_ExactN_WithBuf (t * testing.T ) {
@@ -848,3 +848,79 @@ func TestTeeWriterPolicy_PrimaryWouldBlockOnceCompletes(t *testing.T) {
848848 t .Fatalf ("primary=%q tee=%q" , p .buf .String (), tbuf .String ())
849849 }
850850}
851+
852+ // --- Coverage tests for uncovered patch lines ---
853+
854+ // CopyNPolicy: non-nil/non-EOF error passthrough.
855+ func TestCopyNPolicy_ErrorPassthrough (t * testing.T ) {
856+ readErr := errors .New ("read-fail" )
857+ src := & dataThenErrReader {data : []byte ("a" ), err : readErr }
858+ var dst bytes.Buffer
859+ n , err := iox .CopyNPolicy (& dst , src , 5 , iox.YieldPolicy {})
860+ if ! errors .Is (err , readErr ) || n != 1 {
861+ t .Fatalf ("want (1, read-fail) got (%d, %v)" , n , err )
862+ }
863+ }
864+
865+ // CopyNBufferPolicy: short copy → ErrUnexpectedEOF.
866+ func TestCopyNBufferPolicy_Short_ReturnsUnexpectedEOF (t * testing.T ) {
867+ src := bytes .NewBufferString ("ab" )
868+ var dst bytes.Buffer
869+ buf := make ([]byte , 4 )
870+ n , err := iox .CopyNBufferPolicy (& dst , src , 5 , buf , iox.YieldPolicy {})
871+ if err != io .ErrUnexpectedEOF || n != 2 {
872+ t .Fatalf ("want (2, ErrUnexpectedEOF) got (%d, %v)" , n , err )
873+ }
874+ }
875+
876+ // CopyNBufferPolicy: non-nil/non-EOF error passthrough.
877+ func TestCopyNBufferPolicy_ErrorPassthrough (t * testing.T ) {
878+ readErr := errors .New ("read-fail" )
879+ src := & dataThenErrReader {data : []byte ("x" ), err : readErr }
880+ var dst bytes.Buffer
881+ buf := make ([]byte , 4 )
882+ n , err := iox .CopyNBufferPolicy (& dst , src , 5 , buf , iox.YieldPolicy {})
883+ if ! errors .Is (err , readErr ) || n != 1 {
884+ t .Fatalf ("want (1, read-fail) got (%d, %v)" , n , err )
885+ }
886+ }
887+
888+ // wrappedWBWriter returns a wrapped ErrWouldBlock (passes IsSemantic but not ==).
889+ type wrappedWBWriter struct {
890+ n int
891+ }
892+
893+ func (w * wrappedWBWriter ) Write (p []byte ) (int , error ) {
894+ // Partial write with wrapped semantic error.
895+ n := w .n
896+ if n > len (p ) {
897+ n = len (p )
898+ }
899+ return n , fmt .Errorf ("wrapped: %w" , iox .ErrWouldBlock )
900+ }
901+
902+ // seekableReader wraps bytes.Reader but hides WriterTo so the slow path is used.
903+ type seekableReader struct {
904+ r * bytes.Reader
905+ }
906+
907+ func (s * seekableReader ) Read (p []byte ) (int , error ) { return s .r .Read (p ) }
908+ func (s * seekableReader ) Seek (offset int64 , whence int ) (int64 , error ) {
909+ return s .r .Seek (offset , whence )
910+ }
911+
912+ // copyBufferPolicy: wrapped semantic write error triggers IsSemantic rollback.
913+ func TestCopyPolicy_SlowPath_WrappedSemanticWrite_Rollback (t * testing.T ) {
914+ src := & seekableReader {r : bytes .NewReader ([]byte ("abcd" ))}
915+ dst := & wrappedWBWriter {n : 1 }
916+ pol := & recPolicy {}
917+ n , err := iox .CopyPolicy (dst , src , pol )
918+ if ! iox .IsWouldBlock (err ) || n != 1 {
919+ t .Fatalf ("want (1, wrapped-WouldBlock) got (%d, %v)" , n , err )
920+ }
921+ // Verify src was rolled back: position should be at 1 (1 written, rolled back 3).
922+ pos , _ := src .Seek (0 , io .SeekCurrent )
923+ if pos != 1 {
924+ t .Fatalf ("want src position 1 after rollback, got %d" , pos )
925+ }
926+ }
0 commit comments