pseudoterm

package module
v0.0.0-...-1e7fa84 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2016 License: MIT Imports: 11 Imported by: 0

README

pseudoterm Build Status Coverage Status codebeat badge Go Report Card GoDoc

This framework allows you to create a program using Go able to run an interactive program programmatically in an easy way.

This can be useful both for automation and running integration / functional testing of CLI tools with prompts intended for human beings.

asciicast

See example/main.go to see the code for this example.

Story interface

You can either use the accompanying QueueStory or implement the Story interface on a new type.

// Story is interface you can implement to handle commands
type Story interface {
	Setup() (ctx context.Context, err error)
	Teardown()
	TickHandler() (err error)
	HandleLine(s string) (in string, err error)
}
  1. Setup() is called before the story starts running on the terminal. Use context.Background() if you don't need a cancelable context.
  2. Teardown() is called to setup any required clean up (such as canceling context), after the story ends.
  3. TickHandler() is called every time pseudoterm checks if there is new input on the pseudo tty. This happens between LineReaderInterval. We use a sane LineReaderInterval default value of 10ms. Changing it has side-effects on performance and CPU usage.
  4. HandleLine() is called just after TickHandler() when there is a new line to be processed available. If there is not, it is not called.

Terminal

// Terminal is a pseudo terminal you can use to run commands
// on a pseudo tty programmatically
type Terminal struct {
	Command         *exec.Cmd
	EchoStream      io.Writer
	CopyStreamError error
}

If you want to print to standard output, set EchoStream to os.Stdout. If you need to copy the output both to stdout and somewhere else, you might want to use io.TeeReader.

stderr and stdout are combined before printing on a tty: this is why there is only an "EchoStream" for the Terminal type here and no Step.ReadFromStderr and Step.ReadFromStdout.

Also, CopyStreamError is useful for debugging, but quite problematic to rely on. Ignore it, unless you are debugging something complex.

Terminal methods you need to know about:

  • t.Run(story Story) (err error)
  • t.Wait() (ps *os.ProcessState)
  • t.WriteLine(s string) (n int, err error)

There are others. Read the code and tests, if you need more power. You can also execute a program without implementing a story, though generally you don't want to do that. See examples on the test files for that.

QueueStory

QueueStory is a built-in sequential story type you can use directly for most applications of pseudoterm.

// QueueStory is a command execution story with sequential steps 
// that must be fulfilled before the next is executed
type QueueStory struct {
	Sequence      []Step
	Timeout   time.Duration
}

QueueStory's Timeout is a global timeout for the story. QueueStory methods you need to know about:

  • q.Add(args ...Step) adds multiple steps at once
  • q.Success() bool returns if the story was run successfully or not
  • q.Cancel() is used to cancel a story

t.Run() doesn't return an error due to steps not executed. You might want to verify if a story has run successfully or not with q.Success() if you want to make sure all steps were executed.

QueueStory Step{}
// Step is like a route rule to handle lines
type Step struct {
	Read       string
	ReadRegex  *regexp.Regexp
	ReadFunc   func(in string) bool
	Write      string
	SkipWrite  bool
	Timeout    time.Duration
}

Each step has a string it waits to read, a string it writes when the read operation happens (unless a SkipWrite is set to true), and a timeout.

Matchers order of precedence: ReadFunc > ReadRegex > Read. Only the most important matcher on each Step is tested on QueueStory.

It is highly recommended for all stories to set a Timeout. When not defined, the story or the step never times out and the program might end up executing forever. A Step Timeout doesn't overrides a Story Timeout.

Special error values for line handling

terminal.HandleLine can return two special error values:

  1. SkipWrite is used as a return value from Story HandleLine to indicate that a line should not be written when reading a line on a given step. Useful as a checkpoint when you want to verify if a line was printed on the terminal, but you don't need to write a line in response.
  2. SkipZeroMatches is used as a return value from Story HandleLine to indicate that there are no more steps left to be dealt with.

Dependencies

This framework relies on kr/pty and should work on any operating system where it works (Windows is not on the list). Most of the hard work is done there. This provides a high-level API.

Contributing

In lieu of a formal style guide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Integration tests should be written as well.

The master branch of this repository on GitHub is protected:

  • force-push is disabled
  • tests MUST pass on Travis before merging changes to master
  • branches MUST be up to date with master before merging

Keep your commits neat. Try to always rebase your changes before publishing them.

