From fcb5ba98d00bb0e26d229b968e160c81fb91fb24 Mon Sep 17 00:00:00 2001 From: Anthony Romano Date: Tue, 12 Apr 2016 21:08:15 -0700 Subject: [PATCH 1/3] pkg/expect: support sending Signals to expect process --- pkg/expect/expect.go | 5 +++++ pkg/expect/expect_test.go | 27 ++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/pkg/expect/expect.go b/pkg/expect/expect.go index 21af7a2ac..d5256a3dd 100644 --- a/pkg/expect/expect.go +++ b/pkg/expect/expect.go @@ -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) } diff --git a/pkg/expect/expect_test.go b/pkg/expect/expect_test.go index e7afa0dd2..95b89e489 100644 --- a/pkg/expect/expect_test.go +++ b/pkg/expect/expect_test.go @@ -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: + } +} From 604a73c833aae34282b525b1066b2affa006914e Mon Sep 17 00:00:00 2001 From: Anthony Romano Date: Wed, 13 Apr 2016 00:12:29 -0700 Subject: [PATCH 2/3] e2e: remove sh in spawnCmd certain shells claim the ppid for expect processes which interferes with signals --- e2e/ctl_v2_test.go | 2 +- e2e/ctl_v3_test.go | 2 +- e2e/etcd_test.go | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/e2e/ctl_v2_test.go b/e2e/ctl_v2_test.go index 59e1681cd..84e93e733 100644 --- a/e2e/ctl_v2_test.go +++ b/e2e/ctl_v2_test.go @@ -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") } diff --git a/e2e/ctl_v3_test.go b/e2e/ctl_v3_test.go index 6e98c3743..08feb88cb 100644 --- a/e2e/ctl_v3_test.go +++ b/e2e/ctl_v3_test.go @@ -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" diff --git a/e2e/etcd_test.go b/e2e/etcd_test.go index b12f7d714..39487a167 100644 --- a/e2e/etcd_test.go +++ b/e2e/etcd_test.go @@ -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 { From 8763bd1e97f87b6dc7dbc5efd345d035cc4fa924 Mon Sep 17 00:00:00 2001 From: Anthony Romano Date: Tue, 12 Apr 2016 23:26:14 -0700 Subject: [PATCH 3/3] e2e: etcdctlv3 lock test --- e2e/ctl_v3_lock_test.go | 113 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 e2e/ctl_v3_lock_test.go diff --git a/e2e/ctl_v3_lock_test.go b/e2e/ctl_v3_lock_test.go new file mode 100644 index 000000000..410d629d5 --- /dev/null +++ b/e2e/ctl_v3_lock_test.go @@ -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 +}