blob: 7cfbc61921f320e47c00a961f25721b31b88e0fb [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ipc
import (
"flag"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"path/filepath"
"runtime"
"strings"
"syscall"
"testing"
"time"
"v.io/v23/vom"
"v.io/x/lib/vlog"
"v.io/x/ref/services/agent"
)
type echo int
func (e echo) Echo(m string) (string, error) {
return m, nil
}
type delayedEcho struct {
gotA, gotB chan struct{}
doneA, doneB chan string
}
func newDelayedEcho() delayedEcho {
return delayedEcho{make(chan struct{}, 1), make(chan struct{}, 1), make(chan string, 1), make(chan string, 1)}
}
func (d delayedEcho) EchoA() (string, error) {
close(d.gotA)
return <-d.doneA, nil
}
func (d delayedEcho) EchoB(prefix string) (string, error) {
close(d.gotB)
return prefix + <-d.doneB, nil
}
type returnError int
func (returnError) BreakMe(a, b string) (int, error) {
return 0, fmt.Errorf("%s %s", a, b)
}
type tunnel struct {
ipc *IPC
}
func (t *tunnel) Tunnel() (str string, err error) {
conn := t.ipc.Connections()[0]
err = conn.Call("Echo", []interface{}{"Tunnel"}, &str)
return
}
func newServer(t *testing.T, servers ...interface{}) (ipc *IPC, path string, f func()) {
dir, err := ioutil.TempDir("", "sock")
if err != nil {
t.Fatal(err)
}
path = filepath.Join(dir, "sock")
ipc = NewIPC()
for _, s := range servers {
if err := ipc.Serve(s); err != nil {
t.Fatal(err)
}
}
if err := ipc.Listen(path); err != nil {
os.RemoveAll(dir)
t.Fatal(err)
}
return ipc, path, func() { ipc.Close(); os.RemoveAll(dir) }
}
func isEpipe(err error) bool {
for operr, ok := err.(*net.OpError); ok; operr, ok = err.(*net.OpError) {
err = operr.Err
}
return err == syscall.EPIPE
}
func TestDoubleServe(t *testing.T) {
ipc := NewIPC()
var a echo
var b echo
if err := ipc.Serve(a); err != nil {
t.Fatal(err)
}
if err := ipc.Serve(b); err == nil {
t.Fatal("Expected error")
}
}
func TestListen(t *testing.T) {
ipc1, path, cleanup := newServer(t)
defer cleanup()
if stat, err := os.Stat(path); err == nil {
if stat.Mode()&os.ModeSocket == 0 {
t.Fatalf("Not a socket: %#o", stat.Mode())
}
} else {
t.Fatal(err)
}
ipc1.Close()
if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Fatalf("Expected NotExist, got %v", err)
}
}
func TestBadConnect(t *testing.T) {
t.Skip()
timeA := time.Now()
ipc, path, cleanup := newServer(t)
timeB := time.Now()
if got := ipc.IdleStartTime(); got.Before(timeA) || got.After(timeB) {
t.Fatalf("Wanted time between %v and %v, got %v instead", timeA, timeB, got)
}
defer cleanup()
conn, err := net.Dial("unix", path)
if err != nil {
t.Fatal(err)
}
enc := vom.NewEncoder(conn)
dec := vom.NewDecoder(conn)
var theirInfo agent.ConnInfo
if err = dec.Decode(&theirInfo); err != nil {
t.Fatal(err)
}
if got := ipc.IdleStartTime(); !got.IsZero() {
t.Fatalf("Wanted zero time, got %v instead", got)
}
timeA = time.Now()
if err = enc.Encode(agent.RpcMessageReq{Value: agent.RpcRequest{Id: 0, Method: "foo", NumArgs: 0}}); err != nil {
if !isEpipe(err) {
// The server will close the connection when it gets
// bad data. If that happens fast enough we get EPIPE.
t.Fatal(err)
}
}
var response agent.RpcMessage
if err = dec.Decode(&response); err != io.EOF {
t.Fatalf("Expected eof, got %v", err)
}
conn.Close()
// Give a chance for the ipc object to observe the connection closure.
for len(ipc.Connections()) > 0 {
runtime.Gosched()
}
timeB = time.Now()
if got := ipc.IdleStartTime(); got.Before(timeA) || got.After(timeB) {
t.Fatalf("Wanted time between %v and %v, got %v instead", timeA, timeB, got)
}
}
func TestBadMinVersion(t *testing.T) {
_, path, cleanup := newServer(t)
defer cleanup()
conn, err := net.Dial("unix", path)
if err != nil {
t.Fatal(err)
}
enc := vom.NewEncoder(conn)
dec := vom.NewDecoder(conn)
var info agent.ConnInfo
if err = dec.Decode(&info); err != nil {
t.Fatal(err)
}
info.MaxVersion++
info.MinVersion = info.MaxVersion
if err = enc.Encode(info); err != nil {
t.Fatal(err)
}
var response agent.RpcMessage
if err = dec.Decode(&response); err != io.EOF {
t.Fatalf("Expected EOF, got %v", err)
}
}
func TestBadMaxVersion(t *testing.T) {
_, path, cleanup := newServer(t)
defer cleanup()
conn, err := net.Dial("unix", path)
if err != nil {
t.Fatal(err)
}
enc := vom.NewEncoder(conn)
dec := vom.NewDecoder(conn)
var info agent.ConnInfo
if err = dec.Decode(&info); err != nil {
t.Fatal(err)
}
info.MinVersion--
info.MaxVersion = info.MinVersion
if err = enc.Encode(info); err != nil {
t.Fatal(err)
}
var response agent.RpcMessage
if err = dec.Decode(&response); err != io.EOF {
t.Fatalf("Expected EOF, got %v", err)
}
}
func TestEcho(t *testing.T) {
var e echo
_, path, cleanup := newServer(t, e)
defer cleanup()
ipc2 := NewIPC()
defer ipc2.Close()
client, err := ipc2.Connect(path, 0)
if err != nil {
t.Fatal(err)
}
var result string
err = client.Call("Echo", []interface{}{"hello"}, &result)
if err != nil {
t.Fatal(err)
}
if result != "hello" {
t.Fatalf("Expected hello, got %q", result)
}
}
func TestOutOfOrder(t *testing.T) {
delay := newDelayedEcho()
_, path, cleanup := newServer(t, delay)
defer cleanup()
ipc2 := NewIPC()
defer ipc2.Close()
client, err := ipc2.Connect(path, 0)
if err != nil {
t.Fatal(err)
}
var resultA string
errA := make(chan error)
go func() { errA <- client.Call("EchoA", nil, &resultA) }()
<-delay.gotA
delay.doneB <- " world"
var resultB string
err = client.Call("EchoB", []interface{}{"hello"}, &resultB)
if err != nil {
t.Fatal(err)
}
if resultB != "hello world" {
t.Fatalf("Expected 'hello word', got %q", resultB)
}
delay.doneA <- "foobar"
if err := <-errA; err != nil {
t.Fatal(err)
}
if resultA != "foobar" {
t.Fatalf("Expected foobar, got %q", resultA)
}
}
func TestErrorReturn(t *testing.T) {
var s returnError
_, path, cleanup := newServer(t, s)
defer cleanup()
ipc2 := NewIPC()
defer ipc2.Close()
client, err := ipc2.Connect(path, 0)
if err != nil {
t.Fatal(err)
}
var result int
err = client.Call("BreakMe", []interface{}{"oh", "no"}, &result)
if err == nil {
t.Fatalf("Expected error, got %d", result)
}
if !strings.HasSuffix(err.Error(), "oh no") {
t.Fatalf("Expected 'oh no', got %q", err.Error())
}
}
func TestBidi(t *testing.T) {
s := &tunnel{}
ipc1, path, cleanup := newServer(t, s)
defer cleanup()
s.ipc = ipc1
ipc2 := NewIPC()
defer ipc2.Close()
var e echo
ipc2.Serve(e)
client, err := ipc2.Connect(path, 0)
if err != nil {
t.Fatal(err)
}
var result string
err = client.Call("Tunnel", nil, &result)
if err != nil {
t.Fatal(err)
}
if result != "Tunnel" {
t.Fatalf("Expected Tunnel, got %q", result)
}
}
func TestMain(m *testing.M) {
flag.Parse()
vlog.ConfigureLibraryLoggerFromFlags()
os.Exit(m.Run())
}