test: support regular expression matching on the response

Signed-off-by: Benjamin Wang <wachao@vmware.com>
This commit is contained in:
Benjamin Wang 2023-08-25 10:49:39 +01:00
parent f377db2e93
commit 7d95c68b48
30 changed files with 178 additions and 138 deletions

View File

@ -23,6 +23,7 @@ import (
"io" "io"
"os" "os"
"os/exec" "os/exec"
"regexp"
"strings" "strings"
"sync" "sync"
"syscall" "syscall"
@ -37,6 +38,11 @@ var (
ErrProcessRunning = fmt.Errorf("process is still running") ErrProcessRunning = fmt.Errorf("process is still running")
) )
type ExpectedResponse struct {
Value string
IsRegularExpr bool
}
type ExpectProcess struct { type ExpectProcess struct {
cfg expectConfig cfg expectConfig
@ -223,14 +229,29 @@ func (ep *ExpectProcess) ExpectFunc(ctx context.Context, f func(string) bool) (s
} }
// ExpectWithContext returns the first line containing the given string. // ExpectWithContext returns the first line containing the given string.
func (ep *ExpectProcess) ExpectWithContext(ctx context.Context, s string) (string, error) { func (ep *ExpectProcess) ExpectWithContext(ctx context.Context, s ExpectedResponse) (string, error) {
return ep.ExpectFunc(ctx, func(txt string) bool { return strings.Contains(txt, s) }) var (
expr *regexp.Regexp
err error
)
if s.IsRegularExpr {
expr, err = regexp.Compile(s.Value)
if err != nil {
return "", err
}
}
return ep.ExpectFunc(ctx, func(txt string) bool {
if expr != nil {
return expr.MatchString(txt)
}
return strings.Contains(txt, s.Value)
})
} }
// Expect returns the first line containing the given string. // Expect returns the first line containing the given string.
// Deprecated: please use ExpectWithContext instead. // Deprecated: please use ExpectWithContext instead.
func (ep *ExpectProcess) Expect(s string) (string, error) { func (ep *ExpectProcess) Expect(s string) (string, error) {
return ep.ExpectWithContext(context.Background(), s) return ep.ExpectWithContext(context.Background(), ExpectedResponse{Value: s})
} }
// LineCount returns the number of recorded lines since // LineCount returns the number of recorded lines since

View File

@ -127,7 +127,7 @@ func TestEcho(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
ctx := context.Background() ctx := context.Background()
l, eerr := ep.ExpectWithContext(ctx, "world") l, eerr := ep.ExpectWithContext(ctx, ExpectedResponse{Value: "world"})
if eerr != nil { if eerr != nil {
t.Fatal(eerr) t.Fatal(eerr)
} }
@ -138,7 +138,7 @@ func TestEcho(t *testing.T) {
if cerr := ep.Close(); cerr != nil { if cerr := ep.Close(); cerr != nil {
t.Fatal(cerr) t.Fatal(cerr)
} }
if _, eerr = ep.ExpectWithContext(ctx, "..."); eerr == nil { if _, eerr = ep.ExpectWithContext(ctx, ExpectedResponse{Value: "..."}); eerr == nil {
t.Fatalf("expected error on closed expect process") t.Fatalf("expected error on closed expect process")
} }
} }
@ -149,7 +149,7 @@ func TestLineCount(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
wstr := "3" wstr := "3"
l, eerr := ep.ExpectWithContext(context.Background(), wstr) l, eerr := ep.ExpectWithContext(context.Background(), ExpectedResponse{Value: wstr})
if eerr != nil { if eerr != nil {
t.Fatal(eerr) t.Fatal(eerr)
} }
@ -172,7 +172,7 @@ func TestSend(t *testing.T) {
if err := ep.Send("a\r"); err != nil { if err := ep.Send("a\r"); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := ep.ExpectWithContext(context.Background(), "b"); err != nil { if _, err := ep.ExpectWithContext(context.Background(), ExpectedResponse{Value: "b"}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := ep.Stop(); err != nil { if err := ep.Stop(); err != nil {

View File

@ -25,6 +25,7 @@ import (
"go.etcd.io/etcd/api/v3/etcdserverpb" "go.etcd.io/etcd/api/v3/etcdserverpb"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/server/v3/storage/datadir" "go.etcd.io/etcd/server/v3/storage/datadir"
"go.etcd.io/etcd/server/v3/storage/mvcc/testutil" "go.etcd.io/etcd/server/v3/storage/mvcc/testutil"
"go.etcd.io/etcd/tests/v3/framework/config" "go.etcd.io/etcd/tests/v3/framework/config"
@ -334,11 +335,11 @@ func TestCompactHashCheckDetectCorruptionInterrupt(t *testing.T) {
err = epc.Procs[slowCompactionNodeIndex].Restart(ctx) err = epc.Procs[slowCompactionNodeIndex].Restart(ctx)
// Wait until the node finished compaction and the leader finished compaction hash check // Wait until the node finished compaction and the leader finished compaction hash check
_, err = epc.Procs[slowCompactionNodeIndex].Logs().ExpectWithContext(ctx, "finished scheduled compaction") _, err = epc.Procs[slowCompactionNodeIndex].Logs().ExpectWithContext(ctx, expect.ExpectedResponse{Value: "finished scheduled compaction"})
require.NoError(t, err, "can't get log indicating finished scheduled compaction") require.NoError(t, err, "can't get log indicating finished scheduled compaction")
leaderIndex := epc.WaitLeader(t) leaderIndex := epc.WaitLeader(t)
_, err = epc.Procs[leaderIndex].Logs().ExpectWithContext(ctx, "finished compaction hash check") _, err = epc.Procs[leaderIndex].Logs().ExpectWithContext(ctx, expect.ExpectedResponse{Value: "finished compaction hash check"})
require.NoError(t, err, "can't get log indicating finished compaction hash check") require.NoError(t, err, "can't get log indicating finished compaction hash check")
alarmResponse, err := cc.AlarmList(ctx) alarmResponse, err := cc.AlarmList(ctx)

View File

@ -21,6 +21,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
) )
@ -58,25 +59,25 @@ func authEnable(cx ctlCtx) error {
func ctlV3AuthEnable(cx ctlCtx) error { func ctlV3AuthEnable(cx ctlCtx) error {
cmdArgs := append(cx.PrefixArgs(), "auth", "enable") cmdArgs := append(cx.PrefixArgs(), "auth", "enable")
return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, "Authentication Enabled") return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: "Authentication Enabled"})
} }
func ctlV3PutFailPerm(cx ctlCtx, key, val string) error { func ctlV3PutFailPerm(cx ctlCtx, key, val string) error {
return e2e.SpawnWithExpectWithEnv(append(cx.PrefixArgs(), "put", key, val), cx.envMap, "permission denied") return e2e.SpawnWithExpectWithEnv(append(cx.PrefixArgs(), "put", key, val), cx.envMap, expect.ExpectedResponse{Value: "permission denied"})
} }
func authSetupTestUser(cx ctlCtx) { func authSetupTestUser(cx ctlCtx) {
if err := ctlV3User(cx, []string{"add", "test-user", "--interactive=false"}, "User test-user created", []string{"pass"}); err != nil { if err := ctlV3User(cx, []string{"add", "test-user", "--interactive=false"}, "User test-user created", []string{"pass"}); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
if err := e2e.SpawnWithExpectWithEnv(append(cx.PrefixArgs(), "role", "add", "test-role"), cx.envMap, "Role test-role created"); err != nil { if err := e2e.SpawnWithExpectWithEnv(append(cx.PrefixArgs(), "role", "add", "test-role"), cx.envMap, expect.ExpectedResponse{Value: "Role test-role created"}); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
if err := ctlV3User(cx, []string{"grant-role", "test-user", "test-role"}, "Role test-role is granted to user test-user", nil); err != nil { if err := ctlV3User(cx, []string{"grant-role", "test-user", "test-role"}, "Role test-role is granted to user test-user", nil); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
cmd := append(cx.PrefixArgs(), "role", "grant-permission", "test-role", "readwrite", "foo") cmd := append(cx.PrefixArgs(), "role", "grant-permission", "test-role", "readwrite", "foo")
if err := e2e.SpawnWithExpectWithEnv(cmd, cx.envMap, "Role test-role updated"); err != nil { if err := e2e.SpawnWithExpectWithEnv(cmd, cx.envMap, expect.ExpectedResponse{Value: "Role test-role updated"}); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
} }
@ -118,7 +119,7 @@ func authTestCertCN(cx ctlCtx) {
if err := ctlV3User(cx, []string{"add", "example.com", "--interactive=false"}, "User example.com created", []string{""}); err != nil { if err := ctlV3User(cx, []string{"add", "example.com", "--interactive=false"}, "User example.com created", []string{""}); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
if err := e2e.SpawnWithExpectWithEnv(append(cx.PrefixArgs(), "role", "add", "test-role"), cx.envMap, "Role test-role created"); err != nil { if err := e2e.SpawnWithExpectWithEnv(append(cx.PrefixArgs(), "role", "add", "test-role"), cx.envMap, expect.ExpectedResponse{Value: "Role test-role created"}); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
if err := ctlV3User(cx, []string{"grant-role", "example.com", "test-role"}, "Role test-role is granted to user example.com", nil); err != nil { if err := ctlV3User(cx, []string{"grant-role", "example.com", "test-role"}, "Role test-role is granted to user example.com", nil); err != nil {
@ -379,7 +380,7 @@ func certCNAndUsername(cx ctlCtx, noPassword bool) {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
} }
if err := e2e.SpawnWithExpectWithEnv(append(cx.PrefixArgs(), "role", "add", "test-role-cn"), cx.envMap, "Role test-role-cn created"); err != nil { if err := e2e.SpawnWithExpectWithEnv(append(cx.PrefixArgs(), "role", "add", "test-role-cn"), cx.envMap, expect.ExpectedResponse{Value: "Role test-role-cn created"}); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
if err := ctlV3User(cx, []string{"grant-role", "example.com", "test-role-cn"}, "Role test-role-cn is granted to user example.com", nil); err != nil { if err := ctlV3User(cx, []string{"grant-role", "example.com", "test-role-cn"}, "Role test-role-cn is granted to user example.com", nil); err != nil {
@ -428,9 +429,9 @@ func authTestCertCNAndUsernameNoPassword(cx ctlCtx) {
func ctlV3EndpointHealth(cx ctlCtx) error { func ctlV3EndpointHealth(cx ctlCtx) error {
cmdArgs := append(cx.PrefixArgs(), "endpoint", "health") cmdArgs := append(cx.PrefixArgs(), "endpoint", "health")
lines := make([]string, cx.epc.Cfg.ClusterSize) lines := make([]expect.ExpectedResponse, cx.epc.Cfg.ClusterSize)
for i := range lines { for i := range lines {
lines[i] = "is healthy" lines[i] = expect.ExpectedResponse{Value: "is healthy"}
} }
return e2e.SpawnWithExpects(cmdArgs, cx.envMap, lines...) return e2e.SpawnWithExpects(cmdArgs, cx.envMap, lines...)
} }

View File

@ -17,6 +17,7 @@ package e2e
import ( import (
"testing" "testing"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
) )
@ -35,16 +36,16 @@ func maintenanceInitKeys(cx ctlCtx) {
func ctlV3OnlineDefrag(cx ctlCtx) error { func ctlV3OnlineDefrag(cx ctlCtx) error {
cmdArgs := append(cx.PrefixArgs(), "defrag") cmdArgs := append(cx.PrefixArgs(), "defrag")
lines := make([]string, cx.epc.Cfg.ClusterSize) lines := make([]expect.ExpectedResponse, cx.epc.Cfg.ClusterSize)
for i := range lines { for i := range lines {
lines[i] = "Finished defragmenting etcd member" lines[i] = expect.ExpectedResponse{Value: "Finished defragmenting etcd member"}
} }
return e2e.SpawnWithExpects(cmdArgs, cx.envMap, lines...) return e2e.SpawnWithExpects(cmdArgs, cx.envMap, lines...)
} }
func ctlV3OfflineDefrag(cx ctlCtx) error { func ctlV3OfflineDefrag(cx ctlCtx) error {
cmdArgs := append(cx.PrefixArgsUtl(), "defrag", "--data-dir", cx.dataDir) cmdArgs := append(cx.PrefixArgsUtl(), "defrag", "--data-dir", cx.dataDir)
lines := []string{"finished defragmenting directory"} lines := []expect.ExpectedResponse{{Value: "finished defragmenting directory"}}
return e2e.SpawnWithExpects(cmdArgs, cx.envMap, lines...) return e2e.SpawnWithExpects(cmdArgs, cx.envMap, lines...)
} }

View File

@ -26,6 +26,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/config" "go.etcd.io/etcd/tests/v3/framework/config"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
"go.etcd.io/etcd/tests/v3/framework/testutils" "go.etcd.io/etcd/tests/v3/framework/testutils"
@ -160,7 +161,7 @@ func templateEndpoints(t *testing.T, pattern string, clus *e2e.EtcdProcessCluste
func assertAuthority(t *testing.T, expectAuthorityPattern string, clus *e2e.EtcdProcessCluster) { func assertAuthority(t *testing.T, expectAuthorityPattern string, clus *e2e.EtcdProcessCluster) {
for i := range clus.Procs { for i := range clus.Procs {
line, _ := clus.Procs[i].Logs().ExpectWithContext(context.TODO(), `http2: decoded hpack field header field ":authority"`) line, _ := clus.Procs[i].Logs().ExpectWithContext(context.TODO(), expect.ExpectedResponse{Value: `http2: decoded hpack field header field ":authority"`})
line = strings.TrimSuffix(line, "\n") line = strings.TrimSuffix(line, "\n")
line = strings.TrimSuffix(line, "\r") line = strings.TrimSuffix(line, "\r")

View File

@ -22,6 +22,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
) )
@ -178,7 +179,7 @@ func getFormatTest(cx ctlCtx) {
cmdArgs = append(cmdArgs, "--print-value-only") cmdArgs = append(cmdArgs, "--print-value-only")
} }
cmdArgs = append(cmdArgs, "abc") cmdArgs = append(cmdArgs, "abc")
if err := e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, tt.wstr); err != nil { if err := e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: tt.wstr}); err != nil {
cx.t.Errorf("#%d: error (%v), wanted %v", i, err, tt.wstr) cx.t.Errorf("#%d: error (%v), wanted %v", i, err, tt.wstr)
} }
} }
@ -216,28 +217,28 @@ func getKeysOnlyTest(cx ctlCtx) {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
cmdArgs := append(cx.PrefixArgs(), []string{"get", "--keys-only", "key"}...) cmdArgs := append(cx.PrefixArgs(), []string{"get", "--keys-only", "key"}...)
if err := e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, "key"); err != nil { if err := e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: "key"}); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
lines, err := e2e.SpawnWithExpectLines(ctx, cmdArgs, cx.envMap, "key") lines, err := e2e.SpawnWithExpectLines(ctx, cmdArgs, cx.envMap, expect.ExpectedResponse{Value: "key"})
require.NoError(cx.t, err) require.NoError(cx.t, err)
require.NotContains(cx.t, lines, "val", "got value but passed --keys-only") require.NotContains(cx.t, lines, "val", "got value but passed --keys-only")
} }
func getCountOnlyTest(cx ctlCtx) { func getCountOnlyTest(cx ctlCtx) {
cmdArgs := append(cx.PrefixArgs(), []string{"get", "--count-only", "key", "--prefix", "--write-out=fields"}...) cmdArgs := append(cx.PrefixArgs(), []string{"get", "--count-only", "key", "--prefix", "--write-out=fields"}...)
if err := e2e.SpawnWithExpects(cmdArgs, cx.envMap, "\"Count\" : 0"); err != nil { if err := e2e.SpawnWithExpects(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: "\"Count\" : 0"}); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
if err := ctlV3Put(cx, "key", "val", ""); err != nil { if err := ctlV3Put(cx, "key", "val", ""); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
cmdArgs = append(cx.PrefixArgs(), []string{"get", "--count-only", "key", "--prefix", "--write-out=fields"}...) cmdArgs = append(cx.PrefixArgs(), []string{"get", "--count-only", "key", "--prefix", "--write-out=fields"}...)
if err := e2e.SpawnWithExpects(cmdArgs, cx.envMap, "\"Count\" : 1"); err != nil { if err := e2e.SpawnWithExpects(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: "\"Count\" : 1"}); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
if err := ctlV3Put(cx, "key1", "val", ""); err != nil { if err := ctlV3Put(cx, "key1", "val", ""); err != nil {
@ -247,14 +248,14 @@ func getCountOnlyTest(cx ctlCtx) {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
cmdArgs = append(cx.PrefixArgs(), []string{"get", "--count-only", "key", "--prefix", "--write-out=fields"}...) cmdArgs = append(cx.PrefixArgs(), []string{"get", "--count-only", "key", "--prefix", "--write-out=fields"}...)
if err := e2e.SpawnWithExpects(cmdArgs, cx.envMap, "\"Count\" : 2"); err != nil { if err := e2e.SpawnWithExpects(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: "\"Count\" : 2"}); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
if err := ctlV3Put(cx, "key2", "val", ""); err != nil { if err := ctlV3Put(cx, "key2", "val", ""); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
cmdArgs = append(cx.PrefixArgs(), []string{"get", "--count-only", "key", "--prefix", "--write-out=fields"}...) cmdArgs = append(cx.PrefixArgs(), []string{"get", "--count-only", "key", "--prefix", "--write-out=fields"}...)
if err := e2e.SpawnWithExpects(cmdArgs, cx.envMap, "\"Count\" : 3"); err != nil { if err := e2e.SpawnWithExpects(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: "\"Count\" : 3"}); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
@ -262,7 +263,7 @@ func getCountOnlyTest(cx ctlCtx) {
defer cancel() defer cancel()
cmdArgs = append(cx.PrefixArgs(), []string{"get", "--count-only", "key3", "--prefix", "--write-out=fields"}...) cmdArgs = append(cx.PrefixArgs(), []string{"get", "--count-only", "key3", "--prefix", "--write-out=fields"}...)
lines, err := e2e.SpawnWithExpectLines(ctx, cmdArgs, cx.envMap, "\"Count\"") lines, err := e2e.SpawnWithExpectLines(ctx, cmdArgs, cx.envMap, expect.ExpectedResponse{Value: "\"Count\""})
require.NoError(cx.t, err) require.NoError(cx.t, err)
require.NotContains(cx.t, lines, "\"Count\" : 3") require.NotContains(cx.t, lines, "\"Count\" : 3")
} }
@ -341,7 +342,7 @@ func ctlV3Put(cx ctlCtx, key, value, leaseID string, flags ...string) error {
if len(flags) != 0 { if len(flags) != 0 {
cmdArgs = append(cmdArgs, flags...) cmdArgs = append(cmdArgs, flags...)
} }
return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, "OK") return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: "OK"})
} }
type kv struct { type kv struct {
@ -354,25 +355,15 @@ func ctlV3Get(cx ctlCtx, args []string, kvs ...kv) error {
if !cx.quorum { if !cx.quorum {
cmdArgs = append(cmdArgs, "--consistency", "s") cmdArgs = append(cmdArgs, "--consistency", "s")
} }
var lines []string var lines []expect.ExpectedResponse
for _, elem := range kvs { for _, elem := range kvs {
lines = append(lines, elem.key, elem.val) lines = append(lines, expect.ExpectedResponse{Value: elem.key}, expect.ExpectedResponse{Value: elem.val})
} }
return e2e.SpawnWithExpects(cmdArgs, cx.envMap, lines...) return e2e.SpawnWithExpects(cmdArgs, cx.envMap, lines...)
} }
// ctlV3GetWithErr runs "get" command expecting no output but error
func ctlV3GetWithErr(cx ctlCtx, args []string, errs []string) error {
cmdArgs := append(cx.PrefixArgs(), "get")
cmdArgs = append(cmdArgs, args...)
if !cx.quorum {
cmdArgs = append(cmdArgs, "--consistency", "s")
}
return e2e.SpawnWithExpects(cmdArgs, cx.envMap, errs...)
}
func ctlV3Del(cx ctlCtx, args []string, num int) error { func ctlV3Del(cx ctlCtx, args []string, num int) error {
cmdArgs := append(cx.PrefixArgs(), "del") cmdArgs := append(cx.PrefixArgs(), "del")
cmdArgs = append(cmdArgs, args...) cmdArgs = append(cmdArgs, args...)
return e2e.SpawnWithExpects(cmdArgs, cx.envMap, fmt.Sprintf("%d", num)) return e2e.SpawnWithExpects(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: fmt.Sprintf("%d", num)})
} }

View File

@ -20,6 +20,7 @@ import (
"strings" "strings"
"testing" "testing"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
) )
@ -93,7 +94,7 @@ func ctlV3LeaseKeepAlive(cx ctlCtx, leaseID string) error {
func ctlV3LeaseRevoke(cx ctlCtx, leaseID string) error { func ctlV3LeaseRevoke(cx ctlCtx, leaseID string) error {
cmdArgs := append(cx.PrefixArgs(), "lease", "revoke", leaseID) cmdArgs := append(cx.PrefixArgs(), "lease", "revoke", leaseID)
return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, fmt.Sprintf("lease %s revoked", leaseID)) return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: fmt.Sprintf("lease %s revoked", leaseID)})
} }
func ctlV3LeaseTimeToLive(cx ctlCtx, leaseID string, withKeys bool) error { func ctlV3LeaseTimeToLive(cx ctlCtx, leaseID string, withKeys bool) error {
@ -101,5 +102,5 @@ func ctlV3LeaseTimeToLive(cx ctlCtx, leaseID string, withKeys bool) error {
if withKeys { if withKeys {
cmdArgs = append(cmdArgs, "--keys") cmdArgs = append(cmdArgs, "--keys")
} }
return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, fmt.Sprintf("lease %s granted with", leaseID)) return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: fmt.Sprintf("lease %s granted with", leaseID)})
} }

View File

@ -114,16 +114,16 @@ func testLock(cx ctlCtx) {
func testLockWithCmd(cx ctlCtx) { func testLockWithCmd(cx ctlCtx) {
// exec command with zero exit code // exec command with zero exit code
echoCmd := []string{"echo"} echoCmd := []string{"echo"}
if err := ctlV3LockWithCmd(cx, echoCmd, ""); err != nil { if err := ctlV3LockWithCmd(cx, echoCmd, expect.ExpectedResponse{Value: ""}); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
// exec command with non-zero exit code // exec command with non-zero exit code
code := 3 code := 3
awkCmd := []string{"awk", fmt.Sprintf("BEGIN{exit %d}", code)} awkCmd := []string{"awk", fmt.Sprintf("BEGIN{exit %d}", code)}
expect := fmt.Sprintf("Error: exit status %d", code) expect := expect.ExpectedResponse{Value: fmt.Sprintf("Error: exit status %d", code)}
err := ctlV3LockWithCmd(cx, awkCmd, expect) err := ctlV3LockWithCmd(cx, awkCmd, expect)
require.ErrorContains(cx.t, err, expect) require.ErrorContains(cx.t, err, expect.Value)
} }
// ctlV3Lock creates a lock process with a channel listening for when it acquires the lock. // ctlV3Lock creates a lock process with a channel listening for when it acquires the lock.
@ -149,7 +149,7 @@ func ctlV3Lock(cx ctlCtx, name string) (*expect.ExpectProcess, <-chan string, er
} }
// ctlV3LockWithCmd creates a lock process to exec command. // ctlV3LockWithCmd creates a lock process to exec command.
func ctlV3LockWithCmd(cx ctlCtx, execCmd []string, as ...string) error { func ctlV3LockWithCmd(cx ctlCtx, execCmd []string, as ...expect.ExpectedResponse) error {
// use command as lock name // use command as lock name
cmdArgs := append(cx.PrefixArgs(), "lock", execCmd[0]) cmdArgs := append(cx.PrefixArgs(), "lock", execCmd[0])
cmdArgs = append(cmdArgs, execCmd...) cmdArgs = append(cmdArgs, execCmd...)

View File

@ -25,6 +25,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.etcd.io/etcd/api/v3/etcdserverpb" "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
) )
@ -75,9 +76,9 @@ func memberListSerializableTest(cx ctlCtx) {
func ctlV3MemberList(cx ctlCtx) error { func ctlV3MemberList(cx ctlCtx) error {
cmdArgs := append(cx.PrefixArgs(), "member", "list") cmdArgs := append(cx.PrefixArgs(), "member", "list")
lines := make([]string, cx.cfg.ClusterSize) lines := make([]expect.ExpectedResponse, cx.cfg.ClusterSize)
for i := range lines { for i := range lines {
lines[i] = "started" lines[i] = expect.ExpectedResponse{Value: "started"}
} }
return e2e.SpawnWithExpects(cmdArgs, cx.envMap, lines...) return e2e.SpawnWithExpects(cmdArgs, cx.envMap, lines...)
} }
@ -162,7 +163,7 @@ func memberListWithHexTest(cx ctlCtx) {
func ctlV3MemberRemove(cx ctlCtx, ep, memberID, clusterID string) error { func ctlV3MemberRemove(cx ctlCtx, ep, memberID, clusterID string) error {
cmdArgs := append(cx.prefixArgs([]string{ep}), "member", "remove", memberID) cmdArgs := append(cx.prefixArgs([]string{ep}), "member", "remove", memberID)
return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, fmt.Sprintf("%s removed from cluster %s", memberID, clusterID)) return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: fmt.Sprintf("%s removed from cluster %s", memberID, clusterID)})
} }
func memberAddTest(cx ctlCtx) { func memberAddTest(cx ctlCtx) {
@ -186,7 +187,7 @@ func ctlV3MemberAdd(cx ctlCtx, peerURL string, isLearner bool) error {
cmdArgs = append(cmdArgs, "--learner") cmdArgs = append(cmdArgs, "--learner")
asLearner = " as learner " asLearner = " as learner "
} }
return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, fmt.Sprintf(" added%sto cluster ", asLearner)) return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: fmt.Sprintf(" added%sto cluster ", asLearner)})
} }
func memberUpdateTest(cx ctlCtx) { func memberUpdateTest(cx ctlCtx) {
@ -204,5 +205,5 @@ func memberUpdateTest(cx ctlCtx) {
func ctlV3MemberUpdate(cx ctlCtx, memberID, peerURL string) error { func ctlV3MemberUpdate(cx ctlCtx, memberID, peerURL string) error {
cmdArgs := append(cx.PrefixArgs(), "member", "update", memberID, fmt.Sprintf("--peer-urls=%s", peerURL)) cmdArgs := append(cx.PrefixArgs(), "member", "update", memberID, fmt.Sprintf("--peer-urls=%s", peerURL))
return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, " updated in cluster ") return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: " updated in cluster "})
} }

View File

@ -26,6 +26,7 @@ import (
"go.etcd.io/etcd/client/pkg/v3/transport" "go.etcd.io/etcd/client/pkg/v3/transport"
"go.etcd.io/etcd/client/pkg/v3/types" "go.etcd.io/etcd/client/pkg/v3/types"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
) )
@ -135,7 +136,7 @@ func testCtlV3MoveLeader(t *testing.T, cfg e2e.EtcdProcessClusterConfig, envVars
for i, tc := range tests { for i, tc := range tests {
prefix := cx.prefixArgs(tc.eps) prefix := cx.prefixArgs(tc.eps)
cmdArgs := append(prefix, "move-leader", types.ID(transferee).String()) cmdArgs := append(prefix, "move-leader", types.ID(transferee).String())
err := e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, tc.expect) err := e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: tc.expect})
if tc.expectErr { if tc.expectErr {
require.ErrorContains(t, err, tc.expect) require.ErrorContains(t, err, tc.expect)
} else { } else {

View File

@ -18,6 +18,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
) )
@ -55,7 +56,7 @@ func ctlV3Role(cx ctlCtx, args []string, expStr string) error {
cmdArgs := append(cx.PrefixArgs(), "role") cmdArgs := append(cx.PrefixArgs(), "role")
cmdArgs = append(cmdArgs, args...) cmdArgs = append(cmdArgs, args...)
return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expStr) return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: expStr})
} }
func ctlV3RoleGrantPermission(cx ctlCtx, rolename string, perm grantingPerm) error { func ctlV3RoleGrantPermission(cx ctlCtx, rolename string, perm grantingPerm) error {

View File

@ -94,7 +94,7 @@ func snapshotCorruptTest(cx ctlCtx) {
"--data-dir", datadir, "--data-dir", datadir,
fpath), fpath),
cx.envMap, cx.envMap,
"expected sha256") expect.ExpectedResponse{Value: "expected sha256"})
require.ErrorContains(cx.t, serr, "Error: expected sha256") require.ErrorContains(cx.t, serr, "Error: expected sha256")
} }
@ -125,7 +125,7 @@ func snapshotStatusBeforeRestoreTest(cx ctlCtx) {
"--data-dir", dataDir, "--data-dir", dataDir,
fpath), fpath),
cx.envMap, cx.envMap,
"added member") expect.ExpectedResponse{Value: "added member"})
if serr != nil { if serr != nil {
cx.t.Fatal(serr) cx.t.Fatal(serr)
} }
@ -133,7 +133,7 @@ func snapshotStatusBeforeRestoreTest(cx ctlCtx) {
func ctlV3SnapshotSave(cx ctlCtx, fpath string) error { func ctlV3SnapshotSave(cx ctlCtx, fpath string) error {
cmdArgs := append(cx.PrefixArgs(), "snapshot", "save", fpath) cmdArgs := append(cx.PrefixArgs(), "snapshot", "save", fpath)
return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, fmt.Sprintf("Snapshot saved at %s", fpath)) return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: fmt.Sprintf("Snapshot saved at %s", fpath)})
} }
func getSnapshotStatus(cx ctlCtx, fpath string) (snapshot.Status, error) { func getSnapshotStatus(cx ctlCtx, fpath string) (snapshot.Status, error) {
@ -194,7 +194,7 @@ func testIssue6361(t *testing.T) {
t.Log("Writing some keys...") t.Log("Writing some keys...")
kvs := []kv{{"foo1", "val1"}, {"foo2", "val2"}, {"foo3", "val3"}} kvs := []kv{{"foo1", "val1"}, {"foo2", "val2"}, {"foo3", "val3"}}
for i := range kvs { for i := range kvs {
if err = e2e.SpawnWithExpect(append(prefixArgs, "put", kvs[i].key, kvs[i].val), "OK"); err != nil { if err = e2e.SpawnWithExpect(append(prefixArgs, "put", kvs[i].key, kvs[i].val), expect.ExpectedResponse{Value: "OK"}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
@ -204,7 +204,7 @@ func testIssue6361(t *testing.T) {
t.Log("etcdctl saving snapshot...") t.Log("etcdctl saving snapshot...")
if err = e2e.SpawnWithExpects(append(prefixArgs, "snapshot", "save", fpath), if err = e2e.SpawnWithExpects(append(prefixArgs, "snapshot", "save", fpath),
nil, nil,
fmt.Sprintf("Snapshot saved at %s", fpath), expect.ExpectedResponse{Value: fmt.Sprintf("Snapshot saved at %s", fpath)},
); err != nil { ); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -216,7 +216,14 @@ func testIssue6361(t *testing.T) {
newDataDir := filepath.Join(t.TempDir(), "test.data") newDataDir := filepath.Join(t.TempDir(), "test.data")
t.Log("etcdctl restoring the snapshot...") t.Log("etcdctl restoring the snapshot...")
err = e2e.SpawnWithExpect([]string{e2e.BinPath.Etcdutl, "snapshot", "restore", fpath, "--name", epc.Procs[0].Config().Name, "--initial-cluster", epc.Procs[0].Config().InitialCluster, "--initial-cluster-token", epc.Procs[0].Config().InitialToken, "--initial-advertise-peer-urls", epc.Procs[0].Config().PeerURL.String(), "--data-dir", newDataDir}, "added member") err = e2e.SpawnWithExpect([]string{
e2e.BinPath.Etcdutl, "snapshot", "restore", fpath,
"--name", epc.Procs[0].Config().Name,
"--initial-cluster", epc.Procs[0].Config().InitialCluster,
"--initial-cluster-token", epc.Procs[0].Config().InitialToken,
"--initial-advertise-peer-urls", epc.Procs[0].Config().PeerURL.String(),
"--data-dir", newDataDir},
expect.ExpectedResponse{Value: "added member"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -234,7 +241,7 @@ func testIssue6361(t *testing.T) {
t.Log("Ensuring the restored member has the correct data...") t.Log("Ensuring the restored member has the correct data...")
for i := range kvs { for i := range kvs {
if err = e2e.SpawnWithExpect(append(prefixArgs, "get", kvs[i].key), kvs[i].val); err != nil { if err = e2e.SpawnWithExpect(append(prefixArgs, "get", kvs[i].key), expect.ExpectedResponse{Value: kvs[i].val}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
@ -242,7 +249,7 @@ func testIssue6361(t *testing.T) {
t.Log("Adding new member into the cluster") t.Log("Adding new member into the cluster")
clientURL := fmt.Sprintf("http://localhost:%d", e2e.EtcdProcessBasePort+30) clientURL := fmt.Sprintf("http://localhost:%d", e2e.EtcdProcessBasePort+30)
peerURL := fmt.Sprintf("http://localhost:%d", e2e.EtcdProcessBasePort+31) peerURL := fmt.Sprintf("http://localhost:%d", e2e.EtcdProcessBasePort+31)
err = e2e.SpawnWithExpect(append(prefixArgs, "member", "add", "newmember", fmt.Sprintf("--peer-urls=%s", peerURL)), " added to cluster ") err = e2e.SpawnWithExpect(append(prefixArgs, "member", "add", "newmember", fmt.Sprintf("--peer-urls=%s", peerURL)), expect.ExpectedResponse{Value: " added to cluster "})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -271,7 +278,7 @@ func testIssue6361(t *testing.T) {
t.Log("Ensuring added member has data from incoming snapshot...") t.Log("Ensuring added member has data from incoming snapshot...")
for i := range kvs { for i := range kvs {
if err = e2e.SpawnWithExpect(append(prefixArgs, "get", kvs[i].key), kvs[i].val); err != nil { if err = e2e.SpawnWithExpect(append(prefixArgs, "get", kvs[i].key), expect.ExpectedResponse{Value: kvs[i].val}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
@ -350,7 +357,7 @@ func TestRestoreCompactionRevBump(t *testing.T) {
t.Log("etcdctl saving snapshot...") t.Log("etcdctl saving snapshot...")
cmdPrefix := []string{e2e.BinPath.Etcdctl, "--endpoints", strings.Join(epc.EndpointsGRPC(), ",")} cmdPrefix := []string{e2e.BinPath.Etcdctl, "--endpoints", strings.Join(epc.EndpointsGRPC(), ",")}
require.NoError(t, e2e.SpawnWithExpects(append(cmdPrefix, "snapshot", "save", fpath), nil, fmt.Sprintf("Snapshot saved at %s", fpath))) require.NoError(t, e2e.SpawnWithExpects(append(cmdPrefix, "snapshot", "save", fpath), nil, expect.ExpectedResponse{Value: fmt.Sprintf("Snapshot saved at %s", fpath)}))
// add some more kvs that are not in the snapshot that will be lost after restore // add some more kvs that are not in the snapshot that will be lost after restore
unsnappedKVs := []testutils.KV{{Key: "unsnapped1", Val: "one"}, {Key: "unsnapped2", Val: "two"}, {Key: "unsnapped3", Val: "three"}} unsnappedKVs := []testutils.KV{{Key: "unsnapped1", Val: "one"}, {Key: "unsnapped2", Val: "two"}, {Key: "unsnapped3", Val: "three"}}
@ -378,7 +385,7 @@ func TestRestoreCompactionRevBump(t *testing.T) {
"--bump-revision", fmt.Sprintf("%d", bumpAmount), "--bump-revision", fmt.Sprintf("%d", bumpAmount),
"--mark-compacted", "--mark-compacted",
"--data-dir", newDataDir, "--data-dir", newDataDir,
}, "added member") }, expect.ExpectedResponse{Value: "added member"})
require.NoError(t, err) require.NoError(t, err)
t.Log("(Re)starting the etcd member using the restored snapshot...") t.Log("(Re)starting the etcd member using the restored snapshot...")

View File

@ -26,6 +26,7 @@ import (
"go.etcd.io/etcd/api/v3/version" "go.etcd.io/etcd/api/v3/version"
"go.etcd.io/etcd/client/pkg/v3/testutil" "go.etcd.io/etcd/client/pkg/v3/testutil"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/pkg/v3/flags" "go.etcd.io/etcd/pkg/v3/flags"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
) )
@ -88,7 +89,7 @@ func versionTest(cx ctlCtx) {
func clusterVersionTest(cx ctlCtx, expected string) { func clusterVersionTest(cx ctlCtx, expected string) {
var err error var err error
for i := 0; i < 35; i++ { for i := 0; i < 35; i++ {
if err = e2e.CURLGet(cx.epc, e2e.CURLReq{Endpoint: "/version", Expected: expected}); err != nil { if err = e2e.CURLGet(cx.epc, e2e.CURLReq{Endpoint: "/version", Expected: expect.ExpectedResponse{Value: expected}}); err != nil {
cx.t.Logf("#%d: v3 is not ready yet (%v)", i, err) cx.t.Logf("#%d: v3 is not ready yet (%v)", i, err)
time.Sleep(200 * time.Millisecond) time.Sleep(200 * time.Millisecond)
continue continue
@ -102,7 +103,7 @@ func clusterVersionTest(cx ctlCtx, expected string) {
func ctlV3Version(cx ctlCtx) error { func ctlV3Version(cx ctlCtx) error {
cmdArgs := append(cx.PrefixArgs(), "version") cmdArgs := append(cx.PrefixArgs(), "version")
return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, version.Version) return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: version.Version})
} }
// TestCtlV3DialWithHTTPScheme ensures that client handles Endpoints with HTTPS scheme. // TestCtlV3DialWithHTTPScheme ensures that client handles Endpoints with HTTPS scheme.
@ -112,7 +113,7 @@ func TestCtlV3DialWithHTTPScheme(t *testing.T) {
func dialWithSchemeTest(cx ctlCtx) { func dialWithSchemeTest(cx ctlCtx) {
cmdArgs := append(cx.prefixArgs(cx.epc.EndpointsGRPC()), "put", "foo", "bar") cmdArgs := append(cx.prefixArgs(cx.epc.EndpointsGRPC()), "put", "foo", "bar")
if err := e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, "OK"); err != nil { if err := e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: "OK"}); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
} }

View File

@ -26,6 +26,7 @@ import (
"go.etcd.io/etcd/client/pkg/v3/testutil" "go.etcd.io/etcd/client/pkg/v3/testutil"
"go.etcd.io/etcd/client/pkg/v3/transport" "go.etcd.io/etcd/client/pkg/v3/transport"
"go.etcd.io/etcd/client/v2" "go.etcd.io/etcd/client/v2"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/server/v3/etcdserver/api/rafthttp" "go.etcd.io/etcd/server/v3/etcdserver/api/rafthttp"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
"go.etcd.io/etcd/tests/v3/framework/integration" "go.etcd.io/etcd/tests/v3/framework/integration"
@ -73,10 +74,10 @@ func testClusterUsingDiscovery(t *testing.T, size int, peerTLS bool) {
defer c.Close() defer c.Close()
kubectl := []string{e2e.BinPath.Etcdctl, "--endpoints", strings.Join(c.EndpointsGRPC(), ",")} kubectl := []string{e2e.BinPath.Etcdctl, "--endpoints", strings.Join(c.EndpointsGRPC(), ",")}
if err := e2e.SpawnWithExpect(append(kubectl, "put", "key", "value"), "OK"); err != nil { if err := e2e.SpawnWithExpect(append(kubectl, "put", "key", "value"), expect.ExpectedResponse{Value: "OK"}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := e2e.SpawnWithExpect(append(kubectl, "get", "key"), "value"); err != nil { if err := e2e.SpawnWithExpect(append(kubectl, "get", "key"), expect.ExpectedResponse{Value: "value"}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }

View File

@ -21,6 +21,7 @@ import (
"strings" "strings"
"testing" "testing"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
) )
@ -76,10 +77,10 @@ func testClusterUsingV3Discovery(t *testing.T, discoveryClusterSize, targetClust
// step 4: sanity test on the etcd cluster // step 4: sanity test on the etcd cluster
etcdctl := []string{e2e.BinPath.Etcdctl, "--endpoints", strings.Join(epc.EndpointsGRPC(), ",")} etcdctl := []string{e2e.BinPath.Etcdctl, "--endpoints", strings.Join(epc.EndpointsGRPC(), ",")}
if err := e2e.SpawnWithExpect(append(etcdctl, "put", "key", "value"), "OK"); err != nil { if err := e2e.SpawnWithExpect(append(etcdctl, "put", "key", "value"), expect.ExpectedResponse{Value: "OK"}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := e2e.SpawnWithExpect(append(etcdctl, "get", "key"), "value"); err != nil { if err := e2e.SpawnWithExpect(append(etcdctl, "get", "key"), expect.ExpectedResponse{Value: "value"}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }

View File

@ -296,7 +296,7 @@ func TestGrpcproxyAndCommonName(t *testing.T) {
"--cacert", e2e.CaPath, "--cacert", e2e.CaPath,
} }
err := e2e.SpawnWithExpect(argsWithNonEmptyCN, "cert has non empty Common Name") err := e2e.SpawnWithExpect(argsWithNonEmptyCN, expect.ExpectedResponse{Value: "cert has non empty Common Name"})
require.ErrorContains(t, err, "cert has non empty Common Name") require.ErrorContains(t, err, "cert has non empty Common Name")
p, err := e2e.SpawnCmd(argsWithEmptyCN, nil) p, err := e2e.SpawnCmd(argsWithEmptyCN, nil)

View File

@ -23,6 +23,7 @@ import (
"go.etcd.io/etcd/api/v3/version" "go.etcd.io/etcd/api/v3/version"
"go.etcd.io/etcd/client/pkg/v3/fileutil" "go.etcd.io/etcd/client/pkg/v3/fileutil"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
) )
@ -97,7 +98,7 @@ func TestReleaseUpgrade(t *testing.T) {
// new cluster version needs more time to upgrade // new cluster version needs more time to upgrade
ver := version.Cluster(version.Version) ver := version.Cluster(version.Version)
for i := 0; i < 7; i++ { for i := 0; i < 7; i++ {
if err = e2e.CURLGet(epc, e2e.CURLReq{Endpoint: "/version", Expected: `"etcdcluster":"` + ver}); err != nil { if err = e2e.CURLGet(epc, e2e.CURLReq{Endpoint: "/version", Expected: expect.ExpectedResponse{Value: `"etcdcluster":"` + ver}}); err != nil {
t.Logf("#%d: %v is not ready yet (%v)", i, ver, err) t.Logf("#%d: %v is not ready yet (%v)", i, ver, err)
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue

View File

@ -42,7 +42,7 @@ func TestGateway(t *testing.T) {
p.Close() p.Close()
}() }()
err = e2e.SpawnWithExpect([]string{e2e.BinPath.Etcdctl, "--endpoints=" + defaultGatewayEndpoint, "put", "foo", "bar"}, "OK\r\n") err = e2e.SpawnWithExpect([]string{e2e.BinPath.Etcdctl, "--endpoints=" + defaultGatewayEndpoint, "put", "foo", "bar"}, expect.ExpectedResponse{Value: "OK\r\n"})
if err != nil { if err != nil {
t.Errorf("failed to finish put request through gateway: %v", err) t.Errorf("failed to finish put request through gateway: %v", err)
} }

View File

@ -19,6 +19,7 @@ import (
"testing" "testing"
"go.etcd.io/etcd/api/v3/version" "go.etcd.io/etcd/api/v3/version"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
) )
@ -63,7 +64,7 @@ func metricsTest(cx ctlCtx) {
if err := ctlV3Watch(cx, []string{"k", "--rev", "1"}, []kvExec{{key: "k", val: "v"}}...); err != nil { if err := ctlV3Watch(cx, []string{"k", "--rev", "1"}, []kvExec{{key: "k", val: "v"}}...); err != nil {
cx.t.Fatal(err) cx.t.Fatal(err)
} }
if err := e2e.CURLGet(cx.epc, e2e.CURLReq{Endpoint: test.endpoint, Expected: test.expected}); err != nil { if err := e2e.CURLGet(cx.epc, e2e.CURLReq{Endpoint: test.endpoint, Expected: expect.ExpectedResponse{Value: test.expected}}); err != nil {
cx.t.Fatalf("failed get with curl (%v)", err) cx.t.Fatalf("failed get with curl (%v)", err)
} }
} }

View File

@ -29,6 +29,7 @@ import (
"go.etcd.io/etcd/api/v3/version" "go.etcd.io/etcd/api/v3/version"
"go.etcd.io/etcd/client/pkg/v3/fileutil" "go.etcd.io/etcd/client/pkg/v3/fileutil"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/server/v3/storage/backend" "go.etcd.io/etcd/server/v3/storage/backend"
"go.etcd.io/etcd/server/v3/storage/schema" "go.etcd.io/etcd/server/v3/storage/schema"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
@ -139,7 +140,7 @@ func TestEtctlutlMigrate(t *testing.T) {
t.Log("Write keys to ensure wal snapshot is created and all v3.5 fields are set...") t.Log("Write keys to ensure wal snapshot is created and all v3.5 fields are set...")
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
if err = e2e.SpawnWithExpect(append(prefixArgs, "put", fmt.Sprintf("%d", i), "value"), "OK"); err != nil { if err = e2e.SpawnWithExpect(append(prefixArgs, "put", fmt.Sprintf("%d", i), "value"), expect.ExpectedResponse{Value: "OK"}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
@ -155,7 +156,7 @@ func TestEtctlutlMigrate(t *testing.T) {
if tc.force { if tc.force {
args = append(args, "--force") args = append(args, "--force")
} }
err = e2e.SpawnWithExpect(args, tc.expectLogsSubString) err = e2e.SpawnWithExpect(args, expect.ExpectedResponse{Value: tc.expectLogsSubString})
if err != nil { if err != nil {
if tc.expectLogsSubString != "" { if tc.expectLogsSubString != "" {
require.ErrorContains(t, err, tc.expectLogsSubString) require.ErrorContains(t, err, tc.expectLogsSubString)

View File

@ -29,6 +29,7 @@ import (
"go.uber.org/zap/zaptest" "go.uber.org/zap/zaptest"
"go.etcd.io/etcd/client/pkg/v3/fileutil" "go.etcd.io/etcd/client/pkg/v3/fileutil"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/server/v3/etcdserver" "go.etcd.io/etcd/server/v3/etcdserver"
"go.etcd.io/etcd/server/v3/etcdserver/api/membership" "go.etcd.io/etcd/server/v3/etcdserver/api/membership"
"go.etcd.io/etcd/server/v3/etcdserver/api/snap" "go.etcd.io/etcd/server/v3/etcdserver/api/snap"
@ -58,7 +59,7 @@ func createV2store(t testing.TB, dataDirPath string) string {
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
if err := e2e.CURLPut(epc, e2e.CURLReq{ if err := e2e.CURLPut(epc, e2e.CURLReq{
Endpoint: "/v2/keys/foo", Value: "bar" + fmt.Sprint(i), Endpoint: "/v2/keys/foo", Value: "bar" + fmt.Sprint(i),
Expected: `{"action":"set","node":{"key":"/foo","value":"bar` + fmt.Sprint(i)}); err != nil { Expected: expect.ExpectedResponse{Value: `{"action":"set","node":{"key":"/foo","value":"bar` + fmt.Sprint(i)}}); err != nil {
t.Fatalf("failed put with curl (%v)", err) t.Fatalf("failed put with curl (%v)", err)
} }
} }

View File

@ -23,6 +23,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.etcd.io/etcd/api/v3/version" "go.etcd.io/etcd/api/v3/version"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
) )
@ -49,7 +50,7 @@ func testV3CurlCipherSuites(t *testing.T, valid bool) {
func cipherSuiteTestValid(cx ctlCtx) { func cipherSuiteTestValid(cx ctlCtx) {
if err := e2e.CURLGet(cx.epc, e2e.CURLReq{ if err := e2e.CURLGet(cx.epc, e2e.CURLReq{
Endpoint: "/metrics", Endpoint: "/metrics",
Expected: fmt.Sprintf(`etcd_server_version{server_version="%s"} 1`, version.Version), Expected: expect.ExpectedResponse{Value: fmt.Sprintf(`etcd_server_version{server_version="%s"} 1`, version.Version)},
Ciphers: "ECDHE-RSA-AES128-GCM-SHA256", // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 Ciphers: "ECDHE-RSA-AES128-GCM-SHA256", // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
}); err != nil { }); err != nil {
require.ErrorContains(cx.t, err, fmt.Sprintf(`etcd_server_version{server_version="%s"} 1`, version.Version)) require.ErrorContains(cx.t, err, fmt.Sprintf(`etcd_server_version{server_version="%s"} 1`, version.Version))
@ -59,7 +60,7 @@ func cipherSuiteTestValid(cx ctlCtx) {
func cipherSuiteTestMismatch(cx ctlCtx) { func cipherSuiteTestMismatch(cx ctlCtx) {
err := e2e.CURLGet(cx.epc, e2e.CURLReq{ err := e2e.CURLGet(cx.epc, e2e.CURLReq{
Endpoint: "/metrics", Endpoint: "/metrics",
Expected: "failed setting cipher list", Expected: expect.ExpectedResponse{Value: "failed setting cipher list"},
Ciphers: "ECDHE-RSA-DES-CBC3-SHA", // TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA Ciphers: "ECDHE-RSA-DES-CBC3-SHA", // TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
}) })
require.ErrorContains(cx.t, err, "curl: (59) failed setting cipher list") require.ErrorContains(cx.t, err, "curl: (59) failed setting cipher list")

View File

@ -29,6 +29,7 @@ import (
pb "go.etcd.io/etcd/api/v3/etcdserverpb" pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/client/pkg/v3/testutil" "go.etcd.io/etcd/client/pkg/v3/testutil"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
"go.etcd.io/etcd/tests/v3/framework/testutils" "go.etcd.io/etcd/tests/v3/framework/testutils"
) )
@ -166,7 +167,7 @@ func submitConcurrentWatch(cx ctlCtx, number int, wgDone *sync.WaitGroup, closeC
// make sure that watch request has been created // make sure that watch request has been created
expectedLine := `"created":true}}` expectedLine := `"created":true}}`
_, lerr := proc.ExpectWithContext(context.TODO(), expectedLine) _, lerr := proc.ExpectWithContext(context.TODO(), expect.ExpectedResponse{Value: expectedLine})
if lerr != nil { if lerr != nil {
return fmt.Errorf("%v %v (expected %q). Try EXPECT_DEBUG=TRUE", args, lerr, expectedLine) return fmt.Errorf("%v %v (expected %q). Try EXPECT_DEBUG=TRUE", args, lerr, expectedLine)
} }
@ -213,7 +214,7 @@ func submitRangeAfterConcurrentWatch(cx ctlCtx, expectedValue string) {
} }
cx.t.Log("Submitting range request...") cx.t.Log("Submitting range request...")
if err := e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: "/v3/kv/range", Value: string(rangeData), Expected: expectedValue, Timeout: 5}); err != nil { if err := e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: "/v3/kv/range", Value: string(rangeData), Expected: expect.ExpectedResponse{Value: expectedValue}, Timeout: 5}); err != nil {
require.ErrorContains(cx.t, err, expectedValue) require.ErrorContains(cx.t, err, expectedValue)
} }
cx.t.Log("range request done") cx.t.Log("range request done")

View File

@ -30,6 +30,7 @@ import (
pb "go.etcd.io/etcd/api/v3/etcdserverpb" pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes" "go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
"go.etcd.io/etcd/client/pkg/v3/testutil" "go.etcd.io/etcd/client/pkg/v3/testutil"
"go.etcd.io/etcd/pkg/v3/expect"
epb "go.etcd.io/etcd/server/v3/etcdserver/api/v3election/v3electionpb" epb "go.etcd.io/etcd/server/v3/etcdserver/api/v3election/v3electionpb"
"go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/e2e"
@ -108,14 +109,14 @@ func testV3CurlPutGet(cx ctlCtx) {
p := cx.apiPrefix p := cx.apiPrefix
if err := e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/put"), Value: string(putData), Expected: expectPut}); err != nil { if err := e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/put"), Value: string(putData), Expected: expect.ExpectedResponse{Value: expectPut}}); err != nil {
cx.t.Fatalf("failed testV3CurlPutGet put with curl using prefix (%s) (%v)", p, err) cx.t.Fatalf("failed testV3CurlPutGet put with curl using prefix (%s) (%v)", p, err)
} }
if err := e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/range"), Value: string(rangeData), Expected: expectGet}); err != nil { if err := e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/range"), Value: string(rangeData), Expected: expect.ExpectedResponse{Value: expectGet}}); err != nil {
cx.t.Fatalf("failed testV3CurlPutGet get with curl using prefix (%s) (%v)", p, err) cx.t.Fatalf("failed testV3CurlPutGet get with curl using prefix (%s) (%v)", p, err)
} }
if cx.cfg.Client.ConnectionType == e2e.ClientTLSAndNonTLS { if cx.cfg.Client.ConnectionType == e2e.ClientTLSAndNonTLS {
if err := e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/range"), Value: string(rangeData), Expected: expectGet, IsTLS: true}); err != nil { if err := e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/range"), Value: string(rangeData), Expected: expect.ExpectedResponse{Value: expectGet}, IsTLS: true}); err != nil {
cx.t.Fatalf("failed testV3CurlPutGet get with curl using prefix (%s) (%v)", p, err) cx.t.Fatalf("failed testV3CurlPutGet get with curl using prefix (%s) (%v)", p, err)
} }
} }
@ -139,11 +140,11 @@ func testV3CurlWatch(cx ctlCtx) {
wstr := `{"create_request" : ` + string(wreq) + "}" wstr := `{"create_request" : ` + string(wreq) + "}"
p := cx.apiPrefix p := cx.apiPrefix
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/put"), Value: string(putreq), Expected: "revision"}); err != nil { if err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/put"), Value: string(putreq), Expected: expect.ExpectedResponse{Value: "revision"}}); err != nil {
cx.t.Fatalf("failed testV3CurlWatch put with curl using prefix (%s) (%v)", p, err) cx.t.Fatalf("failed testV3CurlWatch put with curl using prefix (%s) (%v)", p, err)
} }
// expects "bar", timeout after 2 seconds since stream waits forever // expects "bar", timeout after 2 seconds since stream waits forever
err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/watch"), Value: wstr, Expected: `"YmFy"`, Timeout: 2}) err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/watch"), Value: wstr, Expected: expect.ExpectedResponse{Value: `"YmFy"`}, Timeout: 2})
require.ErrorContains(cx.t, err, "unexpected exit code") require.ErrorContains(cx.t, err, "unexpected exit code")
} }
@ -175,13 +176,13 @@ func testV3CurlTxn(cx ctlCtx) {
} }
expected := `"succeeded":true,"responses":[{"response_put":{"header":{"revision":"2"}}}]` expected := `"succeeded":true,"responses":[{"response_put":{"header":{"revision":"2"}}}]`
p := cx.apiPrefix p := cx.apiPrefix
if err := e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/txn"), Value: string(jsonDat), Expected: expected}); err != nil { if err := e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/txn"), Value: string(jsonDat), Expected: expect.ExpectedResponse{Value: expected}}); err != nil {
cx.t.Fatalf("failed testV3CurlTxn txn with curl using prefix (%s) (%v)", p, err) cx.t.Fatalf("failed testV3CurlTxn txn with curl using prefix (%s) (%v)", p, err)
} }
// was crashing etcd server // was crashing etcd server
malformed := `{"compare":[{"result":0,"target":1,"key":"Zm9v","TargetUnion":null}],"success":[{"Request":{"RequestPut":{"key":"Zm9v","value":"YmFy"}}}]}` malformed := `{"compare":[{"result":0,"target":1,"key":"Zm9v","TargetUnion":null}],"success":[{"Request":{"RequestPut":{"key":"Zm9v","value":"YmFy"}}}]}`
if err := e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/txn"), Value: malformed, Expected: "error"}); err != nil { if err := e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/txn"), Value: malformed, Expected: expect.ExpectedResponse{Value: "error"}}); err != nil {
cx.t.Fatalf("failed testV3CurlTxn put with curl using prefix (%s) (%v)", p, err) cx.t.Fatalf("failed testV3CurlTxn put with curl using prefix (%s) (%v)", p, err)
} }
@ -198,7 +199,7 @@ func testV3CurlAuth(cx ctlCtx) {
user, err := json.Marshal(&pb.AuthUserAddRequest{Name: usernames[i], Password: pwds[i], Options: options[i]}) user, err := json.Marshal(&pb.AuthUserAddRequest{Name: usernames[i], Password: pwds[i], Options: options[i]})
testutil.AssertNil(cx.t, err) testutil.AssertNil(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/auth/user/add"), Value: string(user), Expected: "revision"}); err != nil { if err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/auth/user/add"), Value: string(user), Expected: expect.ExpectedResponse{Value: "revision"}}); err != nil {
cx.t.Fatalf("failed testV3CurlAuth add user %v with curl (%v)", usernames[i], err) cx.t.Fatalf("failed testV3CurlAuth add user %v with curl (%v)", usernames[i], err)
} }
} }
@ -207,7 +208,7 @@ func testV3CurlAuth(cx ctlCtx) {
rolereq, err := json.Marshal(&pb.AuthRoleAddRequest{Name: "root"}) rolereq, err := json.Marshal(&pb.AuthRoleAddRequest{Name: "root"})
testutil.AssertNil(cx.t, err) testutil.AssertNil(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/auth/role/add"), Value: string(rolereq), Expected: "revision"}); err != nil { if err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/auth/role/add"), Value: string(rolereq), Expected: expect.ExpectedResponse{Value: "revision"}}); err != nil {
cx.t.Fatalf("failed testV3CurlAuth create role with curl using prefix (%s) (%v)", p, err) cx.t.Fatalf("failed testV3CurlAuth create role with curl using prefix (%s) (%v)", p, err)
} }
@ -216,13 +217,13 @@ func testV3CurlAuth(cx ctlCtx) {
grantroleroot, err := json.Marshal(&pb.AuthUserGrantRoleRequest{User: usernames[i], Role: "root"}) grantroleroot, err := json.Marshal(&pb.AuthUserGrantRoleRequest{User: usernames[i], Role: "root"})
testutil.AssertNil(cx.t, err) testutil.AssertNil(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/auth/user/grant"), Value: string(grantroleroot), Expected: "revision"}); err != nil { if err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/auth/user/grant"), Value: string(grantroleroot), Expected: expect.ExpectedResponse{Value: "revision"}}); err != nil {
cx.t.Fatalf("failed testV3CurlAuth grant role with curl using prefix (%s) (%v)", p, err) cx.t.Fatalf("failed testV3CurlAuth grant role with curl using prefix (%s) (%v)", p, err)
} }
} }
// enable auth // enable auth
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/auth/enable"), Value: "{}", Expected: "revision"}); err != nil { if err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/auth/enable"), Value: "{}", Expected: expect.ExpectedResponse{Value: "revision"}}); err != nil {
cx.t.Fatalf("failed testV3CurlAuth enable auth with curl using prefix (%s) (%v)", p, err) cx.t.Fatalf("failed testV3CurlAuth enable auth with curl using prefix (%s) (%v)", p, err)
} }
@ -232,7 +233,7 @@ func testV3CurlAuth(cx ctlCtx) {
testutil.AssertNil(cx.t, err) testutil.AssertNil(cx.t, err)
// fail put no auth // fail put no auth
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/put"), Value: string(putreq), Expected: "error"}); err != nil { if err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/put"), Value: string(putreq), Expected: expect.ExpectedResponse{Value: "error"}}); err != nil {
cx.t.Fatalf("failed testV3CurlAuth no auth put with curl using prefix (%s) (%v)", p, err) cx.t.Fatalf("failed testV3CurlAuth no auth put with curl using prefix (%s) (%v)", p, err)
} }
@ -265,7 +266,7 @@ func testV3CurlAuth(cx ctlCtx) {
authHeader = "Authorization: " + token authHeader = "Authorization: " + token
// put with auth // put with auth
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/put"), Value: string(putreq), Header: authHeader, Expected: "revision"}); err != nil { if err = e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, "/kv/put"), Value: string(putreq), Header: authHeader, Expected: expect.ExpectedResponse{Value: "revision"}}); err != nil {
cx.t.Fatalf("failed testV3CurlAuth auth put with curl using prefix (%s) and user (%v) (%v)", p, usernames[i], err) cx.t.Fatalf("failed testV3CurlAuth auth put with curl using prefix (%s) and user (%v) (%v)", p, usernames[i], err)
} }
} }
@ -289,7 +290,7 @@ func testV3CurlCampaign(cx ctlCtx) {
Endpoint: path.Join(cx.apiPrefix, "/election/campaign"), Endpoint: path.Join(cx.apiPrefix, "/election/campaign"),
Value: string(cdata), Value: string(cdata),
}) })
lines, err := e2e.SpawnWithExpectLines(context.TODO(), cargs, cx.envMap, `"leader":{"name":"`) lines, err := e2e.SpawnWithExpectLines(context.TODO(), cargs, cx.envMap, expect.ExpectedResponse{Value: `"leader":{"name":"`})
if err != nil { if err != nil {
cx.t.Fatalf("failed post campaign request (%s) (%v)", cx.apiPrefix, err) cx.t.Fatalf("failed post campaign request (%s) (%v)", cx.apiPrefix, err)
} }
@ -327,7 +328,7 @@ func testV3CurlCampaign(cx ctlCtx) {
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{ if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: path.Join(cx.apiPrefix, "/election/proclaim"), Endpoint: path.Join(cx.apiPrefix, "/election/proclaim"),
Value: string(pdata), Value: string(pdata),
Expected: `"revision":`, Expected: expect.ExpectedResponse{Value: `"revision":`},
}); err != nil { }); err != nil {
cx.t.Fatalf("failed post proclaim request (%s) (%v)", cx.apiPrefix, err) cx.t.Fatalf("failed post proclaim request (%s) (%v)", cx.apiPrefix, err)
} }
@ -347,7 +348,7 @@ func testV3CurlProclaimMissiongLeaderKey(cx ctlCtx) {
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{ if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: path.Join(cx.apiPrefix, "/election/proclaim"), Endpoint: path.Join(cx.apiPrefix, "/election/proclaim"),
Value: string(pdata), Value: string(pdata),
Expected: `{"error":"\"leader\" field must be provided","code":2,"message":"\"leader\" field must be provided"}`, Expected: expect.ExpectedResponse{Value: `{"error":"\"leader\" field must be provided","code":2,"message":"\"leader\" field must be provided"}`},
}); err != nil { }); err != nil {
cx.t.Fatalf("failed post proclaim request (%s) (%v)", cx.apiPrefix, err) cx.t.Fatalf("failed post proclaim request (%s) (%v)", cx.apiPrefix, err)
} }
@ -363,7 +364,7 @@ func testV3CurlResignMissiongLeaderKey(cx ctlCtx) {
if err := e2e.CURLPost(cx.epc, e2e.CURLReq{ if err := e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: path.Join(cx.apiPrefix, "/election/resign"), Endpoint: path.Join(cx.apiPrefix, "/election/resign"),
Value: `{}`, Value: `{}`,
Expected: `{"error":"\"leader\" field must be provided","code":2,"message":"\"leader\" field must be provided"}`, Expected: expect.ExpectedResponse{Value: `{"error":"\"leader\" field must be provided","code":2,"message":"\"leader\" field must be provided"}`},
}); err != nil { }); err != nil {
cx.t.Fatalf("failed post resign request (%s) (%v)", cx.apiPrefix, err) cx.t.Fatalf("failed post resign request (%s) (%v)", cx.apiPrefix, err)
} }
@ -399,7 +400,7 @@ func CURLWithExpected(cx ctlCtx, tests []v3cURLTest) error {
p := cx.apiPrefix p := cx.apiPrefix
for _, t := range tests { for _, t := range tests {
value := fmt.Sprintf("%v", t.value) value := fmt.Sprintf("%v", t.value)
if err := e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, t.endpoint), Value: value, Expected: t.expected}); err != nil { if err := e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: path.Join(p, t.endpoint), Value: value, Expected: expect.ExpectedResponse{Value: t.expected}}); err != nil {
return fmt.Errorf("prefix (%s) endpoint (%s): error (%v), wanted %v", p, t.endpoint, err, t.expected) return fmt.Errorf("prefix (%s) endpoint (%s): error (%v), wanted %v", p, t.endpoint, err, t.expected)
} }
} }

