Merge pull request #16472 from ahrtr/expr_expect_20230825

test: support regular expression matching on the response
This commit is contained in:
Benjamin Wang 2023-08-26 02:34:23 +08:00 committed by GitHub
commit b6935cf2e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 234 additions and 138 deletions

View File

@ -23,6 +23,7 @@ import (
"io"
"os"
"os/exec"
"regexp"
"strings"
"sync"
"syscall"
@ -37,6 +38,11 @@ var (
ErrProcessRunning = fmt.Errorf("process is still running")
)
type ExpectedResponse struct {
Value string
IsRegularExpr bool
}
type ExpectProcess struct {
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.
func (ep *ExpectProcess) ExpectWithContext(ctx context.Context, s string) (string, error) {
return ep.ExpectFunc(ctx, func(txt string) bool { return strings.Contains(txt, s) })
func (ep *ExpectProcess) ExpectWithContext(ctx context.Context, s ExpectedResponse) (string, error) {
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.
// Deprecated: please use ExpectWithContext instead.
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

View File

@ -127,7 +127,7 @@ func TestEcho(t *testing.T) {
t.Fatal(err)
}
ctx := context.Background()
l, eerr := ep.ExpectWithContext(ctx, "world")
l, eerr := ep.ExpectWithContext(ctx, ExpectedResponse{Value: "world"})
if eerr != nil {
t.Fatal(eerr)
}
@ -138,7 +138,7 @@ func TestEcho(t *testing.T) {
if cerr := ep.Close(); cerr != nil {
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")
}
}
@ -149,7 +149,7 @@ func TestLineCount(t *testing.T) {
t.Fatal(err)
}
wstr := "3"
l, eerr := ep.ExpectWithContext(context.Background(), wstr)
l, eerr := ep.ExpectWithContext(context.Background(), ExpectedResponse{Value: wstr})
if eerr != nil {
t.Fatal(eerr)
}
@ -172,7 +172,7 @@ func TestSend(t *testing.T) {
if err := ep.Send("a\r"); err != nil {
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)
}
if err := ep.Stop(); err != nil {
@ -218,3 +218,59 @@ func TestExpectForFailFastCommand(t *testing.T) {
_, err = ep.Expect("failed setting cipher list")
require.NoError(t, err)
}
func TestResponseMatchRegularExpr(t *testing.T) {
testCases := []struct {
name string
mockOutput string
expectedResp ExpectedResponse
expectMatch bool
}{
{
name: "exact match",
mockOutput: "hello world",
expectedResp: ExpectedResponse{Value: "hello world"},
expectMatch: true,
},
{
name: "not exact match",
mockOutput: "hello world",
expectedResp: ExpectedResponse{Value: "hello wld"},
expectMatch: false,
},
{
name: "match regular expression",
mockOutput: "hello world",
expectedResp: ExpectedResponse{Value: `.*llo\sworld`, IsRegularExpr: true},
expectMatch: true,
},
{
name: "not match regular expression",
mockOutput: "hello world",
expectedResp: ExpectedResponse{Value: `.*llo wrld`, IsRegularExpr: true},
expectMatch: false,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
ep, err := NewExpect("echo", "-n", tc.mockOutput)
require.NoError(t, err)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
l, err := ep.ExpectWithContext(ctx, tc.expectedResp)
if tc.expectMatch {
require.Equal(t, tc.mockOutput, l)
} else {
require.Error(t, err)
}
cerr := ep.Close()
require.NoError(t, cerr)
})
}
}

View File

@ -25,6 +25,7 @@ import (
"go.etcd.io/etcd/api/v3/etcdserverpb"
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/mvcc/testutil"
"go.etcd.io/etcd/tests/v3/framework/config"
@ -334,11 +335,11 @@ func TestCompactHashCheckDetectCorruptionInterrupt(t *testing.T) {
err = epc.Procs[slowCompactionNodeIndex].Restart(ctx)
// 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")
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")
alarmResponse, err := cc.AlarmList(ctx)

View File

@ -21,6 +21,7 @@ import (
"github.com/stretchr/testify/require"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e"
)
@ -58,25 +59,25 @@ func authEnable(cx ctlCtx) error {
func ctlV3AuthEnable(cx ctlCtx) error {
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 {
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) {
if err := ctlV3User(cx, []string{"add", "test-user", "--interactive=false"}, "User test-user created", []string{"pass"}); err != nil {
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)
}
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)
}
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)
}
}
@ -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 {
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)
}
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)
}
}
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)
}
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 {
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 {
lines[i] = "is healthy"
lines[i] = expect.ExpectedResponse{Value: "is healthy"}
}
return e2e.SpawnWithExpects(cmdArgs, cx.envMap, lines...)
}

