// Copyright 2019 The etcd Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package rafttest import ( "fmt" "strconv" "testing" "github.com/cockroachdb/datadriven" ) // Handle is the entrypoint for data-driven interaction testing. Commands and // parameters are parsed from the supplied TestData. Errors during data parsing // are reported via the supplied *testing.T; errors from the raft nodes and the // storage engine are reported to the output buffer. func (env *InteractionEnv) Handle(t *testing.T, d datadriven.TestData) string { env.Output.Reset() var err error switch d.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 // following stanza immediately before the interesting behavior starts: // // _breakpoint: // ---- // ok // // and set a breakpoint on the `case` above. case "add-nodes": // Example: // // add-nodes voters=(1 2 3) learners=(4 5) index=2 content=foo err = env.handleAddNodes(t, d) case "campaign": // Example: // // campaign err = env.handleCampaign(t, d) case "compact": // Example: // // compact err = env.handleCompact(t, d) case "deliver-msgs": // Deliver the messages for a given recipient. // // Example: // // deliver-msgs err = env.handleDeliverMsgs(t, d) case "process-ready": // Example: // // process-ready 3 err = env.handleProcessReady(t, d) 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(t, d) case "raft-log": // Print the Raft log. // // Example: // // raft-log 3 err = env.handleRaftLog(t, d) case "stabilize": // Deliver messages to and run process-ready on the set of IDs until // no more work is to be done. // // Example: // // stabilize 1 4 err = env.handleStabilize(t, d) case "status": // Print Raft status. // // Example: // // status 5 err = env.handleStatus(t, d) case "tick-heartbeat": // Tick a heartbeat interval. // // Example: // // tick-heartbeat 3 err = env.handleTickHeartbeat(t, d) case "propose": // Propose an entry. // // Example: // // propose 1 foo err = env.handlePropose(t, d) case "propose-conf-change": // Propose a configuration change. // // Example: // // propose-conf-change transition=explicit // v1 v3 l4 r5 // // Example: // // propose-conf-change v1=true // v5 err = env.handleProposeConfChange(t, d) default: err = fmt.Errorf("unknown command") } if err != nil { env.Output.WriteString(err.Error()) } // 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 env.Output.Len() == 0 { return "ok" } if env.Output.Lvl == len(lvlNames)-1 { if err != nil { return err.Error() } return "ok (quiet)" } return env.Output.String() } func firstAsInt(t *testing.T, d datadriven.TestData) int { t.Helper() n, err := strconv.Atoi(d.CmdArgs[0].Key) if err != nil { t.Fatal(err) } return n } func firstAsNodeIdx(t *testing.T, d datadriven.TestData) int { t.Helper() n := firstAsInt(t, d) return n - 1 } func ints(t *testing.T, d datadriven.TestData) []int { var ints []int for i := 0; i < len(d.CmdArgs); i++ { if len(d.CmdArgs[i].Vals) != 0 { continue } n, err := strconv.Atoi(d.CmdArgs[i].Key) if err != nil { t.Fatal(err) } ints = append(ints, n) } return ints }