goreportcard can be used online or locally to detect defects and static analysis results from tools such as go vet, go lint, gocyclo, and more. Run errcheck to fix ignored error returns.

SkipWrite and SkipZeroMatches are akin of filepath.SkipDir. Ignore the golint warnings about naming convention for them as their naming are like this to help understand what it stands for.

Using go test and go cover are essential to make sure your code is covered with unit tests.

Documentation

Overview

Package pseudoterm is a framework for running iterative programs programmatically with Go.

https://github.com/henvic/picel

Index

Constants

This section is empty.

Variables

View Source
var (
	// EOT is the End Of Transmission character
	EOT = []byte{4}

	// LineReaderInterval is the time to sleep between checks for output
	// Too low or too high mess things up (lower performance or high CPU usage).
	LineReaderInterval = 10 * time.Millisecond

	// SkipWrite is used as a return value from Story HandleLine to indicate
	// that a line should not be written when reading a line on a given step.
	SkipWrite = errors.New("Skip writing line input")

	// SkipZeroMatches is used as a return value from Story HandleLine to indicate
	// that there are no more steps left to be dealt with.
	SkipZeroMatches = errors.New("Skip line input due to no match available")

	// ErrUnsupported is used to indicate there is
	ErrUnsupported = pty.ErrUnsupported
)

Functions

This section is empty.

Types

type ExecutionError

type ExecutionError struct {
	RunError     error
	SigtermError error
}

ExecutionError indicates if any happened during the execution

func (ExecutionError) Error

func (e ExecutionError) Error() string

type QueueStory

type QueueStory struct {
	Sequence []Step
	Timeout  time.Duration
	// contains filtered or unexported fields
}

QueueStory is a command execution story with sequential steps that must be fulfilled before the next is executed

func (*QueueStory) Add

func (q *QueueStory) Add(args ...Step)

Add steps to a QueueStory

func (*QueueStory) Cancel

func (q *QueueStory) Cancel()

Cancel Story

func (*QueueStory) HandleLine

func (q *QueueStory) HandleLine(s string) (in string, err error)

HandleLine handles a QueueStory line the program prints

func (*QueueStory) Setup

func (q *QueueStory) Setup() (ctx context.Context, err error)

Setup executed by Terminal on Watch()

func (*QueueStory) Success

func (q *QueueStory) Success() bool

Success tells if all steps are executed and there is none left

func (*QueueStory) Teardown

func (q *QueueStory) Teardown()

Teardown executed by Terminal during Watch() teardown

func (*QueueStory) TickHandler

func (q *QueueStory) TickHandler() error

TickHandler is called on terminal Watch between LineReaderInterval regardless if there are changes or not, before HandleLine

type Step

type Step struct {
	Read      string
	ReadRegex *regexp.Regexp
	ReadFunc  func(in string) bool
	Write     string
	SkipWrite bool
	Timeout   time.Duration
	// contains filtered or unexported fields
}

Step is like a route rule to handle lines

type Story

type Story interface {
	Setup() (ctx context.Context, err error)
	Teardown()
	TickHandler() (err error)
	HandleLine(s string) (in string, err error)
}

Story is interface you can implement to handle commands

type Terminal

type Terminal struct {
	Command         *exec.Cmd
	EchoStream      io.Writer
	CopyStreamError error
	// contains filtered or unexported fields
}

Terminal is a pseudo terminal you can use to run commands on a pseudo tty programmatically

func (*Terminal) Run

func (t *Terminal) Run(story Story) (err error)

Run starts the program and handle lines printed by it

func (*Terminal) Start

func (t *Terminal) Start() (err error)

Start the program

func (*Terminal) Stop

func (t *Terminal) Stop() (err error)

Stop the program

func (*Terminal) Wait

func (t *Terminal) Wait() (ps *os.ProcessState)

Wait for process to end and return process state

func (*Terminal) Watch

func (t *Terminal) Watch(s Story) error

Watch starts handling lines printed by the program

func (*Terminal) Write

func (t *Terminal) Write(b []byte) (n int, err error)

Write bytes to the pseudo terminal

func (*Terminal) WriteLine

func (t *Terminal) WriteLine(s string) (n int, err error)

WriteLine to the pseudo terminal

func (*Terminal) WriteString

func (t *Terminal) WriteString(s string) (n int, err error)

WriteString to the pseudo terminal

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL