summaryrefslogtreecommitdiff
path: root/fixtures
diff options
context:
space:
mode:
authorRobert Collins <robertc@robertcollins.net>2014-09-25 15:01:50 +1200
committerRobert Collins <robertc@robertcollins.net>2014-09-25 15:01:50 +1200
commitf61bdd267c9f4a039bad10249e8ae618b29a515e (patch)
tree40f4b9793826d603b090483d4abbbd7eefb39ba3 /fixtures
parent9f9d89ce718463b24cd3910b9a99efb60b3c9e1b (diff)
downloadfixtures-f61bdd267c9f4a039bad10249e8ae618b29a515e.tar.gz
Migrate to git and pbr.
No functional changes.
Diffstat (limited to 'fixtures')
-rw-r--r--fixtures/__init__.py126
-rw-r--r--fixtures/_fixtures/__init__.py74
-rw-r--r--fixtures/_fixtures/environ.py58
-rw-r--r--fixtures/_fixtures/logger.py109
-rw-r--r--fixtures/_fixtures/monkeypatch.py79
-rw-r--r--fixtures/_fixtures/packagepath.py48
-rw-r--r--fixtures/_fixtures/popen.py125
-rw-r--r--fixtures/_fixtures/pythonpackage.py66
-rw-r--r--fixtures/_fixtures/pythonpath.py43
-rw-r--r--fixtures/_fixtures/streams.py97
-rw-r--r--fixtures/_fixtures/tempdir.py70
-rw-r--r--fixtures/_fixtures/temphomedir.py32
-rw-r--r--fixtures/_fixtures/timeout.py68
-rw-r--r--fixtures/callmany.py100
-rw-r--r--fixtures/fixture.py338
-rw-r--r--fixtures/testcase.py60
-rw-r--r--fixtures/tests/__init__.py47
-rw-r--r--fixtures/tests/_fixtures/__init__.py33
-rw-r--r--fixtures/tests/_fixtures/test_environ.py75
-rw-r--r--fixtures/tests/_fixtures/test_logger.py168
-rw-r--r--fixtures/tests/_fixtures/test_monkeypatch.py83
-rw-r--r--fixtures/tests/_fixtures/test_packagepath.py43
-rw-r--r--fixtures/tests/_fixtures/test_popen.py104
-rw-r--r--fixtures/tests/_fixtures/test_pythonpackage.py52
-rw-r--r--fixtures/tests/_fixtures/test_pythonpath.py44
-rw-r--r--fixtures/tests/_fixtures/test_streams.py105
-rw-r--r--fixtures/tests/_fixtures/test_tempdir.py96
-rw-r--r--fixtures/tests/_fixtures/test_temphomedir.py46
-rw-r--r--fixtures/tests/_fixtures/test_timeout.py66
-rw-r--r--fixtures/tests/helpers.py33
-rw-r--r--fixtures/tests/test_callmany.py68
-rw-r--r--fixtures/tests/test_fixture.py313
-rw-r--r--fixtures/tests/test_testcase.py97
33 files changed, 2966 insertions, 0 deletions
diff --git a/fixtures/__init__.py b/fixtures/__init__.py
new file mode 100644
index 0000000..bfbd3dd
--- /dev/null
+++ b/fixtures/__init__.py
@@ -0,0 +1,126 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, 2011, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+
+"""Fixtures provides a sensible contract for reusable test fixtures.
+
+It also provides glue for using these in common test runners and acts as a
+common repository for widely used Fixture classes.
+
+See the README for a manual, and the docstrings on individual functions and
+methods for details.
+
+Most users will want to look at TestWithFixtures and Fixture, to start with.
+"""
+
+# same format as sys.version_info: "A tuple containing the five components of
+# the version number: major, minor, micro, releaselevel, and serial. All
+# values except releaselevel are integers; the release level is 'alpha',
+# 'beta', 'candidate', or 'final'. The version_info value corresponding to the
+# Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a
+# releaselevel of 'dev' for unreleased under-development code.
+#
+# If the releaselevel is 'alpha' then the major/minor/micro components are not
+# established at this point, and setup.py will use a version of next-$(revno).
+# If the releaselevel is 'final', then the tarball will be major.minor.micro.
+# Otherwise it is major.minor.micro~$(revno).
+__version__ = (0, 3, 16, 'final', 0)
+
+__all__ = [
+ 'ByteStream',
+ 'DetailStream',
+ 'EnvironmentVariable',
+ 'EnvironmentVariableFixture',
+ 'FakeLogger',
+ 'FakePopen',
+ 'Fixture',
+ 'FunctionFixture',
+ 'LogHandler',
+ 'LoggerFixture',
+ 'MethodFixture',
+ 'MonkeyPatch',
+ 'NestedTempfile',
+ 'PackagePathEntry',
+ 'PopenFixture',
+ 'PythonPackage',
+ 'PythonPathEntry',
+ 'StringStream',
+ 'TempDir',
+ 'TempHomeDir',
+ 'TestWithFixtures',
+ 'Timeout',
+ 'TimeoutException',
+ '__version__',
+ 'version',
+ ]
+
+
+import pbr.version
+
+from fixtures.fixture import (
+ Fixture,
+ FunctionFixture,
+ MethodFixture,
+ )
+from fixtures._fixtures import (
+ ByteStream,
+ DetailStream,
+ EnvironmentVariable,
+ EnvironmentVariableFixture,
+ FakeLogger,
+ FakePopen,
+ LoggerFixture,
+ LogHandler,
+ MonkeyPatch,
+ NestedTempfile,
+ PackagePathEntry,
+ PopenFixture,
+ PythonPackage,
+ PythonPathEntry,
+ StringStream,
+ TempDir,
+ TempHomeDir,
+ Timeout,
+ TimeoutException,
+ )
+from fixtures.testcase import TestWithFixtures
+
+# same format as sys.version_info: "A tuple containing the five components of
+# the version number: major, minor, micro, releaselevel, and serial. All
+# values except releaselevel are integers; the release level is 'alpha',
+# 'beta', 'candidate', or 'final'. The version_info value corresponding to the
+# Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a
+# releaselevel of 'dev' for unreleased under-development code.
+#
+# If the releaselevel is 'alpha' then the major/minor/micro components are not
+# established at this point, and setup.py will use a version of next-$(revno).
+# If the releaselevel is 'final', then the tarball will be major.minor.micro.
+# Otherwise it is major.minor.micro~$(revno).
+
+# Uncomment when pbr 0.11 is released.
+#_version = pbr.version.VersionInfo('fixtures').semantic_version()
+#__version__ = _version.version_tuple()
+#version = _version.release_string()
+__version__ = (0, 3, 17, 'alpha', 0)
+
+
+def test_suite():
+ import fixtures.tests
+ return fixtures.tests.test_suite()
+
+
+def load_tests(loader, standard_tests, pattern):
+ standard_tests.addTests(loader.loadTestsFromNames(["fixtures.tests"]))
+ return standard_tests
diff --git a/fixtures/_fixtures/__init__.py b/fixtures/_fixtures/__init__.py
new file mode 100644
index 0000000..1d54858
--- /dev/null
+++ b/fixtures/_fixtures/__init__.py
@@ -0,0 +1,74 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, 2011, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+
+"""Included fixtures."""
+
+__all__ = [
+ 'ByteStream',
+ 'DetailStream',
+ 'EnvironmentVariable',
+ 'EnvironmentVariableFixture',
+ 'FakeLogger',
+ 'FakePopen',
+ 'LoggerFixture',
+ 'LogHandler',
+ 'MonkeyPatch',
+ 'NestedTempfile',
+ 'PackagePathEntry',
+ 'PopenFixture',
+ 'PythonPackage',
+ 'PythonPathEntry',
+ 'StringStream',
+ 'TempDir',
+ 'TempHomeDir',
+ 'Timeout',
+ 'TimeoutException',
+ ]
+
+
+from fixtures._fixtures.environ import (
+ EnvironmentVariable,
+ EnvironmentVariableFixture,
+ )
+from fixtures._fixtures.logger import (
+ FakeLogger,
+ LoggerFixture,
+ LogHandler,
+ )
+from fixtures._fixtures.monkeypatch import MonkeyPatch
+from fixtures._fixtures.popen import (
+ FakePopen,
+ PopenFixture,
+ )
+from fixtures._fixtures.packagepath import PackagePathEntry
+from fixtures._fixtures.pythonpackage import PythonPackage
+from fixtures._fixtures.pythonpath import PythonPathEntry
+from fixtures._fixtures.streams import (
+ ByteStream,
+ DetailStream,
+ StringStream,
+ )
+from fixtures._fixtures.tempdir import (
+ NestedTempfile,
+ TempDir,
+ )
+from fixtures._fixtures.temphomedir import (
+ TempHomeDir,
+ )
+from fixtures._fixtures.timeout import (
+ Timeout,
+ TimeoutException,
+ )
diff --git a/fixtures/_fixtures/environ.py b/fixtures/_fixtures/environ.py
new file mode 100644
index 0000000..5494429
--- /dev/null
+++ b/fixtures/_fixtures/environ.py
@@ -0,0 +1,58 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, 2011, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+__all__ = [
+ 'EnvironmentVariable',
+ 'EnvironmentVariableFixture'
+ ]
+
+import os
+
+from fixtures import Fixture
+
+
+class EnvironmentVariable(Fixture):
+ """Isolate a specific environment variable."""
+
+ def __init__(self, varname, newvalue=None):
+ """Create an EnvironmentVariable fixture.
+
+ :param varname: the name of the variable to isolate.
+ :param newvalue: A value to set the variable to. If None, the variable
+ will be deleted.
+
+ During setup the variable will be deleted or assigned the requested
+ value, and this will be restored in cleanUp.
+ """
+ super(EnvironmentVariable, self).__init__()
+ self.varname = varname
+ self.newvalue = newvalue
+
+ def setUp(self):
+ super(EnvironmentVariable, self).setUp()
+ varname = self.varname
+ orig_value = os.environ.get(varname)
+ if orig_value is not None:
+ self.addCleanup(os.environ.__setitem__, varname, orig_value)
+ del os.environ[varname]
+ else:
+ self.addCleanup(os.environ.pop, varname, '')
+ if self.newvalue is not None:
+ os.environ[varname] = self.newvalue
+ else:
+ os.environ.pop(varname, '')
+
+
+EnvironmentVariableFixture = EnvironmentVariable
diff --git a/fixtures/_fixtures/logger.py b/fixtures/_fixtures/logger.py
new file mode 100644
index 0000000..e46de3a
--- /dev/null
+++ b/fixtures/_fixtures/logger.py
@@ -0,0 +1,109 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2011, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+from logging import StreamHandler, getLogger, INFO, Formatter
+
+from testtools.compat import _u
+
+from fixtures import Fixture
+from fixtures._fixtures.streams import StringStream
+
+__all__ = [
+ 'FakeLogger',
+ 'LoggerFixture',
+ 'LogHandler',
+ ]
+
+
+class LogHandler(Fixture):
+ """Replace a logger's handlers."""
+
+ def __init__(self, handler, name="", level=None, nuke_handlers=True):
+ """Create a LogHandler fixture.
+
+ :param handler: The handler to replace other handlers with.
+ If nuke_handlers is False, then added as an extra handler.
+ :param name: The name of the logger to replace. Defaults to "".
+ :param level: The log level to set, defaults to not changing the level.
+ :param nuke_handlers: If True remove all existing handles (prevents
+ existing messages going to e.g. stdout). Defaults to True.
+ """
+ super(LogHandler, self).__init__()
+ self.handler = handler
+ self._name = name
+ self._level = level
+ self._nuke_handlers = nuke_handlers
+
+ def setUp(self):
+ super(LogHandler, self).setUp()
+ logger = getLogger(self._name)
+ if self._level:
+ self.addCleanup(logger.setLevel, logger.level)
+ logger.setLevel(self._level)
+ if self._nuke_handlers:
+ for handler in reversed(logger.handlers):
+ self.addCleanup(logger.addHandler, handler)
+ logger.removeHandler(handler)
+ try:
+ logger.addHandler(self.handler)
+ finally:
+ self.addCleanup(logger.removeHandler, self.handler)
+
+
+class FakeLogger(Fixture):
+ """Replace a logger and capture its output."""
+
+ def __init__(self, name="", level=INFO, format=None, nuke_handlers=True):
+ """Create a FakeLogger fixture.
+
+ :param name: The name of the logger to replace. Defaults to "".
+ :param level: The log level to set, defaults to INFO.
+ :param format: Logging format to use. Defaults to capturing supplied
+ messages verbatim.
+ :param nuke_handlers: If True remove all existing handles (prevents
+ existing messages going to e.g. stdout). Defaults to True.
+
+ Example:
+
+ def test_log(self)
+ fixture = self.useFixture(LoggerFixture())
+ logging.info('message')
+ self.assertEqual('message', fixture.output)
+ """
+ super(FakeLogger, self).__init__()
+ self._name = name
+ self._level = level
+ self._format = format
+ self._nuke_handlers = nuke_handlers
+
+ def setUp(self):
+ super(FakeLogger, self).setUp()
+ name = _u("pythonlogging:'%s'") % self._name
+ output = self.useFixture(StringStream(name)).stream
+ self._output = output
+ handler = StreamHandler(output)
+ if self._format:
+ handler.setFormatter(Formatter(self._format))
+ self.useFixture(
+ LogHandler(handler, name=self._name, level=self._level,
+ nuke_handlers=self._nuke_handlers))
+
+ @property
+ def output(self):
+ self._output.seek(0)
+ return self._output.read()
+
+
+LoggerFixture = FakeLogger
diff --git a/fixtures/_fixtures/monkeypatch.py b/fixtures/_fixtures/monkeypatch.py
new file mode 100644
index 0000000..bfb7351
--- /dev/null
+++ b/fixtures/_fixtures/monkeypatch.py
@@ -0,0 +1,79 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+__all__ = [
+ 'MonkeyPatch'
+ ]
+
+import sys
+import types
+
+from fixtures import Fixture
+
+
+class MonkeyPatch(Fixture):
+ """Replace or delete an attribute."""
+
+ delete = object()
+
+ def __init__(self, name, new_value=None):
+ """Create a MonkeyPatch.
+
+ :param name: The fully qualified object name to override.
+ :param new_value: A value to set the name to. If set to
+ MonkeyPatch.delete the attribute will be deleted.
+
+ During setup the name will be deleted or assigned the requested value,
+ and this will be restored in cleanUp.
+ """
+ Fixture.__init__(self)
+ self.name = name
+ self.new_value = new_value
+
+ def setUp(self):
+ Fixture.setUp(self)
+ location, attribute = self.name.rsplit('.', 1)
+ # Import, swallowing all errors as any element of location may be
+ # a class or some such thing.
+ try:
+ __import__(location, {}, {})
+ except ImportError:
+ pass
+ components = location.split('.')
+ current = __import__(components[0], {}, {})
+ for component in components[1:]:
+ current = getattr(current, component)
+ sentinel = object()
+ old_value = getattr(current, attribute, sentinel)
+ if self.new_value is self.delete:
+ if old_value is not sentinel:
+ delattr(current, attribute)
+ else:
+ setattr(current, attribute, self.new_value)
+ if old_value is sentinel:
+ self.addCleanup(self._safe_delete, current, attribute)
+ else:
+ # Python 2's setattr transforms function into instancemethod
+ if (sys.version_info[0] == 2 and
+ isinstance(current, (type, types.ClassType)) and
+ isinstance(old_value, types.FunctionType)):
+ old_value = staticmethod(old_value)
+ self.addCleanup(setattr, current, attribute, old_value)
+
+ def _safe_delete(self, obj, attribute):
+ """Delete obj.attribute handling the case where its missing."""
+ sentinel = object()
+ if getattr(obj, attribute, sentinel) is not sentinel:
+ delattr(obj, attribute)
diff --git a/fixtures/_fixtures/packagepath.py b/fixtures/_fixtures/packagepath.py
new file mode 100644
index 0000000..43a9bf3
--- /dev/null
+++ b/fixtures/_fixtures/packagepath.py
@@ -0,0 +1,48 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2011, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+__all__ = [
+ 'PackagePathEntry'
+ ]
+
+import sys
+
+from fixtures import Fixture
+
+
+class PackagePathEntry(Fixture):
+ """Add a path to the path of a python package.
+
+ The python package needs to be already imported.
+
+ If this new path is already in the packages __path__ list then the __path__
+ list will not be altered.
+ """
+
+ def __init__(self, packagename, directory):
+ """Create a PackagePathEntry.
+
+ :param directory: The directory to add to the package.__path__.
+ """
+ self.packagename = packagename
+ self.directory = directory
+
+ def setUp(self):
+ Fixture.setUp(self)
+ path = sys.modules[self.packagename].__path__
+ if self.directory in path:
+ return
+ self.addCleanup(path.remove, self.directory)
+ path.append(self.directory)
diff --git a/fixtures/_fixtures/popen.py b/fixtures/_fixtures/popen.py
new file mode 100644
index 0000000..728e980
--- /dev/null
+++ b/fixtures/_fixtures/popen.py
@@ -0,0 +1,125 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, 2011, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+__all__ = [
+ 'FakePopen',
+ 'PopenFixture'
+ ]
+
+import random
+import subprocess
+
+from fixtures import Fixture
+
+
+class FakeProcess(object):
+ """A test double process, roughly meeting subprocess.Popen's contract."""
+
+ def __init__(self, args, info):
+ self._args = args
+ self.stdin = info.get('stdin')
+ self.stdout = info.get('stdout')
+ self.stderr = info.get('stderr')
+ self.pid = random.randint(0, 65536)
+ self._returncode = info.get('returncode', 0)
+ self.returncode = None
+
+ def communicate(self):
+ self.returncode = self._returncode
+ if self.stdout:
+ out = self.stdout.getvalue()
+ else:
+ out = ''
+ if self.stderr:
+ err = self.stderr.getvalue()
+ else:
+ err = ''
+ return out, err
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.wait()
+
+ def kill(self):
+ pass
+
+ def wait(self, timeout=None, endtime=None):
+ if self.returncode is None:
+ self.communicate()
+ return self.returncode
+
+
+class FakePopen(Fixture):
+ """Replace subprocess.Popen.
+
+ Primarily useful for testing, this fixture replaces subprocess.Popen with a
+ test double.
+
+ :ivar procs: A list of the processes created by the fixture.
+ """
+
+ _unpassed = object()
+
+ def __init__(self, get_info=lambda _:{}):
+ """Create a PopenFixture
+
+ :param get_info: Optional callback to control the behaviour of the
+ created process. This callback takes a kwargs dict for the Popen
+ call, and should return a dict with any desired attributes.
+ Only parameters that are supplied to the Popen call are in the
+ dict, making it possible to detect the difference between 'passed
+ with a default value' and 'not passed at all'.
+
+ e.g.
+ def get_info(proc_args):
+ self.assertEqual(subprocess.PIPE, proc_args['stdin'])
+ return {'stdin': StringIO('foobar')}
+
+ The default behaviour if no get_info is supplied is for the return
+ process to have returncode of None, empty streams and a random pid.
+ """
+ super(FakePopen, self).__init__()
+ self.get_info = get_info
+
+ def setUp(self):
+ super(FakePopen, self).setUp()
+ self.addCleanup(setattr, subprocess, 'Popen', subprocess.Popen)
+ subprocess.Popen = self
+ self.procs = []
+
+ # The method has the correct signature so we error appropriately if called
+ # wrongly.
+ def __call__(self, args, bufsize=_unpassed, executable=_unpassed,
+ stdin=_unpassed, stdout=_unpassed, stderr=_unpassed,
+ preexec_fn=_unpassed, close_fds=_unpassed, shell=_unpassed,
+ cwd=_unpassed, env=_unpassed, universal_newlines=_unpassed,
+ startupinfo=_unpassed, creationflags=_unpassed):
+ proc_args = dict(args=args)
+ local = locals()
+ for param in [
+ "bufsize", "executable", "stdin", "stdout", "stderr",
+ "preexec_fn", "close_fds", "shell", "cwd", "env",
+ "universal_newlines", "startupinfo", "creationflags"]:
+ if local[param] is not FakePopen._unpassed:
+ proc_args[param] = local[param]
+ proc_info = self.get_info(proc_args)
+ result = FakeProcess(proc_args, proc_info)
+ self.procs.append(result)
+ return result
+
+
+PopenFixture = FakePopen
diff --git a/fixtures/_fixtures/pythonpackage.py b/fixtures/_fixtures/pythonpackage.py
new file mode 100644
index 0000000..4fbd278
--- /dev/null
+++ b/fixtures/_fixtures/pythonpackage.py
@@ -0,0 +1,66 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+__all__ = [
+ 'PythonPackage'
+ ]
+
+import os.path
+
+from fixtures import Fixture
+from fixtures._fixtures.tempdir import TempDir
+
+
+class PythonPackage(Fixture):
+ """Create a temporary Python package.
+
+ :ivar base: The path of the directory containing the module. E.g. for a
+ module 'foo', the path base + '/foo/__init__.py' would be the file path
+ for the module.
+ """
+
+ def __init__(self, packagename, modulelist, init=True):
+ """Create a PythonPackage.
+
+ :param packagename: The name of the package to create - e.g.
+ 'toplevel.subpackage.'
+ :param modulelist: List of modules to include in the package.
+ Each module should be a tuple with the filename and content it
+ should have.
+ :param init: If false, do not create a missing __init__.py. When
+ True, if modulelist does not include an __init__.py, an empty
+ one is created.
+ """
+ self.packagename = packagename
+ self.modulelist = modulelist
+ self.init = init
+
+ def setUp(self):
+ Fixture.setUp(self)
+ self.base = self.useFixture(TempDir()).path
+ base = self.base
+ root = os.path.join(base, self.packagename)
+ os.mkdir(root)
+ init_seen = not self.init
+ for modulename, contents in self.modulelist:
+ stream = open(os.path.join(root, modulename), 'wb')
+ try:
+ stream.write(contents)
+ finally:
+ stream.close()
+ if modulename == '__init__.py':
+ init_seen = True
+ if not init_seen:
+ open(os.path.join(root, '__init__.py'), 'wb').close()
diff --git a/fixtures/_fixtures/pythonpath.py b/fixtures/_fixtures/pythonpath.py
new file mode 100644
index 0000000..89c1968
--- /dev/null
+++ b/fixtures/_fixtures/pythonpath.py
@@ -0,0 +1,43 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2011, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+__all__ = [
+ 'PythonPathEntry'
+ ]
+
+import sys
+
+from fixtures import Fixture
+
+
+class PythonPathEntry(Fixture):
+ """Add a path to sys.path.
+
+ If the path is already in sys.path, sys.path will not be altered.
+ """
+
+ def __init__(self, directory):
+ """Create a PythonPathEntry.
+
+ :param directory: The directory to add to sys.path.
+ """
+ self.directory = directory
+
+ def setUp(self):
+ Fixture.setUp(self)
+ if self.directory in sys.path:
+ return
+ self.addCleanup(sys.path.remove, self.directory)
+ sys.path.append(self.directory)
diff --git a/fixtures/_fixtures/streams.py b/fixtures/_fixtures/streams.py
new file mode 100644
index 0000000..188d8e3
--- /dev/null
+++ b/fixtures/_fixtures/streams.py
@@ -0,0 +1,97 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2012, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+__all__ = [
+ 'ByteStream',
+ 'DetailStream',
+ 'StringStream',
+ ]
+
+import io
+import sys
+
+from fixtures import Fixture
+import testtools
+
+
+class Stream(Fixture):
+ """Expose a file-like object as a detail.
+
+ :attr stream: The file-like object.
+ """
+
+ def __init__(self, detail_name, stream_factory):
+ """Create a ByteStream.
+
+ :param detail_name: Use this as the name of the stream.
+ :param stream_factory: Called to construct a pair of streams:
+ (write_stream, content_stream).
+ """
+ self._detail_name = detail_name
+ self._stream_factory = stream_factory
+
+ def setUp(self):
+ super(Stream, self).setUp()
+ write_stream, read_stream = self._stream_factory()
+ self.stream = write_stream
+ self.addDetail(self._detail_name,
+ testtools.content.content_from_stream(read_stream, seek_offset=0))
+
+
+def _byte_stream_factory():
+ result = io.BytesIO()
+ return (result, result)
+
+
+def ByteStream(detail_name):
+ """Provide a file-like object that accepts bytes and expose as a detail.
+
+ :param detail_name: The name of the detail.
+ :return: A fixture which has an attribute `stream` containing the file-like
+ object.
+ """
+ return Stream(detail_name, _byte_stream_factory)
+
+
+def _string_stream_factory():
+ lower = io.BytesIO()
+ upper = io.TextIOWrapper(lower, encoding="utf8")
+ # See http://bugs.python.org/issue7955
+ upper._CHUNK_SIZE = 1
+ # In theory, this is sufficient and correct, but on Python2,
+ # upper.write(_b('foo")) will whinge louadly.
+ if sys.version_info[0] < 3:
+ upper_write = upper.write
+ def safe_write(str_or_bytes):
+ if type(str_or_bytes) is str:
+ str_or_bytes = str_or_bytes.decode('utf8')
+ return upper_write(str_or_bytes)
+ upper.write = safe_write
+ return upper, lower
+
+
+def StringStream(detail_name):
+ """Provide a file-like object that accepts strings and expose as a detail.
+
+ :param detail_name: The name of the detail.
+ :return: A fixture which has an attribute `stream` containing the file-like
+ object.
+ """
+ return Stream(detail_name, _string_stream_factory)
+
+
+def DetailStream(detail_name):
+ """Deprecated alias for ByteStream."""
+ return ByteStream(detail_name)
diff --git a/fixtures/_fixtures/tempdir.py b/fixtures/_fixtures/tempdir.py
new file mode 100644
index 0000000..663d3eb
--- /dev/null
+++ b/fixtures/_fixtures/tempdir.py
@@ -0,0 +1,70 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+__all__ = [
+ 'NestedTempfile',
+ 'TempDir',
+ ]
+
+import os
+import shutil
+import tempfile
+
+import fixtures
+
+
+class TempDir(fixtures.Fixture):
+ """Create a temporary directory.
+
+ :ivar path: The path of the temporary directory.
+ """
+
+ def __init__(self, rootdir=None):
+ """Create a TempDir.
+
+ :param rootdir: If supplied force the temporary directory to be a
+ child of rootdir.
+ """
+ self.rootdir = rootdir
+
+ def setUp(self):
+ super(TempDir, self).setUp()
+ self.path = tempfile.mkdtemp(dir=self.rootdir)
+ self.addCleanup(shutil.rmtree, self.path, ignore_errors=True)
+
+ def join(self, *children):
+ """Return an absolute path, given one relative to this ``TempDir``.
+
+ WARNING: This does not do any checking of ``children`` to make sure
+ they aren't walking up the tree using path segments like '..' or
+ '/usr'. Use at your own risk.
+ """
+ return os.path.abspath(os.path.join(self.path, *children))
+
+
+class NestedTempfile(fixtures.Fixture):
+ """Nest all temporary files and directories inside another directory.
+
+ This temporarily monkey-patches the default location that the `tempfile`
+ package creates temporary files and directories in to be a new temporary
+ directory. This new temporary directory is removed when the fixture is torn
+ down.
+ """
+
+ def setUp(self):
+ super(NestedTempfile, self).setUp()
+ tempdir = self.useFixture(TempDir()).path
+ patch = fixtures.MonkeyPatch("tempfile.tempdir", tempdir)
+ self.useFixture(patch)
diff --git a/fixtures/_fixtures/temphomedir.py b/fixtures/_fixtures/temphomedir.py
new file mode 100644
index 0000000..2601a8d
--- /dev/null
+++ b/fixtures/_fixtures/temphomedir.py
@@ -0,0 +1,32 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Canonical Ltd.
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+__all__ = [
+ 'TempHomeDir',
+ ]
+
+import fixtures
+from fixtures._fixtures.tempdir import TempDir
+
+
+class TempHomeDir(TempDir):
+ """Create a temporary directory and set it as $HOME
+
+ :ivar path: the path of the temporary directory.
+ """
+
+ def setUp(self):
+ super(TempHomeDir, self).setUp()
+ self.useFixture(fixtures.EnvironmentVariable("HOME", self.path))
diff --git a/fixtures/_fixtures/timeout.py b/fixtures/_fixtures/timeout.py
new file mode 100644
index 0000000..7863b0d
--- /dev/null
+++ b/fixtures/_fixtures/timeout.py
@@ -0,0 +1,68 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (C) 2011, Martin Pool <mbp@sourcefrog.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+
+"""Timeout fixture."""
+
+
+import signal
+
+import fixtures
+
+__all__ = [
+ 'Timeout',
+ 'TimeoutException',
+ ]
+
+
+class TimeoutException(Exception):
+ """Timeout expired"""
+
+
+class Timeout(fixtures.Fixture):
+ """Fixture that aborts the contained code after a number of seconds.
+
+ The interrupt can be either gentle, in which case TimeoutException is
+ raised, or not gentle, in which case the process will typically be aborted
+ by SIGALRM.
+
+ Cautions:
+ * This has no effect on Windows.
+ * Only one Timeout can be used at any time per process.
+ """
+
+ def __init__(self, timeout_secs, gentle):
+ self.timeout_secs = timeout_secs
+ self.alarm_fn = getattr(signal, 'alarm', None)
+ self.gentle = gentle
+
+ def signal_handler(self, signum, frame):
+ raise TimeoutException()
+
+ def setUp(self):
+ super(Timeout, self).setUp()
+ if self.alarm_fn is None:
+ return # Can't run on Windows
+ if self.gentle:
+ # Install a handler for SIGARLM so we can raise an exception rather
+ # than the default handler executing, which kills the process.
+ old_handler = signal.signal(signal.SIGALRM, self.signal_handler)
+ # We add the slarm cleanup before the cleanup for the signal handler,
+ # otherwise there is a race condition where the signal handler is
+ # cleaned up but the alarm still fires.
+ self.addCleanup(lambda: self.alarm_fn(0))
+ self.alarm_fn(self.timeout_secs)
+ if self.gentle:
+ self.addCleanup(lambda: signal.signal(signal.SIGALRM, old_handler))
diff --git a/fixtures/callmany.py b/fixtures/callmany.py
new file mode 100644
index 0000000..23580cb
--- /dev/null
+++ b/fixtures/callmany.py
@@ -0,0 +1,100 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+__all__ = [
+ 'CallMany',
+ ]
+
+import sys
+
+from testtools.compat import (
+ reraise,
+ )
+from testtools.helpers import try_import
+
+
+class MultipleExceptions(Exception):
+ """Report multiple exc_info tuples in self.args."""
+
+MultipleExceptions = try_import(
+ "testtools.MultipleExceptions", MultipleExceptions)
+
+
+class CallMany(object):
+ """A stack of functions which will all be called on __call__.
+
+ CallMany also acts as a context manager for convenience.
+
+ Functions are called in last pushed first executed order.
+
+ This is used by Fixture to manage its addCleanup feature.
+ """
+
+ def __init__(self):
+ self._cleanups = []
+
+ def push(self, cleanup, *args, **kwargs):
+ """Add a function to be called from __call__.
+
+ On __call__ all functions are called - see __call__ for details on how
+ multiple exceptions are handled.
+
+ :param cleanup: A callable to call during cleanUp.
+ :param *args: Positional args for cleanup.
+ :param kwargs: Keyword args for cleanup.
+ :return: None
+ """
+ self._cleanups.append((cleanup, args, kwargs))
+
+ def __call__(self, raise_errors=True):
+ """Run all the registered functions.
+
+ :param raise_errors: Deprecated parameter from before testtools gained
+ MultipleExceptions. raise_errors defaults to True. When True
+ if exception(s) are raised while running functions, they are
+ re-raised after all the functions have run. If multiple exceptions
+ are raised, they are all wrapped into a MultipleExceptions object,
+ and that is raised.
+ Thus, to cach a specific exception from a function run by __call__,
+ you need to catch both the exception and MultipleExceptions, and
+ then check within a MultipleExceptions instance for an occurance of
+ the type you wish to catch.
+ :return: Either None or a list of the exc_info() for each exception
+ that occured if raise_errors was False.
+ """
+ cleanups = reversed(self._cleanups)
+ self._cleanups = []
+ result = []
+ for cleanup, args, kwargs in cleanups:
+ try:
+ cleanup(*args, **kwargs)
+ except Exception:
+ result.append(sys.exc_info())
+ if result and raise_errors:
+ if 1 == len(result):
+ error = result[0]
+ reraise(error[0], error[1], error[2])
+ else:
+ raise MultipleExceptions(*result)
+ if not raise_errors:
+ return result
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self()
+ return False # propogate exceptions from the with body.
+
diff --git a/fixtures/fixture.py b/fixtures/fixture.py
new file mode 100644
index 0000000..2cf966d
--- /dev/null
+++ b/fixtures/fixture.py
@@ -0,0 +1,338 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+__all__ = [
+ 'Fixture',
+ 'FunctionFixture',
+ 'MethodFixture',
+ 'MultipleExceptions',
+ ]
+
+import itertools
+import sys
+
+from testtools.compat import (
+ advance_iterator,
+ reraise,
+ )
+from testtools.helpers import try_import
+
+from fixtures.callmany import (
+ CallMany,
+ # Deprecated, imported for compatibility.
+ MultipleExceptions,
+ )
+
+gather_details = try_import("testtools.testcase.gather_details")
+
+# This would be better in testtools (or a common library)
+def combine_details(source_details, target_details):
+ """Add every value from source to target deduping common keys."""
+ for name, content_object in source_details.items():
+ new_name = name
+ disambiguator = itertools.count(1)
+ while new_name in target_details:
+ new_name = '%s-%d' % (name, advance_iterator(disambiguator))
+ name = new_name
+ target_details[name] = content_object
+
+
+class Fixture(object):
+ """A Fixture representing some state or resource.
+
+ Often used in tests, a Fixture must be setUp before using it, and cleanUp
+ called after it is finished with (because many Fixture classes have
+ external resources such as temporary directories).
+
+ The reset() method can be called to perform cleanUp and setUp automatically
+ and potentially faster.
+ """
+
+ def addCleanup(self, cleanup, *args, **kwargs):
+ """Add a clean function to be called from cleanUp.
+
+ All cleanup functions are called - see cleanUp for details on how
+ multiple exceptions are handled.
+
+ If for some reason you need to cancel cleanups, call
+ self._clear_cleanups.
+
+ :param cleanup: A callable to call during cleanUp.
+ :param *args: Positional args for cleanup.
+ :param kwargs: Keyword args for cleanup.
+ :return: None
+ """
+ self._cleanups.push(cleanup, *args, **kwargs)
+
+ def addDetail(self, name, content_object):
+ """Add a detail to the Fixture.
+
+ This may only be called after setUp has been called.
+
+ :param name: The name for the detail being added. Overrides existing
+ identically named details.
+ :param content_object: The content object (meeting the
+ testtools.content.Content protocol) being added.
+ """
+ self._details[name] = content_object
+
+ def cleanUp(self, raise_first=True):
+ """Cleanup the fixture.
+
+ This function will free all resources managed by the Fixture, restoring
+ it (and any external facilities such as databases, temporary
+ directories and so forth_ to their original state.
+
+ This should not typically be overridden, see addCleanup instead.
+
+ :param raise_first: Deprecated parameter from before testtools gained
+ MultipleExceptions. raise_first defaults to True. When True
+ if a single exception is raised, it is reraised after all the
+ cleanUps have run. If multiple exceptions are raised, they are
+ all wrapped into a MultipleExceptions object, and that is reraised.
+ Thus, to cach a specific exception from cleanUp, you need to catch
+ both the exception and MultipleExceptions, and then check within
+ a MultipleExceptions instance for the type you're catching.
+ :return: A list of the exc_info() for each exception that occured if
+ raise_first was False
+ """
+ try:
+ return self._cleanups(raise_errors=raise_first)
+ finally:
+ self._clear_cleanups()
+
+ def _clear_cleanups(self):
+ """Clean the cleanup queue without running them.
+
+ This is a helper that can be useful for subclasses which define
+ reset(): they may perform something equivalent to a typical cleanUp
+ without actually calling the cleanups.
+
+ This also clears the details dict.
+ """
+ self._cleanups = CallMany()
+ self._details = {}
+ self._detail_sources = []
+
+ def __enter__(self):
+ self.setUp()
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ try:
+ self._cleanups()
+ finally:
+ self._clear_cleanups()
+ return False # propogate exceptions from the with body.
+
+ def getDetails(self):
+ """Get the current details registered with the fixture.
+
+ This does not return the internal dictionary: mutating it will have no
+ effect. If you need to mutate it, just do so directly.
+
+ :return: Dict from name -> content_object.
+ """
+ result = dict(self._details)
+ for source in self._detail_sources:
+ combine_details(source.getDetails(), result)
+ return result
+
+ def setUp(self):
+ """Prepare the Fixture for use.
+
+ This should be overridden by most concrete fixtures. When overriding
+ be sure to include self.addCleanup calls to restore the fixture to
+ an un-setUp state, so that a single Fixture instance can be reused.
+
+ After setUp is called, the fixture will have one or more attributes
+ which can be used (these depend totally on the concrete subclass).
+
+ :return: None.
+ """
+ self._clear_cleanups()
+
+ def reset(self):
+ """Reset a setUp Fixture to the 'just setUp' state again.
+
+ The default implementation calls
+ self.cleanUp()
+ self.setUp()
+
+ but this function may be overridden to provide an optimised routine to
+ achieve the same result.
+
+ :return: None.
+ """
+ self.cleanUp()
+ self.setUp()
+
+ def useFixture(self, fixture):
+ """Use another fixture.
+
+ The fixture will be setUp, and self.addCleanup(fixture.cleanUp) called.
+
+ :param fixture: The fixture to use.
+ :return: The fixture, after setting it up and scheduling a cleanup for
+ it.
+ """
+ try:
+ fixture.setUp()
+ except:
+ # The child failed to come up, capture any details it has (copying
+ # the content, it may go away anytime).
+ if gather_details is not None:
+ gather_details(fixture.getDetails(), self._details)
+ raise
+ else:
+ self.addCleanup(fixture.cleanUp)
+ # Calls to getDetails while this fixture is setup will return
+ # details from the child fixture.
+ self._detail_sources.append(fixture)
+ return fixture
+
+
+class FunctionFixture(Fixture):
+ """An adapter to use function(s) as a Fixture.
+
+ Typically used when an existing object or function interface exists but you
+ wish to use it as a Fixture (e.g. because fixtures are in use in your test
+ suite and this will fit in better).
+
+ To adapt an object with differently named setUp and cleanUp methods:
+ fixture = FunctionFixture(object.install, object.__class__.remove)
+ Note that the indirection via __class__ is to get an unbound method
+ which can accept the result from install. See also MethodFixture which
+ is specialised for objects.
+
+ To adapt functions:
+ fixture = FunctionFixture(tempfile.mkdtemp, shutil.rmtree)
+
+ With a reset function:
+ fixture = FunctionFixture(setup, cleanup, reset)
+
+ :ivar fn_result: The result of the setup_fn. Undefined outside of the
+ setUp, cleanUp context.
+ """
+
+ def __init__(self, setup_fn, cleanup_fn=None, reset_fn=None):
+ """Create a FunctionFixture.
+
+ :param setup_fn: A callable which takes no parameters and returns the
+ thing you want to use. e.g.
+ def setup_fn():
+ return 42
+ The result of setup_fn is assigned to the fn_result attribute bu
+ FunctionFixture.setUp.
+ :param cleanup_fn: Optional callable which takes a single parameter, which
+ must be that which is returned from the setup_fn. This is called
+ from cleanUp.
+ :param reset_fn: Optional callable which takes a single parameter like
+ cleanup_fn, but also returns a new object for use as the fn_result:
+ if defined this replaces the use of cleanup_fn and setup_fn when
+ reset() is called.
+ """
+ super(FunctionFixture, self).__init__()
+ self.setup_fn = setup_fn
+ self.cleanup_fn = cleanup_fn
+ self.reset_fn = reset_fn
+
+ def setUp(self):
+ super(FunctionFixture, self).setUp()
+ fn_result = self.setup_fn()
+ self._maybe_cleanup(fn_result)
+
+ def reset(self):
+ if self.reset_fn is None:
+ super(FunctionFixture, self).reset()
+ else:
+ self._clear_cleanups()
+ fn_result = self.reset_fn(self.fn_result)
+ self._maybe_cleanup(fn_result)
+
+ def _maybe_cleanup(self, fn_result):
+ self.addCleanup(delattr, self, 'fn_result')
+ if self.cleanup_fn is not None:
+ self.addCleanup(self.cleanup_fn, fn_result)
+ self.fn_result = fn_result
+
+
+class MethodFixture(Fixture):
+ """An adapter to use a function as a Fixture.
+
+ Typically used when an existing object exists but you wish to use it as a
+ Fixture (e.g. because fixtures are in use in your test suite and this will
+ fit in better).
+
+ To adapt an object with setUp / tearDown methods:
+ fixture = MethodFixture(object)
+ If setUp / tearDown / reset are missing, they simply won't be called.
+
+ The object is exposed on fixture.obj.
+
+ To adapt an object with differently named setUp and cleanUp methods:
+ fixture = MethodFixture(object, setup=object.mySetUp,
+ teardown=object.myTearDown)
+
+ With a differently named reset function:
+ fixture = MethodFixture(object, reset=object.myReset)
+
+ :ivar obj: The object which is being wrapped.
+ """
+
+ def __init__(self, obj, setup=None, cleanup=None, reset=None):
+ """Create a MethodFixture.
+
+ :param obj: The object to wrap. Exposed as fixture.obj
+ :param setup: A method which takes no parameters. e.g.
+ def setUp(self):
+ self.value = 42
+ If setup is not supplied, and the object has a setUp method, that
+ method is used, otherwise nothing will happen during fixture.setUp.
+ :param cleanup: Optional method to cleanup the object's state. If
+ not supplied the method 'tearDown' is used if it exists.
+ :param reset: Optional method to reset the wrapped object for use.
+ If not supplied, then the method 'reset' is used if it exists,
+ otherwise cleanUp and setUp are called as per Fixture.reset().
+ """
+ super(MethodFixture, self).__init__()
+ self.obj = obj
+ if setup is None:
+ setup = getattr(obj, 'setUp', None)
+ if setup is None:
+ setup = lambda:None
+ self._setup = setup
+ if cleanup is None:
+ cleanup = getattr(obj, 'tearDown', None)
+ if cleanup is None:
+ cleanup = lambda:None
+ self._cleanup = cleanup
+ if reset is None:
+ reset = getattr(obj, 'reset', None)
+ self._reset = reset
+
+ def setUp(self):
+ super(MethodFixture, self).setUp()
+ self._setup()
+
+ def cleanUp(self):
+ super(MethodFixture, self).cleanUp()
+ self._cleanup()
+
+ def reset(self):
+ if self._reset is None:
+ super(MethodFixture, self).reset()
+ else:
+ self._reset()
diff --git a/fixtures/testcase.py b/fixtures/testcase.py
new file mode 100644
index 0000000..1d6a85c
--- /dev/null
+++ b/fixtures/testcase.py
@@ -0,0 +1,60 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+__all__ = [
+ 'TestWithFixtures',
+ ]
+
+import unittest
+
+from fixtures.fixture import gather_details
+
+
+class TestWithFixtures(unittest.TestCase):
+ """A TestCase with a helper function to use fixtures.
+
+ Normally used as a mix-in class to add useFixture.
+
+ Note that test classes such as testtools.TestCase which already have a
+ ``useFixture`` method do not need this mixed in.
+ """
+
+ def useFixture(self, fixture):
+ """Use fixture in a test case.
+
+ The fixture will be setUp, and self.addCleanup(fixture.cleanUp) called.
+
+ :param fixture: The fixture to use.
+ :return: The fixture, after setting it up and scheduling a cleanup for
+ it.
+ """
+ use_details = (
+ gather_details is not None and
+ getattr(self, "addDetail", None) is not None)
+ try:
+ fixture.setUp()
+ except:
+ if use_details:
+ # Capture the details now, in case the fixture goes away.
+ gather_details(fixture.getDetails(), self.getDetails())
+ raise
+ else:
+ self.addCleanup(fixture.cleanUp)
+ if use_details:
+ # Capture the details from the fixture during test teardown;
+ # this will evaluate the details before tearing down the
+ # fixture.
+ self.addCleanup(gather_details, fixture, self)
+ return fixture
diff --git a/fixtures/tests/__init__.py b/fixtures/tests/__init__.py
new file mode 100644
index 0000000..5e66653
--- /dev/null
+++ b/fixtures/tests/__init__.py
@@ -0,0 +1,47 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+import doctest
+import sys
+import unittest
+
+import fixtures.tests._fixtures
+
+
+def test_suite():
+ standard_tests = unittest.TestSuite()
+ loader = unittest.TestLoader()
+ return load_tests(loader, standard_tests, None)
+
+
+def load_tests(loader, standard_tests, pattern):
+ test_modules = [
+ 'callmany',
+ 'fixture',
+ 'testcase',
+ ]
+ prefix = "fixtures.tests.test_"
+ test_mod_names = [prefix + test_module for test_module in test_modules]
+ standard_tests.addTests(loader.loadTestsFromNames(test_mod_names))
+ if sys.version_info >= (2, 7):
+ # 2.7 calls load_tests for us
+ standard_tests.addTests(loader.loadTestsFromName('fixtures.tests._fixtures'))
+ else:
+ # We need to call it ourselves.
+ standard_tests.addTests(fixtures.tests._fixtures.load_tests(
+ loader, loader.loadTestsFromName('fixtures.tests._fixtures'), pattern))
+ doctest.set_unittest_reportflags(doctest.REPORT_ONLY_FIRST_FAILURE)
+ standard_tests.addTest(doctest.DocFileSuite("../../README"))
+ return standard_tests
diff --git a/fixtures/tests/_fixtures/__init__.py b/fixtures/tests/_fixtures/__init__.py
new file mode 100644
index 0000000..e4d9403
--- /dev/null
+++ b/fixtures/tests/_fixtures/__init__.py
@@ -0,0 +1,33 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+def load_tests(loader, standard_tests, pattern):
+ test_modules = [
+ 'environ',
+ 'logger',
+ 'monkeypatch',
+ 'packagepath',
+ 'popen',
+ 'pythonpackage',
+ 'pythonpath',
+ 'streams',
+ 'tempdir',
+ 'temphomedir',
+ 'timeout',
+ ]
+ prefix = "fixtures.tests._fixtures.test_"
+ test_mod_names = [prefix + test_module for test_module in test_modules]
+ standard_tests.addTests(loader.loadTestsFromNames(test_mod_names))
+ return standard_tests
diff --git a/fixtures/tests/_fixtures/test_environ.py b/fixtures/tests/_fixtures/test_environ.py
new file mode 100644
index 0000000..64594c9
--- /dev/null
+++ b/fixtures/tests/_fixtures/test_environ.py
@@ -0,0 +1,75 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, 2011, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+import os
+
+import testtools
+
+from fixtures import EnvironmentVariable, TestWithFixtures
+
+
+class TestEnvironmentVariable(testtools.TestCase, TestWithFixtures):
+
+ def test_setup_ignores_missing(self):
+ fixture = EnvironmentVariable('FIXTURES_TEST_VAR')
+ os.environ.pop('FIXTURES_TEST_VAR', '')
+ self.useFixture(fixture)
+ self.assertEqual(None, os.environ.get('FIXTURES_TEST_VAR'))
+
+ def test_setup_sets_when_missing(self):
+ fixture = EnvironmentVariable('FIXTURES_TEST_VAR', 'bar')
+ os.environ.pop('FIXTURES_TEST_VAR', '')
+ self.useFixture(fixture)
+ self.assertEqual('bar', os.environ.get('FIXTURES_TEST_VAR'))
+
+ def test_setup_deletes(self):
+ fixture = EnvironmentVariable('FIXTURES_TEST_VAR')
+ os.environ['FIXTURES_TEST_VAR'] = 'foo'
+ self.useFixture(fixture)
+ self.assertEqual(None, os.environ.get('FIXTURES_TEST_VAR'))
+
+ def test_setup_overrides(self):
+ fixture = EnvironmentVariable('FIXTURES_TEST_VAR', 'bar')
+ os.environ['FIXTURES_TEST_VAR'] = 'foo'
+ self.useFixture(fixture)
+ self.assertEqual('bar', os.environ.get('FIXTURES_TEST_VAR'))
+
+ def test_cleanup_deletes_when_missing(self):
+ fixture = EnvironmentVariable('FIXTURES_TEST_VAR')
+ os.environ.pop('FIXTURES_TEST_VAR', '')
+ with fixture:
+ os.environ['FIXTURES_TEST_VAR'] = 'foo'
+ self.assertEqual(None, os.environ.get('FIXTURES_TEST_VAR'))
+
+ def test_cleanup_deletes_when_set(self):
+ fixture = EnvironmentVariable('FIXTURES_TEST_VAR', 'bar')
+ os.environ.pop('FIXTURES_TEST_VAR', '')
+ with fixture:
+ os.environ['FIXTURES_TEST_VAR'] = 'foo'
+ self.assertEqual(None, os.environ.get('FIXTURES_TEST_VAR'))
+
+ def test_cleanup_restores_when_missing(self):
+ fixture = EnvironmentVariable('FIXTURES_TEST_VAR')
+ os.environ['FIXTURES_TEST_VAR'] = 'bar'
+ with fixture:
+ os.environ.pop('FIXTURES_TEST_VAR', '')
+ self.assertEqual('bar', os.environ.get('FIXTURES_TEST_VAR'))
+
+ def test_cleanup_restores_when_set(self):
+ fixture = EnvironmentVariable('FIXTURES_TEST_VAR')
+ os.environ['FIXTURES_TEST_VAR'] = 'bar'
+ with fixture:
+ os.environ['FIXTURES_TEST_VAR'] = 'quux'
+ self.assertEqual('bar', os.environ.get('FIXTURES_TEST_VAR'))
diff --git a/fixtures/tests/_fixtures/test_logger.py b/fixtures/tests/_fixtures/test_logger.py
new file mode 100644
index 0000000..1f69459
--- /dev/null
+++ b/fixtures/tests/_fixtures/test_logger.py
@@ -0,0 +1,168 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2011, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+import logging
+
+from testtools import TestCase
+from testtools.compat import StringIO
+
+from fixtures import (
+ FakeLogger,
+ LogHandler,
+ TestWithFixtures,
+ )
+
+
+class FakeLoggerTest(TestCase, TestWithFixtures):
+
+ def setUp(self):
+ super(FakeLoggerTest, self).setUp()
+ self.logger = logging.getLogger()
+ self.addCleanup(self.removeHandlers, self.logger)
+
+ def removeHandlers(self, logger):
+ for handler in logger.handlers:
+ logger.removeHandler(handler)
+
+ def test_output_property_has_output(self):
+ fixture = self.useFixture(FakeLogger())
+ logging.info("some message")
+ self.assertEqual("some message\n", fixture.output)
+
+ def test_replace_and_restore_handlers(self):
+ stream = StringIO()
+ logger = logging.getLogger()
+ logger.addHandler(logging.StreamHandler(stream))
+ logger.setLevel(logging.INFO)
+ logging.info("one")
+ fixture = FakeLogger()
+ with fixture:
+ logging.info("two")
+ logging.info("three")
+ self.assertEqual("two\n", fixture.output)
+ self.assertEqual("one\nthree\n", stream.getvalue())
+
+ def test_preserving_existing_handlers(self):
+ stream = StringIO()
+ self.logger.addHandler(logging.StreamHandler(stream))
+ self.logger.setLevel(logging.INFO)
+ fixture = FakeLogger(nuke_handlers=False)
+ with fixture:
+ logging.info("message")
+ self.assertEqual("message\n", fixture.output)
+ self.assertEqual("message\n", stream.getvalue())
+
+ def test_logging_level_restored(self):
+ self.logger.setLevel(logging.DEBUG)
+ fixture = FakeLogger(level=logging.WARNING)
+ with fixture:
+ # The fixture won't capture this, because the DEBUG level
+ # is lower than the WARNING one
+ logging.debug("debug message")
+ self.assertEqual(logging.WARNING, self.logger.level)
+ self.assertEqual("", fixture.output)
+ self.assertEqual(logging.DEBUG, self.logger.level)
+
+ def test_custom_format(self):
+ fixture = FakeLogger(format="%(module)s")
+ self.useFixture(fixture)
+ logging.info("message")
+ self.assertEqual("test_logger\n", fixture.output)
+
+ def test_logging_output_included_in_details(self):
+ fixture = FakeLogger()
+ detail_name = "pythonlogging:''"
+ with fixture:
+ content = fixture.getDetails()[detail_name]
+ # Output after getDetails is called is included.
+ logging.info('some message')
+ self.assertEqual("some message\n", content.as_text())
+ # The old content object returns the old usage after cleanUp (not
+ # strictly needed but convenient). Note that no guarantee is made that
+ # it will work after setUp is called again. [It does on Python 2.x, not
+ # on 3.x]
+ self.assertEqual("some message\n", content.as_text())
+ with fixture:
+ # A new one returns new output:
+ self.assertEqual("", fixture.getDetails()[detail_name].as_text())
+ # The original content object may either fail, or return the old
+ # content (it must not have been reset..).
+ try:
+ self.assertEqual("some message\n", content.as_text())
+ except AssertionError:
+ raise
+ except:
+ pass
+
+
+class LogHandlerTest(TestCase, TestWithFixtures):
+
+ class CustomHandler(logging.Handler):
+
+ def __init__(self, *args, **kwargs):
+ """Create the instance, and add a records attribute."""
+ logging.Handler.__init__(self, *args, **kwargs)
+ self.msgs = []
+
+ def emit(self, record):
+ self.msgs.append(record.msg)
+
+ def setUp(self):
+ super(LogHandlerTest, self).setUp()
+ self.logger = logging.getLogger()
+ self.addCleanup(self.removeHandlers, self.logger)
+
+ def removeHandlers(self, logger):
+ for handler in logger.handlers:
+ logger.removeHandler(handler)
+
+ def test_captures_logging(self):
+ fixture = self.useFixture(LogHandler(self.CustomHandler()))
+ logging.info("some message")
+ self.assertEqual(["some message"], fixture.handler.msgs)
+
+ def test_replace_and_restore_handlers(self):
+ stream = StringIO()
+ logger = logging.getLogger()
+ logger.addHandler(logging.StreamHandler(stream))
+ logger.setLevel(logging.INFO)
+ logging.info("one")
+ fixture = LogHandler(self.CustomHandler())
+ with fixture:
+ logging.info("two")
+ logging.info("three")
+ self.assertEqual(["two"], fixture.handler.msgs)
+ self.assertEqual("one\nthree\n", stream.getvalue())
+
+ def test_preserving_existing_handlers(self):
+ stream = StringIO()
+ self.logger.addHandler(logging.StreamHandler(stream))
+ self.logger.setLevel(logging.INFO)
+ fixture = LogHandler(self.CustomHandler(), nuke_handlers=False)
+ with fixture:
+ logging.info("message")
+ self.assertEqual(["message"], fixture.handler.msgs)
+ self.assertEqual("message\n", stream.getvalue())
+
+ def test_logging_level_restored(self):
+ self.logger.setLevel(logging.DEBUG)
+ fixture = LogHandler(self.CustomHandler(), level=logging.WARNING)
+ with fixture:
+ # The fixture won't capture this, because the DEBUG level
+ # is lower than the WARNING one
+ logging.debug("debug message")
+ self.assertEqual(logging.WARNING, self.logger.level)
+ self.assertEqual([], fixture.handler.msgs)
+ self.assertEqual(logging.DEBUG, self.logger.level)
diff --git a/fixtures/tests/_fixtures/test_monkeypatch.py b/fixtures/tests/_fixtures/test_monkeypatch.py
new file mode 100644
index 0000000..1a84d7f
--- /dev/null
+++ b/fixtures/tests/_fixtures/test_monkeypatch.py
@@ -0,0 +1,83 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+import testtools
+
+from fixtures import MonkeyPatch, TestWithFixtures
+
+reference = 23
+
+class C(object):
+ @staticmethod
+ def foo(): pass
+def bar(): pass
+
+class TestMonkeyPatch(testtools.TestCase, TestWithFixtures):
+
+ def test_patch_and_restore(self):
+ fixture = MonkeyPatch(
+ 'fixtures.tests._fixtures.test_monkeypatch.reference', 45)
+ self.assertEqual(23, reference)
+ fixture.setUp()
+ try:
+ self.assertEqual(45, reference)
+ finally:
+ fixture.cleanUp()
+ self.assertEqual(23, reference)
+
+ def test_patch_missing_attribute(self):
+ fixture = MonkeyPatch(
+ 'fixtures.tests._fixtures.test_monkeypatch.new_attr', True)
+ self.assertFalse('new_attr' in globals())
+ fixture.setUp()
+ try:
+ self.assertEqual(True, new_attr)
+ finally:
+ fixture.cleanUp()
+ self.assertFalse('new_attr' in globals())
+
+ def test_delete_existing_attribute(self):
+ fixture = MonkeyPatch(
+ 'fixtures.tests._fixtures.test_monkeypatch.reference',
+ MonkeyPatch.delete)
+ self.assertEqual(23, reference)
+ fixture.setUp()
+ try:
+ self.assertFalse('reference' in globals())
+ finally:
+ fixture.cleanUp()
+ self.assertEqual(23, reference)
+
+ def test_delete_missing_attribute(self):
+ fixture = MonkeyPatch(
+ 'fixtures.tests._fixtures.test_monkeypatch.new_attr',
+ MonkeyPatch.delete)
+ self.assertFalse('new_attr' in globals())
+ fixture.setUp()
+ try:
+ self.assertFalse('new_attr' in globals())
+ finally:
+ fixture.cleanUp()
+ self.assertFalse('new_attr' in globals())
+
+ def test_patch_staticmethod(self):
+ oldfoo = C.foo
+ fixture = MonkeyPatch(
+ 'fixtures.tests._fixtures.test_monkeypatch.C.foo',
+ bar)
+ with fixture:
+ pass
+ self.assertEqual(oldfoo, C.foo)
+
diff --git a/fixtures/tests/_fixtures/test_packagepath.py b/fixtures/tests/_fixtures/test_packagepath.py
new file mode 100644
index 0000000..6833fdc
--- /dev/null
+++ b/fixtures/tests/_fixtures/test_packagepath.py
@@ -0,0 +1,43 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2011, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+import testtools
+
+import fixtures
+from fixtures import (
+ PackagePathEntry,
+ TempDir,
+ )
+
+
+class TestPackagePathEntry(testtools.TestCase):
+
+ def test_adds_missing_to_end_package_path(self):
+ uniquedir = self.useFixture(TempDir()).path
+ fixture = PackagePathEntry('fixtures', uniquedir)
+ self.assertFalse(uniquedir in fixtures.__path__)
+ with fixture:
+ self.assertTrue(uniquedir in fixtures.__path__)
+ self.assertFalse(uniquedir in fixtures.__path__)
+
+ def test_doesnt_alter_existing_entry(self):
+ existingdir = fixtures.__path__[0]
+ expectedlen = len(fixtures.__path__)
+ fixture = PackagePathEntry('fixtures', existingdir)
+ with fixture:
+ self.assertTrue(existingdir in fixtures.__path__)
+ self.assertEqual(expectedlen, len(fixtures.__path__))
+ self.assertTrue(existingdir in fixtures.__path__)
+ self.assertEqual(expectedlen, len(fixtures.__path__))
diff --git a/fixtures/tests/_fixtures/test_popen.py b/fixtures/tests/_fixtures/test_popen.py
new file mode 100644
index 0000000..98b762f
--- /dev/null
+++ b/fixtures/tests/_fixtures/test_popen.py
@@ -0,0 +1,104 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, 2011, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+import subprocess
+
+import testtools
+from testtools.compat import (
+ _b,
+ BytesIO,
+ )
+
+from fixtures import FakePopen, TestWithFixtures
+from fixtures._fixtures.popen import FakeProcess
+
+
+class TestFakePopen(testtools.TestCase, TestWithFixtures):
+
+ def test_installs_restores_global(self):
+ fixture = FakePopen()
+ popen = subprocess.Popen
+ fixture.setUp()
+ try:
+ self.assertEqual(subprocess.Popen, fixture)
+ finally:
+ fixture.cleanUp()
+ self.assertEqual(subprocess.Popen, popen)
+
+ def test___call___is_recorded(self):
+ fixture = self.useFixture(FakePopen())
+ proc = fixture(['foo', 'bar'], 1, None, 'in', 'out', 'err')
+ self.assertEqual(1, len(fixture.procs))
+ self.assertEqual(dict(args=['foo', 'bar'], bufsize=1, executable=None,
+ stdin='in', stdout='out', stderr='err'), proc._args)
+
+ def test_inject_content_stdout(self):
+ def get_info(args):
+ return {'stdout': 'stdout'}
+ fixture = self.useFixture(FakePopen(get_info))
+ proc = fixture(['foo'])
+ self.assertEqual('stdout', proc.stdout)
+
+ def test_handles_all_2_7_args(self):
+ all_args = dict(
+ args="args", bufsize="bufsize", executable="executable",
+ stdin="stdin", stdout="stdout", stderr="stderr",
+ preexec_fn="preexec_fn", close_fds="close_fds", shell="shell",
+ cwd="cwd", env="env", universal_newlines="universal_newlines",
+ startupinfo="startupinfo", creationflags="creationflags")
+ def get_info(proc_args):
+ self.assertEqual(all_args, proc_args)
+ return {}
+ fixture = self.useFixture(FakePopen(get_info))
+ proc = fixture(**all_args)
+
+ def test_custom_returncode(self):
+ def get_info(proc_args):
+ return dict(returncode=1)
+ proc = self.useFixture(FakePopen(get_info))(['foo'])
+ self.assertEqual(None, proc.returncode)
+ self.assertEqual(1, proc.wait())
+ self.assertEqual(1, proc.returncode)
+
+ def test_with_popen_custom(self):
+ fixture = self.useFixture(FakePopen())
+ with subprocess.Popen(['ls -lh']) as proc:
+ self.assertEqual(None, proc.returncode)
+
+
+class TestFakeProcess(testtools.TestCase):
+
+ def test_wait(self):
+ proc = FakeProcess({}, {})
+ proc.returncode = 45
+ self.assertEqual(45, proc.wait())
+
+ def test_communicate(self):
+ proc = FakeProcess({}, {})
+ self.assertEqual(('', ''), proc.communicate())
+ self.assertEqual(0, proc.returncode)
+
+ def test_communicate_with_out(self):
+ proc = FakeProcess({}, {'stdout': BytesIO(_b('foo'))})
+ self.assertEqual((_b('foo'), ''), proc.communicate())
+ self.assertEqual(0, proc.returncode)
+
+ def test_kill(self):
+ proc = FakeProcess({}, {})
+ self.assertIs(None, proc.kill())
+
+ def test_wait_with_timeout_and_endtime(self):
+ proc = FakeProcess({}, {})
+ self.assertEqual(0 , proc.wait(timeout=4, endtime=7))
diff --git a/fixtures/tests/_fixtures/test_pythonpackage.py b/fixtures/tests/_fixtures/test_pythonpackage.py
new file mode 100644
index 0000000..4e2160b
--- /dev/null
+++ b/fixtures/tests/_fixtures/test_pythonpackage.py
@@ -0,0 +1,52 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+import os.path
+
+import testtools
+from testtools.compat import _b
+
+from fixtures import PythonPackage, TestWithFixtures
+
+
+class TestPythonPackage(testtools.TestCase, TestWithFixtures):
+
+ def test_has_tempdir(self):
+ fixture = PythonPackage('foo', [])
+ fixture.setUp()
+ try:
+ self.assertTrue(os.path.isdir(fixture.base))
+ finally:
+ fixture.cleanUp()
+
+ def test_writes_package(self):
+ fixture = PythonPackage('foo', [('bar.py', _b('woo'))])
+ fixture.setUp()
+ try:
+ self.assertEqual('', open(os.path.join(fixture.base, 'foo',
+ '__init__.py')).read())
+ self.assertEqual('woo', open(os.path.join(fixture.base, 'foo',
+ 'bar.py')).read())
+ finally:
+ fixture.cleanUp()
+
+ def test_no__init__(self):
+ fixture = PythonPackage('foo', [('bar.py', _b('woo'))], init=False)
+ fixture.setUp()
+ try:
+ self.assertFalse(os.path.exists(os.path.join(fixture.base, 'foo',
+ '__init__.py')))
+ finally:
+ fixture.cleanUp()
diff --git a/fixtures/tests/_fixtures/test_pythonpath.py b/fixtures/tests/_fixtures/test_pythonpath.py
new file mode 100644
index 0000000..5bb6851
--- /dev/null
+++ b/fixtures/tests/_fixtures/test_pythonpath.py
@@ -0,0 +1,44 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2011, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+import sys
+
+import testtools
+
+from fixtures import (
+ PythonPathEntry,
+ TempDir,
+ )
+
+
+class TestPythonPathEntry(testtools.TestCase):
+
+ def test_adds_missing_to_end_sys_path(self):
+ uniquedir = self.useFixture(TempDir()).path
+ fixture = PythonPathEntry(uniquedir)
+ self.assertFalse(uniquedir in sys.path)
+ with fixture:
+ self.assertTrue(uniquedir in sys.path)
+ self.assertFalse(uniquedir in sys.path)
+
+ def test_doesnt_alter_existing_entry(self):
+ existingdir = sys.path[0]
+ expectedlen = len(sys.path)
+ fixture = PythonPathEntry(existingdir)
+ with fixture:
+ self.assertTrue(existingdir in sys.path)
+ self.assertEqual(expectedlen, len(sys.path))
+ self.assertTrue(existingdir in sys.path)
+ self.assertEqual(expectedlen, len(sys.path))
diff --git a/fixtures/tests/_fixtures/test_streams.py b/fixtures/tests/_fixtures/test_streams.py
new file mode 100644
index 0000000..68396cd
--- /dev/null
+++ b/fixtures/tests/_fixtures/test_streams.py
@@ -0,0 +1,105 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2012, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+from testtools import TestCase
+from testtools.compat import (
+ _b,
+ _u,
+ )
+from testtools.matchers import Contains
+
+from fixtures import (
+ ByteStream,
+ DetailStream,
+ StringStream,
+ )
+
+
+class DetailStreamTest(TestCase):
+
+ def test_doc_mentions_deprecated(self):
+ self.assertThat(DetailStream.__doc__, Contains('Deprecated'))
+
+
+class TestByteStreams(TestCase):
+
+ def test_empty_detail_stream(self):
+ detail_name = 'test'
+ fixture = ByteStream(detail_name)
+ with fixture:
+ content = fixture.getDetails()[detail_name]
+ self.assertEqual(_u(""), content.as_text())
+
+ def test_stream_content_in_details(self):
+ detail_name = 'test'
+ fixture = ByteStream(detail_name)
+ with fixture:
+ stream = fixture.stream
+ content = fixture.getDetails()[detail_name]
+ # Output after getDetails is called is included.
+ stream.write(_b("testing 1 2 3"))
+ self.assertEqual("testing 1 2 3", content.as_text())
+
+ def test_stream_content_reset(self):
+ detail_name = 'test'
+ fixture = ByteStream(detail_name)
+ with fixture:
+ stream = fixture.stream
+ content = fixture.getDetails()[detail_name]
+ stream.write(_b("testing 1 2 3"))
+ with fixture:
+ # The old content object returns the old usage
+ self.assertEqual(_u("testing 1 2 3"), content.as_text())
+ content = fixture.getDetails()[detail_name]
+ # A new fixture returns the new output:
+ stream = fixture.stream
+ stream.write(_b("1 2 3 testing"))
+ self.assertEqual(_u("1 2 3 testing"), content.as_text())
+
+
+class TestStringStreams(TestCase):
+
+ def test_empty_detail_stream(self):
+ detail_name = 'test'
+ fixture = StringStream(detail_name)
+ with fixture:
+ content = fixture.getDetails()[detail_name]
+ self.assertEqual(_u(""), content.as_text())
+
+ def test_stream_content_in_details(self):
+ detail_name = 'test'
+ fixture = StringStream(detail_name)
+ with fixture:
+ stream = fixture.stream
+ content = fixture.getDetails()[detail_name]
+ # Output after getDetails is called is included.
+ stream.write(_u("testing 1 2 3"))
+ self.assertEqual("testing 1 2 3", content.as_text())
+
+ def test_stream_content_reset(self):
+ detail_name = 'test'
+ fixture = StringStream(detail_name)
+ with fixture:
+ stream = fixture.stream
+ content = fixture.getDetails()[detail_name]
+ stream.write(_u("testing 1 2 3"))
+ with fixture:
+ # The old content object returns the old usage
+ self.assertEqual(_u("testing 1 2 3"), content.as_text())
+ content = fixture.getDetails()[detail_name]
+ # A new fixture returns the new output:
+ stream = fixture.stream
+ stream.write(_u("1 2 3 testing"))
+ self.assertEqual(_u("1 2 3 testing"), content.as_text())
diff --git a/fixtures/tests/_fixtures/test_tempdir.py b/fixtures/tests/_fixtures/test_tempdir.py
new file mode 100644
index 0000000..d0def55
--- /dev/null
+++ b/fixtures/tests/_fixtures/test_tempdir.py
@@ -0,0 +1,96 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+import os
+import tempfile
+
+import testtools
+from testtools.matchers import StartsWith
+
+from fixtures import (
+ NestedTempfile,
+ TempDir,
+ )
+
+
+class TestTempDir(testtools.TestCase):
+
+ def test_basic(self):
+ fixture = TempDir()
+ sentinel = object()
+ self.assertEqual(sentinel, getattr(fixture, 'path', sentinel))
+ fixture.setUp()
+ try:
+ path = fixture.path
+ self.assertTrue(os.path.isdir(path))
+ finally:
+ fixture.cleanUp()
+ self.assertFalse(os.path.isdir(path))
+
+ def test_under_dir(self):
+ root = self.useFixture(TempDir()).path
+ fixture = TempDir(root)
+ fixture.setUp()
+ with fixture:
+ self.assertThat(fixture.path, StartsWith(root))
+
+ def test_join(self):
+ temp_dir = self.useFixture(TempDir())
+ root = temp_dir.path
+ relpath = 'foo/bar/baz'
+ self.assertEqual(
+ os.path.join(root, relpath), temp_dir.join(relpath))
+
+ def test_join_multiple_children(self):
+ temp_dir = self.useFixture(TempDir())
+ root = temp_dir.path
+ self.assertEqual(
+ os.path.join(root, 'foo', 'bar', 'baz'),
+ temp_dir.join('foo', 'bar', 'baz'))
+
+ def test_join_naughty_children(self):
+ temp_dir = self.useFixture(TempDir())
+ root = temp_dir.path
+ self.assertEqual(
+ os.path.abspath(os.path.join(root, '..', 'bar', 'baz')),
+ temp_dir.join('..', 'bar', 'baz'))
+
+
+class NestedTempfileTest(testtools.TestCase):
+ """Tests for `NestedTempfile`."""
+
+ def test_normal(self):
+ # The temp directory is removed when the context is exited.
+ starting_tempdir = tempfile.gettempdir()
+ with NestedTempfile():
+ self.assertEqual(tempfile.tempdir, tempfile.gettempdir())
+ self.assertNotEqual(starting_tempdir, tempfile.tempdir)
+ self.assertTrue(os.path.isdir(tempfile.tempdir))
+ nested_tempdir = tempfile.tempdir
+ self.assertEqual(tempfile.tempdir, tempfile.gettempdir())
+ self.assertEqual(starting_tempdir, tempfile.tempdir)
+ self.assertFalse(os.path.isdir(nested_tempdir))
+
+ def test_exception(self):
+ # The temp directory is removed when the context is exited, even if
+ # the code running in context raises an exception.
+ class ContrivedException(Exception):
+ pass
+ try:
+ with NestedTempfile():
+ nested_tempdir = tempfile.tempdir
+ raise ContrivedException
+ except ContrivedException:
+ self.assertFalse(os.path.isdir(nested_tempdir))
diff --git a/fixtures/tests/_fixtures/test_temphomedir.py b/fixtures/tests/_fixtures/test_temphomedir.py
new file mode 100644
index 0000000..339ce2c
--- /dev/null
+++ b/fixtures/tests/_fixtures/test_temphomedir.py
@@ -0,0 +1,46 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2011 Canonical Ltd.
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+import os
+
+import testtools
+from testtools.matchers import StartsWith
+
+from fixtures import (
+ TempDir,
+ TempHomeDir,
+ )
+
+class TestTempDir(testtools.TestCase):
+
+ def test_basic(self):
+ fixture = TempHomeDir()
+ sentinel = object()
+ self.assertEqual(sentinel, getattr(fixture, 'path', sentinel))
+ fixture.setUp()
+ try:
+ path = fixture.path
+ self.assertTrue(os.path.isdir(path))
+ self.assertEqual(path, os.environ.get("HOME"))
+ finally:
+ fixture.cleanUp()
+ self.assertFalse(os.path.isdir(path))
+
+ def test_under_dir(self):
+ root = self.useFixture(TempDir()).path
+ fixture = TempHomeDir(root)
+ fixture.setUp()
+ with fixture:
+ self.assertThat(fixture.path, StartsWith(root))
diff --git a/fixtures/tests/_fixtures/test_timeout.py b/fixtures/tests/_fixtures/test_timeout.py
new file mode 100644
index 0000000..266e257
--- /dev/null
+++ b/fixtures/tests/_fixtures/test_timeout.py
@@ -0,0 +1,66 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (C) 2011, Martin Pool <mbp@sourcefrog.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+import signal
+import time
+
+import testtools
+from testtools.testcase import (
+ TestSkipped,
+ )
+
+import fixtures
+
+
+def sample_timeout_passes():
+ with fixtures.Timeout(100, gentle=True):
+ pass # Timeout shouldn't fire
+
+def sample_long_delay_with_gentle_timeout():
+ with fixtures.Timeout(1, gentle=True):
+ time.sleep(100) # Expected to be killed here.
+
+def sample_long_delay_with_harsh_timeout():
+ with fixtures.Timeout(1, gentle=False):
+ time.sleep(100) # Expected to be killed here.
+
+
+class TestTimeout(testtools.TestCase, fixtures.TestWithFixtures):
+
+ def requireUnix(self):
+ if getattr(signal, 'alarm', None) is None:
+ raise TestSkipped("no alarm() function")
+
+ def test_timeout_passes(self):
+ # This can pass even on Windows - the test is skipped.
+ sample_timeout_passes()
+
+ def test_timeout_gentle(self):
+ self.requireUnix()
+ self.assertRaises(
+ fixtures.TimeoutException,
+ sample_long_delay_with_gentle_timeout)
+
+ def test_timeout_harsh(self):
+ self.requireUnix()
+ # This will normally kill the whole process, which would be
+ # inconvenient. Let's hook the alarm here so we can observe it.
+ self.got_alarm = False
+ def sigalrm_handler(signum, frame):
+ self.got_alarm = True
+ old_handler = signal.signal(signal.SIGALRM, sigalrm_handler)
+ self.addCleanup(signal.signal, signal.SIGALRM, old_handler)
+ sample_long_delay_with_harsh_timeout()
+ self.assertTrue(self.got_alarm)
diff --git a/fixtures/tests/helpers.py b/fixtures/tests/helpers.py
new file mode 100644
index 0000000..ae0d8d3
--- /dev/null
+++ b/fixtures/tests/helpers.py
@@ -0,0 +1,33 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+import fixtures
+
+class LoggingFixture(fixtures.Fixture):
+
+ def __init__(self, suffix='', calls=None):
+ super(LoggingFixture, self).__init__()
+ if calls is None:
+ calls = []
+ self.calls = calls
+ self.suffix = suffix
+
+ def setUp(self):
+ super(LoggingFixture, self).setUp()
+ self.calls.append('setUp' + self.suffix)
+ self.addCleanup(self.calls.append, 'cleanUp' + self.suffix)
+
+ def reset(self):
+ self.calls.append('reset' + self.suffix)
diff --git a/fixtures/tests/test_callmany.py b/fixtures/tests/test_callmany.py
new file mode 100644
index 0000000..2bf28da
--- /dev/null
+++ b/fixtures/tests/test_callmany.py
@@ -0,0 +1,68 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+import types
+
+import testtools
+
+from fixtures.callmany import CallMany
+
+
+class TestCallMany(testtools.TestCase):
+
+ def test__call__raise_errors_false_callsall_returns_exceptions(self):
+ calls = []
+ def raise_exception1():
+ calls.append('1')
+ raise Exception('woo')
+ def raise_exception2():
+ calls.append('2')
+ raise Exception('woo')
+ call = CallMany()
+ call.push(raise_exception2)
+ call.push(raise_exception1)
+ exceptions = call(raise_errors=False)
+ self.assertEqual(['1', '2'], calls)
+ # There should be two exceptions
+ self.assertEqual(2, len(exceptions))
+ # They should be a sys.exc_info tuple.
+ self.assertEqual(3, len(exceptions[0]))
+ type, value, tb = exceptions[0]
+ self.assertEqual(Exception, type)
+ self.assertIsInstance(value, Exception)
+ self.assertEqual(('woo',), value.args)
+ self.assertIsInstance(tb, types.TracebackType)
+
+ def test_exit_propogates_exceptions(self):
+ call = CallMany()
+ call.__enter__()
+ self.assertEqual(False, call.__exit__(None, None, None))
+
+ def test_exit_runs_all_raises_first_exception(self):
+ calls = []
+ def raise_exception1():
+ calls.append('1')
+ raise Exception('woo')
+ def raise_exception2():
+ calls.append('2')
+ raise Exception('hoo')
+ call = CallMany()
+ call.push(raise_exception2)
+ call.push(raise_exception1)
+ call.__enter__()
+ exc = self.assertRaises(Exception, call.__exit__, None, None, None)
+ self.assertEqual(('woo',), exc.args[0][1].args)
+ self.assertEqual(('hoo',), exc.args[1][1].args)
+ self.assertEqual(['1', '2'], calls)
diff --git a/fixtures/tests/test_fixture.py b/fixtures/tests/test_fixture.py
new file mode 100644
index 0000000..74e6ad0
--- /dev/null
+++ b/fixtures/tests/test_fixture.py
@@ -0,0 +1,313 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+import types
+
+import testtools
+from testtools.content import text_content
+from testtools.testcase import skipIf
+
+import fixtures
+from fixtures.fixture import gather_details
+from fixtures.tests.helpers import LoggingFixture
+
+
+require_gather_details = skipIf(gather_details is None,
+ "gather_details() is not available.")
+
+
+# Note: the cleanup related tests are strictly speaking redundant, IFF they are
+# replaced with contract tests for correct use of CallMany.
+class TestFixture(testtools.TestCase):
+
+ def test_resetCallsSetUpCleanUp(self):
+ calls = []
+ class FixtureWithSetupOnly(fixtures.Fixture):
+ def setUp(self):
+ super(FixtureWithSetupOnly, self).setUp()
+ calls.append('setUp')
+ self.addCleanup(calls.append, 'cleanUp')
+ fixture = FixtureWithSetupOnly()
+ fixture.setUp()
+ fixture.reset()
+ fixture.cleanUp()
+ self.assertEqual(['setUp', 'cleanUp', 'setUp', 'cleanUp'], calls)
+
+ def test_reset_raises_if_cleanup_raises(self):
+ class FixtureWithSetupOnly(fixtures.Fixture):
+ def do_raise(self):
+ raise Exception('foo')
+ def setUp(self):
+ super(FixtureWithSetupOnly, self).setUp()
+ self.addCleanup(self.do_raise)
+ fixture = FixtureWithSetupOnly()
+ fixture.setUp()
+ exc = self.assertRaises(Exception, fixture.reset)
+ self.assertEqual(('foo',), exc.args)
+
+ def test_cleanUp_raise_first_false_callscleanups_returns_exceptions(self):
+ calls = []
+ def raise_exception1():
+ calls.append('1')
+ raise Exception('woo')
+ def raise_exception2():
+ calls.append('2')
+ raise Exception('woo')
+ class FixtureWithException(fixtures.Fixture):
+ def setUp(self):
+ super(FixtureWithException, self).setUp()
+ self.addCleanup(raise_exception2)
+ self.addCleanup(raise_exception1)
+ fixture = FixtureWithException()
+ fixture.setUp()
+ exceptions = fixture.cleanUp(raise_first=False)
+ self.assertEqual(['1', '2'], calls)
+ # There should be two exceptions
+ self.assertEqual(2, len(exceptions))
+ # They should be a sys.exc_info tuple.
+ self.assertEqual(3, len(exceptions[0]))
+ type, value, tb = exceptions[0]
+ self.assertEqual(Exception, type)
+ self.assertIsInstance(value, Exception)
+ self.assertEqual(('woo',), value.args)
+ self.assertIsInstance(tb, types.TracebackType)
+
+ def test_exit_propogates_exceptions(self):
+ fixture = fixtures.Fixture()
+ fixture.__enter__()
+ self.assertEqual(False, fixture.__exit__(None, None, None))
+
+ def test_exit_runs_all_raises_first_exception(self):
+ calls = []
+ def raise_exception1():
+ calls.append('1')
+ raise Exception('woo')
+ def raise_exception2():
+ calls.append('2')
+ raise Exception('hoo')
+ class FixtureWithException(fixtures.Fixture):
+ def setUp(self):
+ super(FixtureWithException, self).setUp()
+ self.addCleanup(raise_exception2)
+ self.addCleanup(raise_exception1)
+ fixture = FixtureWithException()
+ fixture.__enter__()
+ exc = self.assertRaises(Exception, fixture.__exit__, None, None, None)
+ self.assertEqual(('woo',), exc.args[0][1].args)
+ self.assertEqual(('hoo',), exc.args[1][1].args)
+ self.assertEqual(['1', '2'], calls)
+
+ def test_useFixture(self):
+ parent = LoggingFixture('-outer')
+ nested = LoggingFixture('-inner', calls=parent.calls)
+ parent.setUp()
+ parent.useFixture(nested)
+ parent.cleanUp()
+ self.assertEqual(
+ ['setUp-outer', 'setUp-inner', 'cleanUp-inner', 'cleanUp-outer'],
+ parent.calls)
+
+ @require_gather_details
+ def test_useFixture_details_captured_from_setUp(self):
+ # Details added during fixture set-up are gathered even if setUp()
+ # fails with an exception.
+ class SomethingBroke(Exception): pass
+ class BrokenFixture(fixtures.Fixture):
+ def setUp(self):
+ super(BrokenFixture, self).setUp()
+ self.addDetail('content', text_content("foobar"))
+ raise SomethingBroke()
+ broken_fixture = BrokenFixture()
+ class SimpleFixture(fixtures.Fixture):
+ def setUp(self):
+ super(SimpleFixture, self).setUp()
+ self.useFixture(broken_fixture)
+ simple_fixture = SimpleFixture()
+ self.assertRaises(SomethingBroke, simple_fixture.setUp)
+ self.assertEqual(
+ {"content": text_content("foobar")},
+ broken_fixture.getDetails())
+ self.assertEqual(
+ {"content": text_content("foobar")},
+ simple_fixture.getDetails())
+
+ def test_getDetails(self):
+ fixture = fixtures.Fixture()
+ with fixture:
+ self.assertEqual({}, fixture.getDetails())
+
+ def test_details_from_child_fixtures_are_returned(self):
+ parent = fixtures.Fixture()
+ with parent:
+ child = fixtures.Fixture()
+ parent.useFixture(child)
+ # Note that we add the detail *after* using the fixture: the parent
+ # has to query just-in-time.
+ child.addDetail('foo', 'content')
+ self.assertEqual({'foo': 'content'}, parent.getDetails())
+ # And dropping it from the child drops it from the parent.
+ del child._details['foo']
+ self.assertEqual({}, parent.getDetails())
+ # After cleanup the child details are still gone.
+ child.addDetail('foo', 'content')
+ self.assertEqual({}, parent.getDetails())
+
+ def test_duplicate_details_are_disambiguated(self):
+ parent = fixtures.Fixture()
+ with parent:
+ parent.addDetail('foo', 'parent-content')
+ child = fixtures.Fixture()
+ parent.useFixture(child)
+ # Note that we add the detail *after* using the fixture: the parent
+ # has to query just-in-time.
+ child.addDetail('foo', 'child-content')
+ self.assertEqual({'foo': 'parent-content',
+ 'foo-1': 'child-content',}, parent.getDetails())
+
+ def test_addDetail(self):
+ fixture = fixtures.Fixture()
+ with fixture:
+ fixture.addDetail('foo', 'content')
+ self.assertEqual({'foo': 'content'}, fixture.getDetails())
+ del fixture._details['foo']
+ self.assertEqual({}, fixture.getDetails())
+ fixture.addDetail('foo', 'content')
+ # Cleanup clears the details too.
+ self.assertEqual({}, fixture.getDetails())
+
+
+class TestFunctionFixture(testtools.TestCase):
+
+ def test_setup_only(self):
+ fixture = fixtures.FunctionFixture(lambda: 42)
+ fixture.setUp()
+ self.assertEqual(42, fixture.fn_result)
+ fixture.cleanUp()
+ self.assertFalse(hasattr(fixture, 'fn_result'))
+
+ def test_cleanup(self):
+ results = []
+ fixture = fixtures.FunctionFixture(lambda: 84, results.append)
+ fixture.setUp()
+ self.assertEqual(84, fixture.fn_result)
+ self.assertEqual([], results)
+ fixture.cleanUp()
+ self.assertEqual([84], results)
+
+ def test_reset(self):
+ results = []
+ expected = [21, 7]
+ def setUp():
+ return expected.pop(0)
+ def reset(result):
+ results.append(('reset', result))
+ return expected.pop(0)
+ fixture = fixtures.FunctionFixture(setUp, results.append, reset)
+ fixture.setUp()
+ self.assertEqual([], results)
+ fixture.reset()
+ self.assertEqual([('reset', 21)], results)
+ self.assertEqual(7, fixture.fn_result)
+ fixture.cleanUp()
+ self.assertEqual([('reset', 21), 7], results)
+
+
+class TestMethodFixture(testtools.TestCase):
+
+ def test_no_setup_cleanup(self):
+ class Stub:
+ pass
+ fixture = fixtures.MethodFixture(Stub())
+ fixture.setUp()
+ fixture.reset()
+ self.assertIsInstance(fixture.obj, Stub)
+ fixture.cleanUp()
+
+ def test_setup_only(self):
+ class Stub:
+ def setUp(self):
+ self.value = 42
+ fixture = fixtures.MethodFixture(Stub())
+ fixture.setUp()
+ self.assertEqual(42, fixture.obj.value)
+ self.assertIsInstance(fixture.obj, Stub)
+ fixture.cleanUp()
+
+ def test_cleanup_only(self):
+ class Stub:
+ value = None
+ def tearDown(self):
+ self.value = 42
+ fixture = fixtures.MethodFixture(Stub())
+ fixture.setUp()
+ self.assertEqual(None, fixture.obj.value)
+ self.assertIsInstance(fixture.obj, Stub)
+ fixture.cleanUp()
+ self.assertEqual(42, fixture.obj.value)
+
+ def test_cleanup(self):
+ class Stub:
+ def setUp(self):
+ self.value = 42
+ def tearDown(self):
+ self.value = 84
+ fixture = fixtures.MethodFixture(Stub())
+ fixture.setUp()
+ self.assertEqual(42, fixture.obj.value)
+ self.assertIsInstance(fixture.obj, Stub)
+ fixture.cleanUp()
+ self.assertEqual(84, fixture.obj.value)
+
+ def test_custom_setUp(self):
+ class Stub:
+ def mysetup(self):
+ self.value = 42
+ obj = Stub()
+ fixture = fixtures.MethodFixture(obj, setup=obj.mysetup)
+ fixture.setUp()
+ self.assertEqual(42, fixture.obj.value)
+ self.assertEqual(obj, fixture.obj)
+ fixture.cleanUp()
+
+ def test_custom_cleanUp(self):
+ class Stub:
+ value = 42
+ def mycleanup(self):
+ self.value = None
+ obj = Stub()
+ fixture = fixtures.MethodFixture(obj, cleanup=obj.mycleanup)
+ fixture.setUp()
+ self.assertEqual(42, fixture.obj.value)
+ self.assertEqual(obj, fixture.obj)
+ fixture.cleanUp()
+ self.assertEqual(None, fixture.obj.value)
+
+ def test_reset(self):
+ class Stub:
+ def setUp(self):
+ self.value = 42
+ def tearDown(self):
+ self.value = 84
+ def reset(self):
+ self.value = 126
+ obj = Stub()
+ fixture = fixtures.MethodFixture(obj, reset=obj.reset)
+ fixture.setUp()
+ self.assertEqual(obj, fixture.obj)
+ self.assertEqual(42, obj.value)
+ fixture.reset()
+ self.assertEqual(126, obj.value)
+ fixture.cleanUp()
+ self.assertEqual(84, obj.value)
diff --git a/fixtures/tests/test_testcase.py b/fixtures/tests/test_testcase.py
new file mode 100644
index 0000000..3f186c5
--- /dev/null
+++ b/fixtures/tests/test_testcase.py
@@ -0,0 +1,97 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2010, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+import unittest
+import testtools
+from testtools.content import text_content
+from testtools.testcase import skipIf
+
+import fixtures
+from fixtures import TestWithFixtures
+from fixtures.fixture import gather_details
+from fixtures.tests.helpers import LoggingFixture
+
+
+class TestTestWithFixtures(unittest.TestCase):
+
+ def test_useFixture(self):
+ fixture = LoggingFixture()
+ class SimpleTest(testtools.TestCase, TestWithFixtures):
+ def test_foo(self):
+ self.useFixture(fixture)
+ result = unittest.TestResult()
+ SimpleTest('test_foo').run(result)
+ self.assertTrue(result.wasSuccessful())
+ self.assertEqual(['setUp', 'cleanUp'], fixture.calls)
+
+ def test_useFixture_uses_raise_first(self):
+ calls = []
+ def raiser(ignored):
+ calls.append('called')
+ raise Exception('foo')
+ fixture = fixtures.FunctionFixture(lambda:None, raiser)
+ class SimpleTest(testtools.TestCase, TestWithFixtures):
+ def test_foo(self):
+ self.useFixture(fixture)
+ result = unittest.TestResult()
+ SimpleTest('test_foo').run(result)
+ self.assertFalse(result.wasSuccessful())
+ self.assertEqual(['called'], calls)
+
+ @skipIf(gather_details is None, "gather_details() is not available.")
+ def test_useFixture_details_captured_from_setUp(self):
+ # Details added during fixture set-up are gathered even if setUp()
+ # fails with an exception.
+ class SomethingBroke(Exception): pass
+ class BrokenFixture(fixtures.Fixture):
+ def setUp(self):
+ super(BrokenFixture, self).setUp()
+ self.addDetail('content', text_content("foobar"))
+ raise SomethingBroke()
+ broken_fixture = BrokenFixture()
+ class DetailedTestCase(TestWithFixtures, testtools.TestCase):
+ def setUp(self):
+ super(DetailedTestCase, self).setUp()
+ self.useFixture(broken_fixture)
+ def test(self):
+ pass
+ detailed_test_case = DetailedTestCase("test")
+ self.assertRaises(SomethingBroke, detailed_test_case.setUp)
+ self.assertEqual(
+ {"content": text_content("foobar")},
+ broken_fixture.getDetails())
+ self.assertEqual(
+ {"content": text_content("foobar")},
+ detailed_test_case.getDetails())
+
+ @skipIf(gather_details is None, "gather_details() is not available.")
+ def test_useFixture_details_not_captured_from_setUp(self):
+ # Details added during fixture set-up are not gathered if the test
+ # case does not have the ability to accept those details.
+ class SomethingBroke(Exception): pass
+ class BrokenFixture(fixtures.Fixture):
+ def setUp(self):
+ super(BrokenFixture, self).setUp()
+ self.addDetail('content', text_content("foobar"))
+ raise SomethingBroke()
+ broken_fixture = BrokenFixture()
+ class NonDetailedTestCase(TestWithFixtures, unittest.TestCase):
+ def setUp(self):
+ super(NonDetailedTestCase, self).setUp()
+ self.useFixture(broken_fixture)
+ def test(self):
+ pass
+ non_detailed_test_case = NonDetailedTestCase("test")
+ self.assertRaises(SomethingBroke, non_detailed_test_case.setUp)