Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: simplify track reader manager and messenger use #1474

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmd/oras/internal/display/status/console/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ type console struct {

// NewConsole generates a console from a file.
func NewConsole(f *os.File) (Console, error) {
if f != nil && f.Name() == os.DevNull {
return NewDiscardConsole(f), nil
}
c, err := containerd.ConsoleFromFile(f)
if err != nil {
return nil, err
Expand Down
100 changes: 100 additions & 0 deletions cmd/oras/internal/display/status/console/discard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
Copyright The ORAS Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package console

import (
"os"

containerd "github.com/containerd/console"
)

type discardConsole struct {
*os.File
}

// NewDiscardConsole create a console that does not output.
func NewDiscardConsole(f *os.File) Console {
dc := discardConsole{
File: f,
}
return &dc
}

// Fd returns its file descriptor
func (mc *discardConsole) Fd() uintptr {
return os.Stderr.Fd()
}

// Name returns its file name
func (mc *discardConsole) Name() string {
return mc.File.Name()
}

// Resize ignored
func (mc *discardConsole) Resize(_ containerd.WinSize) error {
return nil
}

// ResizeFrom ignored
func (mc *discardConsole) ResizeFrom(containerd.Console) error {
return nil
}

// SetRaw ignored
func (mc *discardConsole) SetRaw() error {
return nil
}

// DisableEcho ignored
func (mc *discardConsole) DisableEcho() error {
return nil
}

// Reset ignored
func (mc *discardConsole) Reset() error {
return nil
}

// Size return default size
func (mc *discardConsole) Size() (containerd.WinSize, error) {
ws := containerd.WinSize{
Width: 80,
Height: 24,
}
return ws, nil
}

// GetHeightWidth returns the width and height of the console.
func (mc *discardConsole) GetHeightWidth() (height, width int) {
windowSize, _ := mc.Size()
return int(windowSize.Height), int(windowSize.Width)
}

// Save ignored
func (mc *discardConsole) Save() {
}

// NewRow ignored
func (mc *discardConsole) NewRow() {
}

// OutputTo ignored
func (mc *discardConsole) OutputTo(_ uint, _ string) {
}

// Restore ignored
func (mc *discardConsole) Restore() {
}
73 changes: 73 additions & 0 deletions cmd/oras/internal/display/status/console/discard_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
Copyright The ORAS Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package console

import (
"os"
"testing"

containerd "github.com/containerd/console"
)

func TestConsole_New(t *testing.T) {
mockFile, err := os.OpenFile(os.DevNull, os.O_RDWR, 0666)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}

sut, err := NewConsole(mockFile)
if err != nil {
t.Errorf("Unexpected error %v", err)
}

if err = sut.Resize(containerd.WinSize{}); err != nil {
t.Errorf("Unexpected erro for Resize: %v", err)
}
if err = sut.ResizeFrom(nil); err != nil {
t.Errorf("Unexpected erro for Resize: %v", err)
}
if err = sut.SetRaw(); err != nil {
t.Errorf("Unexpected erro for Resize: %v", err)
}
if err = sut.DisableEcho(); err != nil {
t.Errorf("Unexpected erro for Resize: %v", err)
}
if err = sut.Reset(); err != nil {
t.Errorf("Unexpected erro for Resize: %v", err)
}
windowSize, _ := sut.Size()
if windowSize.Height != 24 {
t.Errorf("Expected size 24 actual %d", windowSize.Height)
}
if windowSize.Width != 80 {
t.Errorf("Expected size 80 actual %d", windowSize.Width)
}
h, w := sut.GetHeightWidth()
if h != 24 {
t.Errorf("Expected size 24 actual %d", h)
}
if w != 80 {
t.Errorf("Expected size 80 actual %d", w)
}
if sut.Fd() != os.Stderr.Fd() {
t.Errorf("Expected size %d actual %d", sut.Fd(), os.Stderr.Fd())
}
if sut.Name() != os.DevNull {
t.Errorf("Expected size %s actual %s", sut.Name(), os.DevNull)
}
sut.OutputTo(0, "ignored")
sut.Restore()
}
12 changes: 8 additions & 4 deletions cmd/oras/internal/display/status/progress/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,23 @@ type manager struct {
status []*status
statusLock sync.RWMutex
console console.Console
actionPrompt string
donePrompt string
updating sync.WaitGroup
renderDone chan struct{}
renderClosed chan struct{}
}

// NewManager initialized a new progress manager.
func NewManager(tty *os.File) (Manager, error) {
func NewManager(actionPrompt string, donePrompt string, tty *os.File) (Manager, error) {
c, err := console.NewConsole(tty)
if err != nil {
return nil, err
}
m := &manager{
console: c,
actionPrompt: actionPrompt,
donePrompt: donePrompt,
renderDone: make(chan struct{}),
renderClosed: make(chan struct{}),
}
Expand Down Expand Up @@ -131,15 +135,15 @@ func (m *manager) SendAndStop(desc ocispec.Descriptor, prompt string) error {
}

func (m *manager) statusChan(s *status) *Messenger {
ch := make(chan *status, BufferSize)
messenger := NewMessenger(m.actionPrompt, m.donePrompt)
m.updating.Add(1)
go func() {
defer m.updating.Done()
for newStatus := range ch {
for newStatus := range messenger.ch {
s.update(newStatus)
}
}()
return &Messenger{ch: ch}
return messenger
}

// Close stops all status and waits for updating and rendering.
Expand Down
29 changes: 29 additions & 0 deletions cmd/oras/internal/display/status/progress/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package progress

import (
"fmt"
"os"
"testing"

"oras.land/oras/cmd/oras/internal/display/status/console"
Expand Down Expand Up @@ -55,3 +56,31 @@ func Test_manager_render(t *testing.T) {
t.Fatal(err)
}
}

func TestNewManager(t *testing.T) {
mockFile, err := os.OpenFile(os.DevNull, os.O_RDWR, 0666)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}

sut, err := NewManager("Action", "Done", mockFile)
if err != nil {
t.Errorf("Unexpected error %v", err)
}

messenger, err := sut.Add()
if err != nil {
t.Errorf("Unexpected error %v", err)
}
if messenger.actionPrompt != "Action" {
t.Errorf("Expected prompt Action actual %v", messenger.actionPrompt)
}
if messenger.donePrompt != "Done" {
t.Errorf("Expected prompt Done actual %v", messenger.donePrompt)
}

_, err = NewManager("Action", "Done", os.Stderr)
if err == nil {
t.Errorf("Expected error when using Stderr as console")
}
}
28 changes: 25 additions & 3 deletions cmd/oras/internal/display/status/progress/messenger.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,20 @@ import (

// Messenger is progress message channel.
type Messenger struct {
ch chan *status
closed bool
ch chan *status
actionPrompt string
donePrompt string
closed bool
}

// NewMessenger create a new messenger object
func NewMessenger(actionPrompt, donePrompt string) *Messenger {
ch := make(chan *status, BufferSize)
return &Messenger{
ch: ch,
actionPrompt: actionPrompt,
donePrompt: donePrompt,
}
}

// Start initializes the messenger.
Expand All @@ -50,7 +62,17 @@ func (sm *Messenger) Send(prompt string, descriptor ocispec.Descriptor, offset i
}
}

// Stop the messenger after sending a end message.
// SendAction send the action status message.
func (sm *Messenger) SendAction(descriptor ocispec.Descriptor, offset int64) {
sm.Send(sm.actionPrompt, descriptor, offset)
}

// SendDone send the done status message.
func (sm *Messenger) SendDone(descriptor ocispec.Descriptor, offset int64) {
sm.Send(sm.donePrompt, descriptor, offset)
}

// Stop the messenger after sending end message.
func (sm *Messenger) Stop() {
if sm.closed {
return
Expand Down
42 changes: 34 additions & 8 deletions cmd/oras/internal/display/status/progress/messenger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@ import (

func Test_Messenger(t *testing.T) {
var msg *status
ch := make(chan *status, BufferSize)
messenger := &Messenger{ch: ch}
messenger := NewMessenger("Action", "Done")

messenger.Start()
select {
case msg = <-ch:
case msg = <-messenger.ch:
if msg.offset != -1 {
t.Errorf("Expected start message with offset -1, got %d", msg.offset)
}
Expand All @@ -42,7 +41,7 @@ func Test_Messenger(t *testing.T) {
expected := int64(50)
messenger.Send("Reading", desc, expected)
select {
case msg = <-ch:
case msg = <-messenger.ch:
if msg.offset != expected {
t.Errorf("Expected status message with offset %d, got %d", expected, msg.offset)
}
Expand All @@ -56,7 +55,7 @@ func Test_Messenger(t *testing.T) {
messenger.Send("Reading", desc, expected)
messenger.Send("Read", desc, desc.Size)
select {
case msg = <-ch:
case msg = <-messenger.ch:
if msg.offset != desc.Size {
t.Errorf("Expected status message with offset %d, got %d", expected, msg.offset)
}
Expand All @@ -67,15 +66,42 @@ func Test_Messenger(t *testing.T) {
t.Error("Expected status message")
}
select {
case msg = <-ch:
case msg = <-messenger.ch:
t.Errorf("Unexpected status message %v", msg)
default:
}

messenger.SendAction(desc, expected)
select {
case msg = <-messenger.ch:
if msg.offset != expected {
t.Errorf("Expected status message with offset %d, got %d", expected, msg.offset)
}
if msg.prompt != "Action" {
t.Errorf("Expected status message prompt Action, got %s", msg.prompt)
}
default:
t.Error("Expected status message")
}

expected += 1
messenger.SendDone(desc, expected)
select {
case msg = <-messenger.ch:
if msg.offset != expected {
t.Errorf("Expected status message with offset %d, got %d", expected, msg.offset)
}
if msg.prompt != "Done" {
t.Errorf("Expected status message prompt Done, got %s", msg.prompt)
}
default:
t.Error("Expected status message")
}

expected = int64(-1)
messenger.Stop()
select {
case msg = <-ch:
case msg = <-messenger.ch:
if msg.offset != expected {
t.Errorf("Expected END status message with offset %d, got %d", expected, msg.offset)
}
Expand All @@ -85,7 +111,7 @@ func Test_Messenger(t *testing.T) {

messenger.Stop()
select {
case msg = <-ch:
case msg = <-messenger.ch:
if msg != nil {
t.Errorf("Unexpected status message %v", msg)
}
Expand Down
Loading
Loading