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