1. Ned Batchelder
  2. fishmonger

Commits

Ned Batchelder  committed 91d6d1a

First!

  • Participants
  • Branches default

Comments (0)

Files changed (5)

File .hgignore

View file
+syntax: glob
+
+*.pyc
+*.pyo
+.coverage
+htmlcov

File fishmonger/__init__.py

View file
+"""Fishmonger fake filesystem library."""
+
+from fishmonger.main import *

File fishmonger/fm_os.py

View file
+"""Fishmonger - a filesystem fake for Python testing.
+
+Ned Batchelder, June 2011.
+
+"""
+
+import os, posixpath, textwrap
+import cStringIO as StringIO
+
+__all__ = ['FishMonger']
+
+
+class OsErrorMock(OSError):
+    pass
+
+
+class OsMock(object):
+    def __init__(self, monger):
+        self.monger = monger
+        self.path = OsPathMock(self, monger)
+        self.cwd = "/"
+
+    def chdir(self, path):
+        newcwd = self.path.abspath(path)
+        if not self.path.isdir(newcwd):
+            raise OsErrorMock("%r is not a directory" % newcwd)
+        self.cwd = newcwd
+
+    def getcwd(self):
+        return self.cwd
+
+    def listdir(self, path):
+        f = self.monger._getfile(path)
+        if isinstance(f, dict):
+            return f.keys()
+        else:
+            raise OsErrorMock("Can't listdir %r" % path)
+
+    def walk(self, top, topdown=True, onerror=None, followlinks=False):
+        # Code pretty much copied from os.py in stdlib
+        try:
+            names = self.listdir(top)
+        except OSError, err:
+            if onerror is not None:
+                onerror(err)
+            return
+
+        dirs, nondirs = [], []
+        for name in names:
+            if self.path.isdir(self.path.join(top, name)):
+                dirs.append(name)
+            else:
+                nondirs.append(name)
+
+        if topdown:
+            yield top, dirs, nondirs
+        for name in dirs:
+            new_path = self.path.join(top, name)
+            # When fishmonger can simulate links, put back this line:
+            #if followlinks or not self.path.islink(new_path):
+            for x in self.walk(new_path, topdown, onerror, followlinks):
+                yield x
+        if not topdown:
+            yield top, dirs, nondirs
+
+
+class OsPathMock(object):
+    def __init__(self, os, monger):
+        self.os = os
+        self.monger = monger
+
+    def abspath(self, path):
+        return self.normpath(self.join(self.os.getcwd(), path))
+
+    def exists(self, path):
+        f = self.monger._getfile(path)
+        return f is not None
+
+    def isdir(self, path):
+        f = self.monger._getfile(path)
+        return isinstance(f, dict)
+
+    def islink(self, path):
+        return False
+
+    # Pass-throughs to the string-only posixpath functions
+
+    def split(self, path):
+        return posixpath.split(path)
+
+    def join(self, *paths):
+        return posixpath.join(*paths)
+
+    def normpath(self, path):
+        return posixpath.normpath(path)
+
+    sep = posixpath.sep

File fishmonger/main.py

View file
+"""Fishmonger - a filesystem fake for Python testing.
+
+Ned Batchelder, June 2011.
+
+"""
+
+import os, posixpath, textwrap
+import cStringIO as StringIO
+
+from fishmonger.fm_os import OsMock, OsErrorMock
+
+__all__ = ["FishMonger"]
+
+class FishMonger(object):
+    def __init__(self, files):
+        self.files = files
+        self.os = OsMock(self)
+
+    def _getfile(self, path):
+        files = self.files
+        path = self.os.path.abspath(path)
+        for p in path.split("/"):
+            if not p:
+                continue
+            files = files.get(p)
+            if files is None:
+                break
+        return files
+
+    def open(self, filename, mode='r', bufsize=1):
+        f = self._getfile(filename)
+        if f is None:
+            raise OsErrorMock("File does not exist: %r" % filename)
+        if isinstance(f, dict):
+            raise OsErrorMock("Cannot open directory: %r" % filename)
+        return StringIO.StringIO(textwrap.dedent(f))

