Skip to content

Commit 42d0010

Browse files
authored
Document and test Base.rename (#55503)
Follow up of #55384
1 parent ca72e28 commit 42d0010

File tree

2 files changed

+314
-5
lines changed

2 files changed

+314
-5
lines changed

base/file.jl

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,15 +1183,24 @@ function unlink(p::AbstractString)
11831183
end
11841184

11851185
"""
1186-
rename(oldpath::AbstractString, newpath::AbstractString)
1186+
Base.rename(oldpath::AbstractString, newpath::AbstractString)
11871187
1188-
Change the name of a file from `oldpath` to `newpath`. If `newpath` is an existing file it may be replaced.
1189-
Equivalent to [rename(2)](https://man7.org/linux/man-pages/man2/rename.2.html).
1190-
Throws an `IOError` on failure.
1188+
Change the name of a file or directory from `oldpath` to `newpath`.
1189+
If `newpath` is an existing file or empty directory it may be replaced.
1190+
Equivalent to [rename(2)](https://man7.org/linux/man-pages/man2/rename.2.html) on Unix.
1191+
If a path contains a "\\0" throw an `ArgumentError`.
1192+
On other failures throw an `IOError`.
11911193
Return `newpath`.
11921194
11931195
OS-specific restrictions may apply when `oldpath` and `newpath` are in different directories.
11941196
1197+
Currently there are a few differences in behavior on Windows which may be resolved in a future release.
1198+
Specifically, currently on Windows:
1199+
1. `rename` will fail if `oldpath` or `newpath` are opened files.
1200+
2. `rename` will fail if `newpath` is an existing directory.
1201+
3. `rename` may work if `newpath` is a file and `oldpath` is a directory.
1202+
4. `rename` may remove `oldpath` if it is a hardlink to `newpath`.
1203+
11951204
See also: [`mv`](@ref).
11961205
"""
11971206
function rename(oldpath::AbstractString, newpath::AbstractString)

test/file.jl

Lines changed: 301 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,303 @@ mktempdir() do tmpdir
823823
rm(b_tmpdir)
824824
end
825825

826+
@testset "rename" begin
827+
# some of the windows specific behavior may be fixed in new versions of julia
828+
mktempdir() do dir
829+
# see if can make symlinks
830+
local can_symlink = try
831+
symlink("foo", joinpath(dir, "link"))
832+
rm(joinpath(dir, "link"))
833+
true
834+
catch
835+
false
836+
end
837+
local f1 = joinpath(dir, "file1")
838+
local f2 = joinpath(dir, "file2")
839+
local d1 = joinpath(dir, "dir1")
840+
local d2 = joinpath(dir, "dir2")
841+
local subd1f1 = joinpath(d1, "file1")
842+
local subd1f2 = joinpath(d1, "file2")
843+
local subd2f1 = joinpath(d2, "file1")
844+
local subd2f2 = joinpath(d2, "file2")
845+
local h1 = joinpath(dir, "hlink1")
846+
local h2 = joinpath(dir, "hlink2")
847+
local s1 = joinpath(dir, "slink1")
848+
local s2 = joinpath(dir, "slink2")
849+
@testset "renaming to non existing newpath in same directory" begin
850+
# file, make sure isexecutable is copied
851+
for mode in (0o644, 0o755)
852+
write(f1, b"data")
853+
chmod(f1, mode)
854+
Base.rename(f1, f2)
855+
@test !isfile(f1)
856+
@test isfile(f2)
857+
@test read(f2) == b"data"
858+
if mode == 0o644
859+
@test !isexecutable(f2)
860+
else
861+
@test isexecutable(f2)
862+
end
863+
rm(f2)
864+
end
865+
# empty directory
866+
mkdir(d1)
867+
Base.rename(d1, d2)
868+
@test !isdir(d1)
869+
@test isdir(d2)
870+
@test isempty(readdir(d2))
871+
rm(d2)
872+
# non empty directory
873+
mkdir(d1)
874+
write(subd1f1, b"data")
875+
chmod(subd1f1, 0o644)
876+
write(subd1f2, b"exe")
877+
chmod(subd1f2, 0o755)
878+
Base.rename(d1, d2)
879+
@test !isdir(d1)
880+
@test isdir(d2)
881+
@test read(subd2f1) == b"data"
882+
@test read(subd2f2) == b"exe"
883+
@test !isexecutable(subd2f1)
884+
@test isexecutable(subd2f2)
885+
rm(d2; recursive=true)
886+
# hardlink
887+
write(f1, b"data")
888+
hardlink(f1, h1)
889+
Base.rename(h1, h2)
890+
@test isfile(f1)
891+
@test !isfile(h1)
892+
@test isfile(h2)
893+
@test read(h2) == b"data"
894+
write(h2, b"data2")
895+
@test read(f1) == b"data2"
896+
rm(h2)
897+
rm(f1)
898+
# symlink
899+
if can_symlink
900+
symlink("foo", s1)
901+
Base.rename(s1, s2)
902+
@test !islink(s1)
903+
@test islink(s2)
904+
@test readlink(s2) == "foo"
905+
rm(s2)
906+
end
907+
end
908+
@test isempty(readdir(dir)) # make sure everything got cleaned up
909+
910+
# Get the error code from failed rename, or nothing if it worked
911+
function rename_errorcodes(oldpath, newpath)
912+
try
913+
Base.rename(oldpath, newpath)
914+
nothing
915+
catch e
916+
e.code
917+
end
918+
end
919+
@testset "errors" begin
920+
# invalid paths
921+
@test_throws ArgumentError Base.rename(f1*"\0", "")
922+
@test Base.UV_ENOENT == rename_errorcodes("", "")
923+
write(f1, b"data")
924+
@test Base.UV_ENOENT == rename_errorcodes(f1, "")
925+
@test read(f1) == b"data"
926+
@test Base.UV_ENOENT == rename_errorcodes("", f1)
927+
@test read(f1) == b"data"
928+
@test Base.UV_ENOENT == rename_errorcodes(f2, f1)
929+
@test read(f1) == b"data"
930+
@test Base.UV_ENOENT == rename_errorcodes(f1, subd1f1)
931+
@test read(f1) == b"data"
932+
rm(f1)
933+
# attempt to make a directory a subdirectory of itself
934+
mkdir(d1)
935+
if Sys.iswindows()
936+
@test rename_errorcodes(d1, joinpath(d1, "subdir")) (Base.UV_EINVAL, Base.UV_EBUSY)
937+
else
938+
@test Base.UV_EINVAL == rename_errorcodes(d1, joinpath(d1, "subdir"))
939+
end
940+
rm(d1)
941+
# rename to child of a file
942+
mkdir(d1)
943+
write(f2, "foo")
944+
if Sys.iswindows()
945+
@test Base.UV_EINVAL == rename_errorcodes(d1, joinpath(f2, "subdir"))
946+
else
947+
@test Base.UV_ENOTDIR == rename_errorcodes(d1, joinpath(f2, "subdir"))
948+
end
949+
# replace a file with a directory
950+
if !Sys.iswindows()
951+
@test Base.UV_ENOTDIR == rename_errorcodes(d1, f2)
952+
else
953+
# this should work on windows
954+
Base.rename(d1, f2)
955+
@test isdir(f2)
956+
@test !ispath(d1)
957+
end
958+
rm(f2; force=true)
959+
rm(d1; force=true)
960+
# symlink loop
961+
if can_symlink
962+
symlink(s1, s2)
963+
symlink(s2, s1)
964+
@test Base.UV_ELOOP == rename_errorcodes(joinpath(s1, "foo"), f2)
965+
write(f2, b"data")
966+
@test Base.UV_ELOOP == rename_errorcodes(f2, joinpath(s1, "foo"))
967+
rm(s1)
968+
rm(s2)
969+
rm(f2)
970+
end
971+
# newpath is a nonempty directory
972+
mkdir(d1)
973+
mkdir(d2)
974+
write(subd2f1, b"data")
975+
write(f1, b"otherdata")
976+
if Sys.iswindows()
977+
@test Base.UV_EACCES == rename_errorcodes(f1, d1)
978+
@test Base.UV_EACCES == rename_errorcodes(f1, d2)
979+
@test Base.UV_EACCES == rename_errorcodes(d1, d2)
980+
@test Base.UV_EACCES == rename_errorcodes(subd2f1, d2)
981+
else
982+
@test Base.UV_EISDIR == rename_errorcodes(f1, d1)
983+
@test Base.UV_EISDIR == rename_errorcodes(f1, d2)
984+
@test rename_errorcodes(d1, d2) (Base.UV_ENOTEMPTY, Base.UV_EEXIST)
985+
@test rename_errorcodes(subd2f1, d2) (Base.UV_ENOTEMPTY, Base.UV_EEXIST, Base.UV_EISDIR)
986+
end
987+
rm(f1)
988+
rm(d1)
989+
rm(d2; recursive=true)
990+
end
991+
@test isempty(readdir(dir)) # make sure everything got cleaned up
992+
993+
@testset "replacing existing file" begin
994+
write(f2, b"olddata")
995+
chmod(f2, 0o755)
996+
write(f1, b"newdata")
997+
chmod(f1, 0o644)
998+
@test isexecutable(f2)
999+
@test !isexecutable(f1)
1000+
Base.rename(f1, f2)
1001+
@test !ispath(f1)
1002+
@test read(f2) == b"newdata"
1003+
@test !isexecutable(f2)
1004+
rm(f2)
1005+
end
1006+
1007+
@testset "replacing file with itself" begin
1008+
write(f1, b"data")
1009+
Base.rename(f1, f1)
1010+
@test read(f1) == b"data"
1011+
hardlink(f1, h1)
1012+
Base.rename(f1, h1)
1013+
if Sys.iswindows()
1014+
# On Windows f1 gets deleted
1015+
@test !ispath(f1)
1016+
else
1017+
@test read(f1) == b"data"
1018+
end
1019+
@test read(h1) == b"data"
1020+
rm(h1)
1021+
rm(f1; force=true)
1022+
end
1023+
1024+
@testset "replacing existing file in different directories" begin
1025+
mkdir(d1)
1026+
mkdir(d2)
1027+
write(subd2f2, b"olddata")
1028+
chmod(subd2f2, 0o755)
1029+
write(subd1f1, b"newdata")
1030+
chmod(subd1f1, 0o644)
1031+
@test isexecutable(subd2f2)
1032+
@test !isexecutable(subd1f1)
1033+
Base.rename(subd1f1, subd2f2)
1034+
@test !ispath(subd1f1)
1035+
@test read(subd2f2) == b"newdata"
1036+
@test !isexecutable(subd2f2)
1037+
@test isdir(d1)
1038+
@test isdir(d2)
1039+
rm(d1; recursive=true)
1040+
rm(d2; recursive=true)
1041+
end
1042+
1043+
@testset "rename with open files" begin
1044+
# both open
1045+
write(f2, b"olddata")
1046+
write(f1, b"newdata")
1047+
open(f1) do handle1
1048+
open(f2) do handle2
1049+
if Sys.iswindows()
1050+
# currently this doesn't work on windows
1051+
@test Base.UV_EBUSY == rename_errorcodes(f1, f2)
1052+
else
1053+
Base.rename(f1, f2)
1054+
@test !ispath(f1)
1055+
@test read(f2) == b"newdata"
1056+
end
1057+
# rename doesn't break already opened files
1058+
@test read(handle1) == b"newdata"
1059+
@test read(handle2) == b"olddata"
1060+
end
1061+
end
1062+
rm(f1; force=true)
1063+
rm(f2; force=true)
1064+
1065+
# oldpath open
1066+
write(f2, b"olddata")
1067+
write(f1, b"newdata")
1068+
open(f1) do handle1
1069+
if Sys.iswindows()
1070+
# currently this doesn't work on windows
1071+
@test Base.UV_EBUSY == rename_errorcodes(f1, f2)
1072+
else
1073+
Base.rename(f1, f2)
1074+
@test !ispath(f1)
1075+
@test read(f2) == b"newdata"
1076+
end
1077+
# rename doesn't break already opened files
1078+
@test read(handle1) == b"newdata"
1079+
end
1080+
rm(f1; force=true)
1081+
rm(f2; force=true)
1082+
1083+
# newpath open
1084+
write(f2, b"olddata")
1085+
write(f1, b"newdata")
1086+
open(f2) do handle2
1087+
if Sys.iswindows()
1088+
# currently this doesn't work on windows
1089+
@test Base.UV_EACCES == rename_errorcodes(f1, f2)
1090+
else
1091+
Base.rename(f1, f2)
1092+
@test !ispath(f1)
1093+
@test read(f2) == b"newdata"
1094+
end
1095+
# rename doesn't break already opened files
1096+
@test read(handle2) == b"olddata"
1097+
end
1098+
rm(f1; force=true)
1099+
rm(f2; force=true)
1100+
end
1101+
1102+
@testset "replacing empty directory with directory" begin
1103+
mkdir(d1)
1104+
mkdir(d2)
1105+
write(subd1f1, b"data")
1106+
if Sys.iswindows()
1107+
# currently this doesn't work on windows
1108+
@test Base.UV_EACCES == rename_errorcodes(d1, d2)
1109+
rm(d1; recursive=true)
1110+
rm(d2)
1111+
else
1112+
Base.rename(d1, d2)
1113+
@test isdir(d2)
1114+
@test read(subd2f1) == b"data"
1115+
@test !ispath(d1)
1116+
rm(d2; recursive=true)
1117+
end
1118+
end
1119+
@test isempty(readdir(dir)) # make sure everything got cleaned up
1120+
end
1121+
end
1122+
8261123
# issue #10506 #10434
8271124
## Tests for directories and links to directories
8281125
if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER
@@ -1472,7 +1769,7 @@ rm(dir)
14721769

14731770

14741771
##################
1475-
# Return values of mkpath, mkdir, cp, mv and touch
1772+
# Return values of mkpath, mkdir, cp, mv, rename and touch
14761773
####################
14771774
mktempdir() do dir
14781775
name1 = joinpath(dir, "apples")
@@ -1489,6 +1786,9 @@ mktempdir() do dir
14891786
@test cp(name2, name1) == name1
14901787
@test isfile(name1)
14911788
@test isfile(name2)
1789+
@test Base.rename(name1, name2) == name2
1790+
@test !ispath(name1)
1791+
@test isfile(name2)
14921792
namedir = joinpath(dir, "chalk")
14931793
namepath = joinpath(dir, "chalk", "cheese", "fresh")
14941794
@test !ispath(namedir)

0 commit comments

Comments
 (0)