Shorten RemoveDirectory and rename to rmtree. Remove rmtree from fake_repos.
Keep an alias to RemoveDirectory, will be removed in a later change.
TEST=new unit test to test this function
BUG=none
Review URL: http://codereview.chromium.org/6628032
git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@77463 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/gclient_utils.py b/gclient_utils.py
index 7182848..8e2d1c3 100644
--- a/gclient_utils.py
+++ b/gclient_utils.py
@@ -182,10 +182,10 @@
f.close()
-def RemoveDirectory(*path):
- """Recursively removes a directory, even if it's marked read-only.
+def rmtree(path):
+ """shutil.rmtree() on steroids.
- Remove the directory located at *path, if it exists.
+ Recursively removes a directory, even if it's marked read-only.
shutil.rmtree() doesn't work on Windows if any of the files or directories
are read-only, which svn repositories and some .svn files are. We need to
@@ -208,63 +208,55 @@
In the ordinary case, this is not a problem: for our purposes, the user
will never lack write permission on *path's parent.
"""
- logging.debug(path)
- file_path = os.path.join(*path)
- if not os.path.exists(file_path):
+ if not os.path.exists(path):
return
- if os.path.islink(file_path) or not os.path.isdir(file_path):
- raise Error('RemoveDirectory asked to remove non-directory %s' % file_path)
+ if os.path.islink(path) or not os.path.isdir(path):
+ raise Error('Called rmtree(%s) in non-directory' % path)
- has_win32api = False
if sys.platform == 'win32':
- has_win32api = True
# Some people don't have the APIs installed. In that case we'll do without.
try:
win32api = __import__('win32api')
win32con = __import__('win32con')
except ImportError:
- has_win32api = False
+ pass
else:
# On POSIX systems, we need the x-bit set on the directory to access it,
# the r-bit to see its contents, and the w-bit to remove files from it.
# The actual modes of the files within the directory is irrelevant.
- os.chmod(file_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
- for fn in os.listdir(file_path):
- fullpath = os.path.join(file_path, fn)
+ os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
+ def remove(func, subpath):
+ if sys.platform == 'win32':
+ os.chmod(subpath, stat.S_IWRITE)
+ if win32api and win32con:
+ win32api.SetFileAttributes(subpath, win32con.FILE_ATTRIBUTE_NORMAL)
+ try:
+ func(subpath)
+ except OSError, e:
+ if e.errno != errno.EACCES or sys.platform != 'win32':
+ raise
+ # Failed to delete, try again after a 100ms sleep.
+ time.sleep(0.1)
+ func(subpath)
+
+ for fn in os.listdir(path):
# If fullpath is a symbolic link that points to a directory, isdir will
# be True, but we don't want to descend into that as a directory, we just
# want to remove the link. Check islink and treat links as ordinary files
# would be treated regardless of what they reference.
+ fullpath = os.path.join(path, fn)
if os.path.islink(fullpath) or not os.path.isdir(fullpath):
- if sys.platform == 'win32':
- os.chmod(fullpath, stat.S_IWRITE)
- if has_win32api:
- win32api.SetFileAttributes(fullpath, win32con.FILE_ATTRIBUTE_NORMAL)
- try:
- os.remove(fullpath)
- except OSError, e:
- if e.errno != errno.EACCES or sys.platform != 'win32':
- raise
- print 'Failed to delete %s: trying again' % fullpath
- time.sleep(0.1)
- os.remove(fullpath)
+ remove(os.remove, fullpath)
else:
- RemoveDirectory(fullpath)
+ # Recurse.
+ rmtree(fullpath)
- if sys.platform == 'win32':
- os.chmod(file_path, stat.S_IWRITE)
- if has_win32api:
- win32api.SetFileAttributes(file_path, win32con.FILE_ATTRIBUTE_NORMAL)
- try:
- os.rmdir(file_path)
- except OSError, e:
- if e.errno != errno.EACCES or sys.platform != 'win32':
- raise
- print 'Failed to remove %s: trying again' % file_path
- time.sleep(0.1)
- os.rmdir(file_path)
+ remove(os.rmdir, path)
+
+# TODO(maruel): Rename the references.
+RemoveDirectory = rmtree
def CheckCallAndFilterAndHeader(args, always=False, **kwargs):
diff --git a/tests/fake_repos.py b/tests/fake_repos.py
index 4998575..5f8c54e 100755
--- a/tests/fake_repos.py
+++ b/tests/fake_repos.py
@@ -7,20 +7,18 @@
import atexit
import datetime
-import errno
import logging
import os
import pprint
import re
import socket
-import stat
import subprocess
import sys
import tempfile
-import time
# trial_dir must be first for non-system libraries.
from tests import trial_dir
+import gclient_utils
import scm
## Utility functions
@@ -66,71 +64,6 @@
subprocess.Popen.kill = kill_nix
-def rmtree(*path):
- """Recursively removes a directory, even if it's marked read-only.
-
- Remove the directory located at *path, if it exists.
-
- shutil.rmtree() doesn't work on Windows if any of the files or directories
- are read-only, which svn repositories and some .svn files are. We need to
- be able to force the files to be writable (i.e., deletable) as we traverse
- the tree.
-
- Even with all this, Windows still sometimes fails to delete a file, citing
- a permission error (maybe something to do with antivirus scans or disk
- indexing). The best suggestion any of the user forums had was to wait a
- bit and try again, so we do that too. It's hand-waving, but sometimes it
- works. :/
- """
- file_path = os.path.join(*path)
- if not os.path.exists(file_path):
- return
-
- def RemoveWithRetry_win(rmfunc, path):
- os.chmod(path, stat.S_IWRITE)
- if win32_api_avail:
- win32api.SetFileAttributes(path, win32con.FILE_ATTRIBUTE_NORMAL)
- try:
- return rmfunc(path)
- except EnvironmentError, e:
- if e.errno != errno.EACCES:
- raise
- print 'Failed to delete %s: trying again' % repr(path)
- time.sleep(0.1)
- return rmfunc(path)
-
- def RemoveWithRetry_non_win(rmfunc, path):
- if os.path.islink(path):
- return os.remove(path)
- else:
- return rmfunc(path)
-
- win32_api_avail = False
- remove_with_retry = None
- if sys.platform.startswith('win'):
- # Some people don't have the APIs installed. In that case we'll do without.
- try:
- win32api = __import__('win32api')
- win32con = __import__('win32con')
- win32_api_avail = True
- except ImportError:
- pass
- remove_with_retry = RemoveWithRetry_win
- else:
- remove_with_retry = RemoveWithRetry_non_win
-
- for root, dirs, files in os.walk(file_path, topdown=False):
- # For POSIX: making the directory writable guarantees removability.
- # Windows will ignore the non-read-only bits in the chmod value.
- os.chmod(root, 0770)
- for name in files:
- remove_with_retry(os.remove, os.path.join(root, name))
- for name in dirs:
- remove_with_retry(os.rmdir, os.path.join(root, name))
-
- remove_with_retry(os.rmdir, file_path)
-
-
def write(path, content):
f = open(path, 'wb')
f.write(content)
@@ -309,9 +242,9 @@
self.svnserve = None
if not self.trial.SHOULD_LEAK:
logging.debug('Removing %s' % self.svn_repo)
- rmtree(self.svn_repo)
+ gclient_utils.rmtree(self.svn_repo)
logging.debug('Removing %s' % self.svn_checkout)
- rmtree(self.svn_checkout)
+ gclient_utils.rmtree(self.svn_checkout)
else:
return False
return True
@@ -330,7 +263,7 @@
self.wait_for_port_to_free(self.git_port)
if not self.trial.SHOULD_LEAK:
logging.debug('Removing %s' % self.git_root)
- rmtree(self.git_root)
+ gclient_utils.rmtree(self.git_root)
else:
return False
return True
diff --git a/tests/gclient_utils_test.py b/tests/gclient_utils_test.py
index 4b9ebf2..8027675 100755
--- a/tests/gclient_utils_test.py
+++ b/tests/gclient_utils_test.py
@@ -9,7 +9,8 @@
# Fixes include path.
from super_mox import SuperMoxTestBase
-
+import trial_dir
+import os
import gclient_utils
@@ -32,8 +33,8 @@
'ParseXML', 'Popen',
'PrintableObject', 'RemoveDirectory', 'SoftClone', 'SplitUrlRevision',
'SyntaxErrorToError', 'WorkItem',
- 'errno', 'hack_subprocess', 'logging', 'os', 'Queue', 're', 'stat',
- 'subprocess', 'sys','threading', 'time', 'xml',
+ 'errno', 'hack_subprocess', 'logging', 'os', 'Queue', 're', 'rmtree',
+ 'stat', 'subprocess', 'sys','threading', 'time', 'xml',
]
# If this test fails, you should add the relevant test.
self.compareMembers(gclient_utils, members)
@@ -181,6 +182,24 @@
self.assertEquals(out_url, url)
+class GClientUtilsTest(trial_dir.TestCase):
+ def testHardToDelete(self):
+ # Use the fact that tearDown will delete the directory to make it hard to do
+ # so.
+ l1 = os.path.join(self.root_dir, 'l1')
+ l2 = os.path.join(l1, 'l2')
+ l3 = os.path.join(l2, 'l3')
+ f3 = os.path.join(l3, 'f3')
+ os.mkdir(l1)
+ os.mkdir(l2)
+ os.mkdir(l3)
+ gclient_utils.FileWrite(f3, 'foo')
+ os.chmod(f3, 0)
+ os.chmod(l3, 0)
+ os.chmod(l2, 0)
+ os.chmod(l1, 0)
+
+
if __name__ == '__main__':
import unittest
unittest.main()
diff --git a/tests/scm_unittest.py b/tests/scm_unittest.py
index 31d0a7c..5b21c6f 100755
--- a/tests/scm_unittest.py
+++ b/tests/scm_unittest.py
@@ -276,7 +276,7 @@
self._capture(
['propset', 'foo', 'bar',
scm.os.path.join(self.svn_root, 'prout', 'origin')])
- fake_repos.rmtree(scm.os.path.join(self.svn_root, 'prout'))
+ fake_repos.gclient_utils.rmtree(scm.os.path.join(self.svn_root, 'prout'))
with open(scm.os.path.join(self.svn_root, 'faa'), 'w') as f:
f.write('eh')
with open(scm.os.path.join(self.svn_root, 'faala'), 'w') as f: