@@ -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,156 @@ 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+ errOut := fakeCli .ErrBuffer ().String ()
244+ assert .Check (t , is .Contains (errOut , "Successfully copied 5B" ))
245+ assert .Check (t , is .Contains (errOut , "(transferred" ))
246+ }
247+
248+ func TestCopyToContainerReportsFileSize (t * testing.T ) {
249+ // Create a temp file with known content ("hello" = 5 bytes).
250+ // The TAR archive sent to the container is larger, but the success
251+ // message should report the actual content size.
252+ srcFile := fs .NewFile (t , "cp-test-to" , fs .WithContent ("hello" ))
253+
254+ fakeCli := test .NewFakeCli (& fakeClient {
255+ containerStatPathFunc : func (containerID , path string ) (client.ContainerStatPathResult , error ) {
256+ return client.ContainerStatPathResult {
257+ Stat : container.PathStat {
258+ Name : "tmp" ,
259+ Mode : os .ModeDir | 0o755 ,
260+ },
261+ }, nil
262+ },
263+ containerCopyToFunc : func (containerID string , options client.CopyToContainerOptions ) (client.CopyToContainerResult , error ) {
264+ _ , _ = io .Copy (io .Discard , options .Content )
265+ return client.CopyToContainerResult {}, nil
266+ },
267+ })
268+ err := runCopy (context .TODO (), fakeCli , copyOptions {
269+ source : srcFile .Path (),
270+ destination : "container:/tmp" ,
271+ })
272+ assert .NilError (t , err )
273+ errOut := fakeCli .ErrBuffer ().String ()
274+ assert .Check (t , is .Contains (errOut , "Successfully copied 5B" ))
275+ assert .Check (t , is .Contains (errOut , "(transferred" ))
276+ }
277+
278+ func TestCopyToContainerReportsEmptyFileSize (t * testing.T ) {
279+ srcFile := fs .NewFile (t , "cp-test-empty" , fs .WithContent ("" ))
280+
281+ fakeCli := test .NewFakeCli (& fakeClient {
282+ containerStatPathFunc : func (containerID , path string ) (client.ContainerStatPathResult , error ) {
283+ return client.ContainerStatPathResult {
284+ Stat : container.PathStat {
285+ Name : "tmp" ,
286+ Mode : os .ModeDir | 0o755 ,
287+ },
288+ }, nil
289+ },
290+ containerCopyToFunc : func (containerID string , options client.CopyToContainerOptions ) (client.CopyToContainerResult , error ) {
291+ _ , _ = io .Copy (io .Discard , options .Content )
292+ return client.CopyToContainerResult {}, nil
293+ },
294+ })
295+ err := runCopy (context .TODO (), fakeCli , copyOptions {
296+ source : srcFile .Path (),
297+ destination : "container:/tmp" ,
298+ })
299+ assert .NilError (t , err )
300+ errOut := fakeCli .ErrBuffer ().String ()
301+ assert .Check (t , is .Contains (errOut , "Successfully copied 0B" ))
302+ assert .Check (t , is .Contains (errOut , "(transferred" ))
303+ }
304+
305+ func TestCopyToContainerReportsDirectorySize (t * testing.T ) {
306+ // Create a temp directory with files "aaa" (3 bytes) + "bbb" (3 bytes) = 6 bytes.
307+ // The TAR archive is much larger, but the success message should report 6B.
308+ srcDir := fs .NewDir (t , "cp-test-dir" ,
309+ fs .WithFile ("aaa" , "aaa" ),
310+ fs .WithFile ("bbb" , "bbb" ),
311+ )
312+
313+ fakeCli := test .NewFakeCli (& fakeClient {
314+ containerStatPathFunc : func (containerID , path string ) (client.ContainerStatPathResult , error ) {
315+ return client.ContainerStatPathResult {
316+ Stat : container.PathStat {
317+ Name : "tmp" ,
318+ Mode : os .ModeDir | 0o755 ,
319+ },
320+ }, nil
321+ },
322+ containerCopyToFunc : func (containerID string , options client.CopyToContainerOptions ) (client.CopyToContainerResult , error ) {
323+ _ , _ = io .Copy (io .Discard , options .Content )
324+ return client.CopyToContainerResult {}, nil
325+ },
326+ })
327+ err := runCopy (context .TODO (), fakeCli , copyOptions {
328+ source : srcDir .Path () + string (os .PathSeparator ),
329+ destination : "container:/tmp" ,
330+ })
331+ assert .NilError (t , err )
332+ errOut := fakeCli .ErrBuffer ().String ()
333+ assert .Check (t , is .Contains (errOut , "Successfully copied 6B" ))
334+ assert .Check (t , is .Contains (errOut , "(transferred" ))
335+ }
336+
337+ func TestCopyFromContainerReportsDirectorySize (t * testing.T ) {
338+ // When copying a directory from a container, cpRes.Stat.Mode.IsDir() is true,
339+ // so reportedSize falls back to copiedSize (the tar stream bytes).
340+ srcDir := fs .NewDir (t , "cp-test-fromdir" ,
341+ fs .WithFile ("file1" , "hello" ))
342+
343+ destDir := fs .NewDir (t , "cp-test-fromdir-dest" )
344+
345+ fakeCli := test .NewFakeCli (& fakeClient {
346+ containerCopyFromFunc : func (ctr , srcPath string ) (client.CopyFromContainerResult , error ) {
347+ readCloser , err := archive .Tar (srcDir .Path (), compression .None )
348+ return client.CopyFromContainerResult {
349+ Content : readCloser ,
350+ Stat : container.PathStat {
351+ Name : "mydir" ,
352+ Mode : os .ModeDir | 0o755 ,
353+ },
354+ }, err
355+ },
356+ })
357+ err := runCopy (context .TODO (), fakeCli , copyOptions {
358+ source : "container:/mydir" ,
359+ destination : destDir .Path (),
360+ })
361+ assert .NilError (t , err )
362+ errOut := fakeCli .ErrBuffer ().String ()
363+ assert .Check (t , is .Contains (errOut , "Successfully copied" ))
364+ // For directories from container, content size is unknown so
365+ // reportedSize == copiedSize and "(transferred ...)" is omitted.
366+ assert .Check (t , ! strings .Contains (errOut , "(transferred" ))
367+ }
0 commit comments