Has anyone released a Python "mock filesystem" for automated testing?

P

Peter Hansen

The term "mock filesystem" refers to code allowing unit
or acceptance tests to create, read and write, and manipulate
in other ways "virtual" files, without any actual disk
access. Everything is held in memory and therefore fast,
without risk of damaging real files, and with none of the
messiness of leftover files after testing.

Googling the archives and the web suggests that only I and
Remy Blank have done much along these lines. I don't see
any sign that anyone has actually released such a beast
yet, however.

My own past work in this area was always proprietary, and
as "you can't take it with you" I've been starting over
on a new project and have the basics working. If nobody
can point me to a more complete implementation, I'll be
happy to continue to work on mine and release it in the
near future.

For now, in case anyone is interested, I have support
for basic open, read/write, __iter__, close, "r" and "w"
modes, a couple of the most basic exceptions, and a
tiny handful of lesser things. All well supported by
their own unit tests, of course, to ensure quality.
Stripped class/def lines shown below, as that may be
an easier way for you to picture what this is about:

class MockFile(object):
def __init__(self, fs, path, data='', mode=''):
def _getContents(self):
def read(self):
def write(self, data):
def close(self):
def __iter__(self):


class FileSystem(object):
'''Acts like a real file system to support testing.'''
def __init__(self, mocktime=False):
def _createFile(self, path, data):
def _getFile(self, path):
def open(self, path, mode='r'):

Thanks for any pointers to more advanced stuff.

-Peter
 
J

Jack Diederich

The term "mock filesystem" refers to code allowing unit
or acceptance tests to create, read and write, and manipulate
in other ways "virtual" files, without any actual disk
access. Everything is held in memory and therefore fast,
without risk of damaging real files, and with none of the
messiness of leftover files after testing.

Googling the archives and the web suggests that only I and
Remy Blank have done much along these lines. I don't see
any sign that anyone has actually released such a beast
yet, however.

If linux only support is OK you should check out FUSE
http://fuse.sourceforge.net/
It allows userspace implementations of file systems in python. The gmailfs
(mount your gmail account) uses it. You could use it to write a 'real' file
system that just has no disk backend store.

It would be easy to implement special files to do things for testing,
echo '1' > /op/nuke # clean everything
echo '1' > /op/push # push the state of the filesystem
echo '1' > /op/pop # restore the state of the filesystem to last 'push'
And any other handy thing you can think of.
It has the advantage that you can use regular python files in your
program. Bugs in the implementation would hurt testing but that is the
case any way you do it.


Unrelated:
I'm strongly tempted to [re]write a simple webapp like a blog as a
filesystem. Just put apache on top to handle GETs and cat POSTs/PUTs
directly to the filesystem. Can't get more unix-y than that!

-Jack
 
M

Mike Meyer

Peter Hansen said:
The term "mock filesystem" refers to code allowing unit
or acceptance tests to create, read and write, and manipulate
in other ways "virtual" files, without any actual disk
access. Everything is held in memory and therefore fast,
without risk of damaging real files, and with none of the
messiness of leftover files after testing.

Just out of curiosity, does your os implement some form of disk in
memory disk? Go to <URL: http://www.FreeBSD.org/cgi/man.cgi > and
search for either mfs or md for examples.

Second, consider using os.sep for path seperation, so that testing can
be done correctly for the OS the project is running on. Or possibly a
flag to indicate whether or not you want to use os.sep.

<mike
 
B

Bengt Richter

The term "mock filesystem" refers to code allowing unit
or acceptance tests to create, read and write, and manipulate
in other ways "virtual" files, without any actual disk
access. Everything is held in memory and therefore fast,
without risk of damaging real files, and with none of the
messiness of leftover files after testing.

Googling the archives and the web suggests that only I and
Remy Blank have done much along these lines. I don't see
any sign that anyone has actually released such a beast
yet, however.

My own past work in this area was always proprietary, and
as "you can't take it with you" I've been starting over
on a new project and have the basics working. If nobody
can point me to a more complete implementation, I'll be
happy to continue to work on mine and release it in the
near future.

For now, in case anyone is interested, I have support
for basic open, read/write, __iter__, close, "r" and "w"
modes, a couple of the most basic exceptions, and a
tiny handful of lesser things. All well supported by
their own unit tests, of course, to ensure quality.
Stripped class/def lines shown below, as that may be
an easier way for you to picture what this is about:

class MockFile(object):
def __init__(self, fs, path, data='', mode=''):
def _getContents(self):
def read(self):
def write(self, data):
def close(self):
def __iter__(self):


class FileSystem(object):
'''Acts like a real file system to support testing.'''
def __init__(self, mocktime=False):
def _createFile(self, path, data):
def _getFile(self, path):
def open(self, path, mode='r'):

Thanks for any pointers to more advanced stuff.
In a recent post, I suggested a way to virtualize selected files or glob-pattern
sets of files specified by string.

