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  }