[Python-modules-commits] [txfixtures] 01/02: New upstream version 0.1.4
Free Ekanayaka
freee at moszumanska.debian.org
Tue Nov 22 10:21:57 UTC 2016
This is an automated email from the git hooks/post-receive script.
freee pushed a commit to branch master
in repository txfixtures.
commit dacc2c0e6195ca659a0eb3af7d3487a0f2e7ca20
Author: Free Ekanayaka <freee at debian.org>
Date: Tue Nov 22 09:31:00 2016 +0000
New upstream version 0.1.4
---
PKG-INFO | 28 ++++++
README | 14 +++
setup.py | 38 ++++++++
txfixtures/__init__.py | 1 +
txfixtures/osutils.py | 128 +++++++++++++++++++++++++++
txfixtures/tachandler.py | 219 +++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 428 insertions(+)
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..c8100f1
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,28 @@
+Metadata-Version: 1.1
+Name: txfixtures
+Version: 0.1.4
+Summary: Treat Twisted applications as Python test fixtures
+Home-page: https://launchpad.net/txfixtures
+Author: Martin Pool
+Author-email: mbp at canonical.com
+License: UNKNOWN
+Description: ********************************************
+ Twisted integration with Python Testfixtures
+ ********************************************
+
+ txfixtures hooks into the testtools 'test fixture' interface, so that you can
+ write tests that rely on having an external Twisted daemon. ::
+
+ self.useFixture(LibrarianServerFixture)
+
+ See
+ https://launchpad.net/txfixtures
+ httsp://launchpad.net/testtools
+
+ Licence: GPLv3
+
+Platform: UNKNOWN
+Classifier: License :: OSI Approved :: GNU General Public License (GPL)
+Requires: fixtures
+Requires: testtools
+Requires: twisted
diff --git a/README b/README
new file mode 100644
index 0000000..4d4199d
--- /dev/null
+++ b/README
@@ -0,0 +1,14 @@
+********************************************
+Twisted integration with Python Testfixtures
+********************************************
+
+txfixtures hooks into the testtools 'test fixture' interface, so that you can
+write tests that rely on having an external Twisted daemon. ::
+
+ self.useFixture(LibrarianServerFixture)
+
+See
+ https://launchpad.net/txfixtures
+ httsp://launchpad.net/testtools
+
+Licence: GPLv3
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..a07e054
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,38 @@
+#! /usr/bin/env python
+
+"""distutils metadata/installer for txfixtures"""
+
+
+import os.path
+from distutils.core import setup
+
+import txfixtures
+
+
+def get_version():
+ return '.'.join(
+ str(component) for component in txfixtures.__version__[0:3])
+
+
+def get_long_description():
+ readme_path = os.path.join(
+ os.path.dirname(__file__), 'README')
+ return open(readme_path).read()
+
+
+setup(
+ name='txfixtures',
+ maintainer='Martin Pool',
+ maintainer_email='mbp at canonical.com',
+ url='https://launchpad.net/txfixtures',
+ description=('Treat Twisted applications as Python test fixtures'),
+ long_description=get_long_description(),
+ version=get_version(),
+ classifiers=["License :: OSI Approved :: GNU General Public License (GPL)"],
+ packages=['txfixtures'],
+ requires=[
+ 'fixtures',
+ 'testtools',
+ 'twisted',
+ ],
+ )
diff --git a/txfixtures/__init__.py b/txfixtures/__init__.py
new file mode 100644
index 0000000..aad219f
--- /dev/null
+++ b/txfixtures/__init__.py
@@ -0,0 +1 @@
+__version__ = (0, 1, 4)
diff --git a/txfixtures/osutils.py b/txfixtures/osutils.py
new file mode 100644
index 0000000..c5e22ed
--- /dev/null
+++ b/txfixtures/osutils.py
@@ -0,0 +1,128 @@
+# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# GNU General Public License version 3.
+
+
+"""General os utilities useful for txfxtures."""
+
+
+import errno
+import os
+import os.path
+import shutil
+from signal import (
+ SIGKILL,
+ SIGTERM,
+ )
+import socket
+import time
+
+
+def _kill_may_race(pid, signal_number):
+ """Kill a pid accepting that it may not exist."""
+ try:
+ os.kill(pid, signal_number)
+ except OSError, e:
+ if e.errno in (errno.ESRCH, errno.ECHILD):
+ # Process has already been killed.
+ return
+ # Some other issue (e.g. different user owns it)
+ raise
+
+
+def get_pid_from_file(pidfile_path):
+ """Retrieve the PID from the given file, if it exists, None otherwise."""
+ if not os.path.exists(pidfile_path):
+ return None
+ # Get the pid.
+ pid = open(pidfile_path, 'r').read().split()[0]
+ try:
+ pid = int(pid)
+ except ValueError:
+ # pidfile contains rubbish
+ return None
+ return pid
+
+
+def two_stage_kill(pid, poll_interval=0.1, num_polls=50):
+ """Kill process 'pid' with SIGTERM. If it doesn't die, SIGKILL it.
+
+ :param pid: The pid of the process to kill.
+ :param poll_interval: The polling interval used to check if the
+ process is still around.
+ :param num_polls: The number of polls to do before doing a SIGKILL.
+ """
+ # Kill the process.
+ _kill_may_race(pid, SIGTERM)
+
+ # Poll until the process has ended.
+ for i in range(num_polls):
+ try:
+ # Reap the child process and get its return value. If it's not
+ # gone yet, continue.
+ new_pid, result = os.waitpid(pid, os.WNOHANG)
+ if new_pid:
+ return result
+ time.sleep(poll_interval)
+ except OSError, e:
+ if e.errno in (errno.ESRCH, errno.ECHILD):
+ # Raised if the process is gone by the time we try to get the
+ # return value.
+ return
+
+ # The process is still around, so terminate it violently.
+ _kill_may_race(pid, SIGKILL)
+
+
+
+def kill_by_pidfile(pidfile_path, poll_interval=0.1, num_polls=50):
+ """Kill a process identified by the pid stored in a file.
+
+ The pid file is removed from disk.
+ """
+ try:
+ pid = get_pid_from_file(pidfile_path)
+ if pid is None:
+ return
+ two_stage_kill(pid, poll_interval, num_polls)
+ finally:
+ remove_if_exists(pidfile_path)
+
+
+def remove_if_exists(path):
+ """Remove the given file if it exists."""
+ try:
+ os.remove(path)
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ raise
+
+
+def until_no_eintr(retries, function, *args, **kwargs):
+ """Run 'function' until it doesn't raise EINTR errors.
+
+ :param retries: The maximum number of times to try running 'function'.
+ :param function: The function to run.
+ :param *args: Arguments passed to the function.
+ :param **kwargs: Keyword arguments passed to the function.
+ :return: The return value of 'function'.
+ """
+ if not retries:
+ return
+ for i in range(retries):
+ try:
+ return function(*args, **kwargs)
+ except (IOError, OSError), e:
+ if e.errno == errno.EINTR:
+ continue
+ raise
+ except socket.error, e:
+ # In Python 2.6 we can use IOError instead. It also has
+ # reason.errno but we might be using 2.5 here so use the
+ # index hack.
+ if e[0] == errno.EINTR:
+ continue
+ raise
+ else:
+ raise
+
+
diff --git a/txfixtures/tachandler.py b/txfixtures/tachandler.py
new file mode 100644
index 0000000..70f8b0f
--- /dev/null
+++ b/txfixtures/tachandler.py
@@ -0,0 +1,219 @@
+# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# GNU General Public License version 3.
+
+"""Test harness for TAC (Twisted Application Configuration) files."""
+
+__metaclass__ = type
+
+__all__ = [
+ 'TacException',
+ 'TacTestFixture',
+ ]
+
+
+import errno
+import os
+import socket
+import subprocess
+import sys
+import time
+import warnings
+
+from fixtures import Fixture
+
+from txfixtures.osutils import (
+ get_pid_from_file,
+ kill_by_pidfile,
+ two_stage_kill,
+ until_no_eintr,
+ )
+
+
+class TacException(Exception):
+ """Error raised by TacTestSetup."""
+
+
+class TacTestFixture(Fixture):
+ """Setup an TAC file as daemon for use by functional tests.
+
+ You must override setUpRoot to set up a root directory for the daemon.
+
+ You may override _hasDaemonStarted, typically by calling _isPortListening, to
+ tell how long to wait before the daemon is available.
+ """
+
+ def setUp(self, spew=False, umask=None, python_path=None, twistd_script=None):
+ """Initialize a new TacTestFixture fixture.
+
+ :param python_path: If set, run twistd under this Python interpreter.
+ :param twistd_script: If set, run this twistd script rather than the
+ system default. Must be provided if python_path is given.
+ """
+ Fixture.setUp(self)
+ if get_pid_from_file(self.pidfile):
+ # An attempt to run while there was an existing live helper
+ # was made. Note that this races with helpers which use unique
+ # roots, so when moving/eliminating this code check subclasses
+ # for workarounds and remove those too.
+ pid = get_pid_from_file(self.pidfile)
+ warnings.warn("Attempt to start Tachandler %r with an existing "
+ "instance (%d) running in %s." % (
+ self.tacfile, pid, self.pidfile),
+ UserWarning, stacklevel=2)
+ two_stage_kill(pid)
+ # If the pid file still exists, it may indicate that the process
+ # respawned itself, or that two processes were started (race?) and
+ # one is still running while the other has ended, or the process
+ # was killed but it didn't remove the pid file (bug), or the
+ # machine was hard-rebooted and the pid file was not cleaned up
+ # (bug again). In other words, it's not safe to assume that a
+ # stale pid file is safe to delete without human intervention.
+ stale_pid = get_pid_from_file(self.pidfile)
+ if stale_pid:
+ raise TacException(
+ "Could not kill stale process %s from %s." % (
+ stale_pid, self.pidfile,))
+
+ self.setUpRoot()
+ if python_path is None:
+ python_path = sys.executable
+ if twistd_script is None:
+ twistd_script = '/usr/bin/twistd'
+ args = [python_path,
+ '-Wignore::DeprecationWarning',
+ twistd_script,
+ '-o', '-y', self.tacfile, '--pidfile', self.pidfile,
+ '--logfile', self.logfile]
+ if spew:
+ args.append('--spew')
+ if umask is not None:
+ args.extend(('--umask', umask))
+
+ # 2010-04-26, Salgado, http://pad.lv/570246: Deprecation warnings
+ # in Twisted are not our problem. They also aren't easy to suppress,
+ # and cause test failures due to spurious stderr output. Just shut
+ # the whole bloody mess up.
+
+ # Run twistd, and raise an error if the return value is non-zero or
+ # stdout/stderr are written to.
+ proc = subprocess.Popen(
+ args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ )
+ self.addCleanup(self.killTac)
+ stdout = until_no_eintr(10, proc.stdout.read)
+ if stdout:
+ raise TacException('Error running %s: unclean stdout/err: %s'
+ % (args, stdout))
+ rv = proc.wait()
+ # twistd will normally fork off into the background with the
+ # originally-spawned process exiting 0.
+ if rv != 0:
+ raise TacException('Error %d running %s' % (rv, args))
+
+ self._waitForDaemonStartup()
+
+ def _hasDaemonStarted(self):
+ """Has the daemon started?
+ """
+ return self._isPortListening('localhost', self.daemon_port)
+
+ def _isPortListening(self, host, port):
+ """True if a tcp port is accepting connections.
+
+ This can be used by subclasses overriding _hasDaemonStarted, if they
+ want to check the port is up rather than for the contents of the log
+ file.
+ """
+ try:
+ s = socket.socket()
+ s.settimeout(2.0)
+ s.connect((host, port))
+ s.close()
+ return True
+ except socket.error, e:
+ if e.errno == errno.ECONNREFUSED:
+ return False
+ else:
+ raise
+
+ def _waitForDaemonStartup(self):
+ """ Wait for the daemon to fully start.
+
+ Times out after 20 seconds. If that happens, the log file content
+ will be included in the exception message for debugging purpose.
+
+ :raises TacException: Timeout.
+ """
+ # Watch the log file for readyservice.LOG_MAGIC to signal that startup
+ # has completed.
+ now = time.time()
+ deadline = now + 20
+ while now < deadline and not self._hasDaemonStarted():
+ time.sleep(0.1)
+ now = time.time()
+
+ if now >= deadline:
+ raise TacException('Unable to start %s. Content of %s:\n%s' % (
+ self.tacfile, self.logfile, open(self.logfile).read()))
+
+ def tearDown(self):
+ # For compatibility - migrate to cleanUp.
+ self.cleanUp()
+
+ def killTac(self):
+ """Kill the TAC file if it is running."""
+ pidfile = self.pidfile
+ kill_by_pidfile(pidfile)
+
+ def sendSignal(self, sig):
+ """Send the given signal to the tac process."""
+ pid = get_pid_from_file(self.pidfile)
+ if pid is None:
+ return
+ os.kill(pid, sig)
+
+ def setUpRoot(self):
+ """Override this.
+
+ This should be able to cope with the root already existing, because it
+ will be left behind after each test in case it's needed to diagnose a
+ test failure (e.g. log files might contain helpful tracebacks).
+ """
+ raise NotImplementedError
+
+ @property
+ def root(self):
+ raise NotImplementedError
+
+ @property
+ def tacfile(self):
+ raise NotImplementedError
+
+ @property
+ def pidfile(self):
+ raise NotImplementedError
+
+ @property
+ def logfile(self):
+ raise NotImplementedError
+
+ @property
+ def daemon_port(self):
+ raise NotImplementedError
+
+def get_pid_from_file(pidfile_path):
+ """Retrieve the PID from the given file, if it exists, None otherwise."""
+ if not os.path.exists(pidfile_path):
+ return None
+ # Get the pid.
+ pid = open(pidfile_path, 'r').read().split()[0]
+ try:
+ pid = int(pid)
+ except ValueError:
+ # pidfile contains rubbish
+ return None
+ return pid
+
+
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/txfixtures.git
More information about the Python-modules-commits
mailing list