fix: background reader apart from global handler for testing (#1929)

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
This commit is contained in:
Christopher Angelo Phillips 2023-07-12 12:37:19 -04:00 committed by GitHub
parent 05a61897f2
commit 38efe4ec5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 32 additions and 17 deletions

View File

@ -22,11 +22,10 @@ import (
var ( var (
_ tea.Model = (*attestLogFrame)(nil) _ tea.Model = (*attestLogFrame)(nil)
_ cosignOutputReader = (*backgroundLineReader)(nil)
) )
type attestLogFrame struct { type attestLogFrame struct {
reader cosignOutputReader reader *backgroundLineReader
prog progress.Progressable prog progress.Progressable
lines []string lines []string
completed bool completed bool
@ -47,14 +46,16 @@ type attestLogFrameTickMsg struct {
ID uint32 ID uint32
} }
type cosignOutputReader interface {
Lines() []string
}
type backgroundLineReader struct { type backgroundLineReader struct {
limit int limit int
lines *queue.Queue[string] lines *queue.Queue[string]
lock *sync.RWMutex lock *sync.RWMutex
// This is added specifically for tests to assert when the background reader is done.
// The main UI uses the global ui wait group from the handler to otherwise block
// Shared concerns among multiple model made it difficult to test using the global wait group
// so this is added to allow tests to assert when the background reader is done.
running *sync.WaitGroup
} }
func (m *Handler) handleAttestationStarted(e partybus.Event) []tea.Model { func (m *Handler) handleAttestationStarted(e partybus.Event) []tea.Model {
@ -97,7 +98,7 @@ func (m *Handler) handleAttestationStarted(e partybus.Event) []tea.Model {
} }
} }
func newLogFrame(reader cosignOutputReader, prog progress.Progressable, borderStyle lipgloss.Style) attestLogFrame { func newLogFrame(reader *backgroundLineReader, prog progress.Progressable, borderStyle lipgloss.Style) attestLogFrame {
return attestLogFrame{ return attestLogFrame{
reader: reader, reader: reader,
prog: prog, prog: prog,
@ -108,16 +109,23 @@ func newLogFrame(reader cosignOutputReader, prog progress.Progressable, borderSt
} }
func newBackgroundLineReader(wg *sync.WaitGroup, reader io.Reader, stage *progress.Stage) *backgroundLineReader { func newBackgroundLineReader(wg *sync.WaitGroup, reader io.Reader, stage *progress.Stage) *backgroundLineReader {
wg.Add(1)
r := &backgroundLineReader{ r := &backgroundLineReader{
limit: 7, limit: 7,
lock: &sync.RWMutex{}, lock: &sync.RWMutex{},
lines: queue.New[string](), lines: queue.New[string](),
running: &sync.WaitGroup{},
} }
// tracks the background reader for the global handler wait group
wg.Add(1)
// tracks the background reader for the local wait group (used in tests to decouple from the global handler wait group)
r.running.Add(1)
go func() { go func() {
defer wg.Done()
r.read(reader, stage) r.read(reader, stage)
wg.Done()
r.running.Done()
}() }()
return r return r

View File

@ -28,7 +28,7 @@ func TestHandler_handleAttestationStarted(t *testing.T) {
// note: this model depends on a background reader. Multiple iterations ensures that the // note: this model depends on a background reader. Multiple iterations ensures that the
// reader has time to at least start and process the test fixture before the runModel // reader has time to at least start and process the test fixture before the runModel
// test harness completes (which is a fake event loop anyway). // test harness completes (which is a fake event loop anyway).
iterations: 100, iterations: 1,
eventFn: func(t *testing.T) partybus.Event { eventFn: func(t *testing.T) partybus.Event {
reader := strings.NewReader("contents\nof\nstuff!") reader := strings.NewReader("contents\nof\nstuff!")
@ -61,7 +61,7 @@ func TestHandler_handleAttestationStarted(t *testing.T) {
// note: this model depends on a background reader. Multiple iterations ensures that the // note: this model depends on a background reader. Multiple iterations ensures that the
// reader has time to at least start and process the test fixture before the runModel // reader has time to at least start and process the test fixture before the runModel
// test harness completes (which is a fake event loop anyway). // test harness completes (which is a fake event loop anyway).
iterations: 100, iterations: 1,
eventFn: func(t *testing.T) partybus.Event { eventFn: func(t *testing.T) partybus.Event {
reader := strings.NewReader("contents\nof\nstuff!") reader := strings.NewReader("contents\nof\nstuff!")
@ -123,7 +123,7 @@ func TestHandler_handleAttestationStarted(t *testing.T) {
Time: time.Now(), Time: time.Now(),
Sequence: log.sequence, Sequence: log.sequence,
ID: log.id, ID: log.id,
}) }, log.reader.running)
t.Log(got) t.Log(got)
snaps.MatchSnapshot(t, got) snaps.MatchSnapshot(t, got)
}) })

View File

@ -2,13 +2,14 @@ package ui
import ( import (
"reflect" "reflect"
"sync"
"testing" "testing"
"unsafe" "unsafe"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
) )
func runModel(t testing.TB, m tea.Model, iterations int, message tea.Msg) string { func runModel(t testing.TB, m tea.Model, iterations int, message tea.Msg, h ...*sync.WaitGroup) string {
t.Helper() t.Helper()
if iterations == 0 { if iterations == 0 {
iterations = 1 iterations = 1
@ -29,6 +30,12 @@ func runModel(t testing.TB, m tea.Model, iterations int, message tea.Msg) string
} }
cmd = tea.Batch(nextCmds...) cmd = tea.Batch(nextCmds...)
} }
for _, each := range h {
if each != nil {
each.Wait()
}
}
return m.View() return m.View()
} }