The idea was/is to make open and/or file check whether the path it's being passed
has been registered in a hook registry of substitute classes or factory functions
to be called instead of the normal file builtin functionality, so that the returned
object is an instance of _your_ registered custom class.

"Registering" might be as simple API-wise as sys.filehook['filename.ext'] = MockFile
(to use your example ;-)

*args, **kw would be passed to Mockfile and the resulting instance would be passed
back to the caller of file, so e.g. file('filename.ext', 'rb').read() would
have the effect of Mockfile('filename.ext', rb').read()

Of course there are a bunch of other file things that might have to be supplied if
you wanted to virtualize using low level os.open & friends.

Re a whole file system virtualization, I tried to float that one time long ago, but got
no enthusiasm, even though I only wanted to introduce a virtual branch of the root.

If you do do something with a VFS, IMO it should be mountable and require mounting. Maybe
unix-style mapping of the file tree like msys does for windows would allow mounting
in a platform-independent way, referring to c:\some\path\ as /c/some/path/ and so forth.
Not sure about virtualizing fstab... I guess you could do os.filehook['fstab'] = Something
to feed something special to the mount.

With a mount command you could also specify text file cooking or not, etc.

Just stirring the pot, but I think it would open up some different testing possibilities ;-)
If the hook really hooks well, I would think you could write

sys.filehook['foo'] = __import__('StringIO').StringIO("print 'hi from dummy foo.py'\n")
import foo

and expect a greeting (the first time)(untested ;-)

I guess it depends on where i/o funnels back to common layer interfaces.

Regards,
Bengt Richter
 
R

Remy Blank

Peter said:
Googling the archives and the web suggests that only I and
Remy Blank have done much along these lines. I don't see
any sign that anyone has actually released such a beast
yet, however.

Not yet, not yet...
For now, in case anyone is interested, I have support
for basic open, read/write, __iter__, close, "r" and "w"
modes, a couple of the most basic exceptions, and a
tiny handful of lesser things. All well supported by
their own unit tests, of course, to ensure quality.

The status on my side is that I have the "read-only" portion working
quite well, with support for file attributes (stat) but no access
checking yet. Also supported by a unit test suite.

However, when I started on the "write" code, I stumbeled on a few areas
where I wasn't sure how the system should behave. A few examples:

- Where do you land if you chdir("/dir1/symlink/../subdir") when
symlink points to /dir2/subdir? If you os.abspath() the path before
interpretation, you land in /dir1/subdir. If you don't, you land in
/dir2/subdir.
- Who can remove a file?
- Who can change the mode of a file? Its owner and group?
- Which operations change atime? mtime? ctime?
- What exceptions are thrown for various error conditions?

Those are the simple ones I remember, but I know there were other where
I couldn't find the solution easily in manpages. And I didn't want to
have to look everything up in the Linux kernel source code. So the next
idea was to change the test suite so that it could run either on the
real filesystem, or on the mockfs. That way, I could ensure that the
behaviour was identical.

This proved to be more challenging than I thought, because for most
tests (chmod, chown, access control) you need to be root to run the test
suite. And I felt too uncomfortable running a test suite that could "rm
-rf /" by mistake, as root.

So the next idea was to write a framework where you could run test cases
within a chroot()'ed environment, so that (as long as you don't use
chroot() in you test cases) you were pretty safe not to wipe your harddisk.

This has finally worked out better than I had expected. I have a
"JailTestCase" that creates a new directory for every test case,
chroot()s to it in setUp(), runs the test case and exits the chroot jail
in tearDown() (that part was tricky). There is also a preJailEntry()
hook for e.g. unpacking a tar archive into the directory before entering
the chroot jail. The jail directories are never removed automatically,
so they can be examined in case of failure. Obviously, it works only on
*nix type systems.

I have also implemented a FileSystem class that allows the easy creation
of all filesystem entities (files, directories, pipes, sockets, ...).

Here's a class/def description of the library:

def enterJail(path):
def exitJail():

class JailTestCase(unittest.TestCase):
def setJailDir(path):
setJailDir = staticmethod(setJailDir)
def jailDir():
jailDir = staticmethod(jailDir)
def __init__(self, methodName='runTest'):
def preJailEntry(self):
def setUp(self):
def tearDown(self):

class RealFilesystem:
def createFile(self, path, mode=None, uid=None, gid=None,
atime=None, mtime=None, content=None):
def createDir(self, path, mode=None, uid=None, gid=None,
atime=None, mtime=None):
def createSymlink(self, path, uid=None, gid=None, atime=None,
mtime=None, target=None):
def createCharDevice(self, path, major, minor, mode=None,
uid=None, gid=None, atime=None, mtime=None):
def createBlockDevice(self, path, major, minor, mode=None,
uid=None, gid=None, atime=None, mtime=None):
def createPipe(self, path, mode=None, uid=None, gid=None,
atime=None, mtime=None):
def createSocket(self, path, mode=None, uid=None, gid=None,
atime=None, mtime=None):

