Skip to content

Commit

Permalink
Merge branch 'main' into output-output
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiaoxuan Wang committed Oct 10, 2024
2 parents 66249a8 + 00a19d2 commit 6e3b09a
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 62 deletions.
63 changes: 35 additions & 28 deletions cmd/oras/internal/display/status/console/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package console
import (
"os"

"github.com/containerd/console"
containerd "github.com/containerd/console"
"github.com/morikuni/aec"
)

Expand All @@ -33,60 +33,67 @@ const (
Restore = "\0338"
)

// Console is a wrapper around containerd's console.Console and ANSI escape
// codes.
type Console struct {
console.Console
// Console is a wrapper around containerd's Console and ANSI escape codes.
type Console interface {
containerd.Console
GetHeightWidth() (height, width int)
Save()
NewRow()
OutputTo(upCnt uint, str string)
Restore()
}

// Size returns the width and height of the console.
// If the console size cannot be determined, returns a default value of 80x10.
func (c *Console) Size() (width, height int) {
width = MinWidth
height = MinHeight
size, err := c.Console.Size()
if err == nil {
if size.Height > MinHeight {
height = int(size.Height)
}
if size.Width > MinWidth {
width = int(size.Width)
}
}
return
type console struct {
containerd.Console
}

// New generates a Console from a file.
func New(f *os.File) (*Console, error) {
c, err := console.ConsoleFromFile(f)
// NewConsole generates a console from a file.
func NewConsole(f *os.File) (Console, error) {
c, err := containerd.ConsoleFromFile(f)
if err != nil {
return nil, err
}
return &Console{c}, nil
return &console{c}, nil
}

// GetHeightWidth returns the width and height of the console.
// If the console size cannot be determined, returns a default value of 80x10.
func (c *console) GetHeightWidth() (height, width int) {
windowSize, err := c.Console.Size()
if err != nil {
return MinHeight, MinWidth
}
if windowSize.Height < MinHeight {
windowSize.Height = MinHeight
}
if windowSize.Width < MinWidth {
windowSize.Width = MinWidth
}
return int(windowSize.Height), int(windowSize.Width)
}

// Save saves the current cursor position.
func (c *Console) Save() {
func (c *console) Save() {
_, _ = c.Write([]byte(aec.Hide.Apply(Save)))
}

// NewRow allocates a horizontal space to the output area with scroll if needed.
func (c *Console) NewRow() {
func (c *console) NewRow() {
_, _ = c.Write([]byte(Restore))
_, _ = c.Write([]byte("\n"))
_, _ = c.Write([]byte(Save))
}

// OutputTo outputs a string to a specific line.
func (c *Console) OutputTo(upCnt uint, str string) {
func (c *console) OutputTo(upCnt uint, str string) {
_, _ = c.Write([]byte(Restore))
_, _ = c.Write([]byte(aec.PreviousLine(upCnt).Apply(str)))
_, _ = c.Write([]byte("\n"))
_, _ = c.Write([]byte(aec.EraseLine(aec.EraseModes.Tail).String()))
}

// Restore restores the saved cursor position.
func (c *Console) Restore() {
func (c *console) Restore() {
// cannot use aec.Restore since DEC has better compatibility than SCO
_, _ = c.Write([]byte(Restore))
_, _ = c.Write([]byte(aec.Column(0).
Expand Down
103 changes: 88 additions & 15 deletions cmd/oras/internal/display/status/console/console_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,38 @@ limitations under the License.
package console

import (
"os"
"testing"

"github.com/containerd/console"
containerd "github.com/containerd/console"
"oras.land/oras/internal/testutils"
)

func givenConsole(t *testing.T) (c Console, pty containerd.Console) {
pty, _, err := containerd.NewPty()
if err != nil {
t.Fatal(err)
}

c = &console{
Console: pty,
}
return c, pty
}

func givenTestConsole(t *testing.T) (c Console, pty containerd.Console, tty *os.File) {
var err error
pty, tty, err = testutils.NewPty()
if err != nil {
t.Fatal(err)
}

c = &console{
Console: pty,
}
return c, pty, tty
}

func validateSize(t *testing.T, gotWidth, gotHeight, wantWidth, wantHeight int) {
t.Helper()
if gotWidth != wantWidth {
Expand All @@ -33,31 +60,77 @@ func validateSize(t *testing.T, gotWidth, gotHeight, wantWidth, wantHeight int)
}
}

func TestConsole_Size(t *testing.T) {
pty, _, err := console.NewPty()
if err != nil {
t.Fatal(err)
}
c := &Console{
Console: pty,
func TestNewConsole(t *testing.T) {
_, err := NewConsole(os.Stdin)
if err == nil {
t.Error("expected error creating bogus console")
}
}

func TestConsole_GetHeightWidth(t *testing.T) {
c, pty := givenConsole(t)

// minimal width and height
gotWidth, gotHeight := c.Size()
gotHeight, gotWidth := c.GetHeightWidth()
validateSize(t, gotWidth, gotHeight, MinWidth, MinHeight)

// zero width
_ = pty.Resize(console.WinSize{Width: 0, Height: MinHeight})
gotWidth, gotHeight = c.Size()
_ = pty.Resize(containerd.WinSize{Width: 0, Height: MinHeight})
gotHeight, gotWidth = c.GetHeightWidth()
validateSize(t, gotWidth, gotHeight, MinWidth, MinHeight)

// zero height
_ = pty.Resize(console.WinSize{Width: MinWidth, Height: 0})
gotWidth, gotHeight = c.Size()
_ = pty.Resize(containerd.WinSize{Width: MinWidth, Height: 0})
gotHeight, gotWidth = c.GetHeightWidth()
validateSize(t, gotWidth, gotHeight, MinWidth, MinHeight)

// valid zero and height
_ = pty.Resize(console.WinSize{Width: 200, Height: 100})
gotWidth, gotHeight = c.Size()
_ = pty.Resize(containerd.WinSize{Width: 200, Height: 100})
gotHeight, gotWidth = c.GetHeightWidth()
validateSize(t, gotWidth, gotHeight, 200, 100)

}

func TestConsole_NewRow(t *testing.T) {
c, pty, tty := givenTestConsole(t)

c.NewRow()

err := testutils.MatchPty(pty, tty, "^[8\r\n^[7")
if err != nil {
t.Fatalf("NewRow output error: %v", err)
}
}

func TestConsole_OutputTo(t *testing.T) {
c, pty, tty := givenTestConsole(t)

c.OutputTo(1, "test string")

err := testutils.MatchPty(pty, tty, "^[8^[[1Ftest string^[[0m\r\n^[[0K")
if err != nil {
t.Fatalf("OutputTo output error: %v", err)
}
}

func TestConsole_Restore(t *testing.T) {
c, pty, tty := givenTestConsole(t)

c.Restore()

err := testutils.MatchPty(pty, tty, "^[8^[[0G^[[2K^[[?25h")
if err != nil {
t.Fatalf("Restore output error: %v", err)
}
}

func TestConsole_Save(t *testing.T) {
c, pty, tty := givenTestConsole(t)

c.Save()

err := testutils.MatchPty(pty, tty, "^[[?25l^[7^[[0m")
if err != nil {
t.Fatalf("Save output error: %v", err)
}
}
8 changes: 4 additions & 4 deletions cmd/oras/internal/display/status/progress/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ type Manager interface {
type manager struct {
status []*status
statusLock sync.RWMutex
console *console.Console
console console.Console
updating sync.WaitGroup
renderDone chan struct{}
renderClosed chan struct{}
}

// NewManager initialized a new progress manager.
func NewManager(f *os.File) (Manager, error) {
c, err := console.New(f)
func NewManager(tty *os.File) (Manager, error) {
c, err := console.NewConsole(tty)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -88,7 +88,7 @@ func (m *manager) render() {
m.statusLock.RLock()
defer m.statusLock.RUnlock()
// todo: update size in another routine
width, height := m.console.Size()
height, width := m.console.GetHeightWidth()
lineCount := len(m.status) * 2
offset := 0
if lineCount > height {
Expand Down
9 changes: 7 additions & 2 deletions cmd/oras/internal/display/status/progress/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,15 @@ func Test_manager_render(t *testing.T) {
t.Fatal(err)
}
defer device.Close()
sole, err := console.NewConsole(device)
if err != nil {
t.Fatal(err)
}

m := &manager{
console: &console.Console{Console: pty},
console: sole,
}
_, height := m.console.Size()
height, _ := m.console.GetHeightWidth()
for i := 0; i < height; i++ {
if _, err := m.Add(); err != nil {
t.Fatal(err)
Expand Down
6 changes: 3 additions & 3 deletions cmd/oras/root/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ func runPush(cmd *cobra.Command, opts *pushOptions) error {
copyOptions.CopyGraphOptions.OnCopySkipped = displayStatus.OnCopySkipped
copyOptions.CopyGraphOptions.PreCopy = displayStatus.PreCopy
copyOptions.CopyGraphOptions.PostCopy = displayStatus.PostCopy
copy := func(root ocispec.Descriptor) error {
copyWithScopeHint := func(root ocispec.Descriptor) error {
// add both pull and push scope hints for dst repository
// to save potential push-scope token requests during copy
ctx = registryutil.WithScopeHint(ctx, dst, auth.ActionPull, auth.ActionPush)
Expand All @@ -230,7 +230,7 @@ func runPush(cmd *cobra.Command, opts *pushOptions) error {
}

// Push
root, err := doPush(dst, stopTrack, pack, copy)
root, err := doPush(dst, stopTrack, pack, copyWithScopeHint)
if err != nil {
return err
}
Expand Down Expand Up @@ -272,7 +272,7 @@ func doPush(dst oras.Target, stopTrack status.StopTrackTargetFunc, pack packFunc
type packFunc func() (ocispec.Descriptor, error)
type copyFunc func(desc ocispec.Descriptor) error

func pushArtifact(dst oras.Target, pack packFunc, copy copyFunc) (ocispec.Descriptor, error) {
func pushArtifact(_ oras.Target, pack packFunc, copy copyFunc) (ocispec.Descriptor, error) {
root, err := pack()
if err != nil {
return ocispec.Descriptor{}, err
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ require (
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
golang.org/x/sync v0.8.0
golang.org/x/term v0.24.0
golang.org/x/term v0.25.0
gopkg.in/yaml.v3 v3.0.1
oras.land/oras-go/v2 v2.5.0
)
Expand All @@ -29,5 +29,5 @@ require (
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/sys v0.26.0 // indirect
)
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
8 changes: 4 additions & 4 deletions internal/testutils/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ import (
"strings"
"sync"

"github.com/containerd/console"
containerd "github.com/containerd/console"
)

// NewPty creates a new pty pair for testing, caller is responsible for closing
// the returned device file if err is not nil.
func NewPty() (console.Console, *os.File, error) {
pty, devicePath, err := console.NewPty()
func NewPty() (containerd.Console, *os.File, error) {
pty, devicePath, err := containerd.NewPty()
if err != nil {
return nil, nil, err
}
Expand All @@ -42,7 +42,7 @@ func NewPty() (console.Console, *os.File, error) {

// MatchPty checks that the output matches the expected strings in specified
// order.
func MatchPty(pty console.Console, device *os.File, expected ...string) error {
func MatchPty(pty containerd.Console, device *os.File, expected ...string) error {
var wg sync.WaitGroup
wg.Add(1)
var buffer bytes.Buffer
Expand Down

0 comments on commit 6e3b09a

Please sign in to comment.