Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rafttest: support multi-command statements #141

Draft
wants to merge 27 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0fd7150
rafttest: support multi-command statements
pav-kv Jan 26, 2024
b716493
testdata: compress async_storage_writes
pav-kv Jan 26, 2024
ba5768e
testdata: compress async_storage_writes_append_aba_race
pav-kv Jan 26, 2024
25b2f62
testdata: compress campaign_learner_must_vote
pav-kv Jan 26, 2024
23d40b5
testdata: compress checkquorum
pav-kv Jan 26, 2024
6312ada
testdata: compress confchange_disable_validation
pav-kv Jan 26, 2024
8c1d187
testdata: compress confchange_v1_add_single
pav-kv Jan 26, 2024
002a762
testdata: compress confchange_v1_remove_leader
pav-kv Jan 26, 2024
2c9f0da
testdata: compress confchange_v1_remove_leader_stepdown
pav-kv Jan 26, 2024
91a4fb5
testdata: compress confchange_v2_add_double_auto
pav-kv Jan 26, 2024
1c2c831
testdata: compress confchange_v2_add_double_implicit
pav-kv Jan 26, 2024
1ccfbde
testdata: compress confchange_v2_add_single_auto
pav-kv Jan 26, 2024
cdb9c8f
testdata: compress confchange_v2_add_single_explicit
pav-kv Jan 26, 2024
2df089f
testdata: compress confchange_v2_replace_leader
pav-kv Jan 26, 2024
9171140
testdata: compress confchange_v2_replace_leader_stepdown
pav-kv Jan 26, 2024
2cdae74
testdata: compress forget_leader
pav-kv Jan 26, 2024
d79825b
testdata: compress forget_leader_prevote_checkquorum
pav-kv Jan 26, 2024
e6cf75f
testdata: compress forget_leader_read_only_lease_based
pav-kv Jan 26, 2024
96d5a0a
testdata: compress heartbeat_resp_recovers_from_probing
pav-kv Jan 26, 2024
9f87dee
testdata: compress prevote
pav-kv Jan 26, 2024
d10f9f0
testdata: compress prevote_checkquorum
pav-kv Jan 26, 2024
c674bec
testdata: compress probe_and_replicate
pav-kv Jan 26, 2024
1b0951e
testdata: compress replicate_pause
pav-kv Jan 26, 2024
2942896
testdata: compress single_node
pav-kv Jan 26, 2024
88680ed
testdata: compress slow_follower_after_compaction
pav-kv Jan 26, 2024
2d04ec7
testdata: compress snapshot_succeed_via_app_resp
pav-kv Jan 26, 2024
11c763f
testdata: compress snapshot_succeed_via_app_resp_behind
pav-kv Jan 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions rafttest/datadriven_port.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package rafttest

import (
"fmt"
"strconv"

"github.com/cockroachdb/datadriven"
)

// scanArgs is a copy-paste from datadriven. TODO
func scanArgs(args []datadriven.CmdArg, key string, dests ...interface{}) error {
var arg datadriven.CmdArg
for i := range args {
if args[i].Key == key {
arg = args[i]
break
}
}
if arg.Key == "" {
return fmt.Errorf("missing argument: %s", key)
}
if len(dests) != len(arg.Vals) {
return fmt.Errorf("%s: got %d destinations, but %d values", arg.Key, len(dests), len(arg.Vals))
}

for i := range dests {
if err := scanErr(arg, i, dests[i]); err != nil {
return fmt.Errorf("%s: failed to scan argument %d: %v", arg.Key, i, err)
}
}
return nil
}

