diff -r acf6ffc57fcf Doc/library/os.rst --- a/Doc/library/os.rst Sat Mar 09 22:21:32 2013 +0200 +++ b/Doc/library/os.rst Sun Mar 10 22:15:52 2013 -0400 @@ -1720,18 +1720,34 @@ .. function:: rename(src, dst, *, src_dir_fd=None, dst_dir_fd=None) - Rename the file or directory *src* to *dst*. If *dst* is a directory, - :exc:`OSError` will be raised. On Unix, if *dst* exists and is a file, it will - be replaced silently if the user has permission. The operation may fail on some - Unix flavors if *src* and *dst* are on different filesystems. If successful, - the renaming will be an atomic operation (this is a POSIX requirement). On - Windows, if *dst* already exists, :exc:`OSError` will be raised even if it is a - file. + Rename the file or directory *src* to *dst*. If *src* exists as either + a file or directory and *dst* does not exist the rename will occur with + no error raised. In some cases the rename function will behave + differently across platforms which are noted below. In all cases + if *src* does not exist :exc:`OSError` will be raised. + + =================== ================= ===================== ================ + Source Destination + ------------------- -------------------------------------------------------- + Type File Empty Directory Non-Empty + Directory + =================== ================= ===================== ================ + File | unix success OSError OSError + | win OSError + Empty Directory OSError | unix success OSError + | win FileExistsError + Non-Empty Directory OSError | unix success OSError + | win FileExistsError + =================== ================= ===================== ================ + + If successful, the renaming will be an atomic operation (this is a POSIX + requirement). This function can support specifying *src_dir_fd* and/or *dst_dir_fd* to supply :ref:`paths relative to directory descriptors `. - If you want cross-platform overwriting of the destination, use :func:`replace`. + If you want cross-platform overwriting of the destination, use + :func:`replace`. Availability: Unix, Windows. diff -r acf6ffc57fcf Lib/test/test_os.py --- a/Lib/test/test_os.py Sat Mar 09 22:21:32 2013 +0200 +++ b/Lib/test/test_os.py Sun Mar 10 22:15:52 2013 -0400 @@ -52,6 +52,7 @@ # Issue #14110: Some tests fail on FreeBSD if the user is in the wheel group. HAVE_WHEEL_GROUP = sys.platform.startswith('freebsd') and os.getgid() == 0 + # Tests creating TESTFN class FileTests(unittest.TestCase): def setUp(self): @@ -2204,6 +2205,211 @@ else: self.fail("No exception thrown by {}".format(func)) +# Tests os.rename +# do we use support.TESTFN? If not maybe we should not use it. +class RenameTests(unittest.TestCase): + def setUp(self): + if os.path.exists(support.TESTFN): + os.unlink(support.TESTFN) + + self.pid = os.getpid() # get the process id of the test case + # create rename_src_file and place data in the file + self.src_file = "%s_%d_tmp" % ("test_rename_src_file", + self.pid) + self.remove_file_or_directory(self.src_file) + fd = os.open(self.src_file, os.O_CREAT | os.O_WRONLY) + os.write(fd, b"beans\nbacon\neggs\nspam\n") + os.close(fd) + + # create rename_dst_file and place data in the file + self.dst_file = "%s_%d_tmp" % ("test_rename_dst_file", + self.pid) + self.remove_file_or_directory(self.dst_file) + fd = os.open(self.dst_file, os.O_CREAT | os.O_WRONLY) + os.write(fd, b"dst file already exists and has data") + os.close(fd) + + # create rename_dst_directory + self.dst_directory = "%s_%d_tmp" % ( + "test_rename_dst_directory", self.pid) + self.dst_directory_file = os.path.join(self.dst_directory, + "file.txt") + self.remove_file_or_directory(self.dst_directory) + self.remove_file_or_directory(self.dst_directory_file) + os.mkdir(self.dst_directory) + fd = os.open(self.dst_directory_file, + os.O_CREAT | os.O_WRONLY) + os.write(fd, b"dst file already exists and has data") + os.close(fd) + + # create rename_src_directory + self.src_directory = "%s_%d_tmp" % ( + "test_src_directory", self.pid) + self.src_directory_file = os.path.join(self.src_directory, + "file.txt") + self.remove_file_or_directory(self.src_directory) + self.remove_file_or_directory(self.src_directory_file) + os.mkdir(self.src_directory) + fd = os.open(self.src_directory_file, + os.O_CREAT | os.O_WRONLY) + os.write(fd, b"beans\nbacon\neggs\nspam\n") + os.close(fd) + + def tearDown(self): + files = [support.TESTFN, self.src_file, self.dst_file, + self.dst_directory_file, self.dst_directory, + self.src_directory_file, self.src_directory] + for file in files: + self.remove_file_or_directory(file) + + def remove_file_or_directory(self, file): + if os.path.exists(file): + if os.path.isfile(file): + os.unlink(file) + elif os.path.isdir(file): + os.rmdir(file) + + def test_rename_src_file_dest_file_exist(self): + if sys.platform == 'darwin' or sys.platform.startswith == "linux": + os.rename(self.src_file, self.dst_file) + with open(self.dst_file, "rb") as fobj: + self.assertEqual(fobj.read(), + b"beans\nbacon\neggs\nspam\n") + elif sys.platform == 'win': + self.assertRaises(OSError, os.rename, self.src_file, self.dst_file) + with open(self.dst_file, "rb") as fobj: + self.assertEqual(fobj.read(), + b"dst file already exists and has data") + + def test_rename_src_file_dst_not_exist(self): + self.remove_file_or_directory(self.dst_file) + os.rename(self.src_file, self.dst_file) + with open(self.dst_file, "rb") as fobj: + self.assertEqual(fobj.read(), + b"beans\nbacon\neggs\nspam\n") + + def test_rename_src_file_dst_directory_empty(self): + self.remove_file_or_directory(self.dst_directory_file) + self.assertRaises(OSError, os.rename, self.src_file, + self.dst_directory) + + def test_rename_src_file_dst_directory_not_empty(self): + self.assertRaises(OSError, os.rename, self.src_file, + self.dst_directory) + with open(self.dst_directory_file, "rb") as fobj: + self.assertEqual(fobj.read(), + b"dst file already exists and has data") + + def test_rename_src_directory_empty_dst_file_exist(self): + self.remove_file_or_directory(self.src_directory_file) + self.assertRaises(OSError, os.rename, self.src_directory, + self.dst_file) + with open(self.dst_file, "rb") as fobj: + self.assertEqual(fobj.read(), + b"dst file already exists and has data") + + def test_rename_src_directory_empty_dst_not_exist(self): + self.remove_file_or_directory(self.src_directory_file) + self.remove_file_or_directory(self.dst_directory_file) + self.remove_file_or_directory(self.dst_directory) + os.rename(self.src_directory, self.dst_directory) + self.assertTrue(os.path.exists(self.dst_directory)) + self.assertFalse(os.path.exists(self.src_directory)) + + def test_rename_src_directory_empty_dst_directory_empty(self): + self.remove_file_or_directory(self.src_directory_file) + self.remove_file_or_directory(self.dst_directory_file) + if sys.platform.startswith == 'linux' or sys.platform == 'darwin': + os.rename(self.src_directory, self.dst_directory) + self.assertTrue(os.path.exists(self.dst_directory)) + self.assertFalse(os.path.exists(self.src_directory)) + elif (sys.platform == 'win32'): + self.assertRaises(FileExistsError, os.rename, + self.src_directory, self.dst_directory) + # both src and dst will still be intact on windows + self.assertTrue(os.path.exists(self.dst_directory)) + self.assertTrue(os.path.exists(self.src_directory)) + + def test_rename_src_directory_empty_dst_directory_not_empty(self): + self.remove_file_or_directory(self.src_directory_file) + self.assertRaises(OSError, os.rename, self.src_directory, + self.dst_directory) + # destination file should be in tact + with open(self.dst_directory_file, "rb") as fobj: + self.assertEqual(fobj.read(), + b"dst file already exists and has data") + + def test_rename_src_directory_not_empty_dst_file_exist(self): + self.assertRaises(OSError, os.rename, self.src_directory, + self.dst_file) + with open(self.dst_file, "rb") as fobj: + self.assertEqual(fobj.read(), + b"dst file already exists and has data") + + def test_rename_src_directory_not_empty_dst_not_exist(self): + # remove the dst directory and file + self.remove_file_or_directory(self.dst_directory_file) + self.remove_file_or_directory(self.dst_directory) + os.rename(self.src_directory, self.dst_directory) + with open(self.dst_directory_file, "rb") as fobj: + self.assertEqual(fobj.read(), + b"beans\nbacon\neggs\nspam\n") + + def test_rename_src_directory_not_empty_dst_directory_empty(self): + self.remove_file_or_directory(self.dst_directory_file) + if sys.platform.startswith == 'linux' or sys.platform == 'darwin': + os.rename(self.src_directory, self.dst_directory) + with open(self.dst_directory_file, "rb") as fobj: + self.assertEqual(fobj.read(), + b"beans\nbacon\neggs\nspam\n") + elif sys.platform == 'win32': + self.assertRaises(FileExistsError, os.rename, + self.src_directory, self.dst_directory) + # both src and dst will still be intact on windows + self.assertTrue(os.path.exists(self.dst_directory)) + self.assertTrue(os.path.exists(self.src_directory)) + + def test_rename_src_directory_not_empty_dst_directory_not_empty(self): + self.assertRaises(OSError, os.rename, self.src_directory, + self.dst_directory) + # destination file should be in tact + with open(self.dst_directory_file, "rb") as fobj: + self.assertEqual(fobj.read(), + b"dst file already exists and has data") + + def test_rename_src_file_or_directory_not_exist_dst_file_exist(self): + self.remove_file_or_directory(self.src_file) + self.assertRaises(OSError, os.rename, self.src_file, + self.dst_file) + # destination file should be in tact + with open(self.dst_file, "rb") as fobj: + self.assertEqual(fobj.read(), + b"dst file already exists and has data") + + def test_rename_src_file_or_directory_not_exist_dst_not_exist(self): + self.remove_file_or_directory(self.src_file) + self.remove_file_or_directory(self.dst_file) + self.assertRaises(OSError, os.rename, self.src_file, + self.dst_file) + + def test_rename_src_file_or_directory_not_exist_dst_directory_empty(self): + self.remove_file_or_directory(self.src_file) + self.remove_file_or_directory(self.dst_directory_file) + self.assertRaises(OSError, os.rename, self.src_file, + self.dst_directory) + self.assertTrue(os.path.exists(self.dst_directory)) + + def test_rename_src_file_or_directory_not_exist_dst_directory_not_empty(self): + self.remove_file_or_directory(self.src_file) + self.assertRaises(OSError, os.rename, self.src_file, + self.dst_directory) + self.assertTrue(os.path.exists(self.dst_directory)) + # destination file should be in tact + with open(self.dst_directory_file, "rb") as fobj: + self.assertEqual(fobj.read(), + b"dst file already exists and has data") + + @support.reap_threads def test_main(): support.run_unittest( @@ -2234,6 +2440,7 @@ TermsizeTests, OSErrorTests, RemoveDirsTests, + RenameTests, ) if __name__ == "__main__":