View File

@ -17,6 +17,7 @@ package e2e
import (
"testing"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e"
)
@ -35,16 +36,16 @@ func maintenanceInitKeys(cx ctlCtx) {
func ctlV3OnlineDefrag(cx ctlCtx) error {
cmdArgs := append(cx.PrefixArgs(), "defrag")
lines := make([]string, cx.epc.Cfg.ClusterSize)
lines := make([]expect.ExpectedResponse, cx.epc.Cfg.ClusterSize)
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...)
}
func ctlV3OfflineDefrag(cx ctlCtx) error {
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...)
}

View File

@ -26,6 +26,7 @@ import (
"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/e2e"
"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) {
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, "\r")

View File

@ -22,6 +22,7 @@ import (
"github.com/stretchr/testify/require"
"go.etcd.io/etcd/pkg/v3/expect"
"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, "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)
}
}
@ -216,28 +217,28 @@ func getKeysOnlyTest(cx ctlCtx) {
cx.t.Fatal(err)
}
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)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
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.NotContains(cx.t, lines, "val", "got value but passed --keys-only")
}
func getCountOnlyTest(cx ctlCtx) {
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)
}
if err := ctlV3Put(cx, "key", "val", ""); err != nil {
cx.t.Fatal(err)
}
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)
}
if err := ctlV3Put(cx, "key1", "val", ""); err != nil {
@ -247,14 +248,14 @@ func getCountOnlyTest(cx ctlCtx) {
cx.t.Fatal(err)
}
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)
}
if err := ctlV3Put(cx, "key2", "val", ""); err != nil {
cx.t.Fatal(err)
}
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)
}
@ -262,7 +263,7 @@ func getCountOnlyTest(cx ctlCtx) {
defer cancel()
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.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 {
cmdArgs = append(cmdArgs, flags...)
}
return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, "OK")
return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, expect.ExpectedResponse{Value: "OK"})
}
type kv struct {
@ -354,25 +355,15 @@ func ctlV3Get(cx ctlCtx, args []string, kvs ...kv) error {
if !cx.quorum {
cmdArgs = append(cmdArgs, "--consistency", "s")
}
var lines []string
var lines []expect.ExpectedResponse
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...)
}
// 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 {
cmdArgs := append(cx.PrefixArgs(), "del")
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"
"testing"
"go.etcd.io/etcd/pkg/v3/expect"
"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 {
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 {
@ -101,5 +102,5 @@ func ctlV3LeaseTimeToLive(cx ctlCtx, leaseID string, withKeys bool) error {
if withKeys {
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) {
// exec command with zero exit code
echoCmd := []string{"echo"}
if err := ctlV3LockWithCmd(cx, echoCmd, ""); err != nil {
if err := ctlV3LockWithCmd(cx, echoCmd, expect.ExpectedResponse{Value: ""}); err != nil {
cx.t.Fatal(err)
}
// exec command with non-zero exit code
code := 3
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)
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.
@ -149,7 +149,7 @@ func ctlV3Lock(cx ctlCtx, name string) (*expect.ExpectProcess, <-chan string, er
}
// 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
cmdArgs := append(cx.PrefixArgs(), "lock", execCmd[0])
cmdArgs = append(cmdArgs, execCmd...)

View File

@ -25,6 +25,7 @@ import (
"github.com/stretchr/testify/require"
"go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e"
)
@ -75,9 +76,9 @@ func memberListSerializableTest(cx ctlCtx) {
func ctlV3MemberList(cx ctlCtx) error {
cmdArgs := append(cx.PrefixArgs(), "member", "list")
lines := make([]string, cx.cfg.ClusterSize)
lines := make([]expect.ExpectedResponse, cx.cfg.ClusterSize)
for i := range lines {
lines[i] = "started"
lines[i] = expect.ExpectedResponse{Value: "started"}
}
return e2e.SpawnWithExpects(cmdArgs, cx.envMap, lines...)
}
@ -162,7 +163,7 @@ func memberListWithHexTest(cx ctlCtx) {
func ctlV3MemberRemove(cx ctlCtx, ep, memberID, clusterID string) error {
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) {
@ -186,7 +187,7 @@ func ctlV3MemberAdd(cx ctlCtx, peerURL string, isLearner bool) error {
cmdArgs = append(cmdArgs, "--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) {
@ -204,5 +205,5 @@ func memberUpdateTest(cx ctlCtx) {
func ctlV3MemberUpdate(cx ctlCtx, memberID, peerURL string) error {
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/types"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/pkg/v3/expect"
"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 {
prefix := cx.prefixArgs(tc.eps)
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 {
require.ErrorContains(t, err, tc.expect)
} else {

View File

@ -18,6 +18,7 @@ import (
"fmt"
"testing"
"go.etcd.io/etcd/pkg/v3/expect"
"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(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 {

View File

@ -94,7 +94,7 @@ func snapshotCorruptTest(cx ctlCtx) {
"--data-dir", datadir,
fpath),
cx.envMap,
"expected sha256")
expect.ExpectedResponse{Value: "expected sha256"})
require.ErrorContains(cx.t, serr, "Error: expected sha256")
}
@ -125,7 +125,7 @@ func snapshotStatusBeforeRestoreTest(cx ctlCtx) {
"--data-dir", dataDir,
fpath),
cx.envMap,
"added member")
expect.ExpectedResponse{Value: "added member"})
if serr != nil {
cx.t.Fatal(serr)
}
@ -133,7 +133,7 @@ func snapshotStatusBeforeRestoreTest(cx ctlCtx) {
func ctlV3SnapshotSave(cx ctlCtx, fpath string) error {
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) {
@ -194,7 +194,7 @@ func testIssue6361(t *testing.T) {
t.Log("Writing some keys...")
kvs := []kv{{"foo1", "val1"}, {"foo2", "val2"}, {"foo3", "val3"}}
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)
}
}
@ -204,7 +204,7 @@ func testIssue6361(t *testing.T) {
t.Log("etcdctl saving snapshot...")
if err = e2e.SpawnWithExpects(append(prefixArgs, "snapshot", "save", fpath),
nil,
fmt.Sprintf("Snapshot saved at %s", fpath),
expect.ExpectedResponse{Value: fmt.Sprintf("Snapshot saved at %s", fpath)},
); err != nil {
t.Fatal(err)
}
@ -216,7 +216,14 @@ func testIssue6361(t *testing.T) {
newDataDir := filepath.Join(t.TempDir(), "test.data")
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 {
t.Fatal(err)
}
@ -234,7 +241,7 @@ func testIssue6361(t *testing.T) {
t.Log("Ensuring the restored member has the correct data...")
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)
}
}
@ -242,7 +249,7 @@ func testIssue6361(t *testing.T) {
t.Log("Adding new member into the cluster")
clientURL := fmt.Sprintf("http://localhost:%d", e2e.EtcdProcessBasePort+30)
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 {
t.Fatal(err)
}
@ -271,7 +278,7 @@ func testIssue6361(t *testing.T) {
t.Log("Ensuring added member has data from incoming snapshot...")
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)
}
}
@ -350,7 +357,7 @@ func TestRestoreCompactionRevBump(t *testing.T) {
t.Log("etcdctl saving snapshot...")
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
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),
"--mark-compacted",
"--data-dir", newDataDir,
}, "added member")
}, expect.ExpectedResponse{Value: "added member"})
require.NoError(t, err)
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/client/pkg/v3/testutil"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/pkg/v3/flags"
"go.etcd.io/etcd/tests/v3/framework/e2e"
)
@ -88,7 +89,7 @@ func versionTest(cx ctlCtx) {
func clusterVersionTest(cx ctlCtx, expected string) {
var err error
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)
time.Sleep(200 * time.Millisecond)
continue
@ -102,7 +103,7 @@ func clusterVersionTest(cx ctlCtx, expected string) {
func ctlV3Version(cx ctlCtx) error {
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.
@ -112,7 +113,7 @@ func TestCtlV3DialWithHTTPScheme(t *testing.T) {
func dialWithSchemeTest(cx ctlCtx) {
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)
}
}

View File

@ -26,6 +26,7 @@ import (
"go.etcd.io/etcd/client/pkg/v3/testutil"
"go.etcd.io/etcd/client/pkg/v3/transport"
"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/tests/v3/framework/e2e"
"go.etcd.io/etcd/tests/v3/framework/integration"
@ -73,10 +74,10 @@ func testClusterUsingDiscovery(t *testing.T, size int, peerTLS bool) {
defer c.Close()
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)
}
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)
}
}

