magnacarto/vendor/github.com/natefinch/pie/pie_test.go
2017-07-24 11:42:54 +02:00

594 lines
14 KiB
Go

package pie
import (
"bytes"
"errors"
"io"
"io/ioutil"
"net/rpc"
"net/rpc/jsonrpc"
"os"
"reflect"
"strings"
"sync"
"testing"
"time"
)
var _ io.ReadWriteCloser = rwCloser{}
var _ io.ReadWriteCloser = ioPipe{}
func TestRWCloser(t *testing.T) {
rc := &closeRW{}
wc := &closeRW{}
rwc := rwCloser{rc, wc}
if err := rwc.Close(); err != nil {
t.Errorf("unexpected error from rwCloser.Close: %#v", err)
}
if !rc.closed {
t.Error("Close not called on ReadCloser.")
}
if !wc.closed {
t.Error("Close not called on WriteCloser.")
}
}
func TestRWCloserReadCloserError(t *testing.T) {
readCloserErr := errors.New("read")
rc := &closeRW{err: readCloserErr}
wc := &closeRW{}
rwc := rwCloser{rc, wc}
err := rwc.Close()
if !rc.closed {
t.Error("Close not called on ReadCloser.")
}
if !wc.closed {
t.Error("Close not called on WriteCloser.")
}
if err == nil {
t.Error("ReadCloser error not passed through from rwCloser.Close")
}
if err != readCloserErr {
t.Errorf("Different error returned from rwCloser than expected: %#v", err)
}
}
func TestRWCloserWriteCloserError(t *testing.T) {
writeCloserErr := errors.New("write")
rc := &closeRW{}
wc := &closeRW{err: writeCloserErr}
rwc := rwCloser{rc, wc}
err := rwc.Close()
if !rc.closed {
t.Error("Close not called on ReadCloser.")
}
if !wc.closed {
t.Error("Close not called on WriteCloser.")
}
if err == nil {
t.Error("ReadCloser error not passed through from rwCloser.Close")
}
if err != writeCloserErr {
t.Errorf("Different error returned from rwCloser than expected: %#v", err)
}
}
func TestRWCloserBothCloserError(t *testing.T) {
writeCloserErr := errors.New("write")
readCloserErr := errors.New("read")
rc := &closeRW{err: readCloserErr}
wc := &closeRW{err: writeCloserErr}
rwc := rwCloser{rc, wc}
err := rwc.Close()
if !rc.closed {
t.Error("Close not called on ReadCloser.")
}
if !wc.closed {
t.Error("Close not called on WriteCloser.")
}
if err == nil {
t.Error("Error not passed through from rwCloser.Close")
}
// I don't think we actually care which of these errors gets returned, as
// long as one of them does.
if err != writeCloserErr && err != readCloserErr {
t.Errorf("Different error returned from rwCloser than expected: %#v", err)
}
}
func TestIOPipeClose(t *testing.T) {
rc := &closeRW{}
wc := &closeRW{}
p := &proc{}
iop := ioPipe{rc, wc, p}
if err := iop.Close(); err != nil {
t.Errorf("Unexpected error from ioPipe.Close: %#v", err)
}
if !rc.closed {
t.Error("Close not called on ReadCloser.")
}
if !wc.closed {
t.Error("Close not called on WriteCloser.")
}
if p.sig == nil {
t.Errorf("No signal sent to process")
}
if p.sig != os.Interrupt {
t.Errorf("Unexpected signal sent to process, expected os.Interrupt, got %#v", p.sig)
}
if p.killed {
t.Errorf("Kill() called unexpectedly on process.")
}
}
func TestIOPipeSlowProc(t *testing.T) {
defer func(d time.Duration) {
procTimeout = d
}(procTimeout)
procTimeout = 5 * time.Millisecond
rc := &closeRW{}
wc := &closeRW{}
p := &proc{delay: procTimeout * 2}
iop := ioPipe{rc, wc, p}
if err := iop.Close(); err != errProcStopTimeout {
t.Errorf("Unexpected error from ioPipe.Close, expected %#v, got: %#v", errProcStopTimeout, err)
}
if !rc.closed {
t.Error("Close not called on ReadCloser.")
}
if !wc.closed {
t.Error("Close not called on WriteCloser.")
}
if p.sig == nil {
t.Errorf("no signal sent to process")
}
if p.sig != os.Interrupt {
t.Errorf("Unexpected signal sent to process, expected os.Interrupt, got %#v", p.sig)
}
if !p.killed {
t.Errorf("Kill() unexpectedly not called on process.")
}
}
func TestNewProvider(t *testing.T) {
p := NewProvider()
if p.server == nil {
t.Error("Unexpected nil rpc Server")
}
if p.rwc == nil {
t.Error("Unexpected nil ReadWriteCloser")
}
rwc, ok := p.rwc.(rwCloser)
if !ok {
t.Errorf("Expected ReadWriteCloser to be rwCloser, but is %#v", p.rwc)
}
if rwc.ReadCloser != os.Stdin {
t.Errorf("Expected rwc.ReadCloser to be os.Stdin but is %#v", rwc.ReadCloser)
}
if rwc.WriteCloser != os.Stdout {
t.Errorf("Expected rwc.WriteCloser to be os.Stdout but is %#v", rwc.ReadCloser)
}
}
func TestNewConsumer(t *testing.T) {
c := NewConsumer()
if c == nil {
t.Fatal("Unexpected nil pointer from NewConsumer")
}
}
func TestNewConsumerCodec(t *testing.T) {
tcc := &testClientCodec{}
c := NewConsumerCodec(tcc.NewClientCodec)
if c == nil {
t.Fatal("Unexpected nil pointer from NewConsumerCodec")
}
if !tcc.called {
t.Fatal("NewClientCodec function never called.")
}
}
func TestServeAndStart(t *testing.T) {
testServeAndStart(nil, nil, t)
}
func TestServeAndStartCodec(t *testing.T) {
testServeAndStart(jsonrpc.NewServerCodec, jsonrpc.NewClientCodec, t)
}
func testServeAndStart(
servercodec func(io.ReadWriteCloser) rpc.ServerCodec,
clientcodec func(io.ReadWriteCloser) rpc.ClientCodec,
t *testing.T,
) {
// set up some pipes for reading/writing that we can pretend are
// stdin and stdout for a plugin application.
stdinR, stdinW := io.Pipe()
stdoutR, stdoutW := io.Pipe()
process := &proc{}
rwc := rwCloser{
ReadCloser: stdinR,
WriteCloser: stdoutW,
}
// now start a plugin provider using these pipes
s := Server{server: rpc.NewServer(), rwc: rwc}
api := api{}
s.RegisterName("api", api)
api2 := API2{}
s.Register(api2)
done := make(chan struct{})
go func() {
if servercodec == nil {
s.Serve()
} else {
s.ServeCodec(servercodec)
}
close(done)
}()
// now we mock out the makeCommand that'll get called by the host.
f := &fakeCmdData{
stdout: stdoutR,
stdin: stdinW,
p: process,
}
old := makeCommand
makeCommand = f.makeCommand
defer func() { makeCommand = old }()
output := &bytes.Buffer{}
path := "foo"
args := []string{"bar", "baz"}
var client *rpc.Client
var err error
if clientcodec == nil {
client, err = StartProvider(output, path, args...)
if err != nil {
t.Errorf("Unexpected non-nil error from Start: %#v", err)
}
} else {
client, err = StartProviderCodec(clientcodec, output, path, args...)
if err != nil {
t.Errorf("Unexpected non-nil error from StartWithCodec: %#v", err)
}
}
if f.w != output {
t.Error("Output writer not passed to makeCommand")
}
if f.path != path {
t.Error("Path not passed to makeCommand")
}
if !reflect.DeepEqual(f.args, args) {
t.Error("Args not passed to makeCommand")
}
name := "bob"
var response string
if err := client.Call("api.SayHi", name, &response); err != nil {
t.Fatalf("Unexpected non-nil error from client.Call: %#v", err)
}
var expected string
api.SayHi(name, &expected)
if response != expected {
t.Fatalf("Wrong Response from api call, expected %q, got %q", expected, response)
}
if err := client.Call("API2.SayBye", name, &response); err != nil {
t.Fatalf("Unexpected non-nil error from client.Call: %#v", err)
}
api2.SayBye(name, &expected)
if response != expected {
t.Fatalf("Wrong Response from API2 call, expected %q, got %q", expected, response)
}
if err := client.Close(); err != nil {
t.Fatalf("Unexpected non-nil error from client.Call: %#v", err)
}
select {
case <-done:
// pass
case <-time.After(time.Millisecond * 10):
t.Fatal("Server failed to stop after close in 10ms")
}
}
func TestConsumerClientClose(t *testing.T) {
testConsumer(false, nil, nil, t)
}
func TestConsumerServerClose(t *testing.T) {
testConsumer(true, nil, nil, t)
}
func TestConsumerCodecClientCLose(t *testing.T) {
testConsumer(false, jsonrpc.NewServerCodec, jsonrpc.NewClientCodec, t)
}
func TestConsumerCodecServerClose(t *testing.T) {
testConsumer(true, jsonrpc.NewServerCodec, jsonrpc.NewClientCodec, t)
}
func testConsumer(
closeServer bool,
servercodec func(io.ReadWriteCloser) rpc.ServerCodec,
clientcodec func(io.ReadWriteCloser) rpc.ClientCodec,
t *testing.T,
) {
// set up some pipes for reading/writing that we can pretend are
// stdin and stdout for a plugin application.
stdinR, stdinW := io.Pipe()
stdoutR, stdoutW := io.Pipe()
process := &proc{}
// mock out the makeCommand that'll get called by the host.
f := &fakeCmdData{
stdout: stdoutR,
stdin: stdinW,
p: process,
}
old := makeCommand
makeCommand = f.makeCommand
defer func() { makeCommand = old }()
output := &bytes.Buffer{}
path := "foo"
args := []string{"bar", "baz"}
server, err := StartConsumer(output, "foo", args...)
if err != nil {
t.Fatalf("Unexpected error from StartConsumer: %#v", err)
}
if f.w != output {
t.Error("Output writer not passed to makeCommand")
}
if f.path != path {
t.Error("Path not passed to makeCommand")
}
if !reflect.DeepEqual(f.args, args) {
t.Error("Args not passed to makeCommand")
}
api := api{}
server.RegisterName("api", api)
api2 := API2{}
server.Register(api2)
done := make(chan struct{})
go func() {
if servercodec == nil {
server.Serve()
} else {
server.ServeCodec(servercodec)
}
close(done)
}()
var client *rpc.Client
if clientcodec == nil {
client = rpc.NewClient(rwCloser{stdinR, stdoutW})
} else {
client = rpc.NewClientWithCodec(clientcodec(rwCloser{stdinR, stdoutW}))
}
defer client.Close()
name := "bob"
var response string
if err := client.Call("api.SayHi", name, &response); err != nil {
t.Fatalf("Unexpected non-nil error from client.Call: %#v", err)
}
var expected string
api.SayHi(name, &expected)
if response != expected {
t.Fatalf("Wrong Response from api call, expected %q, got %q", expected, response)
}
if err := client.Call("API2.SayBye", name, &response); err != nil {
t.Fatalf("Unexpected non-nil error from client.Call: %#v", err)
}
api2.SayBye(name, &expected)
if response != expected {
t.Fatalf("Wrong Response from api2 call, expected %q, got %q", expected, response)
}
if closeServer {
if err := server.Close(); err != nil {
t.Fatalf("Unexpected non-nil error from server.Close: %#v", err)
}
} else {
if err := client.Close(); err != nil {
t.Fatalf("Unexpected non-nil error from client.Call: %#v", err)
}
}
select {
case <-done:
// pass
case <-time.After(time.Millisecond * 10):
t.Fatal("Server failed to stop after close in 10ms")
}
if closeServer && !process.waited {
t.Fatal("Server was closed, but process was not stopped")
}
}
func TestMakeCommandAndStart(t *testing.T) {
path := "echo"
args := []string{"something"}
c := makeCommand(nil, path, args)
_, ok := c.(execCmd)
if !ok {
t.Fatalf("Expected commander to be type execCmd, but was %#v", c)
}
pipe, err := start(c)
if err != nil {
t.Fatalf("Unexpected non-nil error: %#v", err)
}
defer pipe.Close()
if pipe.proc == nil {
t.Fatal("Unexpected nil proc in ioPipe")
}
p, ok := pipe.proc.(*os.Process)
if !ok {
t.Fatalf("Expected proc to be os.Process but was %#v", pipe.proc)
}
var out []byte
var readerr error
readFinished := make(chan struct{})
go func() {
out, readerr = ioutil.ReadAll(pipe.ReadCloser)
close(readFinished)
}()
go func() {
// When the process finishes, the above ioutil.ReadAll will complete as
// well.
p.Wait()
}()
select {
case <-time.After(time.Millisecond * 100):
p.Kill()
t.Fatalf("Timed out waiting for process to run")
case <-readFinished:
if readerr != nil {
t.Fatalf("Unexpected error reading from the process' stdout: %#v", readerr)
}
actual := strings.TrimSpace(string(out))
if actual != args[0] {
t.Fatalf("Wrong output, expected %q, got %q", args[0], actual)
}
}
}
type testClientCodec struct {
called bool
}
func (t *testClientCodec) NewClientCodec(r io.ReadWriteCloser) rpc.ClientCodec {
t.called = true
return jsonrpc.NewClientCodec(r)
}
func fakeServerCodec(conn io.ReadWriteCloser) rpc.ServerCodec {
return nil
}
type fakeCmdData struct {
stdout io.ReadCloser
stdin io.WriteCloser
p *proc
w io.Writer
path string
args []string
}
func (f *fakeCmdData) makeCommand(w io.Writer, path string, args []string) commander {
f.w = w
f.path = path
f.args = args
return fakeCommand{f.stdin, f.stdout, f.p}
}
type nopWCloser struct {
io.Writer
}
func (nopWCloser) Close() error { return nil }
type fakeCommand struct {
stdin io.WriteCloser
stdout io.ReadCloser
p *proc
}
func (f fakeCommand) Start() (osProcess, error) {
return f.p, nil
}
func (f fakeCommand) StdinPipe() (io.WriteCloser, error) {
return f.stdin, nil
}
func (f fakeCommand) StdoutPipe() (io.ReadCloser, error) {
return f.stdout, nil
}
type api struct{}
func (api) SayHi(name string, response *string) error {
*response = "Hi " + name
return nil
}
type API2 struct{}
func (API2) SayBye(name string, response *string) error {
*response = "Bye " + name
return nil
}
// proc is a helper that fullfills the osProcess interface for testing purposes.
type proc struct {
mu sync.Mutex
delay time.Duration
waitErr error
killErr error
signalErr error
sig os.Signal
killed bool
waited bool
}
// Wait will wait for delay time and then return waitErr.
func (p *proc) Wait() (*os.ProcessState, error) {
p.mu.Lock()
p.waited = true
p.mu.Unlock()
<-time.After(p.delay)
return nil, p.waitErr
}
// Kill returns killErr.
func (p *proc) Kill() error {
p.mu.Lock()
defer p.mu.Unlock()
p.killed = true
return p.killErr
}
// Signal ignores the signal and returns signalErr.
func (p *proc) Signal(sig os.Signal) error {
p.mu.Lock()
defer p.mu.Unlock()
p.sig = sig
return p.signalErr
}
// closeRW is a helper that fulfills io.Reader, io.Writer, and io.Closer for
// testing purposes.
type closeRW struct {
closed bool
err error
}
// Close fulfills io.Closer and will record that it was called, and return this
// value's error, if any.
func (c *closeRW) Close() error {
c.closed = true
return c.err
}
// Read fulfills io.Reader and does nothing.
func (*closeRW) Read(_ []byte) (int, error) {
return 0, nil
}
// Write fulfills io.Writer and does nothing.
func (*closeRW) Write(_ []byte) (int, error) {
return 0, nil
}