@@ -11,6 +11,7 @@ import (
1111 "github.com/docker/cli/internal/test"
1212 "github.com/moby/go-archive"
1313 "github.com/moby/go-archive/compression"
14+ "github.com/moby/moby/api/types/container"
1415 "github.com/moby/moby/client"
1516 "gotest.tools/v3/assert"
1617 is "gotest.tools/v3/assert/cmp"
@@ -211,3 +212,86 @@ func TestRunCopyFromContainerToFilesystemIrregularDestination(t *testing.T) {
211212 expected := `"/dev/random" must be a directory or a regular file`
212213 assert .ErrorContains (t , err , expected )
213214}
215+
216+ func TestCopyFromContainerReportsFileSize (t * testing.T ) {
217+ // The file content is "hello" (5 bytes), but the TAR archive wrapping
218+ // it is much larger due to headers and padding. The success message
219+ // should report the actual file size (5B), not the TAR stream size.
220+ srcDir := fs .NewDir (t , "cp-test-from" ,
221+ fs .WithFile ("file1" , "hello" ))
222+
223+ destDir := fs .NewDir (t , "cp-test-from-dest" )
224+
225+ const fileSize int64 = 5
226+ fakeCli := test .NewFakeCli (& fakeClient {
227+ containerCopyFromFunc : func (ctr , srcPath string ) (client.CopyFromContainerResult , error ) {
228+ readCloser , err := archive .Tar (srcDir .Path (), compression .None )
229+ return client.CopyFromContainerResult {
230+ Content : readCloser ,
231+ Stat : container.PathStat {
232+ Name : "file1" ,
233+ Size : fileSize ,
234+ },
235+ }, err
236+ },
237+ })
238+ err := runCopy (context .TODO (), fakeCli , copyOptions {
239+ source : "container:/file1" ,
240+ destination : destDir .Path (),
241+ })
242+ assert .NilError (t , err )
243+ assert .Check (t , is .Contains (fakeCli .ErrBuffer ().String (), "5B" ))
244+ }
245+
246+ func TestCopyToContainerReportsFileSize (t * testing.T ) {
247+ // Create a temp file with known content ("hello" = 5 bytes).
248+ // The TAR archive sent to the container is larger, but the success
249+ // message should report the actual content size.
250+ srcFile := fs .NewFile (t , "cp-test-to" , fs .WithContent ("hello" ))
251+
252+ fakeCli := test .NewFakeCli (& fakeClient {
253+ containerStatPathFunc : func (containerID , path string ) (client.ContainerStatPathResult , error ) {
254+ return client.ContainerStatPathResult {
255+ Stat : container.PathStat {
256+ Name : "tmp" ,
257+ Mode : os .ModeDir | 0o755 ,
258+ },
259+ }, nil
260+ },
261+ containerCopyToFunc : func (containerID string , options client.CopyToContainerOptions ) (client.CopyToContainerResult , error ) {
262+ _ , _ = io .Copy (io .Discard , options .Content )
263+ return client.CopyToContainerResult {}, nil
264+ },
265+ })
266+ err := runCopy (context .TODO (), fakeCli , copyOptions {
267+ source : srcFile .Path (),
268+ destination : "container:/tmp" ,
269+ })
270+ assert .NilError (t , err )
271+ assert .Check (t , is .Contains (fakeCli .ErrBuffer ().String (), "5B" ))
272+ }
273+
274+ func TestCopyToContainerReportsEmptyFileSize (t * testing.T ) {
275+ srcFile := fs .NewFile (t , "cp-test-empty" , fs .WithContent ("" ))
276+
277+ fakeCli := test .NewFakeCli (& fakeClient {
278+ containerStatPathFunc : func (containerID , path string ) (client.ContainerStatPathResult , error ) {
279+ return client.ContainerStatPathResult {
280+ Stat : container.PathStat {
281+ Name : "tmp" ,
282+ Mode : os .ModeDir | 0o755 ,
283+ },
284+ }, nil
285+ },
286+ containerCopyToFunc : func (containerID string , options client.CopyToContainerOptions ) (client.CopyToContainerResult , error ) {
287+ _ , _ = io .Copy (io .Discard , options .Content )
288+ return client.CopyToContainerResult {}, nil
289+ },
290+ })
291+ err := runCopy (context .TODO (), fakeCli , copyOptions {
292+ source : srcFile .Path (),
293+ destination : "container:/tmp" ,
294+ })
295+ assert .NilError (t , err )
296+ assert .Check (t , is .Contains (fakeCli .ErrBuffer ().String (), "0B" ))
297+ }
0 commit comments