View File

@ -20,6 +20,8 @@ import (
"math/rand" "math/rand"
"strings" "strings"
"time" "time"
"go.etcd.io/etcd/pkg/v3/expect"
) )
type CURLReq struct { type CURLReq struct {
@ -32,7 +34,7 @@ type CURLReq struct {
Endpoint string Endpoint string
Value string Value string
Expected string Expected expect.ExpectedResponse
Header string Header string
Ciphers string Ciphers string

View File

@ -62,7 +62,7 @@ type EtcdProcess interface {
} }
type LogsExpect interface { type LogsExpect interface {
ExpectWithContext(context.Context, string) (string, error) ExpectWithContext(context.Context, expect.ExpectedResponse) (string, error)
Lines() []string Lines() []string
LineCount() int LineCount() int
} }
@ -313,7 +313,7 @@ func AssertProcessLogs(t *testing.T, ep EtcdProcess, expectLog string) {
var err error var err error
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()
_, err = ep.Logs().ExpectWithContext(ctx, expectLog) _, err = ep.Logs().ExpectWithContext(ctx, expect.ExpectedResponse{Value: expectLog})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -28,6 +28,7 @@ import (
"go.etcd.io/etcd/api/v3/authpb" "go.etcd.io/etcd/api/v3/authpb"
"go.etcd.io/etcd/api/v3/etcdserverpb" "go.etcd.io/etcd/api/v3/etcdserverpb"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/config" "go.etcd.io/etcd/tests/v3/framework/config"
) )
@ -80,7 +81,7 @@ func WithEndpoints(endpoints []string) config.ClientOption {
} }
func (ctl *EtcdctlV3) DowngradeEnable(ctx context.Context, version string) error { func (ctl *EtcdctlV3) DowngradeEnable(ctx context.Context, version string) error {
_, err := SpawnWithExpectLines(ctx, ctl.cmdArgs("downgrade", "enable", version), nil, "Downgrade enable success") _, err := SpawnWithExpectLines(ctx, ctl.cmdArgs("downgrade", "enable", version), nil, expect.ExpectedResponse{Value: "Downgrade enable success"})
return err return err
} }
@ -144,7 +145,7 @@ func (ctl *EtcdctlV3) Get(ctx context.Context, key string, o config.GetOptions)
return nil, err return nil, err
} }
defer cmd.Close() defer cmd.Close()
_, err = cmd.ExpectWithContext(ctx, "Count") _, err = cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "Count"})
return &resp, err return &resp, err
} }
err := ctl.spawnJsonCmd(ctx, &resp, args...) err := ctl.spawnJsonCmd(ctx, &resp, args...)
@ -157,7 +158,7 @@ func (ctl *EtcdctlV3) Put(ctx context.Context, key, value string, opts config.Pu
if opts.LeaseID != 0 { if opts.LeaseID != 0 {
args = append(args, "--lease", strconv.FormatInt(int64(opts.LeaseID), 16)) args = append(args, "--lease", strconv.FormatInt(int64(opts.LeaseID), 16))
} }
_, err := SpawnWithExpectLines(ctx, args, nil, "OK") _, err := SpawnWithExpectLines(ctx, args, nil, expect.ExpectedResponse{Value: "OK"})
return err return err
} }
@ -189,7 +190,7 @@ func (ctl *EtcdctlV3) Txn(ctx context.Context, compares, ifSucess, ifFail []stri
return nil, err return nil, err
} }
defer cmd.Close() defer cmd.Close()
_, err = cmd.ExpectWithContext(ctx, "compares:") _, err = cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "compares:"})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -201,7 +202,7 @@ func (ctl *EtcdctlV3) Txn(ctx context.Context, compares, ifSucess, ifFail []stri
if err := cmd.Send("\r"); err != nil { if err := cmd.Send("\r"); err != nil {
return nil, err return nil, err
} }
_, err = cmd.ExpectWithContext(ctx, "success requests (get, put, del):") _, err = cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "success requests (get, put, del):"})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -214,7 +215,7 @@ func (ctl *EtcdctlV3) Txn(ctx context.Context, compares, ifSucess, ifFail []stri
return nil, err return nil, err
} }
_, err = cmd.ExpectWithContext(ctx, "failure requests (get, put, del):") _, err = cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "failure requests (get, put, del):"})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -227,7 +228,7 @@ func (ctl *EtcdctlV3) Txn(ctx context.Context, compares, ifSucess, ifFail []stri
return nil, err return nil, err
} }
var line string var line string
line, err = cmd.ExpectWithContext(ctx, "header") line, err = cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "header"})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -348,7 +349,7 @@ func (ctl *EtcdctlV3) Compact(ctx context.Context, rev int64, o config.CompactOp
args = append(args, "--physical") args = append(args, "--physical")
} }
_, err := SpawnWithExpectLines(ctx, args, nil, fmt.Sprintf("compacted revision %v", rev)) _, err := SpawnWithExpectLines(ctx, args, nil, expect.ExpectedResponse{Value: fmt.Sprintf("compacted revision %v", rev)})
return nil, err return nil, err
} }
@ -387,9 +388,9 @@ func (ctl *EtcdctlV3) HashKV(ctx context.Context, rev int64) ([]*clientv3.HashKV
func (ctl *EtcdctlV3) Health(ctx context.Context) error { func (ctl *EtcdctlV3) Health(ctx context.Context) error {
args := ctl.cmdArgs() args := ctl.cmdArgs()
args = append(args, "endpoint", "health") args = append(args, "endpoint", "health")
lines := make([]string, len(ctl.endpoints)) lines := make([]expect.ExpectedResponse, len(ctl.endpoints))
for i := range lines { for i := range lines {
lines[i] = "is healthy" lines[i] = expect.ExpectedResponse{Value: "is healthy"}
} }
_, err := SpawnWithExpectLines(ctx, args, nil, lines...) _, err := SpawnWithExpectLines(ctx, args, nil, lines...)
return err return err
@ -404,7 +405,7 @@ func (ctl *EtcdctlV3) Grant(ctx context.Context, ttl int64) (*clientv3.LeaseGran
} }
defer cmd.Close() defer cmd.Close()
var resp clientv3.LeaseGrantResponse var resp clientv3.LeaseGrantResponse
line, err := cmd.ExpectWithContext(ctx, "ID") line, err := cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "ID"})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -424,7 +425,7 @@ func (ctl *EtcdctlV3) TimeToLive(ctx context.Context, id clientv3.LeaseID, o con
} }
defer cmd.Close() defer cmd.Close()
var resp clientv3.LeaseTimeToLiveResponse var resp clientv3.LeaseTimeToLiveResponse
line, err := cmd.ExpectWithContext(ctx, "id") line, err := cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "id"})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -437,9 +438,9 @@ func (ctl *EtcdctlV3) Defragment(ctx context.Context, o config.DefragOption) err
if o.Timeout != 0 { if o.Timeout != 0 {
args = append(args, fmt.Sprintf("--command-timeout=%s", o.Timeout)) args = append(args, fmt.Sprintf("--command-timeout=%s", o.Timeout))
} }
lines := make([]string, len(ctl.endpoints)) lines := make([]expect.ExpectedResponse, len(ctl.endpoints))
for i := range lines { for i := range lines {
lines[i] = "Finished defragmenting etcd member" lines[i] = expect.ExpectedResponse{Value: "Finished defragmenting etcd member"}
} }
_, err := SpawnWithExpectLines(ctx, args, map[string]string{}, lines...) _, err := SpawnWithExpectLines(ctx, args, map[string]string{}, lines...)
return err return err
@ -453,7 +454,7 @@ func (ctl *EtcdctlV3) Leases(ctx context.Context) (*clientv3.LeaseLeasesResponse
} }
defer cmd.Close() defer cmd.Close()
var resp clientv3.LeaseLeasesResponse var resp clientv3.LeaseLeasesResponse
line, err := cmd.ExpectWithContext(ctx, "id") line, err := cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "id"})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -469,7 +470,7 @@ func (ctl *EtcdctlV3) KeepAliveOnce(ctx context.Context, id clientv3.LeaseID) (*
} }
defer cmd.Close() defer cmd.Close()
var resp clientv3.LeaseKeepAliveResponse var resp clientv3.LeaseKeepAliveResponse
line, err := cmd.ExpectWithContext(ctx, "ID") line, err := cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "ID"})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -498,7 +499,7 @@ func (ctl *EtcdctlV3) AlarmDisarm(ctx context.Context, _ *clientv3.AlarmMember)
} }
defer ep.Close() defer ep.Close()
var resp clientv3.AlarmResponse var resp clientv3.AlarmResponse
line, err := ep.ExpectWithContext(ctx, "alarm") line, err := ep.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "alarm"})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -514,7 +515,7 @@ func (ctl *EtcdctlV3) AuthEnable(ctx context.Context) error {
} }
defer cmd.Close() defer cmd.Close()
_, err = cmd.ExpectWithContext(ctx, "Authentication Enabled") _, err = cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "Authentication Enabled"})
return err return err
} }
@ -526,7 +527,7 @@ func (ctl *EtcdctlV3) AuthDisable(ctx context.Context) error {
} }
defer cmd.Close() defer cmd.Close()
_, err = cmd.ExpectWithContext(ctx, "Authentication Disabled") _, err = cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "Authentication Disabled"})
return err return err
} }
@ -567,7 +568,7 @@ func (ctl *EtcdctlV3) UserAdd(ctx context.Context, name, password string, opts c
} }
var resp clientv3.AuthUserAddResponse var resp clientv3.AuthUserAddResponse
line, err := cmd.ExpectWithContext(ctx, "header") line, err := cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "header"})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -606,7 +607,7 @@ func (ctl *EtcdctlV3) UserChangePass(ctx context.Context, user, newPass string)
return err return err
} }
_, err = cmd.ExpectWithContext(ctx, "Password updated") _, err = cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "Password updated"})
return err return err
} }
@ -666,7 +667,7 @@ func (ctl *EtcdctlV3) spawnJsonCmd(ctx context.Context, output interface{}, args
return err return err
} }
defer cmd.Close() defer cmd.Close()
line, err := cmd.ExpectWithContext(ctx, "header") line, err := cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "header"})
if err != nil { if err != nil {
return err return err
} }

