github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/config/configfile/configfile_test.go (about) 1 package configfile 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "runtime" 8 "strings" 9 "testing" 10 11 "github.com/rclone/rclone/fs/config" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 ) 15 16 var configData = `[one] 17 type = number1 18 fruit = potato 19 20 [two] 21 type = number2 22 fruit = apple 23 topping = nuts 24 25 [three] 26 type = number3 27 fruit = banana 28 29 ` 30 31 // Fill up a temporary config file with the testdata filename passed in 32 func setConfigFile(t *testing.T, data string) func() { 33 out, err := os.CreateTemp("", "rclone-configfile-test") 34 require.NoError(t, err) 35 filePath := out.Name() 36 37 _, err = out.Write([]byte(data)) 38 require.NoError(t, err) 39 40 require.NoError(t, out.Close()) 41 42 old := config.GetConfigPath() 43 assert.NoError(t, config.SetConfigPath(filePath)) 44 return func() { 45 assert.NoError(t, config.SetConfigPath(old)) 46 _ = os.Remove(filePath) 47 } 48 } 49 50 // toUnix converts \r\n to \n in buf 51 func toUnix(buf string) string { 52 if runtime.GOOS == "windows" { 53 return strings.ReplaceAll(buf, "\r\n", "\n") 54 } 55 return buf 56 } 57 58 func TestConfigFile(t *testing.T) { 59 defer setConfigFile(t, configData)() 60 data := &Storage{} 61 62 require.NoError(t, data.Load()) 63 64 t.Run("Read", func(t *testing.T) { 65 t.Run("Serialize", func(t *testing.T) { 66 buf, err := data.Serialize() 67 require.NoError(t, err) 68 assert.Equal(t, configData, toUnix(buf)) 69 }) 70 t.Run("HasSection", func(t *testing.T) { 71 assert.True(t, data.HasSection("one")) 72 assert.False(t, data.HasSection("missing")) 73 }) 74 t.Run("GetSectionList", func(t *testing.T) { 75 assert.Equal(t, []string{ 76 "one", 77 "two", 78 "three", 79 }, data.GetSectionList()) 80 }) 81 t.Run("GetKeyList", func(t *testing.T) { 82 assert.Equal(t, []string{ 83 "type", 84 "fruit", 85 "topping", 86 }, data.GetKeyList("two")) 87 assert.Equal(t, []string(nil), data.GetKeyList("unicorn")) 88 }) 89 t.Run("GetValue", func(t *testing.T) { 90 value, ok := data.GetValue("one", "type") 91 assert.True(t, ok) 92 assert.Equal(t, "number1", value) 93 value, ok = data.GetValue("three", "fruit") 94 assert.True(t, ok) 95 assert.Equal(t, "banana", value) 96 value, ok = data.GetValue("one", "typeX") 97 assert.False(t, ok) 98 assert.Equal(t, "", value) 99 value, ok = data.GetValue("threeX", "fruit") 100 assert.False(t, ok) 101 assert.Equal(t, "", value) 102 }) 103 }) 104 105 //defer setConfigFile(configData)() 106 107 t.Run("Write", func(t *testing.T) { 108 t.Run("SetValue", func(t *testing.T) { 109 data.SetValue("one", "extra", "42") 110 data.SetValue("two", "fruit", "acorn") 111 112 buf, err := data.Serialize() 113 require.NoError(t, err) 114 assert.Equal(t, `[one] 115 type = number1 116 fruit = potato 117 extra = 42 118 119 [two] 120 type = number2 121 fruit = acorn 122 topping = nuts 123 124 [three] 125 type = number3 126 fruit = banana 127 128 `, toUnix(buf)) 129 t.Run("DeleteKey", func(t *testing.T) { 130 data.DeleteKey("one", "type") 131 data.DeleteKey("two", "missing") 132 data.DeleteKey("three", "fruit") 133 buf, err := data.Serialize() 134 require.NoError(t, err) 135 assert.Equal(t, `[one] 136 fruit = potato 137 extra = 42 138 139 [two] 140 type = number2 141 fruit = acorn 142 topping = nuts 143 144 [three] 145 type = number3 146 147 `, toUnix(buf)) 148 t.Run("DeleteSection", func(t *testing.T) { 149 data.DeleteSection("two") 150 data.DeleteSection("missing") 151 buf, err := data.Serialize() 152 require.NoError(t, err) 153 assert.Equal(t, `[one] 154 fruit = potato 155 extra = 42 156 157 [three] 158 type = number3 159 160 `, toUnix(buf)) 161 t.Run("Save", func(t *testing.T) { 162 require.NoError(t, data.Save()) 163 buf, err := os.ReadFile(config.GetConfigPath()) 164 require.NoError(t, err) 165 assert.Equal(t, `[one] 166 fruit = potato 167 extra = 42 168 169 [three] 170 type = number3 171 172 `, toUnix(string(buf))) 173 }) 174 }) 175 }) 176 }) 177 }) 178 } 179 180 func TestConfigFileReload(t *testing.T) { 181 defer setConfigFile(t, configData)() 182 data := &Storage{} 183 184 require.NoError(t, data.Load()) 185 186 value, ok := data.GetValue("three", "appended") 187 assert.False(t, ok) 188 assert.Equal(t, "", value) 189 190 // Now write a new value on the end 191 out, err := os.OpenFile(config.GetConfigPath(), os.O_APPEND|os.O_WRONLY, 0777) 192 require.NoError(t, err) 193 fmt.Fprintln(out, "appended = what magic") 194 require.NoError(t, out.Close()) 195 196 // And check we magically reloaded it 197 value, ok = data.GetValue("three", "appended") 198 assert.True(t, ok) 199 assert.Equal(t, "what magic", value) 200 } 201 202 func TestConfigFileDoesNotExist(t *testing.T) { 203 defer setConfigFile(t, configData)() 204 data := &Storage{} 205 206 require.NoError(t, os.Remove(config.GetConfigPath())) 207 208 err := data.Load() 209 require.Equal(t, config.ErrorConfigFileNotFound, err) 210 211 // check that using data doesn't crash 212 value, ok := data.GetValue("three", "appended") 213 assert.False(t, ok) 214 assert.Equal(t, "", value) 215 } 216 217 func testConfigFileNoConfig(t *testing.T, configPath string) { 218 assert.NoError(t, config.SetConfigPath(configPath)) 219 data := &Storage{} 220 221 err := data.Load() 222 require.Equal(t, config.ErrorConfigFileNotFound, err) 223 224 data.SetValue("one", "extra", "42") 225 value, ok := data.GetValue("one", "extra") 226 assert.True(t, ok) 227 assert.Equal(t, "42", value) 228 229 err = data.Save() 230 require.Error(t, err) 231 } 232 233 func TestConfigFileNoConfig(t *testing.T) { 234 old := config.GetConfigPath() 235 defer func() { 236 assert.NoError(t, config.SetConfigPath(old)) 237 }() 238 239 t.Run("Empty", func(t *testing.T) { 240 testConfigFileNoConfig(t, "") 241 }) 242 t.Run("NotFound", func(t *testing.T) { 243 testConfigFileNoConfig(t, "/notfound") 244 }) 245 } 246 247 func TestConfigFileSave(t *testing.T) { 248 testDir := t.TempDir() 249 configPath := filepath.Join(testDir, "a", "b", "c", "configfile") 250 251 assert.NoError(t, config.SetConfigPath(configPath)) 252 data := &Storage{} 253 require.Error(t, data.Load(), config.ErrorConfigFileNotFound) 254 255 t.Run("CreatesDirsAndFile", func(t *testing.T) { 256 err := data.Save() 257 require.NoError(t, err) 258 info, err := os.Stat(configPath) 259 require.NoError(t, err) 260 assert.False(t, info.IsDir()) 261 }) 262 t.Run("KeepsFileMode", func(t *testing.T) { 263 if runtime.GOOS != "linux" { 264 t.Skip("this is a Linux only test") 265 } 266 assert.NoError(t, os.Chmod(configPath, 0400)) // -r-------- 267 defer func() { 268 _ = os.Chmod(configPath, 0644) // -rw-r--r-- 269 }() 270 err := data.Save() 271 require.NoError(t, err) 272 info, err := os.Stat(configPath) 273 require.NoError(t, err) 274 assert.Equal(t, os.FileMode(0400), info.Mode().Perm()) 275 }) 276 t.Run("SucceedsEvenIfReadOnlyFile", func(t *testing.T) { 277 // Save succeeds even if file is read-only since it does not write directly to the file. 278 assert.NoError(t, os.Chmod(configPath, 0400)) // -r-------- 279 defer func() { 280 _ = os.Chmod(configPath, 0644) // -rw-r--r-- 281 }() 282 err := data.Save() 283 assert.NoError(t, err) 284 }) 285 t.Run("FailsIfNotAccessToDir", func(t *testing.T) { 286 // Save fails if no access to the directory. 287 if runtime.GOOS != "linux" { 288 // On Windows the os.Chmod only affects the read-only attribute of files) 289 t.Skip("this is a Linux only test") 290 } 291 configDir := filepath.Dir(configPath) 292 assert.NoError(t, os.Chmod(configDir, 0400)) // -r-------- 293 defer func() { 294 _ = os.Chmod(configDir, 0755) // -rwxr-xr-x 295 }() 296 err := data.Save() 297 require.Error(t, err) 298 assert.True(t, strings.HasPrefix(err.Error(), "failed to resolve config file path")) 299 }) 300 t.Run("FailsIfNotAllowedToCreateNewFiles", func(t *testing.T) { 301 // Save fails if read-only access to the directory, since it needs to create temporary files in there. 302 if runtime.GOOS != "linux" { 303 // On Windows the os.Chmod only affects the read-only attribute of files) 304 t.Skip("this is a Linux only test") 305 } 306 configDir := filepath.Dir(configPath) 307 assert.NoError(t, os.Chmod(configDir, 0544)) // -r-xr--r-- 308 defer func() { 309 _ = os.Chmod(configDir, 0755) // -rwxr-xr-x 310 }() 311 err := data.Save() 312 require.Error(t, err) 313 assert.True(t, strings.HasPrefix(err.Error(), "failed to create temp file for new config")) 314 }) 315 } 316 317 func TestConfigFileSaveSymlinkAbsolute(t *testing.T) { 318 if runtime.GOOS != "linux" { 319 // Symlinks may require admin privileges on Windows and os.Symlink will then 320 // fail with "A required privilege is not held by the client." 321 t.Skip("this is a Linux only test") 322 } 323 testDir := t.TempDir() 324 linkDir := filepath.Join(testDir, "a") 325 err := os.Mkdir(linkDir, os.ModePerm) 326 require.NoError(t, err) 327 328 testSymlink := func(t *testing.T, link string, target string, resolvedTarget string) { 329 err = os.Symlink(target, link) 330 require.NoError(t, err) 331 defer func() { 332 _ = os.Remove(link) 333 }() 334 335 assert.NoError(t, config.SetConfigPath(link)) 336 data := &Storage{} 337 require.Error(t, data.Load(), config.ErrorConfigFileNotFound) 338 339 err = data.Save() 340 require.NoError(t, err) 341 342 info, err := os.Lstat(link) 343 require.NoError(t, err) 344 assert.True(t, info.Mode()&os.ModeSymlink != 0) 345 assert.False(t, info.IsDir()) 346 347 info, err = os.Lstat(resolvedTarget) 348 require.NoError(t, err) 349 assert.False(t, info.IsDir()) 350 } 351 352 t.Run("Absolute", func(t *testing.T) { 353 link := filepath.Join(linkDir, "configfilelink") 354 target := filepath.Join(testDir, "b", "configfiletarget") 355 testSymlink(t, link, target, target) 356 }) 357 t.Run("Relative", func(t *testing.T) { 358 link := filepath.Join(linkDir, "configfilelink") 359 target := filepath.Join("b", "c", "configfiletarget") 360 resolvedTarget := filepath.Join(filepath.Dir(link), target) 361 testSymlink(t, link, target, resolvedTarget) 362 }) 363 }