rclone/vfs/test_vfs/test_vfs.go
Nick Craig-Wood 5065c422b4 lib/random: unify random string generation into random.String
This was factored from fstest as we were including the testing
enviroment into the main binary because of it.

This was causing opening the browser to fail because of 8243ff8bc8.
2019-08-06 12:44:08 +01:00

316 lines
5.6 KiB
Go

// Test the VFS to exhaustion, specifically looking for deadlocks
//
// Run on a mounted filesystem
package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"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 int32
)
// Seed the random number generator
func init() {
rand.Seed(time.Now().UnixNano())
}
// 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: atomic.AddInt32(&testNumber, 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 := ioutil.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]
_ = os.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()
}