mirror of
https://github.com/rclone/rclone.git
synced 2024-11-07 09:04:52 +01:00
vfs: add test_vfs code for hunting for deadlocks
This commit is contained in:
parent
2cfe2354df
commit
1f6a1cd26d
330
vfs/test_vfs/test_vfs.go
Normal file
330
vfs/test_vfs/test_vfs.go
Normal file
@ -0,0 +1,330 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
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())
|
||||
|
||||
}
|
||||
|
||||
// RandomString create a random string for test purposes
|
||||
func RandomString(n int) string {
|
||||
const (
|
||||
vowel = "aeiou"
|
||||
consonant = "bcdfghjklmnpqrstvwxyz"
|
||||
digit = "0123456789"
|
||||
)
|
||||
pattern := []string{consonant, vowel, consonant, vowel, consonant, vowel, consonant, digit}
|
||||
out := make([]byte, n)
|
||||
p := 0
|
||||
for i := range out {
|
||||
source := pattern[p]
|
||||
p = (p + 1) % len(pattern)
|
||||
out[i] = source[rand.Intn(len(source))]
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
||||
// 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: RandomString(*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 := RandomString(*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 := os.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()
|
||||
}
|
Loading…
Reference in New Issue
Block a user