datadriven_test.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  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. package quorum
  15. import (
  16. "fmt"
  17. "strings"
  18. "testing"
  19. "github.com/cockroachdb/datadriven"
  20. )
  21. // TestDataDriven parses and executes the test cases in ./testdata/*. An entry
  22. // in such a file specifies the command, which is either of "committed" to check
  23. // CommittedIndex or "vote" to verify a VoteResult. The underlying configuration
  24. // and inputs are specified via the arguments 'cfg' and 'cfgj' (for the majority
  25. // config and, optionally, majority config joint to the first one) and 'idx'
  26. // (for CommittedIndex) and 'votes' (for VoteResult).
  27. //
  28. // Internally, the harness runs some additional checks on each test case for
  29. // which it is known that the result shouldn't change. For example,
  30. // interchanging the majority configurations of a joint quorum must not
  31. // influence the result; if it does, this is noted in the test's output.
  32. func TestDataDriven(t *testing.T) {
  33. datadriven.Walk(t, "testdata", func(t *testing.T, path string) {
  34. datadriven.RunTest(t, path, func(d *datadriven.TestData) string {
  35. // Two majority configs. The first one is always used (though it may
  36. // be empty) and the second one is used iff joint is true.
  37. var joint bool
  38. var ids, idsj []uint64
  39. // The committed indexes for the nodes in the config in the order in
  40. // which they appear in (ids,idsj), without repetition. An underscore
  41. // denotes an omission (i.e. no information for this voter); this is
  42. // different from 0. For example,
  43. //
  44. // cfg=(1,2) cfgj=(2,3,4) idxs=(_,5,_,7) initializes the idx for voter 2
  45. // to 5 and that for voter 4 to 7 (and no others).
  46. //
  47. // cfgj=zero is specified to instruct the test harness to treat cfgj
  48. // as zero instead of not specified (i.e. it will trigger a joint
  49. // quorum test instead of a majority quorum test for cfg only).
  50. var idxs []Index
  51. // Votes. These are initialized similar to idxs except the only values
  52. // used are 1 (voted against) and 2 (voted for). This looks awkward,
  53. // but is convenient because it allows sharing code between the two.
  54. var votes []Index
  55. // Parse the args.
  56. for _, arg := range d.CmdArgs {
  57. for i := range arg.Vals {
  58. switch arg.Key {
  59. case "cfg":
  60. var n uint64
  61. arg.Scan(t, i, &n)
  62. ids = append(ids, n)
  63. case "cfgj":
  64. joint = true
  65. if arg.Vals[i] == "zero" {
  66. if len(arg.Vals) != 1 {
  67. t.Fatalf("cannot mix 'zero' into configuration")
  68. }
  69. } else {
  70. var n uint64
  71. arg.Scan(t, i, &n)
  72. idsj = append(idsj, n)
  73. }
  74. case "idx":
  75. var n uint64
  76. // Register placeholders as zeroes.
  77. if arg.Vals[i] != "_" {
  78. arg.Scan(t, i, &n)
  79. if n == 0 {
  80. // This is a restriction caused by the above
  81. // special-casing for _.
  82. t.Fatalf("cannot use 0 as idx")
  83. }
  84. }
  85. idxs = append(idxs, Index(n))
  86. case "votes":
  87. var s string
  88. arg.Scan(t, i, &s)
  89. switch s {
  90. case "y":
  91. votes = append(votes, 2)
  92. case "n":
  93. votes = append(votes, 1)
  94. case "_":
  95. votes = append(votes, 0)
  96. default:
  97. t.Fatalf("unknown vote: %s", s)
  98. }
  99. default:
  100. t.Fatalf("unknown arg %s", arg.Key)
  101. }
  102. }
  103. }
  104. // Build the two majority configs.
  105. c := MajorityConfig{}
  106. for _, id := range ids {
  107. c[id] = struct{}{}
  108. }
  109. cj := MajorityConfig{}
  110. for _, id := range idsj {
  111. cj[id] = struct{}{}
  112. }
  113. // Helper that returns an AckedIndexer which has the specified indexes
  114. // mapped to the right IDs.
  115. makeLookuper := func(idxs []Index, ids, idsj []uint64) mapAckIndexer {
  116. l := mapAckIndexer{}
  117. var p int // next to consume from idxs
  118. for _, id := range append(append([]uint64(nil), ids...), idsj...) {
  119. if _, ok := l[id]; ok {
  120. continue
  121. }
  122. if p < len(idxs) {
  123. // NB: this creates zero entries for placeholders that we remove later.
  124. // The upshot of doing it that way is to avoid having to specify place-
  125. // holders multiple times when omitting voters present in both halves of
  126. // a joint config.
  127. l[id] = idxs[p]
  128. p++
  129. }
  130. }
  131. for id := range l {
  132. // Zero entries are created by _ placeholders; we don't want
  133. // them in the lookuper because "no entry" is different from
  134. // "zero entry". Note that we prevent tests from specifying
  135. // zero commit indexes, so that there's no confusion between
  136. // the two concepts.
  137. if l[id] == 0 {
  138. delete(l, id)
  139. }
  140. }
  141. return l
  142. }
  143. {
  144. input := idxs
  145. if d.Cmd == "vote" {
  146. input = votes
  147. }
  148. if voters := JointConfig([2]MajorityConfig{c, cj}).IDs(); len(voters) != len(input) {
  149. return fmt.Sprintf("error: mismatched input (explicit or _) for voters %v: %v",
  150. voters, input)
  151. }
  152. }
  153. var buf strings.Builder
  154. switch d.Cmd {
  155. case "committed":
  156. l := makeLookuper(idxs, ids, idsj)
  157. // Branch based on whether this is a majority or joint quorum
  158. // test case.
  159. if !joint {
  160. idx := c.CommittedIndex(l)
  161. fmt.Fprintf(&buf, c.Describe(l))
  162. // These alternative computations should return the same
  163. // result. If not, print to the output.
  164. if aIdx := alternativeMajorityCommittedIndex(c, l); aIdx != idx {
  165. fmt.Fprintf(&buf, "%s <-- via alternative computation\n", aIdx)
  166. }
  167. // Joining a majority with the empty majority should give same result.
  168. if aIdx := JointConfig([2]MajorityConfig{c, {}}).CommittedIndex(l); aIdx != idx {
  169. fmt.Fprintf(&buf, "%s <-- via zero-joint quorum\n", aIdx)
  170. }
  171. // Joining a majority with itself should give same result.
  172. if aIdx := JointConfig([2]MajorityConfig{c, c}).CommittedIndex(l); aIdx != idx {
  173. fmt.Fprintf(&buf, "%s <-- via self-joint quorum\n", aIdx)
  174. }
  175. overlay := func(c MajorityConfig, l AckedIndexer, id uint64, idx Index) AckedIndexer {
  176. ll := mapAckIndexer{}
  177. for iid := range c {
  178. if iid == id {
  179. ll[iid] = idx
  180. } else if idx, ok := l.AckedIndex(iid); ok {
  181. ll[iid] = idx
  182. }
  183. }
  184. return ll
  185. }
  186. for id := range c {
  187. iidx, _ := l.AckedIndex(id)
  188. if idx > iidx && iidx > 0 {
  189. // If the committed index was definitely above the currently
  190. // inspected idx, the result shouldn't change if we lower it
  191. // further.
  192. lo := overlay(c, l, id, iidx-1)
  193. if aIdx := c.CommittedIndex(lo); aIdx != idx {
  194. fmt.Fprintf(&buf, "%s <-- overlaying %d->%d", aIdx, id, iidx)
  195. }
  196. lo = overlay(c, l, id, 0)
  197. if aIdx := c.CommittedIndex(lo); aIdx != idx {
  198. fmt.Fprintf(&buf, "%s <-- overlaying %d->0", aIdx, id)
  199. }
  200. }
  201. }
  202. fmt.Fprintf(&buf, "%s\n", idx)
  203. } else {
  204. cc := JointConfig([2]MajorityConfig{c, cj})
  205. fmt.Fprintf(&buf, cc.Describe(l))
  206. idx := cc.CommittedIndex(l)
  207. // Interchanging the majorities shouldn't make a difference. If it does, print.
  208. if aIdx := JointConfig([2]MajorityConfig{c, cj}).CommittedIndex(l); aIdx != idx {
  209. fmt.Fprintf(&buf, "%s <-- via symmetry\n", aIdx)
  210. }
  211. fmt.Fprintf(&buf, "%s\n", idx)
  212. }
  213. case "vote":
  214. ll := makeLookuper(votes, ids, idsj)
  215. l := map[uint64]bool{}
  216. for id, v := range ll {
  217. l[id] = v != 1 // NB: 1 == false, 2 == true
  218. }
  219. if !joint {
  220. // Test a majority quorum.
  221. r := c.VoteResult(l)
  222. fmt.Fprintf(&buf, "%v\n", r)
  223. } else {
  224. // Run a joint quorum test case.
  225. r := JointConfig([2]MajorityConfig{c, cj}).VoteResult(l)
  226. // Interchanging the majorities shouldn't make a difference. If it does, print.
  227. if ar := JointConfig([2]MajorityConfig{cj, c}).VoteResult(l); ar != r {
  228. fmt.Fprintf(&buf, "%v <-- via symmetry\n", ar)
  229. }
  230. fmt.Fprintf(&buf, "%v\n", r)
  231. }
  232. default:
  233. t.Fatalf("unknown command: %s", d.Cmd)
  234. }
  235. return buf.String()
  236. })
  237. })
  238. }