Merge pull request #5056 from heyitsanthony/expect-signal

pkg/expect, e2e: support sending Signals to expect process, test etcdctl lock
release-3.0
Anthony Romano 2016-04-13 09:42:41 -07:00
commit 228e772b3a
6 changed files with 147 additions and 5 deletions

View File

@ -284,7 +284,7 @@ func etcdctlLs(clus *etcdProcessCluster, key string, quorum bool) error {
}
func etcdctlWatch(clus *etcdProcessCluster, key, value string, noSync bool) <-chan error {
cmdArgs := append(etcdctlPrefixArgs(clus), "watch", "--after-index 1", key)
cmdArgs := append(etcdctlPrefixArgs(clus), "watch", "--after-index=1", key)
if noSync {
cmdArgs = append(cmdArgs, "--no-sync")
}

113
e2e/ctl_v3_lock_test.go Normal file
View File

@ -0,0 +1,113 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package e2e
import (
"os"
"strings"
"testing"
"time"
"github.com/coreos/etcd/pkg/expect"
)
func TestCtlV3Lock(t *testing.T) { testCtl(t, testLock) }
func testLock(cx ctlCtx) {
name := "a"
holder, ch, err := ctlV3Lock(cx, name)
if err != nil {
cx.t.Fatal(err)
}
l1 := ""
select {
case <-time.After(2 * time.Second):
cx.t.Fatalf("timed out locking")
case l1 = <-ch:
if !strings.HasPrefix(l1, name) {
cx.t.Errorf("got %q, expected %q prefix", l1, name)
}
}
// blocked process that won't acquire the lock
blocked, ch, err := ctlV3Lock(cx, name)
if err != nil {
cx.t.Fatal(err)
}
select {
case <-time.After(100 * time.Millisecond):
case <-ch:
cx.t.Fatalf("should block")
}
// overlap with a blocker that will acquire the lock
blockAcquire, ch, err := ctlV3Lock(cx, name)
if err != nil {
cx.t.Fatal(err)
}
defer blockAcquire.Stop()
select {
case <-time.After(100 * time.Millisecond):
case <-ch:
cx.t.Fatalf("should block")
}
// kill blocked process with clean shutdown
if err = blocked.Signal(os.Interrupt); err != nil {
cx.t.Fatal(err)
}
if err = blocked.Close(); err != nil {
cx.t.Fatal(err)
}
// kill the holder with clean shutdown
if err = holder.Signal(os.Interrupt); err != nil {
cx.t.Fatal(err)
}
if err = holder.Close(); err != nil {
cx.t.Fatal(err)
}
// blockAcquire should acquire the lock
select {
case <-time.After(time.Second):
cx.t.Fatalf("timed out from waiting to holding")
case l2 := <-ch:
if l1 == l2 || !strings.HasPrefix(l2, name) {
cx.t.Fatalf("expected different lock name, got l1=%q, l2=%q", l1, l2)
}
}
}
// ctlV3Lock creates a lock process with a channel listening for when it acquires the lock.
func ctlV3Lock(cx ctlCtx, name string) (*expect.ExpectProcess, <-chan string, error) {
cmdArgs := append(cx.PrefixArgs(), "lock", name)
proc, err := spawnCmd(cmdArgs)
outc := make(chan string, 1)
if err != nil {
close(outc)
return proc, outc, err
}
go func() {
s, xerr := proc.ExpectFunc(func(string) bool { return true })
if xerr != nil {
cx.t.Errorf("expect failed (%v)", xerr)
}
outc <- s
}()
return proc, outc, err
}

View File

@ -513,7 +513,7 @@ func ctlV3Version(cx ctlCtx) error {
}
func ctlV3EpHealth(cx ctlCtx) error {
cmdArgs := append(cx.PrefixArgs(), "endpoint health")
cmdArgs := append(cx.PrefixArgs(), "endpoint", "health")
lines := make([]string, cx.epc.cfg.clusterSize)
for i := range lines {
lines[i] = "is healthy"

View File

@ -416,8 +416,7 @@ func (epc *etcdProcessCluster) Close() (err error) {
}
func spawnCmd(args []string) (*expect.ExpectProcess, error) {
cmdargs := append([]string{"-c"}, strings.Join(args, " "))
return expect.NewExpect("/bin/sh", cmdargs...)
return expect.NewExpect(args[0], args[1:]...)
}
func spawnWithExpect(args []string, expected string) error {

View File

@ -113,6 +113,11 @@ func (ep *ExpectProcess) LineCount() int {
// Stop kills the expect process and waits for it to exit.
func (ep *ExpectProcess) Stop() error { return ep.close(true) }
// Signal sends a signal to the expect process
func (ep *ExpectProcess) Signal(sig os.Signal) error {
return ep.cmd.Process.Signal(sig)
}
// Close waits for the expect process to exit.
func (ep *ExpectProcess) Close() error { return ep.close(false) }

View File

@ -16,7 +16,11 @@
package expect
import "testing"
import (
"os"
"testing"
"time"
)
func TestExpectFunc(t *testing.T) {
ep, err := NewExpect("/bin/echo", "hello world")
@ -93,3 +97,24 @@ func TestSend(t *testing.T) {
t.Fatal(err)
}
}
func TestSignal(t *testing.T) {
ep, err := NewExpect("/bin/sleep", "100")
if err != nil {
t.Fatal(err)
}
ep.Signal(os.Interrupt)
donec := make(chan struct{})
go func() {
defer close(donec)
werr := "signal: interrupt"
if cerr := ep.Close(); cerr == nil || cerr.Error() != werr {
t.Fatalf("got error %v, wanted error %s", cerr, werr)
}
}()
select {
case <-time.After(5 * time.Second):
t.Fatalf("signal test timed out")
case <-donec:
}
}