So I am now at the point where I could resume working on mockfs. Now,
one of the reasons why I wanted a mockfs library (besides having to run
tests as root) was that I was afraid that using the real filesystem
would be too slow. Well, I was proven wrong while implementing this
ChrootJail module: it is actually quite fast. At the moment, I am
wondering if it wouldn't be more efficient to just polish up this
ChrootJail module and use that instead of a mockfs.

If you're interested, I can send you the module with its unit test
suite. Feedback is as always very welcome.

Best regards.
-- Remy


PS: Here's the class/def state of my mockfs (some functions are not yet
fully functional):

class Inode(object):
def Size(self):
def IsDirectory(self):
def IsSymLink(self):
def AddLink(self):
def RemoveLink(self):
def ExtraStatFields(self):
def Stat(self):

class DirectoryInode(Inode, dict):
def __setitem__(self, Key, Value):
def __delitem__(self, Key):
def clear(self):
def update(self, Rhs):
def setdefault(self, Key, Value):
def pop(self, *Args):
def popitem(self):

class FileInode(Inode):
class SymLinkInode(Inode):
class PipeInode(Inode):
class SocketInode(Inode):
class DeviceInode(Inode):
class BlockDeviceInode(DeviceInode):
class CharDeviceInode(DeviceInode):

class File(object):
def __init__(self, FileName, Mode="r", BufSize=-1):
def close(self):
def flush(self):
def read(self, Size=-1):
def readline(self, Size=-1):
def readlines(self, SizeHint=None):
def xreadlines(self):
def seek(self, Offset, Whence=0):
def tell(self):
def truncate(self, Size=None):
def write(self, Str):
def writelines(self, Sequence):
def __iter__(self):
def next(self):

class MockFilesystem(object):
def __init__(self):
def AddDirectory(self, Path, **KwArgs):
def AddFile(self, Path, **KwArgs):
def AddSymLink(self, Path, **KwArgs):
def AddPipe(self, Path, **KwArgs):
def AddSocket(self, Path, **KwArgs):
def AddBlockDevice(self, Path, **KwArgs):
def AddCharDevice(self, Path, **KwArgs):
def AddHardLink(self, Path, Target):
# Substitutes for os functions
def chdir(self, Path):
def getcwd(self):
def getcwdu(self):
def chroot(self, Path):
def chmod(self, Path, Mode):
def chown(self, Path, Uid, Gid):
def lchown(self, Path, Uid, Gid):
def listdir(self, Path):
def lstat(self, Path):
def readlink(self, Path):
def remove(self, Path):
def rmdir(self, Path):
def stat(self, Path):
def statvfs(self, Path):
def unlink(self, Path):
def utime(self, Path, Times):



Remove underscore and suffix in reply address for a timely response.
 
P

Peter Hansen

Mike said:
Just out of curiosity, does your os implement some form of disk in
memory disk? Go to <URL: http://www.FreeBSD.org/cgi/man.cgi > and
search for either mfs or md for examples.

I need to be cross-platform, so Windows and Linux must both
be supported. I suspect there isn't good support on Windows
for the sort of thing you describe.

In any case, though I thank you and Jack for the ideas,
the pure-Python in-memory approach seems to be quite
viable and probably quite a bit simpler. In addition it
allows testing with paths and operations that could not
be safely supported if real file access was permitted at
all, so I think I'll stick with it.
Second, consider using os.sep for path seperation, so that testing can
be done correctly for the OS the project is running on. Or possibly a
flag to indicate whether or not you want to use os.sep.

I've never had to resort to using os.sep myself. I use
os.path.split() and os.path.join() and it seems they do the
job for me. ;-)

-Peter
 
?

=?ISO-8859-1?Q?F=E1bio?= Mendes

Em Qui, 2004-11-04 às 21:31 -0500, Peter Hansen escreveu:
The term "mock filesystem" refers to code allowing unit
or acceptance tests to create, read and write, and manipulate
in other ways "virtual" files, without any actual disk
access. Everything is held in memory and therefore fast,
without risk of damaging real files, and with none of the
messiness of leftover files after testing.

Maybe StringIO is a good start point. Depending on what you need (if
it's just read/write), everything is already there.

Fabio
 
J

Jorgen Grahn

Second, consider using os.sep for path seperation, so that testing can
be done correctly for the OS the project is running on. Or possibly a
flag to indicate whether or not you want to use os.sep.

The mock file system should probably have a really bizarre os.sep, to
provoke code which assumes '/'.

Overall, it's an interesting idea.
 
P

Peter Hansen

Jorgen said:
The mock file system should probably have a really bizarre os.sep, to
provoke code which assumes '/'.

Nice idea! One could think of a variety of ways to use such
a thing to help improve "cross-platform-ness".
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top