File fishmonger/test_fishmonger.py

View file
+"""Unit tests for fishmonger."""
+
+import unittest
+from fishmonger import FishMonger
+
+FILES = {
+    'toplevel.txt': 'I am at the top',
+    'empty': {},
+    'one': {
+        'two': {},
+        'inside.txt': 'I am inside',
+        },
+    'lincoln': {
+        'gettysburg': """\
+            Four-score and seven years ago,
+            our fathers brought forth on this continent
+            a new nation,
+            conceived in liberty
+            and dedicated to the proposition
+            that all men are created equal.
+            """,
+        },
+    }
+
+
+def fset(*args):
+    """A helper to conveniently make frozensets."""
+    return frozenset(args)
+
+
+class FishMongerTestCase(unittest.TestCase):
+    """A base class for all FishMonger tests."""
+
+    def setUp(self):
+        self.fish = FishMonger(FILES)
+        self.open = self.fish.open
+        self.os = self.fish.os
+
+    def assertEqualSets(self, a, b):
+        """`a` and `b` have the same elements, in arbitrary orders."""
+        self.assertEqual(set(a), set(b))
+
+
+class OpenTest(FishMongerTestCase):
+    """Test that FishMonger.open is a good substitute for real `open()`."""
+
+    def test_files_are_readable(self):
+        bytes = self.open("toplevel.txt").read()
+        self.assertEqual(bytes, "I am at the top")
+
+    def test_cant_open_non_existent(self):
+        self.assertRaises(OSError, self.open, "oogity_boogity")
+
+    def test_cant_open_directories(self):
+        self.assertRaises(OSError, self.open, "/one")
+
+    def test_open_relative(self):
+        self.os.chdir("one")
+        self.assertEqual(self.open("inside.txt").read(), "I am inside")
+        self.assertEqual(self.open("../toplevel.txt").read(), "I am at the top")
+
+    def test_contents_are_dedented(self):
+        # Strings in the dictionary of files are dedented so that multi-line
+        # text can be created conveniently.
+        f = self.open("lincoln/gettysburg")
+        text = f.read().splitlines()
+        self.assertEqual(text[0], "Four-score and seven years ago,")
+        self.assertEqual(text[-1], "that all men are created equal.")
+
+    def test_open_files_can_be_iterated(self):
+        f = self.open("lincoln/gettysburg")
+        text = list(f)
+        self.assertEqual(text[0], "Four-score and seven years ago,\n")
+        self.assertEqual(text[-1], "that all men are created equal.\n")
+
+    def test_cant_read_past_eof(self):
+        f = self.open("lincoln/gettysburg")
+        text = f.read()
+        self.assertTrue(len(text) > 40)
+        text = f.read()
+        self.assertEqual(text, "")
+        text = f.read()
+        self.assertEqual(text, "")
+
+    def test_files_can_be_closed(self):
+        f = self.open("lincoln/gettysburg")
+        f.close()
+        self.assertRaises(ValueError, f.read)
+
+
+class OsTest(FishMongerTestCase):
+    """Test that FishMonger.os is a good substitute for `os`."""
+
+    def test_getcwd(self):
+        self.assertEqual(self.os.getcwd(), "/")
+
+    def test_chdir(self):
+        self.os.chdir("empty")
+        self.assertEqual(self.os.getcwd(), "/empty")
+
+    def test_chdir_up(self):
+        self.os.chdir("empty")
+        self.os.chdir("..")
+        self.assertEqual(self.os.getcwd(), "/")
+
+    def test_chdir_relative(self):
+        self.os.chdir("one")
+        self.os.chdir("two")
+        self.assertEqual(self.os.getcwd(), "/one/two")
+
+    def test_bad_chdir(self):
+        self.assertRaises(OSError, self.os.chdir, "not_there")
+        self.assertRaises(OSError, self.os.chdir, "one/two/three/four/five")
+
+    def test_listdir(self):
+        self.assertEqualSets(self.os.listdir("/"), ["empty", "one", "toplevel.txt", "lincoln"])
+        self.assertEqualSets(self.os.listdir("one"), ["two", "inside.txt"])
+
+    def test_listdir_relative(self):
+        self.os.chdir("one")
+        self.assertEqualSets(self.os.listdir("."), ["two", "inside.txt"])
+
+    def test_listdir_failures(self):
+        self.assertRaises(OSError, self.os.listdir, "xyzzy")
+        self.assertRaises(OSError, self.os.listdir, "toplevel.txt")
+        self.assertRaises(OSError, self.os.listdir, "one/inside.txt")
+        self.assertRaises(OSError, self.os.listdir, "one/fooey_kablooey.txt")
+
+
+class OsWalkTest(FishMongerTestCase):
+    """Test that FishMonger.os.walk is a good substitute for `os.walk`."""
+
+    def setUp(self):
+        self.os = FishMonger({
+            'a.txt': 'a',
+            'b.txt': 'b',
+            'sub': {
+                'sa.txt': 'sa',
+                'sb.txt': 'sb',
+                },
+            'sub2': {
+                's2a.txt': 's2a',
+                'subsub': {
+                    'ssa.txt': 'ssa',
+                    },
+                },
+            'c.txt': 'c',
+            '.nope': {
+                'hidden': 'dont look',
+                },
+            }).os
+
+    def sorted_walk(self, top, topdown=True, onerror=None, followlinks=False):
+        results = []
+        for dirname, dirnames, filenames in self.os.walk(top, topdown, onerror, followlinks):
+            results.append((dirname, fset(*dirnames), fset(*filenames)))
+            dirnames.sort()
+        return results
+
+    def assertIsTopDown(self, results):
+        dirs = [d for d,_,_ in results]
+        for d in dirs:
+            if d != "/":
+                my_index = dirs.index(d)
+                parent_index = dirs.index(self.os.path.split(d)[0])
+                assert my_index > parent_index, "%r isn't right in %r" % (d, dirs)
+
+    def assertIsBottomUp(self, results):
+        dirs = [d for d,_,_ in results]
+        for d in dirs:
+            if d != "/":
+                my_index = dirs.index(d)
+                parent_index = dirs.index(self.os.path.split(d)[0])
+                assert my_index < parent_index, "%r isn't right in %r" % (d, dirs)
+
+    def test_simple(self):
+        results = self.sorted_walk("/")
+        self.assertEqualSets(results, [
+            ("/", fset(".nope", "sub", "sub2"), fset("a.txt", "b.txt", "c.txt")),
+            ("/.nope", fset(), fset("hidden")),
+            ("/sub", fset(), fset("sa.txt", "sb.txt")),
+            ("/sub2", fset("subsub"), fset("s2a.txt")),
+            ("/sub2/subsub", fset(), fset("ssa.txt")),
+            ])
+        self.assertIsTopDown(results)
+
+    def test_non_root(self):
+        results = self.sorted_walk("sub2")
+        self.assertEqualSets(results, [
+            ("sub2", fset("subsub"), fset("s2a.txt")),
+            ("sub2/subsub", fset(), fset("ssa.txt")),
+            ])
+
+    def test_bottom_up(self):
+        results = self.sorted_walk("/", topdown=False)
+        self.assertEqualSets(results, [
+            ("/.nope", fset(), fset("hidden")),
+            ("/sub", fset(), fset("sa.txt", "sb.txt")),
+            ("/sub2/subsub", fset(), fset("ssa.txt")),
+            ("/sub2", fset("subsub"), fset("s2a.txt")),
+            ("/", fset(".nope", "sub", "sub2"), fset("a.txt", "b.txt", "c.txt")),
+            ])
+        self.assertIsBottomUp(results)
+
+    def test_modifying_dirnames(self):
+        results = []
+        for dirname, dirnames, filenames in self.os.walk("/"):
+            results.append((dirname, fset(*dirnames), fset(*filenames)))
+            if ".nope" in dirnames:
+                dirnames.remove(".nope")
+            dirnames.sort()
+        self.assertEqualSets(results, [
+            ("/", fset(".nope", "sub", "sub2"), fset("a.txt", "b.txt", "c.txt")),
+            ("/sub", fset(), fset("sa.txt", "sb.txt")),
+            ("/sub2", fset("subsub"), fset("s2a.txt")),
+            ("/sub2/subsub", fset(), fset("ssa.txt")),
+            ])
+
+    def test_error(self):
+        results = self.sorted_walk("fooey_kablooey")
+        self.assertEqual(results, [])
+
+    def test_error_handler(self):
+        def raise_it(err):
+            raise err
+        self.assertRaises(Exception, self.sorted_walk, "fooey_kablooey", onerror=raise_it)
+
+
+class OsPathTest(FishMongerTestCase):
+    """Test that FishMonger.os.path is a good substitute for `os.path`."""
+
+    def test_split(self):
+        self.assertEqual(self.os.path.split("a/b/c"), ("a/b", "c"))
+        self.assertEqual(self.os.path.split("a"), ("", "a"))
+        self.assertEqual(self.os.path.split("a.foo"), ("", "a.foo"))
+        self.assertEqual(self.os.path.split("/a.foo"), ("/", "a.foo"))
+
+    def test_join(self):
+        self.assertEqual(self.os.path.join("a", "b"), "a/b")
+        self.assertEqual(self.os.path.join("a/", "b"), "a/b")
+        self.assertEqual(self.os.path.join("a", "b", "c"), "a/b/c")
+        self.assertEqual(self.os.path.join("a/b", "c"), "a/b/c")
+        self.assertEqual(self.os.path.join("a"), "a")
+
+    def test_sep(self):
+        self.assertEqual(self.os.path.join("a", "b"), "a" + self.os.path.sep + "b")
+
+    def test_abspath(self):
+        self.os.chdir("one")
+        self.assertEqual(self.os.path.abspath("."), "/one")
+        self.assertEqual(self.os.path.abspath(".."), "/")
+        self.assertEqual(self.os.path.abspath("two"), "/one/two")
+        self.assertEqual(self.os.path.abspath("./two"), "/one/two")
+        self.assertEqual(self.os.path.abspath("../empty"), "/empty")
+        self.assertEqual(self.os.path.abspath("/empty"), "/empty")
+
+    def test_normpath(self):
+        self.assertEqual(self.os.path.normpath("a//b"), "a/b")
+        self.assertEqual(self.os.path.normpath("a/./b"), "a/b")
+        self.assertEqual(self.os.path.normpath("a/foo/../b"), "a/b")
+
+    def test_exists(self):
+        self.assertTrue(self.os.path.exists("/"))
+        self.assertTrue(self.os.path.exists("/one"))
+        self.assertTrue(self.os.path.exists("/one/two"))
+        self.assertTrue(self.os.path.exists("/toplevel.txt"))
+        self.assertFalse(self.os.path.isdir("/empty/not_there"))
+        self.assertFalse(self.os.path.isdir("gibberish"))
+
+    def test_isdir(self):
+        self.assertTrue(self.os.path.isdir("/empty"))
+        self.assertTrue(self.os.path.isdir("/one/two"))
+        self.assertFalse(self.os.path.isdir("/empty/not_there"))
+        self.assertFalse(self.os.path.isdir("gibberish"))
+        self.assertFalse(self.os.path.isdir("toplevel.txt"))
+
+    def test_islink(self):
+        self.assertFalse(self.os.path.islink("/empty"))
+        self.assertFalse(self.os.path.islink("/doesnt_even_exist"))