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"
2015-02-07 15:52:06 +00:00
"path/filepath"
2016-01-24 12:37:46 +00:00
"regexp"
2017-01-19 17:25:57 +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"
2014-07-24 21:50:11 +00:00
)
2016-01-24 12:37:46 +00:00
var (
// MatchTestRemote matches the remote names used for testing
MatchTestRemote = regexp . MustCompile ( ` ^rclone-test-[abcdefghijklmnopqrstuvwxyz0123456789] { 24}$ ` )
2016-07-11 10:36:46 +00:00
listRetries = flag . Int ( "list-retries" , 6 , "Number or times to retry listing" )
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 ( ) )
}
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-01-10 21:47:03 +00:00
assert . Equal ( t , i . Size , obj . Size ( ) , fmt . Sprintf ( "%s: size incorrect" , i . Path ) )
2014-08-01 16:58:39 +00:00
i . CheckModTime ( t , obj , obj . ModTime ( ) , precision )
2014-07-24 21:50:11 +00:00
}
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 {
is . byName [ items [ i ] . Path ] = & items [ i ]
2015-09-11 09:37:12 +00:00
is . byNameAlt [ 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 ) {
2014-07-24 21:50:11 +00:00
i , ok := is . byName [ obj . Remote ( ) ]
if ! ok {
2015-09-11 09:37:12 +00:00
i , ok = is . byNameAlt [ obj . Remote ( ) ]
2016-06-29 16:59:31 +00:00
assert . True ( t , ok , fmt . Sprintf ( "Unexpected file %q" , obj . 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
func makeListingFromItems ( items [ ] Item ) string {
nameLengths := make ( [ ] string , len ( items ) )
for i , item := range items {
remote := item . Path
if item . WinPath != "" && runtime . GOOS == "windows" {
remote = item . WinPath
}
nameLengths [ i ] = fmt . Sprintf ( "%s (%d)" , remote , item . Size )
}
sort . Strings ( nameLengths )
return strings . Join ( nameLengths , ", " )
}
// makeListingFromObjects returns a string representation of the objects
func makeListingFromObjects ( objs [ ] fs . Object ) string {
nameLengths := make ( [ ] string , len ( objs ) )
for i , obj := range objs {
nameLengths [ i ] = fmt . Sprintf ( "%s (%d)" , obj . Remote ( ) , obj . Size ( ) )
}
sort . Strings ( nameLengths )
return strings . Join ( nameLengths , ", " )
}
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 ) {
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
2016-11-25 21:52:43 +00:00
var dirs [ ] * fs . Dir
2016-04-21 19:06:21 +00:00
var err error
2016-07-11 10:36:46 +00:00
var retries = * listRetries
2016-01-17 10:08:28 +00:00
sleep := time . Second / 2
2017-01-19 17:25:57 +00:00
wantListing := makeListingFromItems ( items )
gotListing := "<unset>"
2015-11-14 12:57:17 +00:00
for i := 1 ; i <= retries ; i ++ {
2016-11-25 21:52:43 +00:00
objs , dirs , err = fs . NewLister ( ) . Start ( f , "" ) . GetAll ( )
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 )
if wantListing == gotListing && ( expectedDirs == nil || len ( dirs ) == 0 || 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-19 17:25:57 +00:00
assert . Equal ( t , wantListing , gotListing , "listing incorrect" )
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 ( )
}
2016-11-25 21:52:43 +00:00
// Check the directories - ignore if no directories returned
// for remotes which can't do directories
if expectedDirs != nil && len ( dirs ) != 0 {
actualDirs := [ ] string { }
for _ , dir := range dirs {
actualDirs = append ( actualDirs , dir . Name )
}
sort . Strings ( actualDirs )
sort . Strings ( expectedDirs )
assert . Equal ( t , expectedDirs , actualDirs , "directories" )
}
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
}