github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/vfs/test_vfs/test_vfs.go (about) 1 // Test the VFS to exhaustion, specifically looking for deadlocks 2 // 3 // Run on a mounted filesystem 4 package main 5 6 import ( 7 "flag" 8 "fmt" 9 "io" 10 "log" 11 "math" 12 "math/rand" 13 "os" 14 "path" 15 "sync" 16 "sync/atomic" 17 "time" 18 19 "github.com/rclone/rclone/lib/file" 20 "github.com/rclone/rclone/lib/random" 21 ) 22 23 var ( 24 nameLength = flag.Int("name-length", 10, "Length of names to create") 25 verbose = flag.Bool("v", false, "Set to show more info") 26 number = flag.Int("n", 4, "Number of tests to run simultaneously") 27 iterations = flag.Int("i", 100, "Iterations of the test") 28 timeout = flag.Duration("timeout", 10*time.Second, "Inactivity time to detect a deadlock") 29 testNumber atomic.Int32 30 ) 31 32 // Test contains stats about the running test which work for files or 33 // directories 34 type Test struct { 35 dir string 36 name string 37 created bool 38 handle *os.File 39 tests []func() 40 isDir bool 41 number int32 42 prefix string 43 timer *time.Timer 44 } 45 46 // NewTest creates a new test and fills in the Tests 47 func NewTest(Dir string) *Test { 48 t := &Test{ 49 dir: Dir, 50 name: random.String(*nameLength), 51 isDir: rand.Intn(2) == 0, 52 number: testNumber.Add(1), 53 timer: time.NewTimer(*timeout), 54 } 55 width := int(math.Floor(math.Log10(float64(*number)))) + 1 56 t.prefix = fmt.Sprintf("%*d: %s: ", width, t.number, t.path()) 57 if t.isDir { 58 t.tests = []func(){ 59 t.list, 60 t.rename, 61 t.mkdir, 62 t.rmdir, 63 } 64 } else { 65 t.tests = []func(){ 66 t.list, 67 t.rename, 68 t.open, 69 t.close, 70 t.remove, 71 t.read, 72 t.write, 73 } 74 } 75 return t 76 } 77 78 // kick the deadlock timeout 79 func (t *Test) kick() { 80 if !t.timer.Stop() { 81 <-t.timer.C 82 } 83 t.timer.Reset(*timeout) 84 } 85 86 // randomTest runs a random test 87 func (t *Test) randomTest() { 88 t.kick() 89 i := rand.Intn(len(t.tests)) 90 t.tests[i]() 91 } 92 93 // logf logs things - not shown unless -v 94 func (t *Test) logf(format string, a ...interface{}) { 95 if *verbose { 96 log.Printf(t.prefix+format, a...) 97 } 98 } 99 100 // errorf logs errors 101 func (t *Test) errorf(format string, a ...interface{}) { 102 log.Printf(t.prefix+"ERROR: "+format, a...) 103 } 104 105 // list test 106 func (t *Test) list() { 107 t.logf("list") 108 fis, err := os.ReadDir(t.dir) 109 if err != nil { 110 t.errorf("%s: failed to read directory: %v", t.dir, err) 111 return 112 } 113 if t.created && len(fis) == 0 { 114 t.errorf("%s: expecting entries in directory, got none", t.dir) 115 return 116 } 117 found := false 118 for _, fi := range fis { 119 if fi.Name() == t.name { 120 found = true 121 } 122 } 123 if t.created { 124 if !found { 125 t.errorf("%s: expecting to find %q in directory, got none", t.dir, t.name) 126 return 127 } 128 } else { 129 if found { 130 t.errorf("%s: not expecting to find %q in directory, got none", t.dir, t.name) 131 return 132 } 133 } 134 } 135 136 // path returns the current path to the item 137 func (t *Test) path() string { 138 return path.Join(t.dir, t.name) 139 } 140 141 // rename test 142 func (t *Test) rename() { 143 if !t.created { 144 return 145 } 146 t.logf("rename") 147 NewName := random.String(*nameLength) 148 newPath := path.Join(t.dir, NewName) 149 err := os.Rename(t.path(), newPath) 150 if err != nil { 151 t.errorf("failed to rename to %q: %v", newPath, err) 152 return 153 } 154 t.name = NewName 155 } 156 157 // close test 158 func (t *Test) close() { 159 if t.handle == nil { 160 return 161 } 162 t.logf("close") 163 err := t.handle.Close() 164 t.handle = nil 165 if err != nil { 166 t.errorf("failed to close: %v", err) 167 return 168 } 169 } 170 171 // open test 172 func (t *Test) open() { 173 t.close() 174 t.logf("open") 175 handle, err := file.OpenFile(t.path(), os.O_RDWR|os.O_CREATE, 0666) 176 if err != nil { 177 t.errorf("failed to open: %v", err) 178 return 179 } 180 t.handle = handle 181 t.created = true 182 } 183 184 // read test 185 func (t *Test) read() { 186 if t.handle == nil { 187 return 188 } 189 t.logf("read") 190 bytes := make([]byte, 10) 191 _, err := t.handle.Read(bytes) 192 if err != nil && err != io.EOF { 193 t.errorf("failed to read: %v", err) 194 return 195 } 196 } 197 198 // write test 199 func (t *Test) write() { 200 if t.handle == nil { 201 return 202 } 203 t.logf("write") 204 bytes := make([]byte, 10) 205 _, err := t.handle.Write(bytes) 206 if err != nil { 207 t.errorf("failed to write: %v", err) 208 return 209 } 210 } 211 212 // remove test 213 func (t *Test) remove() { 214 if !t.created { 215 return 216 } 217 t.logf("remove") 218 err := os.Remove(t.path()) 219 if err != nil { 220 t.errorf("failed to remove: %v", err) 221 return 222 } 223 t.created = false 224 } 225 226 // mkdir test 227 func (t *Test) mkdir() { 228 if t.created { 229 return 230 } 231 t.logf("mkdir") 232 err := os.Mkdir(t.path(), 0777) 233 if err != nil { 234 t.errorf("failed to mkdir %q", t.path()) 235 return 236 } 237 t.created = true 238 } 239 240 // rmdir test 241 func (t *Test) rmdir() { 242 if !t.created { 243 return 244 } 245 t.logf("rmdir") 246 err := os.Remove(t.path()) 247 if err != nil { 248 t.errorf("failed to rmdir %q", t.path()) 249 return 250 } 251 t.created = false 252 } 253 254 // Tidy removes any stray files and stops the deadlock timer 255 func (t *Test) Tidy() { 256 t.timer.Stop() 257 if !t.isDir { 258 t.close() 259 t.remove() 260 } else { 261 t.rmdir() 262 } 263 t.logf("finished") 264 } 265 266 // RandomTests runs random tests with deadlock detection 267 func (t *Test) RandomTests(iterations int, quit chan struct{}) { 268 var finished = make(chan struct{}) 269 go func() { 270 for i := 0; i < iterations; i++ { 271 t.randomTest() 272 } 273 close(finished) 274 }() 275 select { 276 case <-finished: 277 case <-quit: 278 quit <- struct{}{} 279 case <-t.timer.C: 280 t.errorf("deadlock detected") 281 quit <- struct{}{} 282 } 283 } 284 285 func main() { 286 flag.Parse() 287 args := flag.Args() 288 if len(args) != 1 { 289 log.Fatalf("%s: Syntax [opts] <directory>", os.Args[0]) 290 } 291 dir := args[0] 292 _ = file.MkdirAll(dir, 0777) 293 294 var ( 295 wg sync.WaitGroup 296 quit = make(chan struct{}, *iterations) 297 ) 298 for i := 0; i < *number; i++ { 299 wg.Add(1) 300 go func() { 301 defer wg.Done() 302 t := NewTest(dir) 303 defer t.Tidy() 304 t.RandomTests(*iterations, quit) 305 }() 306 } 307 wg.Wait() 308 }