view tests/test-symlink-os-yes-fs-no.py @ 14007:d764463b433e

atomictempfile: avoid infinite recursion in __del__(). The problem is that a programmer using atomictempfile directly can make an innocent everyday mistake -- not enough args to the constructor -- which escalates badly. You would expect a simple TypeError crash in that case, but you actually get an infinite recursion that is surprisingly difficult to kill: it happens between __del__() and __getattr__(), and Python does not handle infinite recursion from __del__() well. The fix is to not implement __getattr__(), but instead assign instance attributes for the methods we wish to delegate to the builtin file type: write() and fileno(). I've audited mercurial.* and hgext.* and found no users of atomictempfile using methods other than write() and rename(). I audited third-party extensions and found one (snap) passing an atomictempfile to util.fstat(), so I also threw in fileno(). The last time I submitted a similar patch, Matt proposed that we make atomictempfile a subclass of file instead of wrapping it. Rejected on grounds of unnecessary complexity: for one thing, it would make the Windows implementation of posixfile quite a bit more complex. It would have to become a subclass of file rather than a simple function -- but since it's written in C, this is non-obvious and non-trivial. Furthermore, there's nothing wrong with wrapping objects and delegating methods: it's a well-established pattern that works just fine in many cases. Subclassing is not the answer to all of life's problems.
author Greg Ward <greg@gerg.ca>
date Sun, 24 Apr 2011 19:25:10 -0400
parents ca6cebd8734e
children cd3032437064
line wrap: on
line source

import os, sys, time
from mercurial import hg, ui, commands

TESTDIR = os.environ["TESTDIR"]

# only makes sense to test on os which supports symlinks
if not hasattr(os, "symlink"):
    sys.exit(80) # SKIPPED_STATUS defined in run-tests.py

# clone with symlink support
u = ui.ui()
hg.clone(u, os.path.join(TESTDIR, 'test-no-symlinks.hg'), 'test0')

repo = hg.repository(u, 'test0')

# wait a bit, or the status call wont update the dirstate
time.sleep(1)
commands.status(u, repo)

# now disable symlink support -- this is what os.symlink would do on a
# non-symlink file system
def symlink_failure(src, dst):
    raise OSError, (1, "Operation not permitted")
os.symlink = symlink_failure

# dereference links as if a Samba server has exported this to a
# Windows client
for f in 'test0/a.lnk', 'test0/d/b.lnk':
    os.unlink(f)
    fp = open(f, 'wb')
    fp.write(open(f[:-4]).read())
    fp.close()

# reload repository
u = ui.ui()
repo = hg.repository(u, 'test0')
commands.status(u, repo)

# try cloning a repo which contains symlinks
u = ui.ui()
hg.clone(u, os.path.join(TESTDIR, 'test-no-symlinks.hg'), 'test1')