[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