fuzz_model_test.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. package core
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "path/filepath"
  6. "testing"
  7. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  8. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  9. )
  10. // TestRandomOpsAgainstModel 模糊测试: 随机插入/更新/删除 (含整表删空再灌),
  11. // 每步与一个内存模型比对; 最后关库重开, 全量校验持久化结果。
  12. // 固定种子 → 失败可复现。这是对抗"人工审计想不到的序列"的主要手段。
  13. func TestRandomOpsAgainstModel(t *testing.T) {
  14. seeds := []int64{1, 42, 1337, 20240927, 777, 90091}
  15. for _, seed := range seeds {
  16. t.Run(fmt.Sprintf("seed-%d", seed), func(t *testing.T) {
  17. path := filepath.Join(t.TempDir(), "fuzz.db")
  18. db, err := Open(path)
  19. if err != nil {
  20. t.Fatalf("open: %v", err)
  21. }
  22. db.SetAutoCommitDelay(0)
  23. tbl, err := db.CreateTable("t", []types.FieldInfo{
  24. {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
  25. {FieldType: types.FieldType_Int64, FieldName: []byte("a")},
  26. {FieldType: types.FieldType_Int64, FieldName: []byte("b")},
  27. })
  28. if err != nil {
  29. t.Fatalf("建表: %v", err)
  30. }
  31. if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
  32. t.Fatalf("建主键索引: %v", err)
  33. }
  34. if _, err := tbl.CreateIndex("idx_a", page.IndexTypeBTree, []byte{1}); err != nil {
  35. t.Fatalf("建二级索引: %v", err)
  36. }
  37. rng := rand.New(rand.NewSource(seed))
  38. model := map[uint64][2]int64{}
  39. ids := []uint64{} // 存活 id 池 (用于随机改/删)
  40. nextID := uint64(1)
  41. insert := func(id uint64, a, b int64) {
  42. if _, err := tbl.InsertRowWithID(id, []types.FieldValue{
  43. {FieldName: []byte("id"), Value: id},
  44. {FieldName: []byte("a"), Value: a},
  45. {FieldName: []byte("b"), Value: b},
  46. }); err != nil {
  47. t.Fatalf("插入 id=%d: %v", id, err)
  48. }
  49. }
  50. remove := func(id uint64) {
  51. if err := tbl.DeleteRowByPK(id); err != nil {
  52. t.Fatalf("删除 id=%d: %v", id, err)
  53. }
  54. }
  55. for op := 0; op < 8000; op++ {
  56. roll := rng.Intn(100)
  57. switch {
  58. case roll < 34: // 插入
  59. id := nextID
  60. nextID++
  61. a := int64(rng.Intn(20))
  62. b := int64(rng.Intn(7))
  63. insert(id, a, b)
  64. model[id] = [2]int64{a, b}
  65. ids = append(ids, id)
  66. case roll < 62 && len(ids) > 0: // 更新 (整行覆盖)
  67. k := rng.Intn(len(ids))
  68. id := ids[k]
  69. a := int64(rng.Intn(20))
  70. b := int64(rng.Intn(7))
  71. if err := tbl.UpdateRowByPK(id, []types.FieldValue{
  72. {FieldName: []byte("a"), Value: a},
  73. {FieldName: []byte("b"), Value: b},
  74. }); err != nil {
  75. t.Fatalf("更新 id=%d: %v", id, err)
  76. }
  77. model[id] = [2]int64{a, b}
  78. case roll < 88 && len(ids) > 0: // 删除
  79. k := rng.Intn(len(ids))
  80. id := ids[k]
  81. ids = append(ids[:k], ids[k+1:]...)
  82. remove(id)
  83. delete(model, id)
  84. case roll < 90: // 整表删空 (制造删空页 + 空闲页复用)
  85. for id := range model {
  86. remove(id)
  87. }
  88. model = map[uint64][2]int64{}
  89. ids = nil
  90. default: // 提交
  91. if err := db.Commit(); err != nil {
  92. t.Fatalf("commit: %v", err)
  93. }
  94. }
  95. }
  96. if err := db.Commit(); err != nil {
  97. t.Fatalf("final commit: %v", err)
  98. }
  99. verify := func(phase string) {
  100. // 行数
  101. if c, _ := tbl.Query().Count(); c != int64(len(model)) {
  102. t.Fatalf("%s: count=%d, 模型 %d", phase, c, len(model))
  103. }
  104. // 全量逐行比对
  105. seen := map[uint64][2]int64{}
  106. err := tbl.Query().ScanWithPKID(func(pkID uint64, row []types.FieldValue) bool {
  107. var pair [2]int64
  108. for _, fv := range row {
  109. switch string(fv.FieldName) {
  110. case "a":
  111. pair[0] = fv.Value.(int64)
  112. case "b":
  113. pair[1] = fv.Value.(int64)
  114. }
  115. }
  116. seen[pkID] = pair
  117. return true
  118. })
  119. if err != nil {
  120. t.Fatalf("%s: scan: %v", phase, err)
  121. }
  122. if len(seen) != len(model) {
  123. t.Fatalf("%s: 扫到 %d 行, 模型 %d 行", phase, len(seen), len(model))
  124. }
  125. for id, want := range model {
  126. got, ok := seen[id]
  127. if !ok || got != want {
  128. t.Fatalf("%s: id=%d 得到 %+v (存在=%v), 模型 %+v", phase, id, got, ok, want)
  129. }
  130. }
  131. // 二级索引等值计数与模型交叉验证
  132. for a := int64(0); a < 20; a++ {
  133. wantN := int64(0)
  134. for _, pair := range model {
  135. if pair[0] == a {
  136. wantN++
  137. }
  138. }
  139. if n, _ := tbl.Query().Where(Eq("a", a, 0)).Count(); n != wantN {
  140. t.Fatalf("%s: a=%d 索引 count=%d, 模型 %d", phase, a, n, wantN)
  141. }
  142. }
  143. }
  144. verify("关库前")
  145. if err := db.Close(); err != nil {
  146. t.Fatalf("close: %v", err)
  147. }
  148. // 重开: 持久化校验
  149. db2, err := Open(path)
  150. if err != nil {
  151. t.Fatalf("重开: %v", err)
  152. }
  153. defer db2.Close()
  154. tbl2, ok := db2.FindTable("t")
  155. if !ok {
  156. t.Fatalf("重开找不到表")
  157. }
  158. tbl = tbl2
  159. verify("重开后")
  160. })
  161. }
  162. }