Skip to content

Commit

Permalink
refactor: simplify reader with manager and messenger
Browse files Browse the repository at this point in the history
Signed-off-by: Terry Howe <[email protected]>
  • Loading branch information
TerryHowe committed Sep 30, 2024
1 parent 952d867 commit f2066cc
Show file tree
Hide file tree
Showing 11 changed files with 335 additions and 69 deletions.
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

0 comments on commit f2066cc

Please sign in to comment.