github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/fstest/run.go (about) 1 /* 2 3 This provides Run for use in creating test suites 4 5 To use this declare a TestMain 6 7 // TestMain drives the tests 8 func TestMain(m *testing.M) { 9 fstest.TestMain(m) 10 } 11 12 And then make and destroy a Run in each test 13 14 func TestMkdir(t *testing.T) { 15 r := fstest.NewRun(t) 16 defer r.Finalise() 17 // test stuff 18 } 19 20 This will make r.Fremote and r.Flocal for a remote remote and a local 21 remote. The remote is determined by the -remote flag passed in. 22 23 */ 24 25 package fstest 26 27 import ( 28 "bytes" 29 "context" 30 "flag" 31 "fmt" 32 "io/ioutil" 33 "log" 34 "os" 35 "path" 36 "path/filepath" 37 "sort" 38 "testing" 39 "time" 40 41 "github.com/rclone/rclone/fs" 42 "github.com/rclone/rclone/fs/cache" 43 "github.com/rclone/rclone/fs/fserrors" 44 "github.com/rclone/rclone/fs/hash" 45 "github.com/rclone/rclone/fs/object" 46 "github.com/rclone/rclone/fs/walk" 47 "github.com/stretchr/testify/assert" 48 "github.com/stretchr/testify/require" 49 ) 50 51 // Run holds the remotes for a test run 52 type Run struct { 53 LocalName string 54 Flocal fs.Fs 55 Fremote fs.Fs 56 FremoteName string 57 cleanRemote func() 58 mkdir map[string]bool // whether the remote has been made yet for the fs name 59 Logf, Fatalf func(text string, args ...interface{}) 60 } 61 62 // TestMain drives the tests 63 func TestMain(m *testing.M) { 64 flag.Parse() 65 if !*Individual { 66 oneRun = newRun() 67 } 68 rc := m.Run() 69 if !*Individual { 70 oneRun.Finalise() 71 } 72 os.Exit(rc) 73 } 74 75 // oneRun holds the master run data if individual is not set 76 var oneRun *Run 77 78 // newRun initialise the remote and local for testing and returns a 79 // run object. 80 // 81 // r.Flocal is an empty local Fs 82 // r.Fremote is an empty remote Fs 83 // 84 // Finalise() will tidy them away when done. 85 func newRun() *Run { 86 r := &Run{ 87 Logf: log.Printf, 88 Fatalf: log.Fatalf, 89 mkdir: make(map[string]bool), 90 } 91 92 Initialise() 93 94 var err error 95 r.Fremote, r.FremoteName, r.cleanRemote, err = RandomRemote() 96 if err != nil { 97 r.Fatalf("Failed to open remote %q: %v", *RemoteName, err) 98 } 99 100 r.LocalName, err = ioutil.TempDir("", "rclone") 101 if err != nil { 102 r.Fatalf("Failed to create temp dir: %v", err) 103 } 104 r.LocalName = filepath.ToSlash(r.LocalName) 105 r.Flocal, err = fs.NewFs(r.LocalName) 106 if err != nil { 107 r.Fatalf("Failed to make %q: %v", r.LocalName, err) 108 } 109 return r 110 } 111 112 // run f(), retrying it until it returns with no error or the limit 113 // expires and it calls t.Fatalf 114 func retry(t *testing.T, what string, f func() error) { 115 var err error 116 for try := 1; try <= *ListRetries; try++ { 117 err = f() 118 if err == nil { 119 return 120 } 121 t.Logf("%s failed - try %d/%d: %v", what, try, *ListRetries, err) 122 time.Sleep(time.Second) 123 } 124 t.Logf("%s failed: %v", what, err) 125 } 126 127 // newRunIndividual initialise the remote and local for testing and 128 // returns a run object. Pass in true to make individual tests or 129 // false to use the global one. 130 // 131 // r.Flocal is an empty local Fs 132 // r.Fremote is an empty remote Fs 133 // 134 // Finalise() will tidy them away when done. 135 func newRunIndividual(t *testing.T, individual bool) *Run { 136 ctx := context.Background() 137 var r *Run 138 if individual { 139 r = newRun() 140 } else { 141 // If not individual, use the global one with the clean method overridden 142 r = new(Run) 143 *r = *oneRun 144 r.cleanRemote = func() { 145 var toDelete []string 146 err := walk.ListR(ctx, r.Fremote, "", true, -1, walk.ListAll, func(entries fs.DirEntries) error { 147 for _, entry := range entries { 148 switch x := entry.(type) { 149 case fs.Object: 150 retry(t, fmt.Sprintf("removing file %q", x.Remote()), func() error { return x.Remove(ctx) }) 151 case fs.Directory: 152 toDelete = append(toDelete, x.Remote()) 153 } 154 } 155 return nil 156 }) 157 if err == fs.ErrorDirNotFound { 158 return 159 } 160 require.NoError(t, err) 161 sort.Strings(toDelete) 162 for i := len(toDelete) - 1; i >= 0; i-- { 163 dir := toDelete[i] 164 retry(t, fmt.Sprintf("removing dir %q", dir), func() error { 165 return r.Fremote.Rmdir(ctx, dir) 166 }) 167 } 168 // Check remote is empty 169 CheckListingWithPrecision(t, r.Fremote, []Item{}, []string{}, r.Fremote.Precision()) 170 // Clear the remote cache 171 cache.Clear() 172 } 173 } 174 r.Logf = t.Logf 175 r.Fatalf = t.Fatalf 176 r.Logf("Remote %q, Local %q, Modify Window %q", r.Fremote, r.Flocal, fs.GetModifyWindow(r.Fremote)) 177 return r 178 } 179 180 // NewRun initialise the remote and local for testing and returns a 181 // run object. Call this from the tests. 182 // 183 // r.Flocal is an empty local Fs 184 // r.Fremote is an empty remote Fs 185 // 186 // Finalise() will tidy them away when done. 187 func NewRun(t *testing.T) *Run { 188 return newRunIndividual(t, *Individual) 189 } 190 191 // NewRunIndividual as per NewRun but makes an individual remote for this test 192 func NewRunIndividual(t *testing.T) *Run { 193 return newRunIndividual(t, true) 194 } 195 196 // RenameFile renames a file in local 197 func (r *Run) RenameFile(item Item, newpath string) Item { 198 oldFilepath := path.Join(r.LocalName, item.Path) 199 newFilepath := path.Join(r.LocalName, newpath) 200 if err := os.Rename(oldFilepath, newFilepath); err != nil { 201 r.Fatalf("Failed to rename file from %q to %q: %v", item.Path, newpath, err) 202 } 203 204 item.Path = newpath 205 206 return item 207 } 208 209 // WriteFile writes a file to local 210 func (r *Run) WriteFile(filePath, content string, t time.Time) Item { 211 item := NewItem(filePath, content, t) 212 // FIXME make directories? 213 filePath = path.Join(r.LocalName, filePath) 214 dirPath := path.Dir(filePath) 215 err := os.MkdirAll(dirPath, 0770) 216 if err != nil { 217 r.Fatalf("Failed to make directories %q: %v", dirPath, err) 218 } 219 err = ioutil.WriteFile(filePath, []byte(content), 0600) 220 if err != nil { 221 r.Fatalf("Failed to write file %q: %v", filePath, err) 222 } 223 err = os.Chtimes(filePath, t, t) 224 if err != nil { 225 r.Fatalf("Failed to chtimes file %q: %v", filePath, err) 226 } 227 return item 228 } 229 230 // ForceMkdir creates the remote 231 func (r *Run) ForceMkdir(ctx context.Context, f fs.Fs) { 232 err := f.Mkdir(ctx, "") 233 if err != nil { 234 r.Fatalf("Failed to mkdir %q: %v", f, err) 235 } 236 r.mkdir[f.String()] = true 237 } 238 239 // Mkdir creates the remote if it hasn't been created already 240 func (r *Run) Mkdir(ctx context.Context, f fs.Fs) { 241 if !r.mkdir[f.String()] { 242 r.ForceMkdir(ctx, f) 243 } 244 } 245 246 // WriteObjectTo writes an object to the fs, remote passed in 247 func (r *Run) WriteObjectTo(ctx context.Context, f fs.Fs, remote, content string, modTime time.Time, useUnchecked bool) Item { 248 put := f.Put 249 if useUnchecked { 250 put = f.Features().PutUnchecked 251 if put == nil { 252 r.Fatalf("Fs doesn't support PutUnchecked") 253 } 254 } 255 r.Mkdir(ctx, f) 256 257 // caclulate all hashes f supports for content 258 hash, err := hash.NewMultiHasherTypes(f.Hashes()) 259 if err != nil { 260 r.Fatalf("Failed to make new multi hasher: %v", err) 261 } 262 _, err = hash.Write([]byte(content)) 263 if err != nil { 264 r.Fatalf("Failed to make write to hash: %v", err) 265 } 266 hashSums := hash.Sums() 267 268 const maxTries = 10 269 for tries := 1; ; tries++ { 270 in := bytes.NewBufferString(content) 271 objinfo := object.NewStaticObjectInfo(remote, modTime, int64(len(content)), true, hashSums, nil) 272 _, err := put(ctx, in, objinfo) 273 if err == nil { 274 break 275 } 276 // Retry if err returned a retry error 277 if fserrors.IsRetryError(err) && tries < maxTries { 278 r.Logf("Retry Put of %q to %v: %d/%d (%v)", remote, f, tries, maxTries, err) 279 time.Sleep(2 * time.Second) 280 continue 281 } 282 r.Fatalf("Failed to put %q to %q: %v", remote, f, err) 283 } 284 return NewItem(remote, content, modTime) 285 } 286 287 // WriteObject writes an object to the remote 288 func (r *Run) WriteObject(ctx context.Context, remote, content string, modTime time.Time) Item { 289 return r.WriteObjectTo(ctx, r.Fremote, remote, content, modTime, false) 290 } 291 292 // WriteUncheckedObject writes an object to the remote not checking for duplicates 293 func (r *Run) WriteUncheckedObject(ctx context.Context, remote, content string, modTime time.Time) Item { 294 return r.WriteObjectTo(ctx, r.Fremote, remote, content, modTime, true) 295 } 296 297 // WriteBoth calls WriteObject and WriteFile with the same arguments 298 func (r *Run) WriteBoth(ctx context.Context, remote, content string, modTime time.Time) Item { 299 r.WriteFile(remote, content, modTime) 300 return r.WriteObject(ctx, remote, content, modTime) 301 } 302 303 // CheckWithDuplicates does a test but allows duplicates 304 func (r *Run) CheckWithDuplicates(t *testing.T, items ...Item) { 305 var want, got []string 306 307 // construct a []string of desired items 308 for _, item := range items { 309 want = append(want, fmt.Sprintf("%q %d", item.Path, item.Size)) 310 } 311 sort.Strings(want) 312 313 // do the listing 314 objs, _, err := walk.GetAll(context.Background(), r.Fremote, "", true, -1) 315 if err != nil && err != fs.ErrorDirNotFound { 316 t.Fatalf("Error listing: %v", err) 317 } 318 319 // construct a []string of actual items 320 for _, o := range objs { 321 got = append(got, fmt.Sprintf("%q %d", o.Remote(), o.Size())) 322 } 323 sort.Strings(got) 324 325 assert.Equal(t, want, got) 326 } 327 328 // Clean the temporary directory 329 func (r *Run) cleanTempDir() { 330 err := os.RemoveAll(r.LocalName) 331 if err != nil { 332 r.Logf("Failed to clean temporary directory %q: %v", r.LocalName, err) 333 } 334 } 335 336 // Finalise cleans the remote and local 337 func (r *Run) Finalise() { 338 // r.Logf("Cleaning remote %q", r.Fremote) 339 r.cleanRemote() 340 // r.Logf("Cleaning local %q", r.LocalName) 341 r.cleanTempDir() 342 // Clear the remote cache 343 cache.Clear() 344 }