feat(engine): add Reporter with buffered and plain implementations
This commit is contained in:
80
internal/engine/reporter.go
Normal file
80
internal/engine/reporter.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// Package engine runs modules through the Detect/Plan/Apply/Verify phases.
|
||||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/MDietermann/ArduinoArchSetup/internal/module"
|
||||
)
|
||||
|
||||
// Event is a single item streamed from a module during Apply.
|
||||
type Event struct {
|
||||
ModuleID string
|
||||
Kind EventKind
|
||||
Current int
|
||||
Total int
|
||||
Message string
|
||||
}
|
||||
|
||||
type EventKind int
|
||||
|
||||
const (
|
||||
EventProgress EventKind = iota
|
||||
EventLog
|
||||
)
|
||||
|
||||
// BufferedReporter stores every event in memory. Used in tests.
|
||||
type BufferedReporter struct {
|
||||
mu sync.Mutex
|
||||
events []Event
|
||||
}
|
||||
|
||||
func NewBufferedReporter() *BufferedReporter { return &BufferedReporter{} }
|
||||
|
||||
func (b *BufferedReporter) Progress(id string, current, total int, msg string) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.events = append(b.events, Event{ModuleID: id, Kind: EventProgress, Current: current, Total: total, Message: msg})
|
||||
}
|
||||
|
||||
func (b *BufferedReporter) Log(id, line string) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.events = append(b.events, Event{ModuleID: id, Kind: EventLog, Message: line})
|
||||
}
|
||||
|
||||
func (b *BufferedReporter) Events() []Event {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
out := make([]Event, len(b.events))
|
||||
copy(out, b.events)
|
||||
return out
|
||||
}
|
||||
|
||||
// PlainReporter writes human-readable lines to w. Concurrency-safe.
|
||||
type PlainReporter struct {
|
||||
mu sync.Mutex
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func NewPlainReporter(w io.Writer) *PlainReporter { return &PlainReporter{w: w} }
|
||||
|
||||
func (p *PlainReporter) Progress(id string, current, total int, msg string) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
fmt.Fprintf(p.w, "[%s] (%d/%d) %s\n", id, current, total, msg)
|
||||
}
|
||||
|
||||
func (p *PlainReporter) Log(id, line string) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
fmt.Fprintf(p.w, "[%s] %s\n", id, line)
|
||||
}
|
||||
|
||||
// Compile-time guarantee both types satisfy module.Reporter.
|
||||
var (
|
||||
_ module.Reporter = (*BufferedReporter)(nil)
|
||||
_ module.Reporter = (*PlainReporter)(nil)
|
||||
)
|
||||
56
internal/engine/reporter_test.go
Normal file
56
internal/engine/reporter_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBufferedReporterRecordsEventsInOrder(t *testing.T) {
|
||||
r := NewBufferedReporter()
|
||||
r.Progress("m", 1, 3, "step 1")
|
||||
r.Log("m", "hello")
|
||||
r.Progress("m", 2, 3, "step 2")
|
||||
|
||||
ev := r.Events()
|
||||
if len(ev) != 3 {
|
||||
t.Fatalf("want 3 events, got %d", len(ev))
|
||||
}
|
||||
if ev[0].Kind != EventProgress || ev[0].Current != 1 {
|
||||
t.Errorf("event[0] = %+v", ev[0])
|
||||
}
|
||||
if ev[1].Kind != EventLog || ev[1].Message != "hello" {
|
||||
t.Errorf("event[1] = %+v", ev[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferedReporterIsConcurrencySafe(t *testing.T) {
|
||||
r := NewBufferedReporter()
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 50; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
r.Progress("m", i, 50, "x")
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
if got := len(r.Events()); got != 50 {
|
||||
t.Fatalf("want 50 events, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlainReporterFormat(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
r := NewPlainReporter(&buf)
|
||||
r.Progress("packages", 2, 10, "installing tio")
|
||||
r.Log("packages", "pacman: done")
|
||||
|
||||
out := buf.String()
|
||||
for _, want := range []string{"[packages] (2/10) installing tio", "[packages] pacman: done"} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Errorf("output missing %q; got:\n%s", want, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user