View File

@ -21,6 +21,7 @@ import (
"strings"
"testing"
"go.etcd.io/etcd/pkg/v3/expect"
"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
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)
}
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)
}
}

View File

@ -296,7 +296,7 @@ func TestGrpcproxyAndCommonName(t *testing.T) {
"--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")
p, err := e2e.SpawnCmd(argsWithEmptyCN, nil)

View File

@ -23,6 +23,7 @@ import (
"go.etcd.io/etcd/api/v3/version"
"go.etcd.io/etcd/client/pkg/v3/fileutil"
"go.etcd.io/etcd/pkg/v3/expect"
"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
ver := version.Cluster(version.Version)
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)
time.Sleep(time.Second)
continue

View File

@ -42,7 +42,7 @@ func TestGateway(t *testing.T) {
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 {
t.Errorf("failed to finish put request through gateway: %v", err)
}

View File

@ -19,6 +19,7 @@ import (
"testing"
"go.etcd.io/etcd/api/v3/version"
"go.etcd.io/etcd/pkg/v3/expect"
"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 {
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)
}
}

View File

@ -29,6 +29,7 @@ import (
"go.etcd.io/etcd/api/v3/version"
"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/schema"
"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...")
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)
}
}
@ -155,7 +156,7 @@ func TestEtctlutlMigrate(t *testing.T) {
if tc.force {
args = append(args, "--force")
}
err = e2e.SpawnWithExpect(args, tc.expectLogsSubString)
err = e2e.SpawnWithExpect(args, expect.ExpectedResponse{Value: tc.expectLogsSubString})
if err != nil {
if tc.expectLogsSubString != "" {
require.ErrorContains(t, err, tc.expectLogsSubString)

View File

@ -29,6 +29,7 @@ import (
"go.uber.org/zap/zaptest"
"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/api/membership"
"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++ {
if err := e2e.CURLPut(epc, e2e.CURLReq{
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)
}
}

View File

@ -23,6 +23,7 @@ import (
"github.com/stretchr/testify/require"
"go.etcd.io/etcd/api/v3/version"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e"
)
@ -49,7 +50,7 @@ func testV3CurlCipherSuites(t *testing.T, valid bool) {
func cipherSuiteTestValid(cx ctlCtx) {
if err := e2e.CURLGet(cx.epc, e2e.CURLReq{
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
}); err != nil {
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) {
err := e2e.CURLGet(cx.epc, e2e.CURLReq{
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
})
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"
"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/testutils"
)
@ -166,7 +167,7 @@ func submitConcurrentWatch(cx ctlCtx, number int, wgDone *sync.WaitGroup, closeC
// make sure that watch request has been created
expectedLine := `"created":true}}`
_, lerr := proc.ExpectWithContext(context.TODO(), expectedLine)
_, lerr := proc.ExpectWithContext(context.TODO(), expect.ExpectedResponse{Value: expectedLine})
if lerr != nil {
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...")
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)
}
cx.t.Log("range request done")

View File

@ -30,6 +30,7 @@ import (
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
"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"
"go.etcd.io/etcd/tests/v3/framework/e2e"
@ -108,14 +109,14 @@ func testV3CurlPutGet(cx ctlCtx) {
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)
}
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)
}
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)
}
}
@ -139,11 +140,11 @@ func testV3CurlWatch(cx ctlCtx) {
wstr := `{"create_request" : ` + string(wreq) + "}"
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)
}
// 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")
}
@ -175,13 +176,13 @@ func testV3CurlTxn(cx ctlCtx) {
}
expected := `"succeeded":true,"responses":[{"response_put":{"header":{"revision":"2"}}}]`
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)
}
// was crashing etcd server
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)
}
@ -198,7 +199,7 @@ func testV3CurlAuth(cx ctlCtx) {
user, err := json.Marshal(&pb.AuthUserAddRequest{Name: usernames[i], Password: pwds[i], Options: options[i]})
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)
}
}
@ -207,7 +208,7 @@ func testV3CurlAuth(cx ctlCtx) {
rolereq, err := json.Marshal(&pb.AuthRoleAddRequest{Name: "root"})
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)
}
@ -216,13 +217,13 @@ func testV3CurlAuth(cx ctlCtx) {
grantroleroot, err := json.Marshal(&pb.AuthUserGrantRoleRequest{User: usernames[i], Role: "root"})
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)
}
}
// 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)
}
@ -232,7 +233,7 @@ func testV3CurlAuth(cx ctlCtx) {
testutil.AssertNil(cx.t, err)
// 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)
}
@ -265,7 +266,7 @@ func testV3CurlAuth(cx ctlCtx) {
authHeader = "Authorization: " + token
// 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)
}
}
@ -289,7 +290,7 @@ func testV3CurlCampaign(cx ctlCtx) {
Endpoint: path.Join(cx.apiPrefix, "/election/campaign"),
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 {
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{
Endpoint: path.Join(cx.apiPrefix, "/election/proclaim"),
Value: string(pdata),
Expected: `"revision":`,
Expected: expect.ExpectedResponse{Value: `"revision":`},
}); err != nil {
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{
Endpoint: path.Join(cx.apiPrefix, "/election/proclaim"),
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 {
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{
Endpoint: path.Join(cx.apiPrefix, "/election/resign"),
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 {
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
for _, t := range tests {
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)
}
}

View File

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

View File

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

View File

@ -28,6 +28,7 @@ import (
"go.etcd.io/etcd/api/v3/authpb"
"go.etcd.io/etcd/api/v3/etcdserverpb"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/pkg/v3/expect"
"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 {
_, 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
}
@ -144,7 +145,7 @@ func (ctl *EtcdctlV3) Get(ctx context.Context, key string, o config.GetOptions)
return nil, err
}
defer cmd.Close()
_, err = cmd.ExpectWithContext(ctx, "Count")
_, err = cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "Count"})
return &resp, err
}
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 {
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
}
@ -189,7 +190,7 @@ func (ctl *EtcdctlV3) Txn(ctx context.Context, compares, ifSucess, ifFail []stri
return nil, err
}
defer cmd.Close()
_, err = cmd.ExpectWithContext(ctx, "compares:")
_, err = cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "compares:"})
if err != nil {
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 {
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 {
return nil, err
}
@ -214,7 +215,7 @@ func (ctl *EtcdctlV3) Txn(ctx context.Context, compares, ifSucess, ifFail []stri
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 {
return nil, err
}
@ -227,7 +228,7 @@ func (ctl *EtcdctlV3) Txn(ctx context.Context, compares, ifSucess, ifFail []stri
return nil, err
}
var line string
line, err = cmd.ExpectWithContext(ctx, "header")
line, err = cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "header"})
if err != nil {
return nil, err
}
@ -348,7 +349,7 @@ func (ctl *EtcdctlV3) Compact(ctx context.Context, rev int64, o config.CompactOp
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
}
@ -387,9 +388,9 @@ func (ctl *EtcdctlV3) HashKV(ctx context.Context, rev int64) ([]*clientv3.HashKV
func (ctl *EtcdctlV3) Health(ctx context.Context) error {
args := ctl.cmdArgs()
args = append(args, "endpoint", "health")
lines := make([]string, len(ctl.endpoints))
lines := make([]expect.ExpectedResponse, len(ctl.endpoints))
for i := range lines {
lines[i] = "is healthy"
lines[i] = expect.ExpectedResponse{Value: "is healthy"}
}
_, err := SpawnWithExpectLines(ctx, args, nil, lines...)
return err
@ -404,7 +405,7 @@ func (ctl *EtcdctlV3) Grant(ctx context.Context, ttl int64) (*clientv3.LeaseGran
}
defer cmd.Close()
var resp clientv3.LeaseGrantResponse
line, err := cmd.ExpectWithContext(ctx, "ID")
line, err := cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "ID"})
if err != nil {
return nil, err
}
@ -424,7 +425,7 @@ func (ctl *EtcdctlV3) TimeToLive(ctx context.Context, id clientv3.LeaseID, o con
}
defer cmd.Close()
var resp clientv3.LeaseTimeToLiveResponse
line, err := cmd.ExpectWithContext(ctx, "id")
line, err := cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "id"})
if err != nil {
return nil, err
}
@ -437,9 +438,9 @@ func (ctl *EtcdctlV3) Defragment(ctx context.Context, o config.DefragOption) err
if o.Timeout != 0 {
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 {
lines[i] = "Finished defragmenting etcd member"
lines[i] = expect.ExpectedResponse{Value: "Finished defragmenting etcd member"}
}
_, err := SpawnWithExpectLines(ctx, args, map[string]string{}, lines...)
return err
@ -453,7 +454,7 @@ func (ctl *EtcdctlV3) Leases(ctx context.Context) (*clientv3.LeaseLeasesResponse
}
defer cmd.Close()
var resp clientv3.LeaseLeasesResponse
line, err := cmd.ExpectWithContext(ctx, "id")
line, err := cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "id"})
if err != nil {
return nil, err
}
@ -469,7 +470,7 @@ func (ctl *EtcdctlV3) KeepAliveOnce(ctx context.Context, id clientv3.LeaseID) (*
}
defer cmd.Close()
var resp clientv3.LeaseKeepAliveResponse
line, err := cmd.ExpectWithContext(ctx, "ID")
line, err := cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "ID"})
if err != nil {
return nil, err
}
@ -498,7 +499,7 @@ func (ctl *EtcdctlV3) AlarmDisarm(ctx context.Context, _ *clientv3.AlarmMember)
}
defer ep.Close()
var resp clientv3.AlarmResponse
line, err := ep.ExpectWithContext(ctx, "alarm")
line, err := ep.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "alarm"})
if err != nil {
return nil, err
}
@ -514,7 +515,7 @@ func (ctl *EtcdctlV3) AuthEnable(ctx context.Context) error {
}
defer cmd.Close()
_, err = cmd.ExpectWithContext(ctx, "Authentication Enabled")
_, err = cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "Authentication Enabled"})
return err
}
@ -526,7 +527,7 @@ func (ctl *EtcdctlV3) AuthDisable(ctx context.Context) error {
}
defer cmd.Close()
_, err = cmd.ExpectWithContext(ctx, "Authentication Disabled")
_, err = cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "Authentication Disabled"})
return err
}
@ -567,7 +568,7 @@ func (ctl *EtcdctlV3) UserAdd(ctx context.Context, name, password string, opts c
}
var resp clientv3.AuthUserAddResponse
line, err := cmd.ExpectWithContext(ctx, "header")
line, err := cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "header"})
if err != nil {
return nil, err
}
@ -606,7 +607,7 @@ func (ctl *EtcdctlV3) UserChangePass(ctx context.Context, user, newPass string)
return err
}
_, err = cmd.ExpectWithContext(ctx, "Password updated")
_, err = cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "Password updated"})
return err
}
@ -666,7 +667,7 @@ func (ctl *EtcdctlV3) spawnJsonCmd(ctx context.Context, output interface{}, args
return err
}
defer cmd.Close()
line, err := cmd.ExpectWithContext(ctx, "header")
line, err := cmd.ExpectWithContext(ctx, expect.ExpectedResponse{Value: "header"})
if err != nil {
return err
}

View File

@ -65,7 +65,7 @@ func (fs *LazyFS) Start(ctx context.Context) (err error) {
if err != nil {
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
}
@ -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.
// 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
}

View File

@ -41,24 +41,24 @@ func WaitReadyExpectProc(ctx context.Context, exproc *expect.ExpectProcess, read
return err
}
func SpawnWithExpect(args []string, expected string) error {
return SpawnWithExpects(args, nil, []string{expected}...)
func SpawnWithExpect(args []string, expected expect.ExpectedResponse) error {
return SpawnWithExpects(args, nil, []expect.ExpectedResponse{expected}...)
}
func SpawnWithExpectWithEnv(args []string, envVars map[string]string, expected string) error {
return SpawnWithExpects(args, envVars, []string{expected}...)
func SpawnWithExpectWithEnv(args []string, envVars map[string]string, expected expect.ExpectedResponse) error {
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...)
}
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...)
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)
if err != nil {
return nil, err
@ -73,7 +73,7 @@ func SpawnWithExpectLines(ctx context.Context, args []string, envVars map[string
l, lerr := proc.ExpectWithContext(ctx, txt)
if lerr != nil {
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)
}