From 5e62c14c6cf78f1573ac0c878132bfc7c99e8d58 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Wed, 8 Nov 2023 03:16:57 +0000 Subject: [PATCH] fix: avoid overflow in progress bar percentage (#1178) Signed-off-by: Billy Zha --- cmd/oras/internal/display/progress/status.go | 13 ++++--- .../internal/display/progress/status_test.go | 36 ++++++++++++++++++- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/cmd/oras/internal/display/progress/status.go b/cmd/oras/internal/display/progress/status.go index ee2a54501..5c67c2053 100644 --- a/cmd/oras/internal/display/progress/status.go +++ b/cmd/oras/internal/display/progress/status.go @@ -55,6 +55,7 @@ type status struct { func newStatus() *status { return &status{ offset: -1, + total: humanize.ToBytes(0), lastRenderTime: time.Now(), } } @@ -99,9 +100,6 @@ func (s *status) String(width int) (string, string) { // todo: doesn't support multiline prompt total := uint64(s.descriptor.Size) var percent float64 - if s.offset >= 0 { - percent = float64(s.offset) / float64(total) - } name := s.descriptor.Annotations["org.opencontainers.image.title"] if name == "" { @@ -112,10 +110,15 @@ func (s *status) String(width int) (string, string) { // mark(1) bar(22) speed(8) action(<=11) name(<=126) size_per_size(<=13) percent(8) time(>=6) // └─ digest(72) var offset string - switch percent { - case 1: // 100%, show exact size + switch s.done { + case true: // 100%, show exact size offset = fmt.Sprint(s.total.Size) + percent = 1 default: // 0% ~ 99%, show 2-digit precision + if total != 0 && s.offset >= 0 { + // percentage calculatable + percent = float64(s.offset) / float64(total) + } offset = fmt.Sprintf("%.2f", humanize.RoundTo(s.total.Size*percent)) } right := fmt.Sprintf(" %s/%s %6.2f%% %6s", offset, s.total, percent*100, s.durationString()) diff --git a/cmd/oras/internal/display/progress/status_test.go b/cmd/oras/internal/display/progress/status_test.go index 38d351872..4ce98bace 100644 --- a/cmd/oras/internal/display/progress/status_test.go +++ b/cmd/oras/internal/display/progress/status_test.go @@ -54,7 +54,6 @@ func Test_status_String(t *testing.T) { if err := testutils.OrderedMatch(statusStr+digestStr, " [\x1b[7m\x1b[0m....................]", s.prompt, "application/v.", "0.00/2 B", "0.00%", s.descriptor.Digest.String()); err != nil { t.Error(err) } - // done s.Update(&status{ endTime: time.Now(), @@ -67,6 +66,41 @@ func Test_status_String(t *testing.T) { } } +func Test_status_String_zeroWitdth(t *testing.T) { + // zero status and progress + s := newStatus() + if status, digest := s.String(console.MinWidth); status != zeroStatus || digest != zeroDigest { + t.Errorf("status.String() = %v, %v, want %v, %v", status, digest, zeroStatus, zeroDigest) + } + + // not done + s.Update(&status{ + prompt: "test", + descriptor: ocispec.Descriptor{ + MediaType: "application/vnd.oci.empty.oras.test.v1+json", + Size: 0, + Digest: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, + startTime: time.Now().Add(-time.Minute), + offset: 0, + total: humanize.ToBytes(0), + }) + // not done + statusStr, digestStr := s.String(120) + if err := testutils.OrderedMatch(statusStr+digestStr, " [\x1b[7m\x1b[0m....................]", s.prompt, s.descriptor.MediaType, "0.00/0 B", "0.00%", s.descriptor.Digest.String()); err != nil { + t.Error(err) + } + // done + s.Update(&status{ + endTime: time.Now(), + offset: s.descriptor.Size, + descriptor: s.descriptor, + }) + statusStr, digestStr = s.String(120) + if err := testutils.OrderedMatch(statusStr+digestStr, "✓", s.prompt, s.descriptor.MediaType, "0/0 B", "100.00%", s.descriptor.Digest.String()); err != nil { + t.Error(err) + } +} func Test_status_durationString(t *testing.T) { // zero duration s := newStatus()