123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- // Test the VFS to exhaustion, specifically looking for deadlocks
- //
- // Run on a mounted filesystem
- package main
- import (
- "flag"
- "fmt"
- "io"
- "log"
- "math"
- "math/rand"
- "os"
- "path"
- "sync"
- "sync/atomic"
- "time"
- "github.com/rclone/rclone/lib/file"
- "github.com/rclone/rclone/lib/random"
- )
- var (
- nameLength = flag.Int("name-length", 10, "Length of names to create")
- verbose = flag.Bool("v", false, "Set to show more info")
- number = flag.Int("n", 4, "Number of tests to run simultaneously")
- iterations = flag.Int("i", 100, "Iterations of the test")
- timeout = flag.Duration("timeout", 10*time.Second, "Inactivity time to detect a deadlock")
- testNumber atomic.Int32
- )
- // Test contains stats about the running test which work for files or
- // directories
- type Test struct {
- dir string
- name string
- created bool
- handle *os.File
- tests []func()
- isDir bool
- number int32
- prefix string
- timer *time.Timer
- }
- // NewTest creates a new test and fills in the Tests
- func NewTest(Dir string) *Test {
- t := &Test{
- dir: Dir,
- name: random.String(*nameLength),
- isDir: rand.Intn(2) == 0,
- number: testNumber.Add(1),
- timer: time.NewTimer(*timeout),
- }
- width := int(math.Floor(math.Log10(float64(*number)))) + 1
- t.prefix = fmt.Sprintf("%*d: %s: ", width, t.number, t.path())
- if t.isDir {
- t.tests = []func(){
- t.list,
- t.rename,
- t.mkdir,
- t.rmdir,
- }
- } else {
- t.tests = []func(){
- t.list,
- t.rename,
- t.open,
- t.close,
- t.remove,
- t.read,
- t.write,
- }
- }
- return t
- }
- // kick the deadlock timeout
- func (t *Test) kick() {
- if !t.timer.Stop() {
- <-t.timer.C
- }
- t.timer.Reset(*timeout)
- }
- // randomTest runs a random test
- func (t *Test) randomTest() {
- t.kick()
- i := rand.Intn(len(t.tests))
- t.tests[i]()
- }
- // logf logs things - not shown unless -v
- func (t *Test) logf(format string, a ...interface{}) {
- if *verbose {
- log.Printf(t.prefix+format, a...)
- }
- }
- // errorf logs errors
- func (t *Test) errorf(format string, a ...interface{}) {
- log.Printf(t.prefix+"ERROR: "+format, a...)
- }
- // list test
- func (t *Test) list() {
- t.logf("list")
- fis, err := os.ReadDir(t.dir)
- if err != nil {
- t.errorf("%s: failed to read directory: %v", t.dir, err)
- return
- }
- if t.created && len(fis) == 0 {
- t.errorf("%s: expecting entries in directory, got none", t.dir)
- return
- }
- found := false
- for _, fi := range fis {
- if fi.Name() == t.name {
- found = true
- }
- }
- if t.created {
- if !found {
- t.errorf("%s: expecting to find %q in directory, got none", t.dir, t.name)
- return
- }
- } else {
- if found {
- t.errorf("%s: not expecting to find %q in directory, got none", t.dir, t.name)
- return
- }
- }
- }
- // path returns the current path to the item
- func (t *Test) path() string {
- return path.Join(t.dir, t.name)
- }
- // rename test
- func (t *Test) rename() {
- if !t.created {
- return
- }
- t.logf("rename")
- NewName := random.String(*nameLength)
- newPath := path.Join(t.dir, NewName)
- err := os.Rename(t.path(), newPath)
- if err != nil {
- t.errorf("failed to rename to %q: %v", newPath, err)
- return
- }
- t.name = NewName
- }
- // close test
- func (t *Test) close() {
- if t.handle == nil {
- return
- }
- t.logf("close")
- err := t.handle.Close()
- t.handle = nil
- if err != nil {
- t.errorf("failed to close: %v", err)
- return
- }
- }
- // open test
- func (t *Test) open() {
- t.close()
- t.logf("open")
- handle, err := file.OpenFile(t.path(), os.O_RDWR|os.O_CREATE, 0666)
- if err != nil {
- t.errorf("failed to open: %v", err)
- return
- }
- t.handle = handle
- t.created = true
- }
- // read test
- func (t *Test) read() {
- if t.handle == nil {
- return
- }
- t.logf("read")
- bytes := make([]byte, 10)
- _, err := t.handle.Read(bytes)
- if err != nil && err != io.EOF {
- t.errorf("failed to read: %v", err)
- return
- }
- }
- // write test
- func (t *Test) write() {
- if t.handle == nil {
- return
- }
- t.logf("write")
- bytes := make([]byte, 10)
- _, err := t.handle.Write(bytes)
- if err != nil {
- t.errorf("failed to write: %v", err)
- return
- }
- }
- // remove test
- func (t *Test) remove() {
- if !t.created {
- return
- }
- t.logf("remove")
- err := os.Remove(t.path())
- if err != nil {
- t.errorf("failed to remove: %v", err)
- return
- }
- t.created = false
- }
- // mkdir test
- func (t *Test) mkdir() {
- if t.created {
- return
- }
- t.logf("mkdir")
- err := os.Mkdir(t.path(), 0777)
- if err != nil {
- t.errorf("failed to mkdir %q", t.path())
- return
- }
- t.created = true
- }
- // rmdir test
- func (t *Test) rmdir() {
- if !t.created {
- return
- }
- t.logf("rmdir")
- err := os.Remove(t.path())
- if err != nil {
- t.errorf("failed to rmdir %q", t.path())
- return
- }
- t.created = false
- }
- // Tidy removes any stray files and stops the deadlock timer
- func (t *Test) Tidy() {
- t.timer.Stop()
- if !t.isDir {
- t.close()
- t.remove()
- } else {
- t.rmdir()
- }
- t.logf("finished")
- }
- // RandomTests runs random tests with deadlock detection
- func (t *Test) RandomTests(iterations int, quit chan struct{}) {
- var finished = make(chan struct{})
- go func() {
- for i := 0; i < iterations; i++ {
- t.randomTest()
- }
- close(finished)
- }()
- select {
- case <-finished:
- case <-quit:
- quit <- struct{}{}
- case <-t.timer.C:
- t.errorf("deadlock detected")
- quit <- struct{}{}
- }
- }
- func main() {
- flag.Parse()
- args := flag.Args()
- if len(args) != 1 {
- log.Fatalf("%s: Syntax [opts] <directory>", os.Args[0])
- }
- dir := args[0]
- _ = file.MkdirAll(dir, 0777)
- var (
- wg sync.WaitGroup
- quit = make(chan struct{}, *iterations)
- )
- for i := 0; i < *number; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- t := NewTest(dir)
- defer t.Tidy()
- t.RandomTests(*iterations, quit)
- }()
- }
- wg.Wait()
- }
|