github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/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/datadriven" 18 "github.com/cockroachdb/errors" 19 "github.com/cockroachdb/pebble/vfs" 20 "github.com/cockroachdb/pebble/vfs/errorfs" 21 "github.com/stretchr/testify/require" 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(t *testing.T, 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 var count atomic.Int32 212 count.Store(int32(i)) 213 inj := errorfs.InjectorFunc(func(op errorfs.Op) error { 214 // Don't inject on Sync errors. They're fatal. 215 if op.Kind == errorfs.OpFileSync { 216 return nil 217 } 218 if v := count.Add(-1); v == 0 { 219 return errorfs.ErrInjected 220 } 221 return nil 222 }) 223 224 mem := vfs.NewMem() 225 fs := errorfs.Wrap(mem, inj) 226 markers := map[string]*Marker{} 227 ops := []struct { 228 op string 229 name string 230 value string 231 }{ 232 {op: "locate", name: "foo", value: ""}, 233 {op: "locate", name: "foo", value: ""}, 234 {op: "locate", name: "bar", value: ""}, 235 {op: "rm-obsolete", name: "foo"}, 236 {op: "move", name: "bar", value: "california"}, 237 {op: "rm-obsolete", name: "bar"}, 238 {op: "move", name: "bar", value: "california"}, 239 {op: "move", name: "bar", value: "new-york"}, 240 {op: "locate", name: "bar", value: "new-york"}, 241 {op: "move", name: "bar", value: "california"}, 242 {op: "rm-obsolete", name: "bar"}, 243 {op: "locate", name: "bar", value: "california"}, 244 {op: "move", name: "foo", value: "connecticut"}, 245 {op: "locate", name: "foo", value: "connecticut"}, 246 } 247 248 for _, op := range ops { 249 runOp := func() error { 250 switch op.op { 251 case "locate": 252 m, v, err := LocateMarker(fs, "", op.name) 253 if err != nil { 254 return err 255 } 256 require.NotNil(t, m) 257 require.Equal(t, op.value, v) 258 if existingMarker := markers[op.name]; existingMarker != nil { 259 require.NoError(t, existingMarker.Close()) 260 } 261 markers[op.name] = m 262 return nil 263 case "move": 264 m := markers[op.name] 265 require.NotNil(t, m) 266 return m.Move(op.value) 267 case "rm-obsolete": 268 m := markers[op.name] 269 require.NotNil(t, m) 270 return m.RemoveObsolete() 271 default: 272 panic("unreachable") 273 } 274 } 275 276 // Run the operation, if it fails with the injected 277 // error, retry it exactly once. The retry should always 278 // succeed. 279 err := runOp() 280 if errors.Is(err, errorfs.ErrInjected) { 281 err = runOp() 282 } 283 require.NoError(t, err) 284 } 285 286 for _, m := range markers { 287 require.NoError(t, m.Close()) 288 } 289 290 // Stop if the number of operations in the test case is 291 // fewer than `i`. 292 done = count.Load() > 0 293 }) 294 } 295 }