summaryrefslogtreecommitdiff
path: root/fixtures/_fixtures/popen.py
diff options
context:
space:
mode:
Diffstat (limited to 'fixtures/_fixtures/popen.py')
-rw-r--r--fixtures/_fixtures/popen.py125
1 files changed, 125 insertions, 0 deletions
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