github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/vfs/atomicfs/marker_test.go (about) 1 // Copyright 2021 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package atomicfs 6 7 import ( 8 "bytes" 9 "fmt" 10 "os" 11 "sort" 12 "strconv" 13 "strings" 14 "sync/atomic" 15 "testing" 16 17 "github.com/cockroachdb/errors" 18 "github.com/stretchr/testify/require" 19 "github.com/zuoyebang/bitalostable/internal/datadriven" 20 "github.com/zuoyebang/bitalostable/internal/errorfs" 21 "github.com/zuoyebang/bitalostable/vfs" 22 ) 23 24 func TestMarker_FilenameRoundtrip(t *testing.T) { 25 filenames := []string{ 26 "marker.foo.000003.MANIFEST-000021", 27 "marker.bar.000003.MANIFEST-000021", 28 "marker.version.000003.1", 29 "marker.version.000003.1.2.3.4", 30 "marker.current.500000.MANIFEST-000001", 31 "marker.current.18446744073709551615.MANIFEST-000001", 32 } 33 for _, testFilename := range filenames { 34 t.Run(testFilename, func(t *testing.T) { 35 name, iter, value, err := parseMarkerFilename(testFilename) 36 require.NoError(t, err) 37 38 filename := markerFilename(name, iter, value) 39 require.Equal(t, testFilename, filename) 40 }) 41 } 42 } 43 44 func TestMarker_Parsefilename(t *testing.T) { 45 testCases := map[string]func(require.TestingT, error, ...interface{}){ 46 "marker.current.000003.MANIFEST-000021": require.NoError, 47 "marker.current.10.MANIFEST-000021": require.NoError, 48 "marker.v.10.1.2.3.4": require.NoError, 49 "marker.name.18446744073709551615.value": require.NoError, 50 "marke.current.000003.MANIFEST-000021": require.Error, 51 "marker.current.foo.MANIFEST-000021": require.Error, 52 "marker.current.ffffff.MANIFEST-000021": require.Error, 53 } 54 for filename, assert := range testCases { 55 t.Run(filename, func(t *testing.T) { 56 _, _, _, err := parseMarkerFilename(filename) 57 assert(t, err) 58 }) 59 } 60 } 61 62 func TestMarker(t *testing.T) { 63 markers := map[string]*Marker{} 64 memFS := vfs.NewMem() 65 66 var buf bytes.Buffer 67 datadriven.RunTest(t, "testdata/marker", func(td *datadriven.TestData) string { 68 switch td.Cmd { 69 case "list": 70 ls, err := memFS.List(td.CmdArgs[0].String()) 71 if err != nil { 72 return err.Error() 73 } 74 sort.Strings(ls) 75 buf.Reset() 76 for _, filename := range ls { 77 fmt.Fprintln(&buf, filename) 78 } 79 return buf.String() 80 81 case "locate": 82 var dir, marker string 83 td.ScanArgs(t, "dir", &dir) 84 td.ScanArgs(t, "marker", &marker) 85 m, v, err := LocateMarker(memFS, dir, marker) 86 if err != nil { 87 return err.Error() 88 } 89 p := memFS.PathJoin(dir, marker) 90 if oldMarker := markers[p]; oldMarker != nil { 91 if err := oldMarker.Close(); err != nil { 92 return err.Error() 93 } 94 } 95 96 markers[p] = m 97 return v 98 99 case "mkdir-all": 100 if len(td.CmdArgs) != 1 { 101 return "usage: mkdir-all <dir>" 102 } 103 if err := memFS.MkdirAll(td.CmdArgs[0].String(), os.ModePerm); err != nil { 104 return err.Error() 105 } 106 return "" 107 108 case "move": 109 var dir, marker string 110 td.ScanArgs(t, "dir", &dir) 111 td.ScanArgs(t, "marker", &marker) 112 m := markers[memFS.PathJoin(dir, marker)] 113 require.NotNil(t, m) 114 err := m.Move(td.Input) 115 if err != nil { 116 return err.Error() 117 } 118 return "" 119 120 case "next-iter": 121 var dir, marker string 122 td.ScanArgs(t, "dir", &dir) 123 td.ScanArgs(t, "marker", &marker) 124 m := markers[memFS.PathJoin(dir, marker)] 125 require.NotNil(t, m) 126 return fmt.Sprintf("%d", m.NextIter()) 127 128 case "read": 129 var dir, marker string 130 td.ScanArgs(t, "dir", &dir) 131 td.ScanArgs(t, "marker", &marker) 132 v, err := ReadMarker(memFS, dir, marker) 133 if err != nil { 134 return err.Error() 135 } 136 return v 137 138 case "remove-obsolete": 139 var dir, marker string 140 td.ScanArgs(t, "dir", &dir) 141 td.ScanArgs(t, "marker", &marker) 142 m := markers[memFS.PathJoin(dir, marker)] 143 require.NotNil(t, m) 144 obsoleteCount := len(m.obsoleteFiles) 145 require.NoError(t, m.RemoveObsolete()) 146 removedCount := obsoleteCount - len(m.obsoleteFiles) 147 return fmt.Sprintf("Removed %d files.", removedCount) 148 149 case "touch": 150 for _, filename := range strings.Split(td.Input, "\n") { 151 f, err := memFS.Create(filename) 152 if err != nil { 153 return err.Error() 154 } 155 if err := f.Close(); err != nil { 156 return err.Error() 157 } 158 } 159 return "" 160 161 default: 162 panic(fmt.Sprintf("unknown command %q", td.Cmd)) 163 } 164 }) 165 } 166 167 func TestMarker_StrictSync(t *testing.T) { 168 // Use an in-memory FS that strictly enforces syncs. 169 mem := vfs.NewStrictMem() 170 syncDir := func(dir string) { 171 fdir, err := mem.OpenDir(dir) 172 require.NoError(t, err) 173 require.NoError(t, fdir.Sync()) 174 require.NoError(t, fdir.Close()) 175 } 176 177 require.NoError(t, mem.MkdirAll("foo", os.ModePerm)) 178 syncDir("") 179 m, v, err := LocateMarker(mem, "foo", "bar") 180 require.NoError(t, err) 181 require.Equal(t, "", v) 182 require.NoError(t, m.Move("hello")) 183 require.NoError(t, m.Close()) 184 185 // Discard any unsynced writes to make sure we set up the test 186 // preconditions correctly. 187 mem.ResetToSyncedState() 188 m, v, err = LocateMarker(mem, "foo", "bar") 189 require.NoError(t, err) 190 require.Equal(t, "hello", v) 191 require.NoError(t, m.Move("hello-world")) 192 require.NoError(t, m.Close()) 193 194 // Discard any unsynced writes. 195 mem.ResetToSyncedState() 196 m, v, err = LocateMarker(mem, "foo", "bar") 197 require.NoError(t, err) 198 require.Equal(t, "hello-world", v) 199 require.NoError(t, m.Close()) 200 } 201 202 // TestMarker_FaultTolerance attempts a series of operations on atomic 203 // markers, injecting errors at successively higher indexed operations. 204 // It completes when an error is never injected, because the index is 205 // higher than the number of filesystem operations performed by the 206 // test. 207 func TestMarker_FaultTolerance(t *testing.T) { 208 done := false 209 for i := 1; !done && i < 1000; i++ { 210 t.Run(strconv.Itoa(i), func(t *testing.T) { 211 count := int32(i) 212 inj := errorfs.InjectorFunc(func(op errorfs.Op, path string) error { 213 // Don't inject on Sync errors. They're fatal. 214 if op == errorfs.OpFileSync { 215 return nil 216 } 217 if v := atomic.AddInt32(&count, -1); v == 0 { 218 return errorfs.ErrInjected 219 } 220 return nil 221 }) 222 223 mem := vfs.NewMem() 224 fs := errorfs.Wrap(mem, inj) 225 markers := map[string]*Marker{} 226 ops := []struct { 227 op string 228 name string 229 value string 230 }{ 231 {op: "locate", name: "foo", value: ""}, 232 {op: "locate", name: "foo", value: ""}, 233 {op: "locate", name: "bar", value: ""}, 234 {op: "rm-obsolete", name: "foo"}, 235 {op: "move", name: "bar", value: "california"}, 236 {op: "rm-obsolete", name: "bar"}, 237 {op: "move", name: "bar", value: "california"}, 238 {op: "move", name: "bar", value: "new-york"}, 239 {op: "locate", name: "bar", value: "new-york"}, 240 {op: "move", name: "bar", value: "california"}, 241 {op: "rm-obsolete", name: "bar"}, 242 {op: "locate", name: "bar", value: "california"}, 243 {op: "move", name: "foo", value: "connecticut"}, 244 {op: "locate", name: "foo", value: "connecticut"}, 245 } 246 247 for _, op := range ops { 248 runOp := func() error { 249 switch op.op { 250 case "locate": 251 m, v, err := LocateMarker(fs, "", op.name) 252 if err != nil { 253 return err 254 } 255 require.NotNil(t, m) 256 require.Equal(t, op.value, v) 257 if existingMarker := markers[op.name]; existingMarker != nil { 258 require.NoError(t, existingMarker.Close()) 259 } 260 markers[op.name] = m 261 return nil 262 case "move": 263 m := markers[op.name] 264 require.NotNil(t, m) 265 return m.Move(op.value) 266 case "rm-obsolete": 267 m := markers[op.name] 268 require.NotNil(t, m) 269 return m.RemoveObsolete() 270 default: 271 panic("unreachable") 272 } 273 } 274 275 // Run the operation, if it fails with the injected 276 // error, retry it exactly once. The retry should always 277 // succeed. 278 err := runOp() 279 if errors.Is(err, errorfs.ErrInjected) { 280 err = runOp() 281 } 282 require.NoError(t, err) 283 } 284 285 for _, m := range markers { 286 require.NoError(t, m.Close()) 287 } 288 289 // Stop if the number of operations in the test case is 290 // fewer than `i`. 291 done = atomic.LoadInt32(&count) > 0 292 }) 293 } 294 }