293dd2e848
* Add vendor dir so builds dont require dep * Pin specific version go-eth version
644 lines
17 KiB
Go
Executable File
644 lines
17 KiB
Go
Executable File
package npipe
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/rand"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/rpc"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
clientMsg = "Hi server!\n"
|
|
serverMsg = "Hi there, client!\n"
|
|
fileTemplate = "62DA0493-99A1-4327-B5A8-6C4E4466C3FC.txt"
|
|
)
|
|
|
|
// TestBadDial tests that if you dial something other than a valid pipe path, that you get back a
|
|
// PipeError and that you don't accidently create a file on disk (since dial uses OpenFile)
|
|
func TestBadDial(t *testing.T) {
|
|
fn := filepath.Join("C:\\", fileTemplate)
|
|
ns := []string{fn, "http://www.google.com", "somethingbadhere"}
|
|
for _, n := range ns {
|
|
c, err := Dial(n)
|
|
if _, ok := err.(PipeError); !ok {
|
|
t.Errorf("Dialing '%s' did not result in correct error! Expected PipeError, got '%v'",
|
|
n, err)
|
|
}
|
|
if c != nil {
|
|
t.Errorf("Dialing '%s' returned non-nil connection", n)
|
|
}
|
|
if b, _ := exists(n); b {
|
|
t.Errorf("Dialing '%s' incorrectly created file on disk", n)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestDialExistingFile tests that if you dial with the name of an existing file,
|
|
// that you don't accidentally open the file (since dial uses OpenFile)
|
|
func TestDialExistingFile(t *testing.T) {
|
|
tempdir := os.TempDir()
|
|
fn := filepath.Join(tempdir, fileTemplate)
|
|
if f, err := os.Create(fn); err != nil {
|
|
t.Fatalf("Unexpected error creating file '%s': '%v'", fn, err)
|
|
} else {
|
|
// we don't actually need to write to the file, just need it to exist
|
|
f.Close()
|
|
defer os.Remove(fn)
|
|
}
|
|
c, err := Dial(fn)
|
|
if _, ok := err.(PipeError); !ok {
|
|
t.Errorf("Dialing '%s' did not result in error! Expected PipeError, got '%v'", fn, err)
|
|
}
|
|
if c != nil {
|
|
t.Errorf("Dialing '%s' returned non-nil connection", fn)
|
|
}
|
|
}
|
|
|
|
// TestBadListen tests that if you listen on a bad address, that we get back a PipeError
|
|
func TestBadListen(t *testing.T) {
|
|
addrs := []string{"not a valid pipe address", `\\127.0.0.1\pipe\TestBadListen`}
|
|
for _, address := range addrs {
|
|
ln, err := Listen(address)
|
|
if _, ok := err.(PipeError); !ok {
|
|
t.Errorf("Listening on '%s' did not result in correct error! Expected PipeError, got '%v'",
|
|
address, err)
|
|
}
|
|
if ln != nil {
|
|
t.Errorf("Listening on '%s' returned non-nil listener.", address)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestDoubleListen makes sure we can't listen to the same address twice.
|
|
func TestDoubleListen(t *testing.T) {
|
|
address := `\\.\pipe\TestDoubleListen`
|
|
ln1, err := Listen(address)
|
|
if err != nil {
|
|
t.Fatalf("Listen(%q): %v", address, err)
|
|
}
|
|
defer ln1.Close()
|
|
|
|
ln2, err := Listen(address)
|
|
if err == nil {
|
|
ln2.Close()
|
|
t.Fatalf("second Listen on %q succeeded.", address)
|
|
}
|
|
}
|
|
|
|
// TestPipeConnected tests whether we correctly handle clients connecting
|
|
// and then closing the connection between creating and connecting the
|
|
// pipe on the server side.
|
|
func TestPipeConnected(t *testing.T) {
|
|
address := `\\.\pipe\TestPipeConnected`
|
|
ln, err := Listen(address)
|
|
if err != nil {
|
|
t.Fatalf("Listen(%q): %v", address, err)
|
|
}
|
|
defer ln.Close()
|
|
|
|
// Create a client connection and close it immediately.
|
|
clientConn, err := Dial(address)
|
|
if err != nil {
|
|
t.Fatalf("Error from dial: %v", err)
|
|
}
|
|
clientConn.Close()
|
|
|
|
content := "test"
|
|
go func() {
|
|
// Now create a real connection and send some data.
|
|
clientConn, err := Dial(address)
|
|
if err != nil {
|
|
t.Fatalf("Error from dial: %v", err)
|
|
}
|
|
if _, err := clientConn.Write([]byte(content)); err != nil {
|
|
t.Fatalf("Error writing to pipe: %v", err)
|
|
}
|
|
clientConn.Close()
|
|
}()
|
|
|
|
serverConn, err := ln.Accept()
|
|
if err != nil {
|
|
t.Fatalf("Error from accept: %v", err)
|
|
}
|
|
result, err := ioutil.ReadAll(serverConn)
|
|
if err != nil {
|
|
t.Fatalf("Error from ReadAll: %v", err)
|
|
}
|
|
if string(result) != content {
|
|
t.Fatalf("Got %s, expected: %s", string(result), content)
|
|
}
|
|
serverConn.Close()
|
|
}
|
|
|
|
// TestListenCloseListen tests whether Close() actually closes a named pipe properly.
|
|
func TestListenCloseListen(t *testing.T) {
|
|
address := `\\.\pipe\TestListenCloseListen`
|
|
ln1, err := Listen(address)
|
|
if err != nil {
|
|
t.Fatalf("Listen(%q): %v", address, err)
|
|
}
|
|
ln1.Close()
|
|
|
|
ln2, err := Listen(address)
|
|
if err != nil {
|
|
t.Fatalf("second Listen on %q failed.", address)
|
|
}
|
|
ln2.Close()
|
|
}
|
|
|
|
// TestCloseFileHandles tests that all PipeListener handles are actualy closed after
|
|
// calling Close()
|
|
func TestCloseFileHandles(t *testing.T) {
|
|
address := `\\.\pipe\TestCloseFileHandles`
|
|
ln, err := Listen(address)
|
|
if err != nil {
|
|
t.Fatalf("Error listening on %q: %v", address, err)
|
|
}
|
|
defer ln.Close()
|
|
server := rpc.NewServer()
|
|
service := &RPCService{}
|
|
server.Register(service)
|
|
go func() {
|
|
for {
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
// Ignore errors produced by a closed listener.
|
|
if err != ErrClosed {
|
|
t.Errorf("ln.Accept(): %v", err.Error())
|
|
}
|
|
break
|
|
}
|
|
go server.ServeConn(conn)
|
|
}
|
|
}()
|
|
conn, err := Dial(address)
|
|
if err != nil {
|
|
t.Fatalf("Error dialing %q: %v", address, err)
|
|
}
|
|
client := rpc.NewClient(conn)
|
|
defer client.Close()
|
|
req := "dummy"
|
|
resp := ""
|
|
if err = client.Call("RPCService.GetResponse", req, &resp); err != nil {
|
|
t.Fatalf("Error calling RPCService.GetResponse: %v", err)
|
|
}
|
|
if req != resp {
|
|
t.Fatalf("Unexpected result (expected: %q, got: %q)", req, resp)
|
|
}
|
|
ln.Close()
|
|
|
|
if ln.acceptHandle != 0 {
|
|
t.Fatalf("Failed to close acceptHandle")
|
|
}
|
|
if ln.acceptOverlapped.HEvent != 0 {
|
|
t.Fatalf("Failed to close acceptOverlapped handle")
|
|
}
|
|
}
|
|
|
|
// TestCancelListen tests whether Accept() can be cancelled by closing the listener.
|
|
func TestCancelAccept(t *testing.T) {
|
|
address := `\\.\pipe\TestCancelListener`
|
|
ln, err := Listen(address)
|
|
if err != nil {
|
|
t.Fatalf("Listen(%q): %v", address, err)
|
|
}
|
|
|
|
cancelled := make(chan struct{})
|
|
started := make(chan struct{})
|
|
go func() {
|
|
close(started)
|
|
conn, _ := ln.Accept()
|
|
if conn != nil {
|
|
t.Fatalf("Unexpected incoming connection: %v", conn)
|
|
conn.Close()
|
|
}
|
|
cancelled <- struct{}{}
|
|
}()
|
|
<-started
|
|
// Close listener after 20ms. This should give the go routine enough time to be actually
|
|
// waiting for incoming connections inside ln.Accept().
|
|
time.AfterFunc(20*time.Millisecond, func() {
|
|
if err := ln.Close(); err != nil {
|
|
t.Fatalf("Error closing listener: %v", err)
|
|
}
|
|
})
|
|
// Any Close() should abort the ln.Accept() call within 100ms.
|
|
// We fail with a timeout otherwise, to avoid blocking forever on a failing test.
|
|
timeout := time.After(100 * time.Millisecond)
|
|
select {
|
|
case <-cancelled:
|
|
// This is what should happen.
|
|
case <-timeout:
|
|
t.Fatal("Timeout trying to cancel accept.")
|
|
}
|
|
}
|
|
|
|
// Test that PipeConn's read deadline works correctly
|
|
func TestReadDeadline(t *testing.T) {
|
|
address := `\\.\pipe\TestReadDeadline`
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
|
|
go listenAndWait(address, wg, t)
|
|
defer wg.Done()
|
|
|
|
c, err := Dial(address)
|
|
if err != nil {
|
|
t.Fatalf("Error dialing into pipe: %v", err)
|
|
}
|
|
if c == nil {
|
|
t.Fatal("Unexpected nil connection from Dial")
|
|
}
|
|
defer c.Close()
|
|
deadline := time.Now().Add(time.Millisecond * 50)
|
|
c.SetReadDeadline(deadline)
|
|
msg, err := bufio.NewReader(c).ReadString('\n')
|
|
end := time.Now()
|
|
if msg != "" {
|
|
t.Errorf("Pipe read timeout returned a non-empty message: %s", msg)
|
|
}
|
|
if err == nil {
|
|
t.Error("Pipe read timeout returned nil error")
|
|
} else {
|
|
pe, ok := err.(PipeError)
|
|
if !ok {
|
|
t.Errorf("Got wrong error returned, expected PipeError, got '%t'", err)
|
|
}
|
|
if !pe.Timeout() {
|
|
t.Error("Pipe read timeout didn't return an error indicating the timeout")
|
|
}
|
|
}
|
|
checkDeadline(deadline, end, t)
|
|
}
|
|
|
|
// listenAndWait simply sets up a pipe listener that does nothing and closes after the waitgroup
|
|
// is done.
|
|
func listenAndWait(address string, wg sync.WaitGroup, t *testing.T) {
|
|
ln, err := Listen(address)
|
|
if err != nil {
|
|
t.Fatalf("Error starting to listen on pipe: %v", err)
|
|
}
|
|
if ln == nil {
|
|
t.Fatal("Got unexpected nil listener")
|
|
}
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
t.Fatalf("Error accepting connection: %v", err)
|
|
}
|
|
if conn == nil {
|
|
t.Fatal("Got unexpected nil connection")
|
|
}
|
|
defer conn.Close()
|
|
// don't read or write anything
|
|
wg.Wait()
|
|
}
|
|
|
|
// TestWriteDeadline tests that PipeConn's write deadline works correctly
|
|
func TestWriteDeadline(t *testing.T) {
|
|
address := `\\.\pipe\TestWriteDeadline`
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
|
|
go listenAndWait(address, wg, t)
|
|
defer wg.Done()
|
|
c, err := Dial(address)
|
|
if err != nil {
|
|
t.Fatalf("Error dialing into pipe: %v", err)
|
|
}
|
|
if c == nil {
|
|
t.Fatal("Unexpected nil connection from Dial")
|
|
}
|
|
|
|
// windows pipes have a buffer, so even if we don't read from the pipe,
|
|
// the write may succeed anyway, so we have to write a whole bunch to
|
|
// test the time out
|
|
deadline := time.Now().Add(time.Millisecond * 50)
|
|
c.SetWriteDeadline(deadline)
|
|
buffer := make([]byte, 1<<16)
|
|
if _, err = io.ReadFull(rand.Reader, buffer); err != nil {
|
|
t.Fatalf("Couldn't generate random buffer: %v", err)
|
|
}
|
|
_, err = c.Write(buffer)
|
|
|
|
end := time.Now()
|
|
|
|
if err == nil {
|
|
t.Error("Pipe write timeout returned nil error")
|
|
} else {
|
|
pe, ok := err.(PipeError)
|
|
if !ok {
|
|
t.Errorf("Got wrong error returned, expected PipeError, got '%t'", err)
|
|
}
|
|
if !pe.Timeout() {
|
|
t.Error("Pipe write timeout didn't return an error indicating the timeout")
|
|
}
|
|
}
|
|
checkDeadline(deadline, end, t)
|
|
}
|
|
|
|
// TestDialTimeout tests that the DialTimeout function will actually timeout correctly
|
|
func TestDialTimeout(t *testing.T) {
|
|
timeout := time.Millisecond * 150
|
|
deadline := time.Now().Add(timeout)
|
|
c, err := DialTimeout(`\\.\pipe\TestDialTimeout`, timeout)
|
|
end := time.Now()
|
|
if c != nil {
|
|
t.Errorf("DialTimeout returned non-nil connection: %v", c)
|
|
}
|
|
if err == nil {
|
|
t.Error("DialTimeout returned nil error after timeout")
|
|
} else {
|
|
pe, ok := err.(PipeError)
|
|
if !ok {
|
|
t.Errorf("Got wrong error returned, expected PipeError, got '%t'", err)
|
|
}
|
|
if !pe.Timeout() {
|
|
t.Error("Dial timeout didn't return an error indicating the timeout")
|
|
}
|
|
}
|
|
checkDeadline(deadline, end, t)
|
|
}
|
|
|
|
// TestDialNoTimeout tests that the DialTimeout function will properly wait for the pipe and
|
|
// connect when it is available
|
|
func TestDialNoTimeout(t *testing.T) {
|
|
timeout := time.Millisecond * 500
|
|
address := `\\.\pipe\TestDialNoTimeout`
|
|
go func() {
|
|
<-time.After(50 * time.Millisecond)
|
|
listenAndClose(address, t)
|
|
}()
|
|
|
|
deadline := time.Now().Add(timeout)
|
|
c, err := DialTimeout(address, timeout)
|
|
end := time.Now()
|
|
|
|
if c == nil {
|
|
t.Error("DialTimeout returned unexpected nil connection")
|
|
}
|
|
if err != nil {
|
|
t.Error("DialTimeout returned unexpected non-nil error: ", err)
|
|
}
|
|
if end.After(deadline) {
|
|
t.Fatalf("Ended %v after deadline", end.Sub(deadline))
|
|
}
|
|
}
|
|
|
|
// TestDial tests that you can dial before a pipe is available,
|
|
// and that it'll pick up the pipe once it's ready
|
|
func TestDial(t *testing.T) {
|
|
address := `\\.\pipe\TestDial`
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
wg.Done()
|
|
conn, err := Dial(address)
|
|
if err != nil {
|
|
t.Fatalf("Got unexpected error from Dial: %v", err)
|
|
}
|
|
if conn == nil {
|
|
t.Fatal("Got unexpected nil connection from Dial")
|
|
}
|
|
if err := conn.Close(); err != nil {
|
|
t.Fatalf("Got unexpected error from conection.Close(): %v", err)
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
<-time.After(50 * time.Millisecond)
|
|
listenAndClose(address, t)
|
|
}
|
|
|
|
type RPCService struct{}
|
|
|
|
func (s *RPCService) GetResponse(request string, response *string) error {
|
|
*response = request
|
|
return nil
|
|
}
|
|
|
|
// TestGoRPC tests that you can run go RPC over the pipe,
|
|
// and that overlapping bi-directional communication is working
|
|
// (write while a blocking read is in progress).
|
|
func TestGoRPC(t *testing.T) {
|
|
address := `\\.\pipe\TestRPC`
|
|
ln, err := Listen(address)
|
|
if err != nil {
|
|
t.Fatalf("Error listening on %q: %v", address, err)
|
|
}
|
|
waitExit := make(chan struct{})
|
|
defer func() {
|
|
ln.Close()
|
|
<-waitExit
|
|
}()
|
|
|
|
go func() {
|
|
server := rpc.NewServer()
|
|
server.Register(&RPCService{})
|
|
for {
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
// Ignore errors produced by a closed listener.
|
|
if err != ErrClosed {
|
|
t.Errorf("ln.Accept(): %v", err.Error())
|
|
}
|
|
break
|
|
}
|
|
go server.ServeConn(conn)
|
|
}
|
|
close(waitExit)
|
|
}()
|
|
conn, err := Dial(address)
|
|
if err != nil {
|
|
t.Fatalf("Error dialing %q: %v", address, err)
|
|
}
|
|
client := rpc.NewClient(conn)
|
|
defer client.Close()
|
|
req := "dummy"
|
|
var resp string
|
|
if err = client.Call("RPCService.GetResponse", req, &resp); err != nil {
|
|
t.Fatalf("Error calling RPCService.GetResponse: %v", err)
|
|
}
|
|
if req != resp {
|
|
t.Fatalf("Unexpected result (expected: %q, got: %q)", req, resp)
|
|
}
|
|
}
|
|
|
|
// listenAndClose is a helper method to just listen on a pipe and close as soon as someone connects.
|
|
func listenAndClose(address string, t *testing.T) {
|
|
ln, err := Listen(address)
|
|
if err != nil {
|
|
t.Fatalf("Got unexpected error from Listen: %v", err)
|
|
}
|
|
if ln == nil {
|
|
t.Fatal("Got unexpected nil listener from Listen")
|
|
}
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
t.Fatalf("Got unexpected error from Accept: %v", err)
|
|
}
|
|
if conn == nil {
|
|
t.Fatal("Got unexpected nil connection from Accept")
|
|
}
|
|
if err := conn.Close(); err != nil {
|
|
t.Fatalf("Got unexpected error from conection.Close(): %v", err)
|
|
}
|
|
}
|
|
|
|
// TestCommonUseCase is a full run-through of the most common use case, where you create a listener
|
|
// and then dial into it with several clients in succession
|
|
func TestCommonUseCase(t *testing.T) {
|
|
addrs := []string{`\\.\pipe\TestCommonUseCase`, `\\127.0.0.1\pipe\TestCommonUseCase`}
|
|
// always listen on the . version, since IP won't work for listening
|
|
ln, err := Listen(addrs[0])
|
|
if err != nil {
|
|
t.Fatalf("Listen(%q) failed: %v", addrs[0], err)
|
|
}
|
|
defer ln.Close()
|
|
|
|
for _, address := range addrs {
|
|
convos := 5
|
|
clients := 10
|
|
|
|
wg := sync.WaitGroup{}
|
|
|
|
for x := 0; x < clients; x++ {
|
|
wg.Add(1)
|
|
go startClient(address, &wg, convos, t)
|
|
}
|
|
|
|
go startServer(ln, convos, t)
|
|
|
|
select {
|
|
case <-wait(&wg):
|
|
// good!
|
|
case <-time.After(time.Second):
|
|
t.Fatal("Failed to finish after a reasonable timeout")
|
|
}
|
|
}
|
|
}
|
|
|
|
// wait simply waits on the waitgroup and closes the returned channel when done.
|
|
func wait(wg *sync.WaitGroup) <-chan struct{} {
|
|
done := make(chan struct{})
|
|
go func() {
|
|
wg.Wait()
|
|
close(done)
|
|
}()
|
|
return done
|
|
}
|
|
|
|
// startServer accepts connections and spawns goroutines to handle them
|
|
func startServer(ln *PipeListener, iter int, t *testing.T) {
|
|
for {
|
|
conn, err := ln.Accept()
|
|
if err == ErrClosed {
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("Error accepting connection: %v", err)
|
|
}
|
|
go handleConnection(conn, iter, t)
|
|
}
|
|
}
|
|
|
|
// handleConnection is the goroutine that handles connections on the server side
|
|
// it expects to read a message and then write a message, convos times, before exiting.
|
|
func handleConnection(conn net.Conn, convos int, t *testing.T) {
|
|
r := bufio.NewReader(conn)
|
|
for x := 0; x < convos; x++ {
|
|
msg, err := r.ReadString('\n')
|
|
if err != nil {
|
|
t.Fatalf("Error reading from server connection: %v", err)
|
|
}
|
|
if msg != clientMsg {
|
|
t.Fatalf("Read incorrect message from client. Expected '%s', got '%s'", clientMsg, msg)
|
|
}
|
|
|
|
if _, err := fmt.Fprint(conn, serverMsg); err != nil {
|
|
t.Fatalf("Error on server writing to pipe: %v", err)
|
|
}
|
|
}
|
|
if err := conn.Close(); err != nil {
|
|
t.Fatalf("Error closing server side of connection: %v", err)
|
|
}
|
|
}
|
|
|
|
// startClient waits on a pipe at the given address. It expects to write a message and then
|
|
// read a message from the pipe, convos times, and then sends a message on the done
|
|
// channel
|
|
func startClient(address string, wg *sync.WaitGroup, convos int, t *testing.T) {
|
|
defer wg.Done()
|
|
c := make(chan *PipeConn)
|
|
go asyncdial(address, c, t)
|
|
|
|
var conn *PipeConn
|
|
select {
|
|
case conn = <-c:
|
|
case <-time.After(time.Second):
|
|
// Yes this is a long timeout, but sometimes it really does take a long time.
|
|
t.Fatalf("Client timed out waiting for dial to resolve")
|
|
}
|
|
r := bufio.NewReader(conn)
|
|
for x := 0; x < convos; x++ {
|
|
if _, err := fmt.Fprint(conn, clientMsg); err != nil {
|
|
t.Fatalf("Error on client writing to pipe: %v", err)
|
|
}
|
|
|
|
msg, err := r.ReadString('\n')
|
|
if err != nil {
|
|
t.Fatalf("Error reading from client connection: %v", err)
|
|
}
|
|
if msg != serverMsg {
|
|
t.Fatalf("Read incorrect message from server. Expected '%s', got '%s'", serverMsg, msg)
|
|
}
|
|
}
|
|
|
|
if err := conn.Close(); err != nil {
|
|
t.Fatalf("Error closing client side of pipe %v", err)
|
|
}
|
|
}
|
|
|
|
// asyncdial is a helper that dials and returns the connection on the given channel.
|
|
// this is useful for being able to give dial a timeout
|
|
func asyncdial(address string, c chan *PipeConn, t *testing.T) {
|
|
conn, err := Dial(address)
|
|
if err != nil {
|
|
t.Fatalf("Error from dial: %v", err)
|
|
}
|
|
c <- conn
|
|
}
|
|
|
|
// exists is a simple helper function to detect if a file exists on disk
|
|
func exists(path string) (bool, error) {
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
func checkDeadline(deadline, end time.Time, t *testing.T) {
|
|
if end.Before(deadline) {
|
|
t.Fatalf("Ended %v before deadline", deadline.Sub(end))
|
|
}
|
|
diff := end.Sub(deadline)
|
|
|
|
// we need a huge fudge factor here because Windows has really poor
|
|
// resolution for timeouts, and in practice, the timeout can be 400ms or
|
|
// more after the expected timeout.
|
|
if diff > 500*time.Millisecond {
|
|
t.Fatalf("Ended significantly (%v) after deadline", diff)
|
|
}
|
|
}
|