From 561fdca3a2d599bf9c8083af19e67c69573b36b2 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 20 Aug 2009 17:37:06 +0200 Subject: [PATCH] move localpath implementation to a single file, simplify unix/posix difference and fix a bit --HG-- branch : trunk --- bin-for-dist/all-plat.sh | 4 +- doc/changelog.txt | 5 + py/__init__.py | 2 +- py/path/{local => }/local.py | 135 ++++++++++-- py/path/local/__init__.py | 1 - py/path/local/common.py | 25 --- py/path/local/posix.py | 129 ----------- py/path/local/testing/__init__.py | 1 - py/path/local/testing/test_posix.py | 190 ----------------- py/path/local/testing/test_win.py | 56 ----- py/path/local/win.py | 41 ---- py/path/testing/fscommon.py | 10 +- py/path/{local => }/testing/test_local.py | 249 +++++++++++++++++++++- 13 files changed, 378 insertions(+), 470 deletions(-) mode change 100644 => 100755 bin-for-dist/all-plat.sh rename py/path/{local => }/local.py (85%) delete mode 100644 py/path/local/__init__.py delete mode 100644 py/path/local/common.py delete mode 100644 py/path/local/posix.py delete mode 100644 py/path/local/testing/__init__.py delete mode 100644 py/path/local/testing/test_posix.py delete mode 100644 py/path/local/testing/test_win.py delete mode 100644 py/path/local/win.py rename py/path/{local => }/testing/test_local.py (58%) diff --git a/bin-for-dist/all-plat.sh b/bin-for-dist/all-plat.sh old mode 100644 new mode 100755 index db7b454ce..7916bf61f --- a/bin-for-dist/all-plat.sh +++ b/bin-for-dist/all-plat.sh @@ -1,6 +1,6 @@ py.test --dist=each $* \ - --tx 'popen//python=python2.6' \ - --tx 'ssh=noco//python=/usr/local/bin/python2.4//chdir=/tmp/pytest-python2.4' \ --tx 'socket=192.168.1.106:8888' + #--tx 'popen//python=python2.6' \ + #--tx 'ssh=noco//python=/usr/local/bin/python2.4//chdir=/tmp/pytest-python2.4' \ diff --git a/doc/changelog.txt b/doc/changelog.txt index 33866d2bd..815444d16 100644 --- a/doc/changelog.txt +++ b/doc/changelog.txt @@ -1,3 +1,8 @@ +Changes between 1.0.x and 'trunk' +===================================== + +* simplified internal localpath implementation + Changes between 1.0.0 and 1.0.1 ===================================== diff --git a/py/__init__.py b/py/__init__.py index 040b735b6..e09aeb696 100644 --- a/py/__init__.py +++ b/py/__init__.py @@ -106,7 +106,7 @@ initpkg(__name__, 'path.__doc__' : ('./path/__init__.py', '__doc__'), 'path.svnwc' : ('./path/svn/wccommand.py', 'SvnWCCommandPath'), 'path.svnurl' : ('./path/svn/urlcommand.py', 'SvnCommandPath'), - 'path.local' : ('./path/local/local.py', 'LocalPath'), + 'path.local' : ('./path/local.py', 'LocalPath'), 'path.SvnAuth' : ('./path/svn/svncommon.py', 'SvnAuth'), # some nice slightly magic APIs diff --git a/py/path/local/local.py b/py/path/local.py similarity index 85% rename from py/path/local/local.py rename to py/path/local.py index 4c432d804..92bfdd957 100644 --- a/py/path/local/local.py +++ b/py/path/local.py @@ -1,9 +1,5 @@ """ -specialized local path implementation. - -This Path implementation offers some methods like chmod(), owner() -and so on that may only make sense on unix. - +local path implementation. """ from __future__ import generators import sys, os, stat, re, atexit @@ -12,17 +8,93 @@ from py.__.path import common iswin32 = sys.platform == "win32" -if iswin32: - from py.__.path.local.win import WinMixin as PlatformMixin -else: - from py.__.path.local.posix import PosixMixin as PlatformMixin +class Stat(object): + pformat = "%s = property(lambda x: getattr(x._osstatresult, 'st_%s'))" -class LocalPath(common.FSPathBase, PlatformMixin): - """ Local path implementation offering access/modification - methods similar to os.path. + for name in ('atime blksize blocks ctime dev gid ' + 'ino mode mtime nlink rdev size uid'.split()): + code = pformat.replace("%s", name) + exec code + + def __init__(self, path, osstatresult): + self.path = path + self._osstatresult = osstatresult + + def owner(self): + if iswin32: + raise NotImplementedError("XXX win32") + import pwd + entry = self.path._callex(pwd.getpwuid, self.uid) + return entry[0] + owner = property(owner, None, None, "owner of path") + + def group(self): + """ return group name of file. """ + if iswin32: + raise NotImplementedError("XXX win32") + import grp + entry = self.path._callex(grp.getgrgid, self.gid) + return entry[0] + group = property(group) + +class PosixPath(common.FSPathBase): + def chown(self, user, group, rec=0): + """ change ownership to the given user and group. + user and group may be specified by a number or + by a name. if rec is True change ownership + recursively. + """ + uid = getuserid(user) + gid = getgroupid(group) + if rec: + for x in self.visit(rec=lambda x: x.check(link=0)): + if x.check(link=0): + self._callex(os.chown, str(x), uid, gid) + self._callex(os.chown, str(self), uid, gid) + + def readlink(self): + """ return value of a symbolic link. """ + return self._callex(os.readlink, self.strpath) + + def mklinkto(self, oldname): + """ posix style hard link to another name. """ + self._callex(os.link, str(oldname), str(self)) + + def mksymlinkto(self, value, absolute=1): + """ create a symbolic link with the given value (pointing to another name). """ + if absolute: + self._callex(os.symlink, str(value), self.strpath) + else: + base = self.common(value) + # with posix local paths '/' is always a common base + relsource = self.__class__(value).relto(base) + reldest = self.relto(base) + n = reldest.count(self.sep) + target = self.sep.join(('..', )*n + (relsource, )) + self._callex(os.symlink, target, self.strpath) + + def samefile(self, other): + """ return True if other refers to the same stat object as self. """ + return py.std.os.path.samefile(str(self), str(other)) + +def getuserid(user): + import pwd + if not isinstance(user, int): + user = pwd.getpwnam(user)[2] + return user + +def getgroupid(group): + import grp + if not isinstance(group, int): + group = grp.getgrnam(group)[2] + return group + +FSBase = not iswin32 and PosixPath or common.FSPathBase + +class LocalPath(FSBase): + """ object oriented interface to os.path and other local filesystem + related information. """ - _path_cache = {} - sep = os.sep class Checkers(common.FSCheckers): def _stat(self): @@ -76,6 +148,21 @@ class LocalPath(common.FSPathBase, PlatformMixin): def __hash__(self): return hash(self.strpath) + def remove(self, rec=1): + """ remove a file or directory (or a directory tree if rec=1). """ + if self.check(dir=1, link=0): + if rec: + # force remove of readonly files on windows + if iswin32: + self.chmod(0700, rec=1) + self._callex(py.std.shutil.rmtree, self.strpath) + else: + self._callex(os.rmdir, self.strpath) + else: + if iswin32: + self.chmod(0700) + self._callex(os.remove, self.strpath) + def computehash(self, hashtype="md5", chunksize=524288): """ return hexdigest of hashvalue for this file. """ hash = self._gethashinstance(hashtype) @@ -311,14 +398,12 @@ class LocalPath(common.FSPathBase, PlatformMixin): def stat(self): """ Return an os.stat() tuple. """ - stat = self._callex(os.stat, self.strpath) - return self._makestat(stat) + return Stat(self, self._callex(os.stat, self.strpath)) def lstat(self): """ Return an os.lstat() tuple. """ - return self._makestat(self._callex(os.lstat, self.strpath)) + return Stat(self, self._callex(os.lstat, self.strpath)) - # xlocal implementation def setmtime(self, mtime=None): """ set modification time for the given path. if 'mtime' is None (the default) then the file's mtime is set to current time. @@ -379,6 +464,18 @@ class LocalPath(common.FSPathBase, PlatformMixin): #print "prepending to sys.path", s sys.path.insert(0, s) + def chmod(self, mode, rec=0): + """ change permissions to the given mode. If mode is an + integer it directly encodes the os-specific modes. + if rec is True perform recursively. + """ + if not isinstance(mode, int): + raise TypeError("mode %r must be an integer" % (mode,)) + if rec: + for x in self.visit(rec=rec): + self._callex(os.chmod, str(x), mode) + self._callex(os.chmod, str(self), mode) + def pyimport(self, modname=None, ensuresyspath=True): """ return path as an imported python module. if modname is None, look for the containing package @@ -438,7 +535,7 @@ class LocalPath(common.FSPathBase, PlatformMixin): def _getpycodeobj(self): """ read the path and compile it to a code object. """ - dotpy = self.check(ext='.py') + dotpy = self.ext == ".py" if dotpy: my_magic = py.std.imp.get_magic() my_timestamp = int(self.mtime()) diff --git a/py/path/local/__init__.py b/py/path/local/__init__.py deleted file mode 100644 index 792d60054..000000000 --- a/py/path/local/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# diff --git a/py/path/local/common.py b/py/path/local/common.py deleted file mode 100644 index 72f04dc3c..000000000 --- a/py/path/local/common.py +++ /dev/null @@ -1,25 +0,0 @@ -import py - -class Stat(object): - def __init__(self, path, osstatresult): - self.path = path - self._osstatresult = osstatresult - - for name in ('atime blksize blocks ctime dev gid ' - 'ino mode mtime nlink rdev size uid'.split()): - - code = """if 1: - def fget(self): - return getattr(self._osstatresult, "st_%(name)s", None) - %(name)s = property(fget) - def fget_deprecated(self): - py.std.warnings.warn("statresult.st_%(name)s is deprecated, use " - "statresult.%(name)s instead.", - DeprecationWarning, stacklevel=2) - return getattr(self._osstatresult, "st_%(name)s", None) - st_%(name)s = property(fget_deprecated) -""" % locals() - exec code - del fget - del fget_deprecated - diff --git a/py/path/local/posix.py b/py/path/local/posix.py deleted file mode 100644 index 86817658a..000000000 --- a/py/path/local/posix.py +++ /dev/null @@ -1,129 +0,0 @@ -""" -module to access local filesystem pathes -(mostly filename manipulations but also file operations) -""" -import os, sys, stat - -import py -#__________________________________________________________ -# -# Local Path Posix Mixin -#__________________________________________________________ - -from py.__.path.local.common import Stat - -class PosixStat(Stat): - def owner(self): - entry = self.path._callex(py.std.pwd.getpwuid, self.uid) - return entry[0] - owner = property(owner, None, None, "owner of path") - - def group(self): - """ return group name of file. """ - entry = self.path._callex(py.std.grp.getgrgid, self.gid) - return entry[0] - group = property(group) - -class PosixMixin(object): - def _makestat(self, statresult): - return PosixStat(self, statresult) - - def _deprecated(self, name): - py.std.warnings.warn("'path.%s()' is deprecated, use " - "'path.stat().%s' instead." % (name,name), - DeprecationWarning, stacklevel=3) - - # an instance needs to be a local path instance - def owner(self): - """ return owner name of file. """ - self._deprecated('owner') - return self.stat().owner - - def group(self): - """ return group name of file. """ - self._deprecated('group') - return self.stat().group - - def mode(self): - """ return permission mode of the path object """ - self._deprecated('mode') - return self.stat().mode - - def chmod(self, mode, rec=0): - """ change permissions to the given mode. If mode is an - integer it directly encodes the os-specific modes. - if rec is True perform recursively. - - (xxx if mode is a string then it specifies access rights - in '/bin/chmod' style, e.g. a+r). - """ - if not isinstance(mode, int): - raise TypeError("mode %r must be an integer" % (mode,)) - if rec: - for x in self.visit(rec=rec): - self._callex(os.chmod, str(x), mode) - self._callex(os.chmod, str(self), mode) - - def chown(self, user, group, rec=0): - """ change ownership to the given user and group. - user and group may be specified by a number or - by a name. if rec is True change ownership - recursively. - """ - uid = getuserid(user) - gid = getgroupid(group) - if rec: - for x in self.visit(rec=lambda x: x.check(link=0)): - if x.check(link=0): - self._callex(os.chown, str(x), uid, gid) - self._callex(os.chown, str(self), uid, gid) - - def readlink(self): - """ return value of a symbolic link. """ - return self._callex(os.readlink, self.strpath) - - def mklinkto(self, oldname): - """ posix style hard link to another name. """ - self._callex(os.link, str(oldname), str(self)) - - def mksymlinkto(self, value, absolute=1): - """ create a symbolic link with the given value (pointing to another name). """ - if absolute: - self._callex(os.symlink, str(value), self.strpath) - else: - base = self.common(value) - # with posix local paths '/' is always a common base - relsource = self.__class__(value).relto(base) - reldest = self.relto(base) - n = reldest.count(self.sep) - target = self.sep.join(('..', )*n + (relsource, )) - self._callex(os.symlink, target, self.strpath) - - - def remove(self, rec=1): - """ remove a file or directory (or a directory tree if rec=1). """ - if self.check(dir=1, link=0): - if rec: - self._callex(py.std.shutil.rmtree, self.strpath) - else: - self._callex(os.rmdir, self.strpath) - else: - self._callex(os.remove, self.strpath) - - def samefile(self, other): - """ return True if other refers to the same stat object as self. """ - return py.std.os.path.samefile(str(self), str(other)) - -def getuserid(user): - import pwd - if isinstance(user, int): - return user - entry = pwd.getpwnam(user) - return entry[2] - -def getgroupid(group): - import grp - if isinstance(group, int): - return group - entry = grp.getgrnam(group) - return entry[2] diff --git a/py/path/local/testing/__init__.py b/py/path/local/testing/__init__.py deleted file mode 100644 index 792d60054..000000000 --- a/py/path/local/testing/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# diff --git a/py/path/local/testing/test_posix.py b/py/path/local/testing/test_posix.py deleted file mode 100644 index d393fb98b..000000000 --- a/py/path/local/testing/test_posix.py +++ /dev/null @@ -1,190 +0,0 @@ -import py - -class TestPOSIXLocalPath: - disabled = py.std.sys.platform == 'win32' - - def setup_class(cls): - cls.root = py.test.ensuretemp(cls.__name__) - - def setup_method(self, method): - name = method.im_func.func_name - self.tmpdir = self.root.ensure(name, dir=1) - - def test_samefile(self): - assert self.tmpdir.samefile(self.tmpdir) - p = self.tmpdir.ensure("hello") - assert p.samefile(p) - - def test_hardlink(self): - tmpdir = self.tmpdir - linkpath = tmpdir.join('test') - filepath = tmpdir.join('file') - filepath.write("Hello") - nlink = filepath.stat().st_nlink - linkpath.mklinkto(filepath) - assert filepath.stat().st_nlink == nlink + 1 - - def test_symlink_are_identical(self): - tmpdir = self.tmpdir - filepath = tmpdir.join('file') - filepath.write("Hello") - linkpath = tmpdir.join('test') - linkpath.mksymlinkto(filepath) - assert linkpath.readlink() == str(filepath) - - def test_symlink_isfile(self): - tmpdir = self.tmpdir - linkpath = tmpdir.join('test') - filepath = tmpdir.join('file') - filepath.write("") - linkpath.mksymlinkto(filepath) - assert linkpath.check(file=1) - assert not linkpath.check(link=0, file=1) - - def test_symlink_relative(self): - tmpdir = self.tmpdir - linkpath = tmpdir.join('test') - filepath = tmpdir.join('file') - filepath.write("Hello") - linkpath.mksymlinkto(filepath, absolute=False) - assert linkpath.readlink() == "file" - assert filepath.read() == linkpath.read() - - def test_symlink_not_existing(self): - tmpdir = self.tmpdir - linkpath = tmpdir.join('testnotexisting') - assert not linkpath.check(link=1) - assert linkpath.check(link=0) - - def test_relto_with_root(self): - y = self.root.join('x').relto(py.path.local('/')) - assert y[0] == str(self.root)[1] - - def test_visit_recursive_symlink(self): - tmpdir = self.tmpdir - linkpath = tmpdir.join('test') - linkpath.mksymlinkto(tmpdir) - visitor = tmpdir.visit(None, lambda x: x.check(link=0)) - assert list(visitor) == [linkpath] - - def test_symlink_isdir(self): - tmpdir = self.tmpdir - linkpath = tmpdir.join('test') - linkpath.mksymlinkto(tmpdir) - assert linkpath.check(dir=1) - assert not linkpath.check(link=0, dir=1) - - def test_symlink_remove(self): - tmpdir = self.tmpdir.realpath() - linkpath = tmpdir.join('test') - linkpath.mksymlinkto(linkpath) # point to itself - assert linkpath.check(link=1) - linkpath.remove() - assert not linkpath.check() - - def test_realpath_file(self): - tmpdir = self.tmpdir - linkpath = tmpdir.join('test') - filepath = tmpdir.join('file') - filepath.write("") - linkpath.mksymlinkto(filepath) - realpath = linkpath.realpath() - assert realpath.basename == 'file' - - def test_owner(self): - from pwd import getpwuid - from grp import getgrgid - stat = self.root.stat() - assert stat.path == self.root - - uid = stat.st_uid - gid = stat.st_gid - owner = getpwuid(uid)[0] - group = getgrgid(gid)[0] - - assert uid == stat.uid - assert owner == stat.owner - assert gid == stat.gid - assert group == stat.group - - def test_atime(self): - import time - path = self.root.ensure('samplefile') - now = time.time() - atime1 = path.atime() - # we could wait here but timer resolution is very - # system dependent - path.read() - atime2 = path.atime() - duration = time.time() - now - assert (atime2-atime1) <= duration - - def test_commondir(self): - # XXX This is here in local until we find a way to implement this - # using the subversion command line api. - p1 = self.root.join('something') - p2 = self.root.join('otherthing') - assert p1.common(p2) == self.root - assert p2.common(p1) == self.root - - def test_commondir_nocommon(self): - # XXX This is here in local until we find a way to implement this - # using the subversion command line api. - p1 = self.root.join('something') - p2 = py.path.local(self.root.sep+'blabla') - assert p1.common(p2) == '/' - - def test_join_to_root(self): - root = self.root.parts()[0] - assert len(str(root)) == 1 - assert str(root.join('a')) == '/a' - - def test_join_root_to_root_with_no_abs(self): - nroot = self.root.join('/') - assert str(self.root) == str(nroot) - assert self.root == nroot - - def test_chmod_simple_int(self): - print "self.root is", self.root - mode = self.root.mode() - self.root.chmod(mode/2) - try: - assert self.root.mode() != mode - finally: - self.root.chmod(mode) - assert self.root.mode() == mode - - def test_chmod_rec_int(self): - # XXX fragile test - print "self.root is", self.root - recfilter = lambda x: x.check(dotfile=0, link=0) - oldmodes = {} - for x in self.root.visit(rec=recfilter): - oldmodes[x] = x.mode() - self.root.chmod(0772, rec=recfilter) - try: - for x in self.root.visit(rec=recfilter): - assert x.mode() & 0777 == 0772 - finally: - for x,y in oldmodes.items(): - x.chmod(y) - - def test_chown_identity(self): - owner = self.root.owner() - group = self.root.group() - self.root.chown(owner, group) - - def test_chown_dangling_link(self): - owner = self.root.owner() - group = self.root.group() - x = self.root.join('hello') - x.mksymlinkto('qlwkejqwlek') - try: - self.root.chown(owner, group, rec=1) - finally: - x.remove(rec=0) - - def test_chown_identity_rec_mayfail(self): - owner = self.root.owner() - group = self.root.group() - self.root.chown(owner, group) diff --git a/py/path/local/testing/test_win.py b/py/path/local/testing/test_win.py deleted file mode 100644 index f9ad92112..000000000 --- a/py/path/local/testing/test_win.py +++ /dev/null @@ -1,56 +0,0 @@ -import py - -class TestWINLocalPath: - #root = local(TestLocalPath.root) - disabled = py.std.sys.platform != 'win32' - - def setup_class(cls): - cls.root = py.test.ensuretemp(cls.__name__) - - def setup_method(self, method): - name = method.im_func.func_name - self.tmpdir = self.root.ensure(name, dir=1) - - def test_chmod_simple_int(self): - print "self.root is", self.root - mode = self.root.stat().st_mode - # Ensure that we actually change the mode to something different. - self.root.chmod(mode == 0 and 1 or 0) - try: - print self.root.stat().st_mode - print mode - assert self.root.stat().st_mode != mode - finally: - self.root.chmod(mode) - assert self.root.stat().st_mode == mode - - def test_path_comparison_lowercase_mixed(self): - t1 = self.root.join("a_path") - t2 = self.root.join("A_path") - assert t1 == t1 - assert t1 == t2 - - def test_relto_with_mixed_case(self): - t1 = self.root.join("a_path", "fiLe") - t2 = self.root.join("A_path") - assert t1.relto(t2) == "fiLe" - - def test_allow_unix_style_paths(self): - t1 = self.root.join('a_path') - assert t1 == str(self.root) + '\\a_path' - t1 = self.root.join('a_path/') - assert t1 == str(self.root) + '\\a_path' - t1 = self.root.join('dir/a_path') - assert t1 == str(self.root) + '\\dir\\a_path' - - def test_sysfind_in_currentdir(self): - cmd = py.path.local.sysfind('cmd') - root = cmd.new(dirname='', basename='') # c:\ in most installations - - old = root.chdir() - try: - x = py.path.local.sysfind(cmd.relto(root)) - assert x.check(file=1) - finally: - old.chdir() - diff --git a/py/path/local/win.py b/py/path/local/win.py deleted file mode 100644 index c3bb4e147..000000000 --- a/py/path/local/win.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -module for win-specific local path stuff - -(implementor needed :-) -""" - -import os -import py -from py.__.path.local.common import Stat - -class WinMixin: - def _makestat(self, statresult): - return Stat(self, statresult) - - def chmod(self, mode, rec=0): - """ change permissions to the given mode. If mode is an - integer it directly encodes the os-specific modes. - if rec is True perform recursively. - - (xxx if mode is a string then it specifies access rights - in '/bin/chmod' style, e.g. a+r). - """ - if not isinstance(mode, int): - raise TypeError("mode %r must be an integer" % (mode,)) - if rec: - for x in self.visit(rec=rec): - self._callex(os.chmod, str(x), mode) - self._callex(os.chmod, str(self), mode) - - def remove(self, rec=1): - """ remove a file or directory (or a directory tree if rec=1). """ - if self.check(dir=1, link=0): - if rec: - # force remove of readonly files on windows - self.chmod(0700, rec=1) - self._callex(py.std.shutil.rmtree, self.strpath) - else: - self._callex(os.rmdir, self.strpath) - else: - self.chmod(0700) - self._callex(os.remove, self.strpath) diff --git a/py/path/testing/fscommon.py b/py/path/testing/fscommon.py index 5edd57295..4a6f9ace8 100644 --- a/py/path/testing/fscommon.py +++ b/py/path/testing/fscommon.py @@ -94,8 +94,6 @@ class CommonFSTests(common.CommonPathTests): assert not self.root.join("sampledir").check(file=1) assert self.root.join("sampledir").check(file=0) - #def test_fnmatch_dir(self): - def test_non_existent(self): assert self.root.join("sampledir.nothere").check(dir=0) assert self.root.join("sampledir.nothere").check(file=0) @@ -205,8 +203,12 @@ class CommonFSTests(common.CommonPathTests): p = self.root.join('samplefile') newp = p.dirpath('moved_samplefile') p.move(newp) - assert newp.check(file=1) - assert not p.check() + try: + assert newp.check(file=1) + assert not p.check() + finally: + newp.move(p) + assert p.check() def test_move_directory(self): source = self.root.join('sampledir') diff --git a/py/path/local/testing/test_local.py b/py/path/testing/test_local.py similarity index 58% rename from py/path/local/testing/test_local.py rename to py/path/testing/test_local.py index 99cc733e3..43943dbd1 100644 --- a/py/path/local/testing/test_local.py +++ b/py/path/testing/test_local.py @@ -12,8 +12,10 @@ class LocalSetup: def setup_method(self, method): self.tmpdir = self.root.mkdir(method.__name__) -class TestLocalPath(LocalSetup, CommonFSTests): + def teardown_method(self, method): + assert self.root.join("samplefile").check() +class TestLocalPath(LocalSetup, CommonFSTests): def test_join_normpath(self): assert self.tmpdir.join(".") == self.tmpdir p = self.tmpdir.join("../%s" % self.tmpdir.basename) @@ -347,3 +349,248 @@ def test_homedir(): homedir = py.path.local._gethomedir() assert homedir.check(dir=1) +class TestWINLocalPath: + #root = local(TestLocalPath.root) + disabled = py.std.sys.platform != 'win32' + + def setup_class(cls): + cls.root = py.test.ensuretemp(cls.__name__) + + def setup_method(self, method): + name = method.im_func.func_name + self.tmpdir = self.root.ensure(name, dir=1) + + def test_owner_group_not_implemented(self): + py.test.raises(NotImplementedError, "self.root.stat().owner") + py.test.raises(NotImplementedError, "self.root.stat().group") + + def test_chmod_simple_int(self): + print "self.root is", self.root + mode = self.root.stat().mode + # Ensure that we actually change the mode to something different. + self.root.chmod(mode == 0 and 1 or 0) + try: + print self.root.stat().mode + print mode + assert self.root.stat().mode != mode + finally: + self.root.chmod(mode) + assert self.root.stat().mode == mode + + def test_path_comparison_lowercase_mixed(self): + t1 = self.root.join("a_path") + t2 = self.root.join("A_path") + assert t1 == t1 + assert t1 == t2 + + def test_relto_with_mixed_case(self): + t1 = self.root.join("a_path", "fiLe") + t2 = self.root.join("A_path") + assert t1.relto(t2) == "fiLe" + + def test_allow_unix_style_paths(self): + t1 = self.root.join('a_path') + assert t1 == str(self.root) + '\\a_path' + t1 = self.root.join('a_path/') + assert t1 == str(self.root) + '\\a_path' + t1 = self.root.join('dir/a_path') + assert t1 == str(self.root) + '\\dir\\a_path' + + def test_sysfind_in_currentdir(self): + cmd = py.path.local.sysfind('cmd') + root = cmd.new(dirname='', basename='') # c:\ in most installations + old = root.chdir() + try: + x = py.path.local.sysfind(cmd.relto(root)) + assert x.check(file=1) + finally: + old.chdir() + +class TestPOSIXLocalPath: + disabled = py.std.sys.platform == 'win32' + + def setup_class(cls): + cls.root = py.test.ensuretemp(cls.__name__) + + def setup_method(self, method): + name = method.im_func.func_name + self.tmpdir = self.root.ensure(name, dir=1) + + def test_samefile(self): + assert self.tmpdir.samefile(self.tmpdir) + p = self.tmpdir.ensure("hello") + assert p.samefile(p) + + def test_hardlink(self): + tmpdir = self.tmpdir + linkpath = tmpdir.join('test') + filepath = tmpdir.join('file') + filepath.write("Hello") + nlink = filepath.stat().nlink + linkpath.mklinkto(filepath) + assert filepath.stat().nlink == nlink + 1 + + def test_symlink_are_identical(self): + tmpdir = self.tmpdir + filepath = tmpdir.join('file') + filepath.write("Hello") + linkpath = tmpdir.join('test') + linkpath.mksymlinkto(filepath) + assert linkpath.readlink() == str(filepath) + + def test_symlink_isfile(self): + tmpdir = self.tmpdir + linkpath = tmpdir.join('test') + filepath = tmpdir.join('file') + filepath.write("") + linkpath.mksymlinkto(filepath) + assert linkpath.check(file=1) + assert not linkpath.check(link=0, file=1) + + def test_symlink_relative(self): + tmpdir = self.tmpdir + linkpath = tmpdir.join('test') + filepath = tmpdir.join('file') + filepath.write("Hello") + linkpath.mksymlinkto(filepath, absolute=False) + assert linkpath.readlink() == "file" + assert filepath.read() == linkpath.read() + + def test_symlink_not_existing(self): + tmpdir = self.tmpdir + linkpath = tmpdir.join('testnotexisting') + assert not linkpath.check(link=1) + assert linkpath.check(link=0) + + def test_relto_with_root(self): + y = self.root.join('x').relto(py.path.local('/')) + assert y[0] == str(self.root)[1] + + def test_visit_recursive_symlink(self): + tmpdir = self.tmpdir + linkpath = tmpdir.join('test') + linkpath.mksymlinkto(tmpdir) + visitor = tmpdir.visit(None, lambda x: x.check(link=0)) + assert list(visitor) == [linkpath] + + def test_symlink_isdir(self): + tmpdir = self.tmpdir + linkpath = tmpdir.join('test') + linkpath.mksymlinkto(tmpdir) + assert linkpath.check(dir=1) + assert not linkpath.check(link=0, dir=1) + + def test_symlink_remove(self): + tmpdir = self.tmpdir.realpath() + linkpath = tmpdir.join('test') + linkpath.mksymlinkto(linkpath) # point to itself + assert linkpath.check(link=1) + linkpath.remove() + assert not linkpath.check() + + def test_realpath_file(self): + tmpdir = self.tmpdir + linkpath = tmpdir.join('test') + filepath = tmpdir.join('file') + filepath.write("") + linkpath.mksymlinkto(filepath) + realpath = linkpath.realpath() + assert realpath.basename == 'file' + + def test_owner(self): + from pwd import getpwuid + from grp import getgrgid + stat = self.root.stat() + assert stat.path == self.root + + uid = stat.uid + gid = stat.gid + owner = getpwuid(uid)[0] + group = getgrgid(gid)[0] + + assert uid == stat.uid + assert owner == stat.owner + assert gid == stat.gid + assert group == stat.group + + def test_atime(self): + import time + path = self.root.ensure('samplefile') + now = time.time() + atime1 = path.atime() + # we could wait here but timer resolution is very + # system dependent + path.read() + atime2 = path.atime() + duration = time.time() - now + assert (atime2-atime1) <= duration + + def test_commondir(self): + # XXX This is here in local until we find a way to implement this + # using the subversion command line api. + p1 = self.root.join('something') + p2 = self.root.join('otherthing') + assert p1.common(p2) == self.root + assert p2.common(p1) == self.root + + def test_commondir_nocommon(self): + # XXX This is here in local until we find a way to implement this + # using the subversion command line api. + p1 = self.root.join('something') + p2 = py.path.local(self.root.sep+'blabla') + assert p1.common(p2) == '/' + + def test_join_to_root(self): + root = self.root.parts()[0] + assert len(str(root)) == 1 + assert str(root.join('a')) == '/a' + + def test_join_root_to_root_with_no_abs(self): + nroot = self.root.join('/') + assert str(self.root) == str(nroot) + assert self.root == nroot + + def test_chmod_simple_int(self): + print "self.root is", self.root + mode = self.root.stat().mode + self.root.chmod(mode/2) + try: + assert self.root.stat().mode != mode + finally: + self.root.chmod(mode) + assert self.root.stat().mode == mode + + def test_chmod_rec_int(self): + # XXX fragile test + print "self.root is", self.root + recfilter = lambda x: x.check(dotfile=0, link=0) + oldmodes = {} + for x in self.root.visit(rec=recfilter): + oldmodes[x] = x.stat().mode + self.root.chmod(0772, rec=recfilter) + try: + for x in self.root.visit(rec=recfilter): + assert x.stat().mode & 0777 == 0772 + finally: + for x,y in oldmodes.items(): + x.chmod(y) + + def test_chown_identity(self): + owner = self.root.stat().owner + group = self.root.stat().group + self.root.chown(owner, group) + + def test_chown_dangling_link(self): + owner = self.root.stat().owner + group = self.root.stat().group + x = self.root.join('hello') + x.mksymlinkto('qlwkejqwlek') + try: + self.root.chown(owner, group, rec=1) + finally: + x.remove(rec=0) + + def test_chown_identity_rec_mayfail(self): + owner = self.root.stat().owner + group = self.root.stat().group + self.root.chown(owner, group)