1package vfs 2 3import ( 4 "context" 5 "io" 6 "os" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/pkg/errors" 12 "github.com/rclone/rclone/fs" 13 "github.com/rclone/rclone/fstest" 14 "github.com/rclone/rclone/lib/random" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17) 18 19// Open a file for write 20func writeHandleCreate(t *testing.T) (r *fstest.Run, vfs *VFS, fh *WriteFileHandle, cleanup func()) { 21 r, vfs, cleanup = newTestVFS(t) 22 23 h, err := vfs.OpenFile("file1", os.O_WRONLY|os.O_CREATE, 0777) 24 require.NoError(t, err) 25 fh, ok := h.(*WriteFileHandle) 26 require.True(t, ok) 27 28 return r, vfs, fh, cleanup 29} 30 31func TestWriteFileHandleMethods(t *testing.T) { 32 r, vfs, fh, cleanup := writeHandleCreate(t) 33 defer cleanup() 34 35 // String 36 assert.Equal(t, "file1 (w)", fh.String()) 37 assert.Equal(t, "<nil *WriteFileHandle>", (*WriteFileHandle)(nil).String()) 38 assert.Equal(t, "<nil *WriteFileHandle.file>", new(WriteFileHandle).String()) 39 40 // Node 41 node := fh.Node() 42 assert.Equal(t, "file1", node.Name()) 43 44 // Offset #1 45 assert.Equal(t, int64(0), fh.Offset()) 46 assert.Equal(t, int64(0), node.Size()) 47 48 // Write (smoke test only since heavy lifting done in WriteAt) 49 n, err := fh.Write([]byte("hello")) 50 assert.NoError(t, err) 51 assert.Equal(t, 5, n) 52 53 // Offset #2 54 assert.Equal(t, int64(5), fh.Offset()) 55 assert.Equal(t, int64(5), node.Size()) 56 57 // Stat 58 var fi os.FileInfo 59 fi, err = fh.Stat() 60 assert.NoError(t, err) 61 assert.Equal(t, int64(5), fi.Size()) 62 assert.Equal(t, "file1", fi.Name()) 63 64 // Read 65 var buf = make([]byte, 16) 66 _, err = fh.Read(buf) 67 assert.Equal(t, EPERM, err) 68 69 // ReadAt 70 _, err = fh.ReadAt(buf, 0) 71 assert.Equal(t, EPERM, err) 72 73 // Sync 74 err = fh.Sync() 75 assert.NoError(t, err) 76 77 // Truncate - can only truncate where the file pointer is 78 err = fh.Truncate(5) 79 assert.NoError(t, err) 80 err = fh.Truncate(6) 81 assert.Equal(t, EPERM, err) 82 83 // Close 84 assert.NoError(t, fh.Close()) 85 86 // Check double close 87 err = fh.Close() 88 assert.Equal(t, ECLOSED, err) 89 90 // check vfs 91 root, err := vfs.Root() 92 require.NoError(t, err) 93 checkListing(t, root, []string{"file1,5,false"}) 94 95 // check the underlying r.Fremote but not the modtime 96 file1 := fstest.NewItem("file1", "hello", t1) 97 fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, []string{}, fs.ModTimeNotSupported) 98 99 // Check trying to open the file now it exists then closing it 100 // immediately is OK 101 h, err := vfs.OpenFile("file1", os.O_WRONLY|os.O_CREATE, 0777) 102 require.NoError(t, err) 103 assert.NoError(t, h.Close()) 104 checkListing(t, root, []string{"file1,5,false"}) 105 106 // Check trying to open the file and writing it now it exists 107 // returns an error 108 h, err = vfs.OpenFile("file1", os.O_WRONLY|os.O_CREATE, 0777) 109 require.NoError(t, err) 110 _, err = h.Write([]byte("hello1")) 111 require.Equal(t, EPERM, err) 112 assert.NoError(t, h.Close()) 113 checkListing(t, root, []string{"file1,5,false"}) 114 115 // Check opening the file with O_TRUNC does actually truncate 116 // it even if we don't write to it 117 h, err = vfs.OpenFile("file1", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777) 118 require.NoError(t, err) 119 err = h.Close() 120 if errors.Cause(err) != fs.ErrorCantUploadEmptyFiles { 121 assert.NoError(t, err) 122 checkListing(t, root, []string{"file1,0,false"}) 123 } 124 125 // Check opening the file with O_TRUNC and writing does work 126 h, err = vfs.OpenFile("file1", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777) 127 require.NoError(t, err) 128 _, err = h.WriteString("hello12") 129 require.NoError(t, err) 130 assert.NoError(t, h.Close()) 131 checkListing(t, root, []string{"file1,7,false"}) 132} 133 134func TestWriteFileHandleWriteAt(t *testing.T) { 135 r, vfs, fh, cleanup := writeHandleCreate(t) 136 defer cleanup() 137 138 // Preconditions 139 assert.Equal(t, int64(0), fh.offset) 140 assert.False(t, fh.writeCalled) 141 142 // Write the data 143 n, err := fh.WriteAt([]byte("hello"), 0) 144 assert.NoError(t, err) 145 assert.Equal(t, 5, n) 146 147 // After write 148 assert.Equal(t, int64(5), fh.offset) 149 assert.True(t, fh.writeCalled) 150 151 // Check can't seek 152 n, err = fh.WriteAt([]byte("hello"), 100) 153 assert.Equal(t, ESPIPE, err) 154 assert.Equal(t, 0, n) 155 156 // Write more data 157 n, err = fh.WriteAt([]byte(" world"), 5) 158 assert.NoError(t, err) 159 assert.Equal(t, 6, n) 160 161 // Close 162 assert.NoError(t, fh.Close()) 163 164 // Check can't write on closed handle 165 n, err = fh.WriteAt([]byte("hello"), 0) 166 assert.Equal(t, ECLOSED, err) 167 assert.Equal(t, 0, n) 168 169 // check vfs 170 root, err := vfs.Root() 171 require.NoError(t, err) 172 checkListing(t, root, []string{"file1,11,false"}) 173 174 // check the underlying r.Fremote but not the modtime 175 file1 := fstest.NewItem("file1", "hello world", t1) 176 fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, []string{}, fs.ModTimeNotSupported) 177} 178 179func TestWriteFileHandleFlush(t *testing.T) { 180 _, vfs, fh, cleanup := writeHandleCreate(t) 181 defer cleanup() 182 183 // Check Flush already creates file for unwritten handles, without closing it 184 err := fh.Flush() 185 assert.NoError(t, err) 186 assert.False(t, fh.closed) 187 root, err := vfs.Root() 188 assert.NoError(t, err) 189 checkListing(t, root, []string{"file1,0,false"}) 190 191 // Write some data 192 n, err := fh.Write([]byte("hello")) 193 assert.NoError(t, err) 194 assert.Equal(t, 5, n) 195 196 // Check Flush closes file if write called 197 err = fh.Flush() 198 assert.NoError(t, err) 199 assert.True(t, fh.closed) 200 201 // Check flush does nothing if called again 202 err = fh.Flush() 203 assert.NoError(t, err) 204 assert.True(t, fh.closed) 205 206 // Check file was written properly 207 root, err = vfs.Root() 208 assert.NoError(t, err) 209 checkListing(t, root, []string{"file1,5,false"}) 210} 211 212func TestWriteFileHandleRelease(t *testing.T) { 213 _, _, fh, cleanup := writeHandleCreate(t) 214 defer cleanup() 215 216 // Check Release closes file 217 err := fh.Release() 218 if errors.Cause(err) == fs.ErrorCantUploadEmptyFiles { 219 t.Logf("skipping test: %v", err) 220 return 221 } 222 assert.NoError(t, err) 223 assert.True(t, fh.closed) 224 225 // Check Release does nothing if called again 226 err = fh.Release() 227 assert.NoError(t, err) 228 assert.True(t, fh.closed) 229} 230 231var ( 232 canSetModTimeOnce sync.Once 233 canSetModTimeValue = true 234) 235 236// returns whether the remote can set modtime 237func canSetModTime(t *testing.T, r *fstest.Run) bool { 238 canSetModTimeOnce.Do(func() { 239 mtime1 := time.Date(2008, time.November, 18, 17, 32, 31, 0, time.UTC) 240 _ = r.WriteObject(context.Background(), "time_test", "stuff", mtime1) 241 obj, err := r.Fremote.NewObject(context.Background(), "time_test") 242 require.NoError(t, err) 243 mtime2 := time.Date(2009, time.November, 18, 17, 32, 31, 0, time.UTC) 244 err = obj.SetModTime(context.Background(), mtime2) 245 switch err { 246 case nil: 247 canSetModTimeValue = true 248 case fs.ErrorCantSetModTime, fs.ErrorCantSetModTimeWithoutDelete: 249 canSetModTimeValue = false 250 default: 251 require.NoError(t, err) 252 } 253 require.NoError(t, obj.Remove(context.Background())) 254 fs.Debugf(nil, "Can set mod time: %v", canSetModTimeValue) 255 }) 256 return canSetModTimeValue 257} 258 259// tests mod time on open files 260func TestWriteFileModTimeWithOpenWriters(t *testing.T) { 261 r, vfs, fh, cleanup := writeHandleCreate(t) 262 defer cleanup() 263 264 if !canSetModTime(t, r) { 265 t.Skip("can't set mod time") 266 } 267 268 mtime := time.Date(2012, time.November, 18, 17, 32, 31, 0, time.UTC) 269 270 _, err := fh.Write([]byte{104, 105}) 271 require.NoError(t, err) 272 273 err = fh.Node().SetModTime(mtime) 274 require.NoError(t, err) 275 276 err = fh.Close() 277 require.NoError(t, err) 278 279 info, err := vfs.Stat("file1") 280 require.NoError(t, err) 281 282 if r.Fremote.Precision() != fs.ModTimeNotSupported { 283 // avoid errors because of timezone differences 284 assert.Equal(t, info.ModTime().Unix(), mtime.Unix()) 285 } 286} 287 288func testFileReadAt(t *testing.T, n int) { 289 _, vfs, fh, cleanup := writeHandleCreate(t) 290 defer cleanup() 291 292 contents := []byte(random.String(n)) 293 if n != 0 { 294 written, err := fh.Write(contents) 295 require.NoError(t, err) 296 assert.Equal(t, n, written) 297 } 298 299 // Close the file without writing to it if n==0 300 err := fh.Close() 301 if errors.Cause(err) == fs.ErrorCantUploadEmptyFiles { 302 t.Logf("skipping test: %v", err) 303 return 304 } 305 assert.NoError(t, err) 306 307 // read the file back in using ReadAt into a buffer 308 // this simulates what mount does 309 rd, err := vfs.OpenFile("file1", os.O_RDONLY, 0) 310 require.NoError(t, err) 311 312 buf := make([]byte, 1024) 313 read, err := rd.ReadAt(buf, 0) 314 if err != io.EOF { 315 assert.NoError(t, err) 316 } 317 assert.Equal(t, read, n) 318 assert.Equal(t, contents, buf[:read]) 319 320 err = rd.Close() 321 assert.NoError(t, err) 322} 323 324func TestFileReadAtZeroLength(t *testing.T) { 325 testFileReadAt(t, 0) 326} 327 328func TestFileReadAtNonZeroLength(t *testing.T) { 329 testFileReadAt(t, 100) 330} 331