Commits

Zero Piraeus committed 8bd3372

Add 'Context' class; improve 'with_fixture'; add a bunch of tests.

  • Participants
  • Parent commits 9917a84

Comments (0)

Files changed (4)

File .session.vim

 below split tests/test_fix/__init__.py
 below split tests/test_fix/examples.py
 below split tests/test_fix/test.py
+below split tests/test_fix/test_context.py
 below split tests/test_fix/test_with_fixture.py
 below split .hgignore
 below split .session.vim
 from functools import wraps
 
 
+class Context(dict):
+
+    """Store context information for a fixture."""
+
+    def __init__(self, *args, **kwargs):
+        dict.__init__(self, *args, **kwargs)
+        self.__dict__ = self
+
+    def __repr__(self):
+        kwarg_str = ", ".join("%s=%r" % kv for kv in sorted(self.items()))
+        return "%s(%s)" % (self.__class__.__name__, kwarg_str)
+
+
 def with_fixture(create_fixture, **kwargs):
     """Decorate a callable with a fixture."""
-    context = {}
+    context = Context()
     fixture = create_fixture(context, **kwargs)
     if isinstance(fixture, tuple):
         setup, teardown = fixture
     def wrap(func):
         """Wrap the callable."""
         @wraps(func)
-        def call():
+        def call(*args, **kwargs):
             """Call `setup` and `teardown` before and after `func`."""
-            setup()
-            result = func(context)
-            teardown()
+            try:
+                setup()
+                result = func(context, *args, **kwargs)
+            finally:
+                try:
+                    teardown()
+                finally:
+                    context.clear()
             return result
         return call
 

File tests/test_fix/test_context.py

+"""Tests for the `fix.fix.Context` class."""
+
+from fix.fix import Context
+
+KWARGS = {"squee": "kapow", "boing": "thunk"}
+
+
+def test_exists():
+    """`fix.Context` function exists"""
+    assert isinstance(Context, type)
+
+
+def test_instantiate():
+    """`Context` class can be instantiated"""
+    context = Context()
+    assert isinstance(context, Context)
+
+
+def test_instantiate_dict():
+    """`Context` class can be instantiated with a dict"""
+    context = Context(KWARGS)
+    assert isinstance(context, Context)
+
+
+def test_instantiate_kwargs():
+    """`Context` class can be instantiated with keyword arguments"""
+    context = Context(**KWARGS)
+    assert isinstance(context, Context)
+
+
+def test_attr_access():
+    """`Context` instance has attribute access"""
+    context = Context(**KWARGS)
+    assert context.boing == "thunk"  # pylint: disable=E1101
+
+
+def test_key_access():
+    """`Context` instance has key access"""
+    context = Context(**KWARGS)
+    assert context["squee"] == "kapow"
+
+
+def test_repr():
+    """`Context` instance has a sensible `repr` value"""
+    kwarg_str = ", ".join("%s=%r" % kv for kv in sorted(KWARGS.items()))
+    assert repr(Context(**KWARGS)) == "Context(%s)" % kwarg_str

File tests/test_fix/test_with_fixture.py

 """Tests for the `fix.fix.with_fixture` function."""
 
+from __future__ import with_statement
+
+import os
+import shutil
+import tempfile
+
 from types import FunctionType
 
 from fix import with_fixture
 
 
 def test_setup_only():
-    """`@with_fixture()` handles fixtures which return one function"""
+    """`setup_only` fixture works as expected"""
 
-    def fixture(context):
-        """Create a fixture that assigns "bar" to `context["foo"]`."""
+    def setup_only(context):
+        """A fixture with no `teardown()`."""
+
         def setup():
-            """Assign "bar" to `context["foo"]`."""
-            context["foo"] = "bar"
+            """Add something to the context."""
+            assert context == {}
+            context.squee = "kapow"
+
         return setup
 
-    @with_fixture(fixture)
+    @with_fixture(setup_only)
     def case(context):
-        """Return `context["foo"]`."""
-        return context["foo"]
+        """Check that the context has been set up."""
+        assert context == {"squee": "kapow"}
 
-    assert case() == "bar"  # pylint: disable=E1120
+    case()  # pylint: disable=E1120
 
 
 def test_setup_teardown():
-    """`@with_fixture()` handles fixtures which return a tuple of functions"""
-    pseudoglobal = {}
+    """`setup_teardown` fixture works as expected"""
 
-    def fixture(context):
-        """Create a fixture that assigns "bar" to `context["foo"]`."""
+    def setup_teardown(context):
+        """A fixture with both `setup()` and `teardown()`."""
 
         def setup():
-            """Assign "bar" to `context["foo"]`."""
-            context["foo"] = "squee"
+            """Add something to the context."""
+            assert context == {}
+            context.squee = "kapow"
 
         def teardown():
-            """Delete `context["foo"]`."""
-            del context["foo"]
-            pseudoglobal["teardown_context"] = context
+            """Check that `context.squee` has changed."""
+            assert context == {"squee": "boing"}
 
         return setup, teardown
 
-    @with_fixture(fixture)
+    @with_fixture(setup_teardown)
     def case(context):
-        """Return `context["foo"]`."""
-        return context["foo"]
+        """Alter the context."""
+        assert context == {"squee": "kapow"}
+        context.squee = "boing"
 
-    assert case() == "squee"  # pylint: disable=E1120
-    assert pseudoglobal["teardown_context"] == {}
+    case()  # pylint: disable=E1120
+
+
+def test_multiple_invocation():
+    """`multiple` fixture creates a fresh context each invocation"""
+
+    def multiple(context):
+        """A fixture to be invoked multiple times."""
+
+        def setup():
+            """Add something to the context."""
+            assert context == {}
+            context.squee = "kapow"
+
+        def teardown():
+            """Check that `context.squee` has changed."""
+            assert context == {"squee": "kapow", "boing": "thunk"}
+
+        return setup, teardown
+
+    @with_fixture(multiple)
+    def case(context):
+        """Add to the context."""
+        assert context == {"squee": "kapow"}
+        context.boing = "thunk"
+
+    for _ in range(3):
+        case()  # pylint: disable=E1120
+
+
+def test_external():
+    """`external` fixture interacts as expected with the 'real world'."""
+
+    def external(context, files=3):
+        """A fixture to manipulate temporary files and directories."""
+
+        def setup():
+            """Create some temporary files."""
+            context.temp_dir = tempfile.mkdtemp()
+            context.filenames = ["file_%03d" % i for i in range(files)]
+            for filename in context.filenames:
+                with open(os.path.join(context.temp_dir, filename), "w") as f:
+                    f.write("This is the file %r.\n" % filename)
+
+        def teardown():
+            """Delete the temporary files created in `setup()`."""
+            shutil.rmtree(context.temp_dir)
+
+        return setup, teardown
+
+    @with_fixture(external, files=5)
+    def check_files(context):
+        """Return the number of present and absent files."""
+        present = 0
+        absent = 0
+        for filename in context.filenames:
+            if os.path.exists(os.path.join(context.temp_dir, filename)):
+                present += 1
+            else:
+                absent += 1
+        return context.temp_dir, present, absent
+
+    temp_dir, present, absent = check_files()  # pylint: disable=E1120
+    assert not os.path.exists(temp_dir)
+    assert present == 5
+    assert absent == 0