1 // Copyright 2019 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package rafttest 16 17 import ( 18 "fmt" 19 "strconv" 20 "testing" 21 22 "github.com/cockroachdb/datadriven" 23 ) 24 25 // Handle is the entrypoint for data-driven interaction testing. Commands and 26 // parameters are parsed from the supplied TestData. Errors during data parsing 27 // are reported via the supplied *testing.T; errors from the raft nodes and the 28 // storage engine are reported to the output buffer. 29 func (env *InteractionEnv) Handle(t *testing.T, d datadriven.TestData) string { 30 env.Output.Reset() 31 var err error 32 switch d.Cmd { 33 case "_breakpoint": 34 // This is a helper case to attach a debugger to when a problem needs 35 // to be investigated in a longer test file. In such a case, add the 36 // following stanza immediately before the interesting behavior starts: 37 // 38 // _breakpoint: 39 // ---- 40 // ok 41 // 42 // and set a breakpoint on the `case` above. 43 case "add-nodes": 44 // Example: 45 // 46 // add-nodes <number-of-nodes-to-add> voters=(1 2 3) learners=(4 5) index=2 content=foo 47 err = env.handleAddNodes(t, d) 48 case "campaign": 49 // Example: 50 // 51 // campaign <id-of-candidate> 52 err = env.handleCampaign(t, d) 53 case "compact": 54 // Example: 55 // 56 // compact <id> <new-first-index> 57 err = env.handleCompact(t, d) 58 case "deliver-msgs": 59 // Deliver the messages for a given recipient. 60 // 61 // Example: 62 // 63 // deliver-msgs <idx> 64 err = env.handleDeliverMsgs(t, d) 65 case "process-ready": 66 // Example: 67 // 68 // process-ready 3 69 err = env.handleProcessReady(t, d) 70 case "log-level": 71 // Set the log level. NONE disables all output, including from the test 72 // harness (except errors). 73 // 74 // Example: 75 // 76 // log-level WARN 77 err = env.handleLogLevel(t, d) 78 case "raft-log": 79 // Print the Raft log. 80 // 81 // Example: 82 // 83 // raft-log 3 84 err = env.handleRaftLog(t, d) 85 case "stabilize": 86 // Deliver messages to and run process-ready on the set of IDs until 87 // no more work is to be done. 88 // 89 // Example: 90 // 91 // stabilize 1 4 92 err = env.handleStabilize(t, d) 93 case "status": 94 // Print Raft status. 95 // 96 // Example: 97 // 98 // status 5 99 err = env.handleStatus(t, d) 100 case "tick-heartbeat": 101 // Tick a heartbeat interval. 102 // 103 // Example: 104 // 105 // tick-heartbeat 3 106 err = env.handleTickHeartbeat(t, d) 107 case "propose": 108 // Propose an entry. 109 // 110 // Example: 111 // 112 // propose 1 foo 113 err = env.handlePropose(t, d) 114 case "propose-conf-change": 115 // Propose a configuration change. 116 // 117 // Example: 118 // 119 // propose-conf-change transition=explicit 120 // v1 v3 l4 r5 121 // 122 // Example: 123 // 124 // propose-conf-change v1=true 125 // v5 126 err = env.handleProposeConfChange(t, d) 127 default: 128 err = fmt.Errorf("unknown command") 129 } 130 if err != nil { 131 env.Output.WriteString(err.Error()) 132 } 133 // NB: the highest log level suppresses all output, including that of the 134 // handlers. This comes in useful during setup which can be chatty. 135 // However, errors are always logged. 136 if env.Output.Len() == 0 { 137 return "ok" 138 } 139 if env.Output.Lvl == len(lvlNames)-1 { 140 if err != nil { 141 return err.Error() 142 } 143 return "ok (quiet)" 144 } 145 return env.Output.String() 146 } 147 148 func firstAsInt(t *testing.T, d datadriven.TestData) int { 149 t.Helper() 150 n, err := strconv.Atoi(d.CmdArgs[0].Key) 151 if err != nil { 152 t.Fatal(err) 153 } 154 return n 155 } 156 157 func firstAsNodeIdx(t *testing.T, d datadriven.TestData) int { 158 t.Helper() 159 n := firstAsInt(t, d) 160 return n - 1 161 } 162 163 func nodeIdxs(t *testing.T, d datadriven.TestData) []int { 164 var ints []int 165 for i := 0; i < len(d.CmdArgs); i++ { 166 if len(d.CmdArgs[i].Vals) != 0 { 167 continue 168 } 169 n, err := strconv.Atoi(d.CmdArgs[i].Key) 170 if err != nil { 171 t.Fatal(err) 172 } 173 ints = append(ints, n-1) 174 } 175 return ints 176 } 177