2015-09-22 17:47:16 +00:00
// Package fstest provides utilities for testing the Fs
2014-07-24 21:50:11 +00:00
package fstest
// FIXME put name of test FS in Fs structure
import (
2016-01-17 10:08:28 +00:00
"bytes"
2016-07-11 10:36:46 +00:00
"flag"
2016-06-29 16:59:31 +00:00
"fmt"
2016-01-17 10:08:28 +00:00
"io"
2014-07-24 21:50:11 +00:00
"io/ioutil"
"log"
"math/rand"
"os"
2017-08-09 14:51:27 +00:00
"path"
2015-02-07 15:52:06 +00:00
"path/filepath"
2016-01-24 12:37:46 +00:00
"regexp"
2017-09-17 13:05:33 +00:00
"runtime"
2016-11-25 21:52:43 +00:00
"sort"
2014-07-24 21:50:11 +00:00
"strings"
2014-08-01 16:58:39 +00:00
"testing"
2014-07-24 21:50:11 +00:00
"time"
"github.com/ncw/rclone/fs"
2016-06-29 16:59:31 +00:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2017-09-17 13:05:33 +00:00
"golang.org/x/text/unicode/norm"
2014-07-24 21:50:11 +00:00
)
2017-07-24 21:46:43 +00:00
// Globals
2016-01-24 12:37:46 +00:00
var (
2017-07-24 21:46:43 +00:00
RemoteName = flag . String ( "remote" , "" , "Remote to test with, defaults to local filesystem" )
SubDir = flag . Bool ( "subdir" , false , "Set to test with a sub directory" )
Verbose = flag . Bool ( "verbose" , false , "Set to enable logging" )
DumpHeaders = flag . Bool ( "dump-headers" , false , "Set to dump headers (needs -verbose)" )
DumpBodies = flag . Bool ( "dump-bodies" , false , "Set to dump bodies (needs -verbose)" )
Individual = flag . Bool ( "individual" , false , "Make individual bucket/container/directory for each test - much slower" )
LowLevelRetries = flag . Int ( "low-level-retries" , 10 , "Number of low level retries" )
UseListR = flag . Bool ( "fast-list" , false , "Use recursive list if available. Uses more memory but fewer transactions." )
2017-02-22 10:14:40 +00:00
// ListRetries is the number of times to retry a listing to overcome eventual consistency
ListRetries = flag . Int ( "list-retries" , 6 , "Number or times to retry listing" )
2017-07-24 21:46:43 +00:00
// MatchTestRemote matches the remote names used for testing
MatchTestRemote = regexp . MustCompile ( ` ^rclone-test-[abcdefghijklmnopqrstuvwxyz0123456789] { 24}$ ` )
2016-01-24 12:37:46 +00:00
)
2014-07-26 16:18:29 +00:00
// Seed the random number generator
func init ( ) {
rand . Seed ( time . Now ( ) . UnixNano ( ) )
}
2017-07-24 21:46:43 +00:00
// Initialise rclone for testing
func Initialise ( ) {
// Never ask for passwords, fail instead.
// If your local config is encrypted set environment variable
// "RCLONE_CONFIG_PASS=hunter2" (or your password)
* fs . AskPassword = false
fs . LoadConfig ( )
if * Verbose {
fs . Config . LogLevel = fs . LogLevelDebug
}
fs . Config . DumpHeaders = * DumpHeaders
fs . Config . DumpBodies = * DumpBodies
fs . Config . LowLevelRetries = * LowLevelRetries
fs . Config . UseListR = * UseListR
}
2015-09-22 17:47:16 +00:00
// Item represents an item for checking
2014-07-24 21:50:11 +00:00
type Item struct {
Path string
2016-01-17 11:27:31 +00:00
Hashes map [ fs . HashType ] string
2014-07-24 21:50:11 +00:00
ModTime time . Time
Size int64
2015-09-11 09:37:12 +00:00
WinPath string
2014-07-24 21:50:11 +00:00
}
2016-01-17 10:08:28 +00:00
// NewItem creates an item from a string content
func NewItem ( Path , Content string , modTime time . Time ) Item {
i := Item {
Path : Path ,
ModTime : modTime ,
Size : int64 ( len ( Content ) ) ,
}
hash := fs . NewMultiHasher ( )
buf := bytes . NewBufferString ( Content )
_ , err := io . Copy ( hash , buf )
if err != nil {
log . Fatalf ( "Failed to create item: %v" , err )
}
i . Hashes = hash . Sums ( )
return i
}
2015-09-22 17:47:16 +00:00
// CheckTimeEqualWithPrecision checks the times are equal within the
// precision, returns the delta and a flag
2015-08-16 22:24:34 +00:00
func CheckTimeEqualWithPrecision ( t0 , t1 time . Time , precision time . Duration ) ( time . Duration , bool ) {
dt := t0 . Sub ( t1 )
if dt >= precision || dt <= - precision {
return dt , false
}
return dt , true
}
2015-09-22 17:47:16 +00:00
// CheckModTime checks the mod time to the given precision
2014-08-01 16:58:39 +00:00
func ( i * Item ) CheckModTime ( t * testing . T , obj fs . Object , modTime time . Time , precision time . Duration ) {
2015-08-16 22:24:34 +00:00
dt , ok := CheckTimeEqualWithPrecision ( modTime , i . ModTime , precision )
2016-06-29 16:59:31 +00:00
assert . True ( t , ok , fmt . Sprintf ( "%s: Modification time difference too big |%s| > %s (%s vs %s) (precision %s)" , obj . Remote ( ) , dt , precision , modTime , i . ModTime , precision ) )
2014-07-26 16:18:29 +00:00
}
2016-01-17 11:27:31 +00:00
// CheckHashes checks all the hashes the object supports are correct
func ( i * Item ) CheckHashes ( t * testing . T , obj fs . Object ) {
2016-06-29 16:59:31 +00:00
require . NotNil ( t , obj )
2016-01-11 12:39:33 +00:00
types := obj . Fs ( ) . Hashes ( ) . Array ( )
for _ , hash := range types {
// Check attributes
sum , err := obj . Hash ( hash )
2016-06-29 16:59:31 +00:00
require . NoError ( t , err )
assert . True ( t , fs . HashEquals ( i . Hashes [ hash ] , sum ) , fmt . Sprintf ( "%s/%s: %v hash incorrect - expecting %q got %q" , obj . Fs ( ) . String ( ) , obj . Remote ( ) , hash , i . Hashes [ hash ] , sum ) )
2014-07-24 21:50:11 +00:00
}
2016-01-17 11:27:31 +00:00
}
// Check checks all the attributes of the object are correct
func ( i * Item ) Check ( t * testing . T , obj fs . Object , precision time . Duration ) {
i . CheckHashes ( t , obj )
2017-08-03 19:42:35 +00:00
assert . Equal ( t , i . Size , obj . Size ( ) , fmt . Sprintf ( "%s: size incorrect file=%d vs obj=%d" , i . Path , i . Size , obj . Size ( ) ) )
2014-08-01 16:58:39 +00:00
i . CheckModTime ( t , obj , obj . ModTime ( ) , precision )
2014-07-24 21:50:11 +00:00
}
2017-09-17 13:05:33 +00:00
// Normalize runs a utf8 normalization on the string if running on OS
// X. This is because OS X denormalizes file names it writes to the
// local file system.
func Normalize ( name string ) string {
if runtime . GOOS == "darwin" {
name = norm . NFC . String ( name )
}
return name
}
2015-09-22 17:47:16 +00:00
// Items represents all items for checking
2014-07-24 21:50:11 +00:00
type Items struct {
2015-09-11 09:37:12 +00:00
byName map [ string ] * Item
byNameAlt map [ string ] * Item
items [ ] Item
2014-07-24 21:50:11 +00:00
}
2015-09-22 17:47:16 +00:00
// NewItems makes an Items
2014-07-24 21:50:11 +00:00
func NewItems ( items [ ] Item ) * Items {
is := & Items {
2015-09-11 09:37:12 +00:00
byName : make ( map [ string ] * Item ) ,
byNameAlt : make ( map [ string ] * Item ) ,
items : items ,
2014-07-24 21:50:11 +00:00
}
// Fill up byName
for i := range items {
2017-09-17 13:05:33 +00:00
is . byName [ Normalize ( items [ i ] . Path ) ] = & items [ i ]
is . byNameAlt [ Normalize ( items [ i ] . WinPath ) ] = & items [ i ]
2014-07-24 21:50:11 +00:00
}
return is
}
2015-09-22 17:47:16 +00:00
// Find checks off an item
2014-08-01 16:58:39 +00:00
func ( is * Items ) Find ( t * testing . T , obj fs . Object , precision time . Duration ) {
2017-09-17 13:05:33 +00:00
remote := Normalize ( obj . Remote ( ) )
i , ok := is . byName [ remote ]
2014-07-24 21:50:11 +00:00
if ! ok {
2017-09-17 13:05:33 +00:00
i , ok = is . byNameAlt [ remote ]
assert . True ( t , ok , fmt . Sprintf ( "Unexpected file %q" , remote ) )
2014-07-24 21:50:11 +00:00
}
2016-07-11 10:36:46 +00:00
if i != nil {
delete ( is . byName , i . Path )
delete ( is . byName , i . WinPath )
i . Check ( t , obj , precision )
}
2014-07-24 21:50:11 +00:00
}
2015-09-22 17:47:16 +00:00
// Done checks all finished
2014-08-01 16:58:39 +00:00
func ( is * Items ) Done ( t * testing . T ) {
2014-07-24 21:50:11 +00:00
if len ( is . byName ) != 0 {
for name := range is . byName {
2015-12-29 00:16:53 +00:00
t . Logf ( "Not found %q" , name )
2014-07-24 21:50:11 +00:00
}
}
2016-07-11 10:36:46 +00:00
assert . Equal ( t , 0 , len ( is . byName ) , fmt . Sprintf ( "%d objects not found" , len ( is . byName ) ) )
2014-07-24 21:50:11 +00:00
}
2017-01-19 17:25:57 +00:00
// makeListingFromItems returns a string representation of the items
2017-01-20 17:12:05 +00:00
//
// it returns two possible strings, one normal and one for windows
func makeListingFromItems ( items [ ] Item ) ( string , string ) {
nameLengths1 := make ( [ ] string , len ( items ) )
nameLengths2 := make ( [ ] string , len ( items ) )
2017-01-19 17:25:57 +00:00
for i , item := range items {
2017-09-17 13:05:33 +00:00
remote1 := Normalize ( item . Path )
remote2 := remote1
2017-01-20 17:12:05 +00:00
if item . WinPath != "" {
remote2 = item . WinPath
2017-01-19 17:25:57 +00:00
}
2017-01-20 17:12:05 +00:00
nameLengths1 [ i ] = fmt . Sprintf ( "%s (%d)" , remote1 , item . Size )
nameLengths2 [ i ] = fmt . Sprintf ( "%s (%d)" , remote2 , item . Size )
2017-01-19 17:25:57 +00:00
}
2017-01-20 17:12:05 +00:00
sort . Strings ( nameLengths1 )
sort . Strings ( nameLengths2 )
return strings . Join ( nameLengths1 , ", " ) , strings . Join ( nameLengths2 , ", " )
2017-01-19 17:25:57 +00:00
}
// makeListingFromObjects returns a string representation of the objects
func makeListingFromObjects ( objs [ ] fs . Object ) string {
nameLengths := make ( [ ] string , len ( objs ) )
for i , obj := range objs {
2017-09-17 13:05:33 +00:00
nameLengths [ i ] = fmt . Sprintf ( "%s (%d)" , Normalize ( obj . Remote ( ) ) , obj . Size ( ) )
2017-01-19 17:25:57 +00:00
}
sort . Strings ( nameLengths )
return strings . Join ( nameLengths , ", " )
}
2017-08-09 14:51:27 +00:00
// filterEmptyDirs removes any empty (or containing only directories)
// directories from expectedDirs
func filterEmptyDirs ( t * testing . T , items [ ] Item , expectedDirs [ ] string ) ( newExpectedDirs [ ] string ) {
dirs := map [ string ] struct { } { "" : struct { } { } }
for _ , item := range items {
base := item . Path
for {
base = path . Dir ( base )
if base == "." || base == "/" {
break
}
dirs [ base ] = struct { } { }
}
}
for _ , expectedDir := range expectedDirs {
if _ , found := dirs [ expectedDir ] ; found {
newExpectedDirs = append ( newExpectedDirs , expectedDir )
} else {
t . Logf ( "Filtering empty directory %q" , expectedDir )
}
}
return newExpectedDirs
}
2015-09-22 17:47:16 +00:00
// CheckListingWithPrecision checks the fs to see if it has the
// expected contents with the given precision.
2016-11-27 11:49:31 +00:00
//
// If expectedDirs is non nil then we check those too. Note that no
// directories returned is also OK as some remotes don't return
// directories.
2016-11-25 21:52:43 +00:00
func CheckListingWithPrecision ( t * testing . T , f fs . Fs , items [ ] Item , expectedDirs [ ] string , precision time . Duration ) {
2017-08-09 14:51:27 +00:00
if expectedDirs != nil && ! f . Features ( ) . CanHaveEmptyDirectories {
expectedDirs = filterEmptyDirs ( t , items , expectedDirs )
}
2014-07-24 21:50:11 +00:00
is := NewItems ( items )
2015-08-24 20:42:23 +00:00
oldErrors := fs . Stats . GetErrors ( )
2015-09-14 20:01:25 +00:00
var objs [ ] fs . Object
2017-06-30 12:37:29 +00:00
var dirs [ ] fs . Directory
2016-04-21 19:06:21 +00:00
var err error
2017-02-22 10:14:40 +00:00
var retries = * ListRetries
2016-01-17 10:08:28 +00:00
sleep := time . Second / 2
2017-01-20 17:12:05 +00:00
wantListing1 , wantListing2 := makeListingFromItems ( items )
2017-01-19 17:25:57 +00:00
gotListing := "<unset>"
2017-01-20 17:12:05 +00:00
listingOK := false
2015-11-14 12:57:17 +00:00
for i := 1 ; i <= retries ; i ++ {
2017-02-24 22:51:01 +00:00
objs , dirs , err = fs . WalkGetAll ( f , "" , true , - 1 )
2016-04-21 19:06:21 +00:00
if err != nil && err != fs . ErrorDirNotFound {
t . Fatalf ( "Error listing: %v" , err )
2015-09-14 20:01:25 +00:00
}
2017-01-19 17:25:57 +00:00
gotListing = makeListingFromObjects ( objs )
2017-01-20 17:12:05 +00:00
listingOK = wantListing1 == gotListing || wantListing2 == gotListing
2017-08-09 14:51:27 +00:00
if listingOK && ( expectedDirs == nil || len ( dirs ) == len ( expectedDirs ) ) {
2016-01-17 10:08:28 +00:00
// Put an extra sleep in if we did any retries just to make sure it really
2016-07-11 11:42:44 +00:00
// is consistent (here is looking at you Amazon Drive!)
2016-01-17 10:08:28 +00:00
if i != 1 {
extraSleep := 5 * time . Second + sleep
t . Logf ( "Sleeping for %v just to make sure" , extraSleep )
time . Sleep ( extraSleep )
}
2015-09-14 20:01:25 +00:00
break
}
2016-01-17 10:08:28 +00:00
sleep *= 2
t . Logf ( "Sleeping for %v for list eventual consistency: %d/%d" , sleep , i , retries )
time . Sleep ( sleep )
2017-01-13 17:21:47 +00:00
if doDirCacheFlush := f . Features ( ) . DirCacheFlush ; doDirCacheFlush != nil {
2016-12-09 15:39:29 +00:00
t . Logf ( "Flushing the directory cache" )
2017-01-13 17:21:47 +00:00
doDirCacheFlush ( )
2016-12-09 15:39:29 +00:00
}
2015-09-14 20:01:25 +00:00
}
2017-01-20 17:12:05 +00:00
assert . True ( t , listingOK , fmt . Sprintf ( "listing wrong, want\n %s or\n %s got\n %s" , wantListing1 , wantListing2 , gotListing ) )
2015-09-14 20:01:25 +00:00
for _ , obj := range objs {
2016-06-29 16:59:31 +00:00
require . NotNil ( t , obj )
2014-08-01 16:58:39 +00:00
is . Find ( t , obj , precision )
2014-07-24 21:50:11 +00:00
}
2014-08-01 16:58:39 +00:00
is . Done ( t )
2015-08-24 20:42:23 +00:00
// Don't notice an error when listing an empty directory
if len ( items ) == 0 && oldErrors == 0 && fs . Stats . GetErrors ( ) == 1 {
fs . Stats . ResetErrors ( )
}
2017-08-09 14:51:27 +00:00
// Check the directories
if expectedDirs != nil {
2017-09-17 13:05:33 +00:00
expectedDirsCopy := make ( [ ] string , len ( expectedDirs ) )
for i , dir := range expectedDirs {
expectedDirsCopy [ i ] = Normalize ( dir )
}
2016-11-25 21:52:43 +00:00
actualDirs := [ ] string { }
for _ , dir := range dirs {
2017-09-17 13:05:33 +00:00
actualDirs = append ( actualDirs , Normalize ( dir . Remote ( ) ) )
2016-11-25 21:52:43 +00:00
}
sort . Strings ( actualDirs )
2017-09-17 13:05:33 +00:00
sort . Strings ( expectedDirsCopy )
assert . Equal ( t , expectedDirsCopy , actualDirs , "directories" )
2016-11-25 21:52:43 +00:00
}
2014-08-01 16:58:39 +00:00
}
2015-09-22 17:47:16 +00:00
// CheckListing checks the fs to see if it has the expected contents
2014-08-01 16:58:39 +00:00
func CheckListing ( t * testing . T , f fs . Fs , items [ ] Item ) {
precision := f . Precision ( )
2016-11-25 21:52:43 +00:00
CheckListingWithPrecision ( t , f , items , nil , precision )
2014-07-24 21:50:11 +00:00
}
2016-01-17 10:08:28 +00:00
// CheckItems checks the fs to see if it has only the items passed in
// using a precision of fs.Config.ModifyWindow
func CheckItems ( t * testing . T , f fs . Fs , items ... Item ) {
2016-11-25 21:52:43 +00:00
CheckListingWithPrecision ( t , f , items , nil , fs . Config . ModifyWindow )
2016-01-17 10:08:28 +00:00
}
2015-09-22 17:47:16 +00:00
// Time parses a time string or logs a fatal error
2014-07-24 21:50:11 +00:00
func Time ( timeString string ) time . Time {
t , err := time . Parse ( time . RFC3339Nano , timeString )
if err != nil {
2014-08-01 16:58:39 +00:00
log . Fatalf ( "Failed to parse time %q: %v" , timeString , err )
2014-07-24 21:50:11 +00:00
}
return t
}
2015-09-22 17:47:16 +00:00
// RandomString create a random string for test purposes
2014-07-24 21:50:11 +00:00
func RandomString ( n int ) string {
2016-01-24 12:37:46 +00:00
const (
vowel = "aeiou"
consonant = "bcdfghjklmnpqrstvwxyz"
digit = "0123456789"
)
pattern := [ ] string { consonant , vowel , consonant , vowel , consonant , vowel , consonant , digit }
2014-07-24 21:50:11 +00:00
out := make ( [ ] byte , n )
2016-01-24 12:37:46 +00:00
p := 0
2014-07-24 21:50:11 +00:00
for i := range out {
2016-01-24 12:37:46 +00:00
source := pattern [ p ]
p = ( p + 1 ) % len ( pattern )
2014-07-24 21:50:11 +00:00
out [ i ] = source [ rand . Intn ( len ( source ) ) ]
}
return string ( out )
}
2015-09-22 17:47:16 +00:00
// LocalRemote creates a temporary directory name for local remotes
2014-07-31 20:24:52 +00:00
func LocalRemote ( ) ( path string , err error ) {
path , err = ioutil . TempDir ( "" , "rclone" )
if err == nil {
// Now remove the directory
err = os . Remove ( path )
}
2015-02-07 15:52:06 +00:00
path = filepath . ToSlash ( path )
2014-07-31 20:24:52 +00:00
return
}
2015-09-22 17:47:16 +00:00
// RandomRemoteName makes a random bucket or subdirectory name
2014-07-24 21:50:11 +00:00
//
2014-07-31 20:24:52 +00:00
// Returns a random remote name plus the leaf name
func RandomRemoteName ( remoteName string ) ( string , string , error ) {
2014-07-24 21:50:11 +00:00
var err error
2014-07-31 20:24:52 +00:00
var leafName string
// Make a directory if remote name is null
2014-07-24 21:50:11 +00:00
if remoteName == "" {
2014-07-31 20:24:52 +00:00
remoteName , err = LocalRemote ( )
2014-07-24 21:50:11 +00:00
if err != nil {
2014-07-31 20:24:52 +00:00
return "" , "" , err
}
} else {
if ! strings . HasSuffix ( remoteName , ":" ) {
remoteName += "/"
2014-07-24 21:50:11 +00:00
}
2016-01-24 12:37:46 +00:00
leafName = "rclone-test-" + RandomString ( 24 )
if ! MatchTestRemote . MatchString ( leafName ) {
log . Fatalf ( "%q didn't match the test remote name regexp" , leafName )
}
2014-07-31 20:24:52 +00:00
remoteName += leafName
2014-07-24 21:50:11 +00:00
}
2014-07-31 20:24:52 +00:00
return remoteName , leafName , nil
}
2014-07-24 21:50:11 +00:00
2015-09-22 17:47:16 +00:00
// RandomRemote makes a random bucket or subdirectory on the remote
2014-07-31 20:24:52 +00:00
//
// Call the finalise function returned to Purge the fs at the end (and
// the parent if necessary)
2016-07-11 10:36:46 +00:00
//
// Returns the remote, its url, a finaliser and an error
func RandomRemote ( remoteName string , subdir bool ) ( fs . Fs , string , func ( ) , error ) {
2014-07-31 20:24:52 +00:00
var err error
2014-07-24 21:50:11 +00:00
var parentRemote fs . Fs
2014-07-31 20:24:52 +00:00
remoteName , _ , err = RandomRemoteName ( remoteName )
if err != nil {
2016-07-11 10:36:46 +00:00
return nil , "" , nil , err
2014-07-31 20:24:52 +00:00
}
2014-07-24 21:50:11 +00:00
if subdir {
parentRemote , err = fs . NewFs ( remoteName )
if err != nil {
2016-07-11 10:36:46 +00:00
return nil , "" , nil , err
2014-07-24 21:50:11 +00:00
}
2016-01-24 12:37:46 +00:00
remoteName += "/rclone-test-subdir-" + RandomString ( 8 )
2014-07-24 21:50:11 +00:00
}
remote , err := fs . NewFs ( remoteName )
if err != nil {
2016-07-11 10:36:46 +00:00
return nil , "" , nil , err
2014-07-24 21:50:11 +00:00
}
finalise := func ( ) {
2014-07-28 20:02:00 +00:00
_ = fs . Purge ( remote ) // ignore error
2014-07-24 21:50:11 +00:00
if parentRemote != nil {
2014-07-28 20:02:00 +00:00
err = fs . Purge ( parentRemote ) // ignore error
if err != nil {
log . Printf ( "Failed to purge %v: %v" , parentRemote , err )
}
2014-07-24 21:50:11 +00:00
}
}
2016-07-11 10:36:46 +00:00
return remote , remoteName , finalise , nil
2014-07-24 21:50:11 +00:00
}
2015-09-22 17:47:16 +00:00
// TestMkdir tests Mkdir works
2014-08-01 16:58:39 +00:00
func TestMkdir ( t * testing . T , remote fs . Fs ) {
2016-11-25 21:52:43 +00:00
err := fs . Mkdir ( remote , "" )
2016-06-29 16:59:31 +00:00
require . NoError ( t , err )
2014-08-01 16:58:39 +00:00
CheckListing ( t , remote , [ ] Item { } )
2014-07-24 21:50:11 +00:00
}
2015-09-22 17:47:16 +00:00
// TestPurge tests Purge works
2014-08-01 16:58:39 +00:00
func TestPurge ( t * testing . T , remote fs . Fs ) {
2014-07-24 21:50:11 +00:00
err := fs . Purge ( remote )
2016-06-29 16:59:31 +00:00
require . NoError ( t , err )
2014-08-01 16:58:39 +00:00
CheckListing ( t , remote , [ ] Item { } )
2014-07-24 21:50:11 +00:00
}
2015-09-22 17:47:16 +00:00
// TestRmdir tests Rmdir works
2014-08-01 16:58:39 +00:00
func TestRmdir ( t * testing . T , remote fs . Fs ) {
2016-11-25 21:52:43 +00:00
err := fs . Rmdir ( remote , "" )
2016-06-29 16:59:31 +00:00
require . NoError ( t , err )
2014-07-24 21:50:11 +00:00
}