cli/clicore: Export FakeTTY and FakeSignalSource.

This commit is contained in:
Qi Xiao 2019-04-27 22:05:37 +01:00
parent 6c4b737e9f
commit 156a1cc504
3 changed files with 57 additions and 46 deletions

View File

@ -163,7 +163,7 @@ func TestReadCode_RendersHighlightedCode(t *testing.T) {
wantBuf := ui.NewBufferBuilder(80).
WriteString("abc", "31" /* SGR for red foreground */).
SetDotToCursor().Buffer()
if !checkBuffer(wantBuf, terminal.BufCh) {
if !terminal.VerifyBuffer(wantBuf) {
t.Errorf("Did not see buffer containing highlighted code")
}
@ -189,7 +189,7 @@ func TestReadCode_RendersPrompt(t *testing.T) {
wantBuf := ui.NewBufferBuilder(80).
WriteUnstyled("> a").
SetDotToCursor().Buffer()
if !checkBuffer(wantBuf, terminal.BufCh) {
if !terminal.VerifyBuffer(wantBuf) {
t.Errorf("Did not see buffer containing prompt")
}
@ -207,7 +207,7 @@ func TestReadCode_RendersRPrompt(t *testing.T) {
wantBuf := ui.NewBufferBuilder(4).
WriteUnstyled("a").SetDotToCursor().WriteUnstyled(" R").Buffer()
if !checkBuffer(wantBuf, terminal.BufCh) {
if !terminal.VerifyBuffer(wantBuf) {
t.Errorf("Did not see buffer containing rprompt")
}
@ -242,7 +242,7 @@ func TestReadCode_RedrawsOnPromptLateUpdate(t *testing.T) {
bufOldPrompt := ui.NewBufferBuilder(80).
WriteUnstyled("old").SetDotToCursor().Buffer()
// Wait until old prompt is rendered
if !checkBuffer(bufOldPrompt, terminal.BufCh) {
if !terminal.VerifyBuffer(bufOldPrompt) {
t.Errorf("Did not see buffer containing old prompt")
}
@ -250,7 +250,7 @@ func TestReadCode_RedrawsOnPromptLateUpdate(t *testing.T) {
prompt.lateUpdates <- nil
bufNewPrompt := ui.NewBufferBuilder(80).
WriteUnstyled("new").SetDotToCursor().Buffer()
if !checkBuffer(bufNewPrompt, terminal.BufCh) {
if !terminal.VerifyBuffer(bufNewPrompt) {
t.Errorf("Did not see buffer containing new prompt")
}
@ -268,14 +268,14 @@ func TestReadCode_DrawsAndFlushesNotes(t *testing.T) {
// Sanity-check initial state.
initBuf := ui.NewBufferBuilder(80).Buffer()
if !checkBuffer(initBuf, terminal.BufCh) {
if !terminal.VerifyBuffer(initBuf) {
t.Errorf("did not get initial state")
}
ed.Notify("note")
wantNotesBuf := ui.NewBufferBuilder(80).WriteUnstyled("note").Buffer()
if !checkBuffer(wantNotesBuf, terminal.NotesBufCh) {
if !terminal.VerifyNotesBuffer(wantNotesBuf) {
t.Errorf("did not render notes")
}
@ -299,7 +299,7 @@ func TestReadCode_UsesFinalStateInFinalRedraw(t *testing.T) {
// Wait until a non-final state is drawn.
wantBuf := ui.NewBufferBuilder(80).WriteUnstyled("s").SetDotToCursor().
WriteUnstyled("ome code").Buffer()
if !checkBuffer(wantBuf, terminal.BufCh) {
if !terminal.VerifyBuffer(wantBuf) {
t.Errorf("did not get expected buffer before sending Enter")
}
@ -323,7 +323,7 @@ func TestReadCode_QuitsOnSIGHUP(t *testing.T) {
wantBuf := ui.NewBufferBuilder(80).WriteUnstyled("a").
SetDotToCursor().Buffer()
if !checkBuffer(wantBuf, terminal.BufCh) {
if !terminal.VerifyBuffer(wantBuf) {
t.Errorf("did not get expected buffer before sending SIGHUP")
}
@ -348,14 +348,14 @@ func TestReadCode_ResetsOnSIGINT(t *testing.T) {
codeCh, _ := ed.readCodeAsync()
wantBuf := ui.NewBufferBuilder(80).WriteUnstyled("a").
SetDotToCursor().Buffer()
if !checkBuffer(wantBuf, terminal.BufCh) {
if !terminal.VerifyBuffer(wantBuf) {
t.Errorf("did not get expected buffer before sending SIGINT")
}
sigs.Ch <- syscall.SIGINT
wantBuf = ui.NewBufferBuilder(80).Buffer()
if !checkBuffer(wantBuf, terminal.BufCh) {
if !terminal.VerifyBuffer(wantBuf) {
t.Errorf("Terminal state is not reset after SIGINT")
}
@ -372,7 +372,7 @@ func TestReadCode_RedrawsOnSIGWINCH(t *testing.T) {
wantBuf := ui.NewBufferBuilder(80).WriteUnstyled("1234567890").
SetDotToCursor().Buffer()
if !checkBuffer(wantBuf, terminal.BufCh) {
if !terminal.VerifyBuffer(wantBuf) {
t.Errorf("did not get expected buffer before sending SIGWINCH")
}
@ -381,21 +381,21 @@ func TestReadCode_RedrawsOnSIGWINCH(t *testing.T) {
wantBuf = ui.NewBufferBuilder(4).WriteUnstyled("1234567890").
SetDotToCursor().Buffer()
if !checkBuffer(wantBuf, terminal.BufCh) {
if !terminal.VerifyBuffer(wantBuf) {
t.Errorf("Terminal is not redrawn after SIGWINCH")
}
cleanup(terminal, codeCh)
}
func setup() (*App, *fakeTTY, *fakeSignalSource) {
terminal := newFakeTTY()
sigsrc := newFakeSignalSource()
func setup() (*App, *FakeTTY, *FakeSignalSource) {
terminal := NewFakeTTY()
sigsrc := NewFakeSignalSource()
ed := NewApp(terminal, sigsrc)
return ed, terminal, sigsrc
}
func cleanup(t *fakeTTY, codeCh <-chan string) {
func cleanup(t *FakeTTY, codeCh <-chan string) {
// Causes BasicMode to quit
t.EventCh <- tty.KeyEvent{Rune: '\n'}
// Wait until ReadCode has finished execution

View File

@ -4,24 +4,25 @@ import "os"
const maxFakeSignals = 1024
// An implementation of SignalSource that is useful in tests.
type fakeSignalSource struct {
// FakeSignalSource is an implementation of SignalSource that is useful in
// tests.
type FakeSignalSource struct {
// A channel on which fake signals can be injected.
Ch chan os.Signal
}
// Creates a new FakeSignalSource.
func newFakeSignalSource() *fakeSignalSource {
return &fakeSignalSource{make(chan os.Signal, maxFakeSignals)}
// NewFakeSignalSource creates a new FakeSignalSource.
func NewFakeSignalSource() *FakeSignalSource {
return &FakeSignalSource{make(chan os.Signal, maxFakeSignals)}
}
// NotifySignals returns sigs.Ch.
func (sigs *fakeSignalSource) NotifySignals() <-chan os.Signal {
func (sigs *FakeSignalSource) NotifySignals() <-chan os.Signal {
return sigs.Ch
}
// StopSignals closes sig.Ch and set it to nil.
func (sigs *fakeSignalSource) StopSignals() {
func (sigs *FakeSignalSource) StopSignals() {
close(sigs.Ch)
sigs.Ch = nil
}

View File

@ -10,14 +10,14 @@ import (
)
const (
// Maximum number of buffer updates fakeTTY expect to see.
// Maximum number of buffer updates FakeTTY expect to see.
maxBufferUpdates = 1024
// Maximum number of events fakeTTY produces.
// Maximum number of events FakeTTY produces.
maxEvents = 1024
)
// An implementation of the TTY interface that is useful in tests.
type fakeTTY struct {
// FakeTTY is an implementation of the TTY interface that is useful in tests.
type FakeTTY struct {
// Callback to be returned from Setup.
RestoreFunc func()
// Error to be returned from Setup.
@ -36,9 +36,9 @@ type fakeTTY struct {
height, width int
}
// Creates a new FakeTTY.
func newFakeTTY() *fakeTTY {
return &fakeTTY{
// NewFakeTTY creates a new FakeTTY.
func NewFakeTTY() *FakeTTY {
return &FakeTTY{
RestoreFunc: func() {},
EventCh: make(chan tty.Event, maxEvents),
BufCh: make(chan *ui.Buffer, maxBufferUpdates),
@ -48,74 +48,84 @@ func newFakeTTY() *fakeTTY {
}
// Setup returns t.RestoreFunc and t.SetupErr.
func (t *fakeTTY) Setup() (func(), error) {
func (t *FakeTTY) Setup() (func(), error) {
return t.RestoreFunc, t.SetupErr
}
// Size returns the size previously set by SetSize.
func (t *fakeTTY) Size() (h, w int) {
func (t *FakeTTY) Size() (h, w int) {
t.sizeMutex.RLock()
defer t.sizeMutex.RUnlock()
return t.height, t.width
}
// SetSize sets the size that will be returned by Size.
func (t *fakeTTY) SetSize(h, w int) {
func (t *FakeTTY) SetSize(h, w int) {
t.sizeMutex.Lock()
defer t.sizeMutex.Unlock()
t.height, t.width = h, w
}
// StartInput returns t.EventCh.
func (t *fakeTTY) StartInput() <-chan tty.Event {
func (t *FakeTTY) StartInput() <-chan tty.Event {
return t.EventCh
}
// SetRawInput does nothing.
func (t *fakeTTY) SetRawInput(b bool) {}
func (t *FakeTTY) SetRawInput(b bool) {}
// StopInput closes t.EventCh
func (t *fakeTTY) StopInput() { close(t.EventCh) }
func (t *FakeTTY) StopInput() { close(t.EventCh) }
// Newline does nothing.
func (t *fakeTTY) Newline() {}
func (t *FakeTTY) Newline() {}
// Buffer returns the last recorded buffer.
func (t *fakeTTY) Buffer() *ui.Buffer { return t.Bufs[len(t.Bufs)-1] }
func (t *FakeTTY) Buffer() *ui.Buffer { return t.Bufs[len(t.Bufs)-1] }
// ResetBuffer records a nil buffer.
func (t *fakeTTY) ResetBuffer() { t.recordBuf(nil) }
func (t *FakeTTY) ResetBuffer() { t.recordBuf(nil) }
// UpdateBuffer records a new pair of buffers, i.e. sending them to their
// respective channels and appending them to their respective slices.
func (t *fakeTTY) UpdateBuffer(bufNotes, buf *ui.Buffer, _ bool) error {
func (t *FakeTTY) UpdateBuffer(bufNotes, buf *ui.Buffer, _ bool) error {
t.recordNotesBuf(bufNotes)
t.recordBuf(buf)
return nil
}
func (t *fakeTTY) recordBuf(buf *ui.Buffer) {
func (t *FakeTTY) recordBuf(buf *ui.Buffer) {
t.Bufs = append(t.Bufs, buf)
t.BufCh <- buf
}
func (t *fakeTTY) recordNotesBuf(buf *ui.Buffer) {
func (t *FakeTTY) recordNotesBuf(buf *ui.Buffer) {
t.NotesBufs = append(t.NotesBufs, buf)
t.NotesBufCh <- buf
}
var checkBufferTimeout = time.Second
// VerifyBuffer verifies that a buffer will appear within one second.
func (t *FakeTTY) VerifyBuffer(b *ui.Buffer) bool {
return verifyBuffer(b, t.BufCh)
}
// VerifyNotesBuffer verifies that a notes buffer will appear within one second.
func (t *FakeTTY) VerifyNotesBuffer(b *ui.Buffer) bool {
return verifyBuffer(b, t.NotesBufCh)
}
var verifyBufferTimeout = time.Second
// Check that an expected buffer will eventually appear. Also useful for waiting
// until the editor reaches a certain state.
func checkBuffer(want *ui.Buffer, ch <-chan *ui.Buffer) bool {
func verifyBuffer(want *ui.Buffer, ch <-chan *ui.Buffer) bool {
for {
select {
case buf := <-ch:
if reflect.DeepEqual(buf, want) {
return true
}
case <-time.After(checkBufferTimeout):
case <-time.After(verifyBufferTimeout):
return false
}
}