github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/vfs/vfs_case_test.go (about) 1 package vfs 2 3 import ( 4 "context" 5 "os" 6 "testing" 7 8 "github.com/rclone/rclone/fs" 9 "github.com/rclone/rclone/fstest" 10 "github.com/rclone/rclone/vfs/vfscommon" 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 "golang.org/x/text/unicode/norm" 14 ) 15 16 func TestCaseSensitivity(t *testing.T) { 17 r := fstest.NewRun(t) 18 19 if r.Fremote.Features().CaseInsensitive { 20 t.Skip("Can't test case sensitivity - this remote is officially not case-sensitive") 21 } 22 23 // Create test files 24 ctx := context.Background() 25 file1 := r.WriteObject(ctx, "FiLeA", "data1", t1) 26 file2 := r.WriteObject(ctx, "FiLeB", "data2", t2) 27 r.CheckRemoteItems(t, file1, file2) 28 29 // Create file3 with name differing from file2 name only by case. 30 // On a case-Sensitive remote this will be a separate file. 31 // On a case-INsensitive remote this file will either not exist 32 // or overwrite file2 depending on how file system diverges. 33 // On a box.com remote this step will even fail. 34 file3 := r.WriteObject(ctx, "FilEb", "data3", t3) 35 36 // Create a case-Sensitive and case-INsensitive VFS 37 optCS := vfscommon.DefaultOpt 38 optCS.CaseInsensitive = false 39 vfsCS := New(r.Fremote, &optCS) 40 defer cleanupVFS(t, vfsCS) 41 42 optCI := vfscommon.DefaultOpt 43 optCI.CaseInsensitive = true 44 vfsCI := New(r.Fremote, &optCI) 45 defer cleanupVFS(t, vfsCI) 46 47 // Run basic checks that must pass on VFS of any type. 48 assertFileDataVFS(t, vfsCI, "FiLeA", "data1") 49 assertFileDataVFS(t, vfsCS, "FiLeA", "data1") 50 51 // Detect case sensitivity of the underlying remote. 52 remoteIsOK := true 53 if !checkFileDataVFS(t, vfsCS, "FiLeA", "data1") { 54 remoteIsOK = false 55 } 56 if !checkFileDataVFS(t, vfsCS, "FiLeB", "data2") { 57 remoteIsOK = false 58 } 59 if !checkFileDataVFS(t, vfsCS, "FilEb", "data3") { 60 remoteIsOK = false 61 } 62 63 // The remaining test is only meaningful on a case-Sensitive file system. 64 if !remoteIsOK { 65 t.Skip("Can't test case sensitivity - this remote doesn't comply as case-sensitive") 66 } 67 68 // Continue with test as the underlying remote is fully case-Sensitive. 69 r.CheckRemoteItems(t, file1, file2, file3) 70 71 // See how VFS handles case-INsensitive flag 72 assertFileDataVFS(t, vfsCI, "FiLeA", "data1") 73 assertFileDataVFS(t, vfsCI, "fileA", "data1") 74 assertFileDataVFS(t, vfsCI, "filea", "data1") 75 assertFileDataVFS(t, vfsCI, "FILEA", "data1") 76 77 assertFileDataVFS(t, vfsCI, "FiLeB", "data2") 78 assertFileDataVFS(t, vfsCI, "FilEb", "data3") 79 80 fd, err := vfsCI.OpenFile("fileb", os.O_RDONLY, 0777) 81 assert.Nil(t, fd) 82 assert.Error(t, err) 83 assert.NotEqual(t, err, ENOENT) 84 85 fd, err = vfsCI.OpenFile("FILEB", os.O_RDONLY, 0777) 86 assert.Nil(t, fd) 87 assert.Error(t, err) 88 assert.NotEqual(t, err, ENOENT) 89 90 // Run the same set of checks with case-Sensitive VFS, for comparison. 91 assertFileDataVFS(t, vfsCS, "FiLeA", "data1") 92 93 assertFileAbsentVFS(t, vfsCS, "fileA") 94 assertFileAbsentVFS(t, vfsCS, "filea") 95 assertFileAbsentVFS(t, vfsCS, "FILEA") 96 97 assertFileDataVFS(t, vfsCS, "FiLeB", "data2") 98 assertFileDataVFS(t, vfsCS, "FilEb", "data3") 99 100 assertFileAbsentVFS(t, vfsCS, "fileb") 101 assertFileAbsentVFS(t, vfsCS, "FILEB") 102 } 103 104 func checkFileDataVFS(t *testing.T, vfs *VFS, name string, expect string) bool { 105 fd, err := vfs.OpenFile(name, os.O_RDONLY, 0777) 106 if fd == nil || err != nil { 107 return false 108 } 109 defer func() { 110 // File must be closed - otherwise Run.cleanUp() will fail on Windows. 111 _ = fd.Close() 112 }() 113 114 fh, ok := fd.(*ReadFileHandle) 115 if !ok { 116 return false 117 } 118 119 size := len(expect) 120 buf := make([]byte, size) 121 num, err := fh.Read(buf) 122 if err != nil || num != size { 123 return false 124 } 125 126 return string(buf) == expect 127 } 128 129 func assertFileDataVFS(t *testing.T, vfs *VFS, name string, expect string) { 130 fd, errOpen := vfs.OpenFile(name, os.O_RDONLY, 0777) 131 assert.NotNil(t, fd) 132 assert.NoError(t, errOpen) 133 134 defer func() { 135 // File must be closed - otherwise Run.cleanUp() will fail on Windows. 136 if errOpen == nil && fd != nil { 137 _ = fd.Close() 138 } 139 }() 140 141 fh, ok := fd.(*ReadFileHandle) 142 require.True(t, ok) 143 144 size := len(expect) 145 buf := make([]byte, size) 146 numRead, errRead := fh.Read(buf) 147 assert.NoError(t, errRead) 148 assert.Equal(t, numRead, size) 149 150 assert.Equal(t, string(buf), expect) 151 } 152 153 func assertFileAbsentVFS(t *testing.T, vfs *VFS, name string) { 154 fd, err := vfs.OpenFile(name, os.O_RDONLY, 0777) 155 defer func() { 156 // File must be closed - otherwise Run.cleanUp() will fail on Windows. 157 if err == nil && fd != nil { 158 _ = fd.Close() 159 } 160 }() 161 assert.Nil(t, fd) 162 assert.Error(t, err) 163 assert.Equal(t, err, ENOENT) 164 } 165 166 func TestUnicodeNormalization(t *testing.T) { 167 r := fstest.NewRun(t) 168 169 var ( 170 nfc = norm.NFC.String(norm.NFD.String("測試_Русский___ě_áñ")) 171 nfd = norm.NFD.String(nfc) 172 both = "normal name with no special characters.txt" 173 ) 174 175 // Create test files 176 ctx := context.Background() 177 file1 := r.WriteObject(ctx, both, "data1", t1) 178 file2 := r.WriteObject(ctx, nfc, "data2", t2) 179 r.CheckRemoteItems(t, file1, file2) 180 181 // Create VFS 182 opt := vfscommon.DefaultOpt 183 vfs := New(r.Fremote, &opt) 184 defer cleanupVFS(t, vfs) 185 186 // assert that both files are found under NFD-normalized names 187 assertFileDataVFS(t, vfs, norm.NFD.String(both), "data1") 188 assertFileDataVFS(t, vfs, nfd, "data2") 189 190 // change ci.NoUnicodeNormalization to true and verify that only file1 is found 191 ci := fs.GetConfig(ctx) // need to set the global config here as the *Dir methods don't take a ctx param 192 oldVal := ci.NoUnicodeNormalization 193 defer func() { fs.GetConfig(ctx).NoUnicodeNormalization = oldVal }() // restore the prior value after the test 194 ci.NoUnicodeNormalization = true 195 assertFileDataVFS(t, vfs, norm.NFD.String(both), "data1") 196 assertFileAbsentVFS(t, vfs, nfd) 197 }