View File

@ -65,7 +65,7 @@ func (fs *LazyFS) Start(ctx context.Context) (err error) {
if err != nil { if err != nil {
return err return err
} }
_, err = fs.ep.ExpectWithContext(ctx, "waiting for fault commands") _, err = fs.ep.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "waiting for fault commands"})
return err return err
} }
@ -109,6 +109,6 @@ func (fs *LazyFS) ClearCache(ctx context.Context) error {
} }
// TODO: Wait for response on socket instead of reading logs to get command completion. // TODO: Wait for response on socket instead of reading logs to get command completion.
// Set `fifo_path_completed` config for LazyFS to create separate socket to write when it has completed command. // Set `fifo_path_completed` config for LazyFS to create separate socket to write when it has completed command.
_, err = fs.ep.ExpectWithContext(ctx, "cache is cleared") _, err = fs.ep.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "cache is cleared"})
return err return err
} }

View File

@ -41,24 +41,24 @@ func WaitReadyExpectProc(ctx context.Context, exproc *expect.ExpectProcess, read
return err return err
} }
func SpawnWithExpect(args []string, expected string) error { func SpawnWithExpect(args []string, expected expect.ExpectedResponse) error {
return SpawnWithExpects(args, nil, []string{expected}...) return SpawnWithExpects(args, nil, []expect.ExpectedResponse{expected}...)
} }
func SpawnWithExpectWithEnv(args []string, envVars map[string]string, expected string) error { func SpawnWithExpectWithEnv(args []string, envVars map[string]string, expected expect.ExpectedResponse) error {
return SpawnWithExpects(args, envVars, []string{expected}...) return SpawnWithExpects(args, envVars, []expect.ExpectedResponse{expected}...)
} }
func SpawnWithExpects(args []string, envVars map[string]string, xs ...string) error { func SpawnWithExpects(args []string, envVars map[string]string, xs ...expect.ExpectedResponse) error {
return SpawnWithExpectsContext(context.TODO(), args, envVars, xs...) return SpawnWithExpectsContext(context.TODO(), args, envVars, xs...)
} }
func SpawnWithExpectsContext(ctx context.Context, args []string, envVars map[string]string, xs ...string) error { func SpawnWithExpectsContext(ctx context.Context, args []string, envVars map[string]string, xs ...expect.ExpectedResponse) error {
_, err := SpawnWithExpectLines(ctx, args, envVars, xs...) _, err := SpawnWithExpectLines(ctx, args, envVars, xs...)
return err return err
} }
func SpawnWithExpectLines(ctx context.Context, args []string, envVars map[string]string, xs ...string) ([]string, error) { func SpawnWithExpectLines(ctx context.Context, args []string, envVars map[string]string, xs ...expect.ExpectedResponse) ([]string, error) {
proc, err := SpawnCmd(args, envVars) proc, err := SpawnCmd(args, envVars)
if err != nil { if err != nil {
return nil, err return nil, err
@ -73,7 +73,7 @@ func SpawnWithExpectLines(ctx context.Context, args []string, envVars map[string
l, lerr := proc.ExpectWithContext(ctx, txt) l, lerr := proc.ExpectWithContext(ctx, txt)
if lerr != nil { if lerr != nil {
proc.Close() proc.Close()
return nil, fmt.Errorf("%v %v (expected %q, got %q). Try EXPECT_DEBUG=TRUE", args, lerr, txt, lines) return nil, fmt.Errorf("%v %v (expected %q, got %q). Try EXPECT_DEBUG=TRUE", args, lerr, txt.Value, lines)
} }
lines = append(lines, l) lines = append(lines, l)
} }