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