| 293 | } |
| 294 | |
| 295 | func TestBashSafeLink(t *testing.T) { |
| 296 | // macOS `mv` takes different arguments than GNU coreutils. |
| 297 | if output, err := exec.CommandContext(t.Context(), "mv", "--help").CombinedOutput(); err != nil { |
| 298 | t.Skip(`requires "mv" executable`) |
| 299 | } else if !strings.Contains(string(output), "no-target-directory") { |
| 300 | t.Skip(`requires "mv" that overwrites a directory symlink`) |
| 301 | } |
| 302 | |
| 303 | // execute calls the bash function with args. |
| 304 | execute := func(args ...string) (string, error) { |
| 305 | cmd := exec.CommandContext(t.Context(), "bash") |
| 306 | cmd.Args = append(cmd.Args, "-ceu", "--", bashSafeLink+`safelink "$@"`, "-") |
| 307 | cmd.Args = append(cmd.Args, args...) |
| 308 | output, err := cmd.CombinedOutput() |
| 309 | return string(output), err |
| 310 | } |
| 311 | |
| 312 | t.Run("CurrentIsFullDirectory", func(t *testing.T) { |
| 313 | // setupDirectory creates a non-empty directory. |
| 314 | setupDirectory := func(t testing.TB) (root, current string) { |
| 315 | t.Helper() |
| 316 | root = t.TempDir() |
| 317 | current = filepath.Join(root, "original") |
| 318 | assert.NilError(t, os.MkdirAll(current, 0o700)) |
| 319 | file, err := os.Create(filepath.Join(current, "original.file")) |
| 320 | assert.NilError(t, err) |
| 321 | assert.NilError(t, file.Close()) |
| 322 | return |
| 323 | } |
| 324 | |
| 325 | // assertSetupContents ensures that directory contents match setupDirectory. |
| 326 | assertSetupContents := func(t testing.TB, directory string) { |
| 327 | t.Helper() |
| 328 | entries, err := os.ReadDir(directory) |
| 329 | assert.NilError(t, err) |
| 330 | assert.Equal(t, len(entries), 1) |
| 331 | assert.Equal(t, entries[0].Name(), "original.file") |
| 332 | } |
| 333 | |
| 334 | // This situation is unexpected and succeeds. |
| 335 | t.Run("DesiredIsEmptyDirectory", func(t *testing.T) { |
| 336 | root, current := setupDirectory(t) |
| 337 | |
| 338 | // desired is an empty directory. |
| 339 | desired := filepath.Join(root, "desired") |
| 340 | assert.NilError(t, os.MkdirAll(desired, 0o700)) |
| 341 | |
| 342 | output, err := execute(desired, current) |
| 343 | assert.NilError(t, err, "\n%s", output) |
| 344 | |
| 345 | result, err := os.Readlink(current) |
| 346 | assert.NilError(t, err, "expected symlink") |
| 347 | assert.Equal(t, result, desired) |
| 348 | assertSetupContents(t, desired) |
| 349 | }) |
| 350 | |
| 351 | // This situation is unexpected and aborts. |
| 352 | t.Run("DesiredIsFullDirectory", func(t *testing.T) { |