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 <terrylhowe@gmail.com>
  • Loading branch information
TerryHowe committed Sep 30, 2024
1 parent 952d867 commit c1f4e72
Show file tree
Hide file tree
Showing 11 changed files with 316 additions and 68 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
85 changes: 85 additions & 0 deletions cmd/oras/internal/display/status/console/discard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
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
}

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()
}

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

func (mc *discardConsole) ResizeFrom(containerd.Console) error {
return nil
}
func (mc *discardConsole) SetRaw() error {
return nil
}
func (mc *discardConsole) DisableEcho() error {
return nil
}
func (mc *discardConsole) Reset() error {
return nil
}
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)
}

func (mc *discardConsole) Save() {
}

func (mc *discardConsole) NewRow() {
}

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

func (mc *discardConsole) Restore() {

Check warning on line 84 in cmd/oras/internal/display/status/console/discard.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/internal/display/status/console/discard.go#L84

Added line #L84 was not covered by tests
}
72 changes: 72 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,72 @@
/*
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")
}
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")
}
}
25 changes: 23 additions & 2 deletions cmd/oras/internal/display/status/progress/messenger.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,19 @@ import (

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

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,6 +61,16 @@ func (sm *Messenger) Send(prompt string, descriptor ocispec.Descriptor, offset i
}
}

// 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 a end message.
func (sm *Messenger) Stop() {
if sm.closed {
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 c1f4e72

Please sign in to comment.