// scanErr is like Scan but returns an error rather than taking a testing.T to fatal.
func scanErr(arg datadriven.CmdArg, i int, dest interface{}) error {
if i < 0 || i >= len(arg.Vals) {
return fmt.Errorf("cannot scan index %d of key %s", i, arg.Key)
}
val := arg.Vals[i]
switch dest := dest.(type) {
case *string:
*dest = val
case *int:
n, err := strconv.ParseInt(val, 10, 64)
if err != nil {
return err
}
*dest = int(n) // assume 64bit ints

Check failure

Code scanning / CodeQL

Incorrect conversion between integer types High

Incorrect conversion of a signed 64-bit integer from
strconv.ParseInt
to a lower bit size type int without an upper bound check.
case *int64:
n, err := strconv.ParseInt(val, 10, 64)
if err != nil {
return err
}
*dest = n
case *uint64:
n, err := strconv.ParseUint(val, 10, 64)
if err != nil {
return err
}
*dest = n
case *bool:
b, err := strconv.ParseBool(val)
if err != nil {
return err
}
*dest = b
default:
return fmt.Errorf("unsupported type %T for destination #%d (might be easy to add it)", dest, i+1)
}
return nil
}
171 changes: 105 additions & 66 deletions rafttest/interaction_env_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package rafttest
import (
"fmt"
"strconv"
"strings"
"testing"

"github.com/cockroachdb/datadriven"
Expand All @@ -30,6 +31,81 @@ func (env *InteractionEnv) Handle(t *testing.T, d datadriven.TestData) string {
env.Output.Reset()
var err error
switch d.Cmd {
case "propose-conf-change":
// Propose a configuration change, or transition out of a previously
// proposed joint configuration change that requested explicit
// transitions. When adding nodes, this command can be used to
// logically add nodes to the configuration, but add-nodes is needed
// to "create" the nodes.
//
// propose-conf-change node_id [v1=<bool>] [transition=<string>]
// command string
// See ConfChangesFromString for command string format.
// Arguments are:
// node_id - the node proposing the configuration change.
// v1 - make one change at a time, false by default.
// transition - "auto" (the default), "explicit" or "implicit".
// Example:
//
// propose-conf-change 1 transition=explicit
// v1 v3 l4 r5
//
// Example:
//
// propose-conf-change 2 v1=true
// v5
err = env.handleProposeConfChange(t, d)
default:
err = env.handleMultiCommand(t, d)
}
// NB: the highest log level suppresses all output, including that of the
// handlers. This comes in useful during setup which can be chatty.
// However, errors are always logged.
if err != nil {
if env.Output.Quiet() {
return err.Error()
}
env.Output.WriteString(err.Error())
}
if env.Output.Len() == 0 {
return "ok"
}
return env.Output.String()
}

func (env *InteractionEnv) handleMultiCommand(t *testing.T, d datadriven.TestData) error {
err := env.handleSingle(t, d.Cmd, d.CmdArgs)
if err != nil {
return err
}

commands := d.Input
for len(commands) != 0 {
command := commands
next := strings.IndexByte(commands, '\n')
if next == -1 {
commands = ""
} else {
command, commands = commands[:next], commands[next+1:]
}
cmd, args, err := datadriven.ParseLine(command)
if err != nil {
t.Fatalf("could not parse the command line %q: %v", command, err)
}

if err := env.handleSingle(t, cmd, args); err != nil {
if env.Output.Quiet() {
return err
}
env.Output.WriteString(err.Error())
}
}

return nil
}

func (env *InteractionEnv) handleSingle(t *testing.T, cmd string, args []datadriven.CmdArg) error {
switch cmd {
case "_breakpoint":
// This is a helper case to attach a debugger to when a problem needs
// to be investigated in a longer test file. In such a case, add the
Expand All @@ -44,194 +120,157 @@ func (env *InteractionEnv) Handle(t *testing.T, d datadriven.TestData) string {
// Example:
//
// add-nodes <number-of-nodes-to-add> voters=(1 2 3) learners=(4 5) index=2 content=foo async-storage-writes=true
err = env.handleAddNodes(t, d)
return env.handleAddNodes(t, args)
case "campaign":
// Example:
//
// campaign <id-of-candidate>
err = env.handleCampaign(t, d)
return env.handleCampaign(t, args)
case "compact":
// Example:
//
// compact <id> <new-first-index>
err = env.handleCompact(t, d)
return env.handleCompact(t, args)
case "deliver-msgs":
// Deliver the messages for a given recipient.
//
// Example:
//
// deliver-msgs <idx> type=MsgApp drop=(2,3)
err = env.handleDeliverMsgs(t, d)
return env.handleDeliverMsgs(t, args)
case "process-ready":
// Example:
//
// process-ready 3
err = env.handleProcessReady(t, d)
return env.handleProcessReady(t, args)
case "process-append-thread":
// Example:
//
// process-append-thread 3
err = env.handleProcessAppendThread(t, d)
return env.handleProcessAppendThread(t, args)
case "process-apply-thread":
// Example:
//
// process-apply-thread 3
err = env.handleProcessApplyThread(t, d)
return env.handleProcessApplyThread(t, args)
case "log-level":
// Set the log level. NONE disables all output, including from the test
// harness (except errors).
//
// Example:
//
// log-level WARN
err = env.handleLogLevel(d)
return env.handleLogLevel(args)
case "raft-log":
// Print the Raft log.
//
// Example:
//
// raft-log 3
err = env.handleRaftLog(t, d)
return env.handleRaftLog(t, args)
case "raft-state":
// Print Raft state of all nodes (whether the node is leading,
// following, etc.). The information for node n is based on
// n's view.
err = env.handleRaftState()
return env.handleRaftState()
case "set-randomized-election-timeout":
// Set the randomized election timeout for the given node. Will be reset
// again when the node changes state.
//
// Example:
//
// set-randomized-election-timeout 1 timeout=5
err = env.handleSetRandomizedElectionTimeout(t, d)
return env.handleSetRandomizedElectionTimeout(t, args)
case "stabilize":
// Deliver messages to and run process-ready on the set of IDs until
// no more work is to be done. If no ids are given, all nodes are used.
//
// Example:
//
// stabilize 1 4
err = env.handleStabilize(t, d)
return env.handleStabilize(t, args)
case "status":
// Print Raft status.
//
// Example:
//
// status 5
err = env.handleStatus(t, d)
return env.handleStatus(t, args)
case "tick-election":
// Tick an election timeout interval for the given node (but beware the
// randomized timeout).
//
// Example:
//
// tick-election 3
err = env.handleTickElection(t, d)
return env.handleTickElection(t, args)
case "tick-heartbeat":
// Tick a heartbeat interval.
//
// Example:
//
// tick-heartbeat 3
err = env.handleTickHeartbeat(t, d)
return env.handleTickHeartbeat(t, args)
case "transfer-leadership":
// Transfer the Raft leader.
//
// Example:
//
// transfer-leadership from=1 to=4
err = env.handleTransferLeadership(t, d)
return env.handleTransferLeadership(t, args)
case "forget-leader":
// Forgets the current leader of the given node.
//
// Example:
//
// forget-leader 1
err = env.handleForgetLeader(t, d)
return env.handleForgetLeader(t, args)
case "send-snapshot":
// Sends a snapshot to a node. Takes the source and destination node.
// The message will be queued, but not delivered automatically.
//
// Example: send-snapshot 1 3
env.handleSendSnapshot(t, d)
return env.handleSendSnapshot(t, args)
case "propose":
// Propose an entry.
//
// Example:
//
// propose 1 foo
err = env.handlePropose(t, d)
case "propose-conf-change":
// Propose a configuration change, or transition out of a previously
// proposed joint configuration change that requested explicit
// transitions. When adding nodes, this command can be used to
// logically add nodes to the configuration, but add-nodes is needed
// to "create" the nodes.
//
// propose-conf-change node_id [v1=<bool>] [transition=<string>]
// command string
// See ConfChangesFromString for command string format.
// Arguments are:
// node_id - the node proposing the configuration change.
// v1 - make one change at a time, false by default.
// transition - "auto" (the default), "explicit" or "implicit".
// Example:
//
// propose-conf-change 1 transition=explicit
// v1 v3 l4 r5
//
// Example:
//
// propose-conf-change 2 v1=true
// v5
err = env.handleProposeConfChange(t, d)
return env.handlePropose(t, args)
case "report-unreachable":
// Calls <1st>.ReportUnreachable(<2nd>).
//
// Example:
// report-unreachable 1 2
err = env.handleReportUnreachable(t, d)
return env.handleReportUnreachable(t, args)
default:
err = fmt.Errorf("unknown command")
}
// NB: the highest log level suppresses all output, including that of the
// handlers. This comes in useful during setup which can be chatty.
// However, errors are always logged.
if err != nil {
if env.Output.Quiet() {
return err.Error()
}
env.Output.WriteString(err.Error())
}
if env.Output.Len() == 0 {
return "ok"
}
return env.Output.String()
return fmt.Errorf("unknown command")
}

func firstAsInt(t *testing.T, d datadriven.TestData) int {
func firstAsInt(t *testing.T, args []datadriven.CmdArg) int {
t.Helper()
n, err := strconv.Atoi(d.CmdArgs[0].Key)
n, err := strconv.Atoi(args[0].Key)
if err != nil {
t.Fatal(err)
}
return n
}

func firstAsNodeIdx(t *testing.T, d datadriven.TestData) int {
func firstAsNodeIdx(t *testing.T, args []datadriven.CmdArg) int {
t.Helper()
n := firstAsInt(t, d)
n := firstAsInt(t, args)
return n - 1
}

func nodeIdxs(t *testing.T, d datadriven.TestData) []int {
func nodeIdxs(t *testing.T, args []datadriven.CmdArg) []int {
var ints []int
for i := 0; i < len(d.CmdArgs); i++ {
if len(d.CmdArgs[i].Vals) != 0 {
for i := 0; i < len(args); i++ {
if len(args[i].Vals) != 0 {
continue
}
n, err := strconv.Atoi(d.CmdArgs[i].Key)
n, err := strconv.Atoi(args[i].Key)
if err != nil {
t.Fatal(err)
}
Expand Down
Loading
Loading