Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-portpicker for 
openSUSE:Factory checked in at 2024-01-10 21:52:08
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-portpicker (Old)
 and      /work/SRC/openSUSE:Factory/.python-portpicker.new.21961 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-portpicker"

Wed Jan 10 21:52:08 2024 rev:7 rq:1137821 version:1.6.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-portpicker/python-portpicker.changes      
2022-07-19 17:20:30.828458774 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-portpicker.new.21961/python-portpicker.changes
   2024-01-10 21:52:29.384605335 +0100
@@ -1,0 +2,27 @@
+Tue Jan  9 21:49:51 UTC 2024 - Dirk Müller <dmuel...@suse.com>
+
+- update to 1.6.0:
+  * Resolve an internal source of potential flakiness on the
+    bind/close port
+  * checks when used in active environments by calling
+    `.shutdown()` before `.close()`.
+  * Add `-h` and `--help` text to the command line tool.
+  * The command line interface now defaults to associating the
+    returned port with its parent process PID (usually the calling
+    script) when no argument was given as that makes more sense.
+  * When portpicker is used as a command line tool from a
+    script, if a port is chosen without a portserver it can now
+    be kept bound to a socket by a child process for a user
+    specified timeout. When successful, this helps
+    minimize race conditions as subsequent portpicker CLI
+    invocations within the timeout window cannot choose the same
+    port.
+  * Some pylint based refactorings to portpicker and
+    portpicker\_test.
+  * Drop 3.6 from our CI test matrix and metadata. It probably
+    still works there, but expect our unittests to include
+    3.7-ism's in the future. We'll *attempt* to avoid modern
+    constructs in portpicker.py itself but zero
+    guarantees. Using an old Python? Use an old portpicker.
+
+-------------------------------------------------------------------

Old:
----
  portpicker-1.5.2.tar.gz

New:
----
  portpicker-1.6.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-portpicker.spec ++++++
--- /var/tmp/diff_new_pack.lo9uEW/_old  2024-01-10 21:52:29.964626398 +0100
+++ /var/tmp/diff_new_pack.lo9uEW/_new  2024-01-10 21:52:29.968626544 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-portpicker
 #
-# Copyright (c) 2022 SUSE LLC
+# Copyright (c) 2024 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-portpicker
-Version:        1.5.2
+Version:        1.6.0
 Release:        0
 Summary:        A library to choose unique available network ports
 License:        Apache-2.0
@@ -27,12 +27,11 @@
 Source0:        
https://files.pythonhosted.org/packages/source/p/portpicker/portpicker-%{version}.tar.gz
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
+BuildRequires:  net-tools-deprecated
 BuildRequires:  python-rpm-macros
 Requires(post): update-alternatives
 Requires(postun):update-alternatives
 BuildArch:      noarch
-# SECTION test requirements
-# /SECTION
 %python_subpackages
 
 %description

++++++ portpicker-1.5.2.tar.gz -> portpicker-1.6.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/portpicker-1.5.2/ChangeLog.md 
new/portpicker-1.6.0/ChangeLog.md
--- old/portpicker-1.5.2/ChangeLog.md   2022-06-08 22:16:19.000000000 +0200
+++ new/portpicker-1.6.0/ChangeLog.md   2023-08-15 06:28:54.000000000 +0200
@@ -1,8 +1,31 @@
+## 1.6.0
+
+*   Resolve an internal source of potential flakiness on the bind/close port
+    checks when used in active environments by calling `.shutdown()` before
+    `.close()`.
+
+## 1.6.0b1
+
+*   Add `-h` and `--help` text to the command line tool.
+*   The command line interface now defaults to associating the returned port
+    with its parent process PID (usually the calling script) when no argument
+    was given as that makes more sense.
+*   When portpicker is used as a command line tool from a script, if a port is
+    chosen without a portserver it can now be kept bound to a socket by a
+    child process for a user specified timeout. When successful, this helps
+    minimize race conditions as subsequent portpicker CLI invocations within
+    the timeout window cannot choose the same port.
+*   Some pylint based refactorings to portpicker and portpicker\_test.
+*   Drop 3.6 from our CI test matrix and metadata. It probably still works
+    there, but expect our unittests to include 3.7-ism's in the future. We'll
+    *attempt* to avoid modern constructs in portpicker.py itself but zero
+    guarantees. Using an old Python? Use an old portpicker.
+
 ## 1.5.2
 
 *   Do not re-pick a known used (not-yet-returned) port when running stand 
alone
     without a portserver.
-    
+
 ## 1.5.1
 
 *   When not using a portserver *(you really should)*, try the `bind(0)`
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/portpicker-1.5.2/PKG-INFO 
new/portpicker-1.6.0/PKG-INFO
--- old/portpicker-1.5.2/PKG-INFO       2022-06-08 22:56:07.827623800 +0200
+++ new/portpicker-1.6.0/PKG-INFO       2023-08-15 06:31:51.458956000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: portpicker
-Version: 1.5.2
+Version: 1.6.0
 Summary: A library to choose unique available network ports.
 Home-page: https://github.com/google/python_portpicker
 Maintainer: Google LLC
@@ -13,11 +13,12 @@
 Classifier: Intended Audience :: Developers
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Requires-Python: >=3.6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/portpicker-1.5.2/README.md 
new/portpicker-1.6.0/README.md
--- old/portpicker-1.5.2/README.md      2021-05-25 00:49:41.000000000 +0200
+++ new/portpicker-1.6.0/README.md      2022-06-08 23:05:35.000000000 +0200
@@ -1,8 +1,7 @@
 # Python portpicker module
 
 [![PyPI 
version](https://badge.fury.io/py/portpicker.svg)](https://badge.fury.io/py/portpicker)
-![GH Action 
Status](https://github.com/google/python_portpicker/actions/workflows/python-package.yml/badge.svg)
-[![Travis CI org 
Status](https://travis-ci.org/google/python_portpicker.svg?branch=master)](https://travis-ci.org/google/python_portpicker)
+[![GH Action 
Status](https://github.com/google/python_portpicker/actions/workflows/python-package.yml/badge.svg)](https://github.com/google/python_portpicker/actions)
 
 This module is useful for finding unused network ports on a host. If you need
 legacy Python 2 support, use the 1.3.x releases.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/portpicker-1.5.2/pyproject.toml 
new/portpicker-1.6.0/pyproject.toml
--- old/portpicker-1.5.2/pyproject.toml 2021-05-25 00:49:41.000000000 +0200
+++ new/portpicker-1.6.0/pyproject.toml 2022-06-17 23:59:56.000000000 +0200
@@ -5,7 +5,7 @@
 [tool.tox]
 legacy_tox_ini = """
 [tox]
-envlist = py{36,37,38,39}
+envlist = py{37,38,39,310,311}
 isolated_build = true
 skip_missing_interpreters = true
 # minimum tox version
@@ -17,5 +17,5 @@
 commands =
     check-manifest --ignore 'src/tests/**'
     python -c 'from setuptools import setup; setup()' check -m -s
-    py.test -s {posargs}
+    py.test -vv -s {posargs}
 """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/portpicker-1.5.2/setup.cfg 
new/portpicker-1.6.0/setup.cfg
--- old/portpicker-1.5.2/setup.cfg      2022-06-08 22:56:07.827623800 +0200
+++ new/portpicker-1.6.0/setup.cfg      2023-08-15 06:31:51.458956000 +0200
@@ -1,6 +1,6 @@
 [metadata]
 name = portpicker
-version = 1.5.2
+version = 1.6.0
 maintainer = Google LLC
 maintainer_email = g...@krypto.org
 license = Apache 2.0
@@ -21,11 +21,12 @@
        Intended Audience :: Developers
        Programming Language :: Python
        Programming Language :: Python :: 3
-       Programming Language :: Python :: 3.6
        Programming Language :: Python :: 3.7
        Programming Language :: Python :: 3.8
        Programming Language :: Python :: 3.9
        Programming Language :: Python :: 3.10
+       Programming Language :: Python :: 3.11
+       Programming Language :: Python :: 3.12
        Programming Language :: Python :: Implementation :: CPython
        Programming Language :: Python :: Implementation :: PyPy
 platforms = POSIX, Windows
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/portpicker-1.5.2/src/portpicker.egg-info/PKG-INFO 
new/portpicker-1.6.0/src/portpicker.egg-info/PKG-INFO
--- old/portpicker-1.5.2/src/portpicker.egg-info/PKG-INFO       2022-06-08 
22:56:07.000000000 +0200
+++ new/portpicker-1.6.0/src/portpicker.egg-info/PKG-INFO       2023-08-15 
06:31:51.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: portpicker
-Version: 1.5.2
+Version: 1.6.0
 Summary: A library to choose unique available network ports.
 Home-page: https://github.com/google/python_portpicker
 Maintainer: Google LLC
@@ -13,11 +13,12 @@
 Classifier: Intended Audience :: Developers
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Requires-Python: >=3.6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/portpicker-1.5.2/src/portpicker.py 
new/portpicker-1.6.0/src/portpicker.py
--- old/portpicker-1.5.2/src/portpicker.py      2022-06-08 22:54:14.000000000 
+0200
+++ new/portpicker-1.6.0/src/portpicker.py      2023-08-15 06:28:54.000000000 
+0200
@@ -35,6 +35,9 @@
   test_port = portpicker.pick_unused_port()
 """
 
+# pylint: disable=consider-using-f-string
+# Some people still use this on old Pythons despite our test matrix and
+# supported versions.  Be kind for now, until it gets in our way.
 from __future__ import print_function
 
 import logging
@@ -42,11 +45,14 @@
 import random
 import socket
 import sys
+import time
 
+_winapi = None  # pylint: disable=invalid-name
 if sys.platform == 'win32':
-    import _winapi
-else:
-    _winapi = None
+    try:
+        import _winapi
+    except ImportError:
+        _winapi = None
 
 # The legacy Bind, IsPortFree, etc. names are not exported.
 __all__ = ('bind', 'is_port_free', 'pick_unused_port', 'return_port',
@@ -107,8 +113,33 @@
     Returns:
       The port number on success or None on failure.
     """
+    return _bind(port, socket_type, socket_proto)
+
+
+def _bind(port, socket_type, socket_proto, return_socket=None,
+          return_family=socket.AF_INET6):
+    """Internal implementation of bind.
+
+    Args:
+      port, socket_type, socket_proto: see bind().
+      return_socket: If supplied, a list that we will append an open bound
+          reuseaddr socket on the port in question to.
+      return_family: The socket family to return in return_socket.
+
+    Returns:
+      The port number on success or None on failure.
+    """
+    # Our return family must come last when returning a bound socket
+    # as we cannot keep it bound while testing a bind on the other
+    # family with many network stack configurations.
+    if return_socket is None or return_family == socket.AF_INET:
+        socket_families = (socket.AF_INET6, socket.AF_INET)
+    elif return_family == socket.AF_INET6:
+        socket_families = (socket.AF_INET, socket.AF_INET6)
+    else:
+        raise ValueError('unknown return_family %s' % return_family)
     got_socket = False
-    for family in (socket.AF_INET6, socket.AF_INET):
+    for family in socket_families:
         try:
             sock = socket.socket(family, socket_type, socket_proto)
             got_socket = True
@@ -123,27 +154,51 @@
         except socket.error:
             return None
         finally:
-            sock.close()
+            if return_socket is None or family != return_family:
+                try:
+                    # Adding this resolved 1 in ~500 flakiness that we were
+                    # seeing from an integration test framework managing a set
+                    # of ports with is_port_free().  close() doesn't move the
+                    # TCP state machine along quickly.
+                    sock.shutdown(socket.SHUT_RDWR)
+                except OSError:
+                    pass
+                sock.close()
+        if return_socket is not None and family == return_family:
+            return_socket.append(sock)
+            break  # Final iteration due to pre-loop logic; don't close.
     return port if got_socket else None
 
-Bind = bind  # legacy API. pylint: disable=invalid-name
-
 
 def is_port_free(port):
     """Check if specified port is free.
 
     Args:
       port: integer, port to check
+
     Returns:
-      boolean, whether it is free to use for both TCP and UDP
+      bool, whether port is free to use for both TCP and UDP.
     """
-    return bind(port, *_PROTOS[0]) and bind(port, *_PROTOS[1])
+    return _is_port_free(port)
+
+
+def _is_port_free(port, return_sockets=None):
+    """Internal implementation of is_port_free.
+
+    Args:
+      port: integer, port to check
+      return_sockets: If supplied, a list that we will append open bound
+        sockets on the port in question to rather than closing them.
 
-IsPortFree = is_port_free  # legacy API. pylint: disable=invalid-name
+    Returns:
+      bool, whether port is free to use for both TCP and UDP.
+    """
+    return (_bind(port, *_PROTOS[0], return_socket=return_sockets) and
+            _bind(port, *_PROTOS[1], return_socket=return_sockets))
 
 
 def pick_unused_port(pid=None, portserver_address=None):
-    """A pure python implementation of PickUnusedPort.
+    """Picks an unused port and reserves it for use by a given process id.
 
     Args:
       pid: PID to tell the portserver to associate the reservation with. If
@@ -156,12 +211,30 @@
         address, the environment will be checked for a PORTSERVER_ADDRESS
         variable.  If that is not set, no port server will be used.
 
+    If no portserver is used, no pid based reservation is managed by any
+    central authority. Race conditions and duplicate assignments may occur.
+
     Returns:
       A port number that is unused on both TCP and UDP.
 
     Raises:
       NoFreePortFoundError: No free port could be found.
     """
+    return _pick_unused_port(pid, portserver_address)
+
+
+def _pick_unused_port(pid=None, portserver_address=None,
+                     noserver_bind_timeout=0):
+    """Internal implementation of pick_unused_port.
+
+    Args:
+      pid, portserver_address: See pick_unused_port().
+      noserver_bind_timeout: If no portserver was used, this is the number of
+        seconds we will attempt to keep a child process around with the ports
+        returned open and bound SO_REUSEADDR style to help avoid race condition
+        port reuse. A non-zero value attempts os.fork(). Do not use it in a
+        multithreaded process.
+    """
     try:  # Instead of `if _free_ports:` to handle the race condition.
         port = _free_ports.pop()
     except KeyError:
@@ -179,12 +252,46 @@
                                          pid=pid)
         if port:
             return port
-    return _pick_unused_port_without_server()
+    return _pick_unused_port_without_server(bind_timeout=noserver_bind_timeout)
 
-PickUnusedPort = pick_unused_port  # legacy API. pylint: disable=invalid-name
+
+def _spawn_bound_port_holding_daemon(port, bound_sockets, timeout):
+    """If possible, fork()s a daemon process to hold bound_sockets open.
+
+    Emits a warning to stderr if it cannot.
+
+    Args:
+      port: The port number the sockets are bound to (informational).
+      bound_sockets: The list of bound sockets our child process will hold
+          open. If the list is empty, no action is taken.
+      timeout: A positive number of seconds the child should sleep for before
+          closing the sockets and exiting.
+    """
+    if bound_sockets and timeout > 0:
+        try:
+            fork_pid = os.fork()  # This concept only works on POSIX.
+        except Exception as err:  # pylint: disable=broad-except
+            print('WARNING: Cannot timeout unbinding close of port', port,
+                  ' closing on exit. -', err, file=sys.stderr)
+        else:
+            if fork_pid == 0:
+                # This child process inherits and holds bound_sockets open
+                # for bind_timeout seconds.
+                try:
+                    # Close the stdio fds as may be connected to
+                    # a pipe that will cause a grandparent process
+                    # to wait on before returning. (cl/427587550)
+                    os.close(sys.stdin.fileno())
+                    os.close(sys.stdout.fileno())
+                    os.close(sys.stderr.fileno())
+                    time.sleep(timeout)
+                    for held_socket in bound_sockets:
+                        held_socket.close()
+                finally:
+                    os._exit(0)
 
 
-def _pick_unused_port_without_server():  # Protected. pylint: 
disable=invalid-name
+def _pick_unused_port_without_server(bind_timeout=0):
     """Pick an available network port without the help of a port server.
 
     This code ensures that the port is available on both TCP and UDP.
@@ -192,6 +299,11 @@
     This function is an implementation detail of PickUnusedPort(), and
     should not be called by code outside of this module.
 
+    Args:
+      bind_timeout: number of seconds to attempt to keep a child process
+          process around bound SO_REUSEADDR style to the port. If we cannot
+          do that we emit a warning to stderr.
+
     Returns:
       A port number that is unused on both TCP and UDP.
 
@@ -201,28 +313,42 @@
     # Next, try a few times to get an OS-assigned port.
     # Ambrose discovered that on the 2.6 kernel, calling Bind() on UDP socket
     # returns the same port over and over. So always try TCP first.
+    port = None
+    bound_sockets = [] if bind_timeout > 0 else None
     for _ in range(10):
         # Ask the OS for an unused port.
-        port = bind(0, _PROTOS[0][0], _PROTOS[0][1])
+        port = _bind(0, socket.SOCK_STREAM, socket.IPPROTO_TCP, bound_sockets)
         # Check if this port is unused on the other protocol.
         if (port and port not in _random_ports and
-            bind(port, _PROTOS[1][0], _PROTOS[1][1])):
+            _bind(port, socket.SOCK_DGRAM, socket.IPPROTO_UDP, bound_sockets)):
             _random_ports.add(port)
+            _spawn_bound_port_holding_daemon(port, bound_sockets, bind_timeout)
             return port
+        if bound_sockets:
+            for held_socket in bound_sockets:
+                held_socket.close()
+            del bound_sockets[:]
 
     # Try random ports as a last resort.
     rng = random.Random()
     for _ in range(10):
         port = int(rng.randrange(15000, 25000))
-        if port not in _random_ports and is_port_free(port):
-            _random_ports.add(port)
-            return port
+        if port not in _random_ports:
+            if _is_port_free(port, bound_sockets):
+                _random_ports.add(port)
+                _spawn_bound_port_holding_daemon(
+                        port, bound_sockets, bind_timeout)
+                return port
+            if bound_sockets:
+                for held_socket in bound_sockets:
+                    held_socket.close()
+                del bound_sockets[:]
 
     # Give up.
     raise NoFreePortFoundError()
 
 
-def _get_linux_port_from_port_server(portserver_address, pid):
+def _posix_get_port_from_port_server(portserver_address, pid):
     # An AF_UNIX address may start with a zero byte, in which case it is in the
     # "abstract namespace", and doesn't have any filesystem representation.
     # See 'man 7 unix' for details.
@@ -233,7 +359,7 @@
     try:
         # Create socket.
         if hasattr(socket, 'AF_UNIX'):
-            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) # pylint: 
disable=no-member
+            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
         else:
             # fallback to AF_INET if this is not unix
             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -255,7 +381,7 @@
         return None
 
 
-def _get_windows_port_from_port_server(portserver_address, pid):
+def _windows_get_port_from_port_server(portserver_address, pid):
     if portserver_address[0] == '@':
         portserver_address = '\\\\.\\pipe\\' + portserver_address[1:]
 
@@ -277,6 +403,7 @@
               file=sys.stderr)
         return None
 
+
 def get_port_from_port_server(portserver_address, pid=None):
     """Request a free a port from a system-wide portserver.
 
@@ -305,9 +432,9 @@
         pid = os.getpid()
 
     if _winapi:
-        buf = _get_windows_port_from_port_server(portserver_address, pid)
+        buf = _windows_get_port_from_port_server(portserver_address, pid)
     else:
-        buf = _get_linux_port_from_port_server(portserver_address, pid)
+        buf = _posix_get_port_from_port_server(portserver_address, pid)
 
     if buf is None:
         return None
@@ -321,12 +448,48 @@
     return port
 
 
-GetPortFromPortServer = get_port_from_port_server  # legacy API. pylint: 
disable=invalid-name
+# Legacy APIs.
+# pylint: disable=invalid-name
+Bind = bind
+GetPortFromPortServer = get_port_from_port_server
+IsPortFree = is_port_free
+PickUnusedPort = pick_unused_port
+# pylint: enable=invalid-name
 
 
 def main(argv):
-    """If passed an arg, treat it as a PID, otherwise portpicker uses 
getpid."""
-    port = pick_unused_port(pid=int(argv[1]) if len(argv) > 1 else None)
+    """If passed an arg, treat it as a PID, otherwise we use getppid().
+
+    A second optional argument can be a bind timeout in seconds that will be
+    used ONLY if no portserver is found. We attempt to leave a process around
+    holding the port open and bound with SO_REUSEADDR set for timeout seconds.
+    If the timeout bind was not possible, a warning is emitted to stderr.
+
+      #!/bin/bash
+      port="$(python -m portpicker $$ 1.23)"
+      test_my_server "$port"
+
+    This will pick a port for your script's PID and assign it to $port, if no
+    portserver was used, it attempts to keep a socket bound to $port for 1.23
+    seconds after the portpicker process has exited. This is a convenient hack
+    to attempt to prevent port reallocation during scripts outside of
+    portserver managed environments.
+
+    Older versions of the portpicker CLI ignore everything beyond the first 
arg.
+    Older versions also used getpid() instead of getppid(), so script users are
+    strongly encouraged to be explicit and pass $$ or your languages equivalent
+    to associate the port with the PID of the controlling process.
+    """
+    # Our command line is trivial so I avoid an argparse import. If we ever
+    # grow more than 1-2 args, switch to a using argparse.
+    if '-h' in argv or '--help' in argv:
+        print(argv[0], 'usage:\n')
+        import inspect
+        print(inspect.getdoc(main))
+        sys.exit(1)
+    pid=int(argv[1]) if len(argv) > 1 else os.getppid()
+    bind_timeout=float(argv[2]) if len(argv) > 2 else 0
+    port = _pick_unused_port(pid=pid, noserver_bind_timeout=bind_timeout)
     if not port:
         sys.exit(1)
     print(port)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/portpicker-1.5.2/src/tests/portpicker_test.py 
new/portpicker-1.6.0/src/tests/portpicker_test.py
--- old/portpicker-1.5.2/src/tests/portpicker_test.py   2021-11-09 
03:49:11.000000000 +0100
+++ new/portpicker-1.6.0/src/tests/portpicker_test.py   2022-07-07 
21:25:41.000000000 +0200
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 #
 # Copyright 2007 Google Inc. All Rights Reserved.
 #
@@ -14,32 +14,27 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-"""Unittests for the portpicker module."""
+"""Unittests for portpicker."""
 
-from __future__ import print_function
+# pylint: 
disable=invalid-name,protected-access,missing-class-docstring,missing-function-docstring
+
+from contextlib import ExitStack
 import errno
 import os
-import random
 import socket
+import subprocess
 import sys
+import time
 import unittest
-from contextlib import ExitStack
-
-if sys.platform == 'win32':
-    import _winapi
-else:
-    _winapi = None
-
-try:
-    # pylint: disable=no-name-in-module
-    from unittest import mock  # Python >= 3.3.
-except ImportError:
-    import mock  # https://pypi.python.org/pypi/mock
+from unittest import mock
 
 import portpicker
+_winapi = portpicker._winapi
 
+# pylint: 
disable=invalid-name,protected-access,missing-class-docstring,missing-function-docstring
 
-class PickUnusedPortTest(unittest.TestCase):
+
+class CommonTestMixin:
     def IsUnusedTCPPort(self, port):
         return self._bind(port, socket.SOCK_STREAM, socket.IPPROTO_TCP)
 
@@ -47,21 +42,69 @@
         return self._bind(port, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
 
     def setUp(self):
+        super().setUp()
         # So we can Bind even if portpicker.bind is stubbed out.
         self._bind = portpicker.bind
         portpicker._owned_ports.clear()
         portpicker._free_ports.clear()
         portpicker._random_ports.clear()
 
-    def testPickUnusedPortActuallyWorks(self):
-        """This test can be flaky."""
-        for _ in range(10):
-            port = portpicker.pick_unused_port()
-            self.assertTrue(self.IsUnusedTCPPort(port))
-            self.assertTrue(self.IsUnusedUDPPort(port))
 
-    @unittest.skipIf('PORTSERVER_ADDRESS' not in os.environ,
-                     'no port server to test against')
+@unittest.skipIf(
+        ('PORTSERVER_ADDRESS' not in os.environ) and
+        not hasattr(socket, 'AF_UNIX'),
+        'no existing port server; test launching code requires AF_UNIX.')
+class PickUnusedPortTestWithAPortServer(CommonTestMixin, unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.portserver_process = None
+        if 'PORTSERVER_ADDRESS' not in os.environ:
+            # Launch a portserver child process for our tests to use if we are
+            # able to. Obviously not host-exclusive, but good for integration
+            # testing purposes on CI without a portserver of its own.
+            cls.portserver_address = '@pid%d-test-ports' % os.getpid()
+            try:
+                cls.portserver_process = subprocess.Popen(
+                        ['portserver.py',  # Installed in PATH within the venv.
+                         '--portserver_address=%s' % cls.portserver_address])
+            except EnvironmentError as err:
+                raise unittest.SkipTest(
+                        'Unable to launch portserver.py: %s' % err)
+            linux_addr = '\0' + cls.portserver_address[1:]  # The @ means 0.
+            # loop for a few seconds waiting for that socket to work.
+            err = '???'
+            for _ in range(123):
+                time.sleep(0.05)
+                try:
+                    ps_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+                    ps_sock.connect(linux_addr)
+                except socket.error as err:  # pylint: disable=unused-variable
+                    continue
+                ps_sock.close()
+                break
+            else:
+                # The socket failed or never accepted connections, assume our
+                # portserver setup attempt failed and bail out.
+                if cls.portserver_process.poll() is not None:
+                    cls.portserver_process.kill()
+                    cls.portserver_process.wait()
+                cls.portserver_process = None
+                raise unittest.SkipTest(
+                        'Unable to connect to our own portserver.py: %s' % err)
+            # Point child processes at our shiny portserver process.
+            os.environ['PORTSERVER_ADDRESS'] = cls.portserver_address
+
+    @classmethod
+    def tearDownClass(cls):
+        if cls.portserver_process:
+            if os.environ.get('PORTSERVER_ADDRESS') == cls.portserver_address:
+                del os.environ['PORTSERVER_ADDRESS']
+            if cls.portserver_process.poll() is None:
+                cls.portserver_process.kill()
+                cls.portserver_process.wait()
+            cls.portserver_process = None
+
     def testPickUnusedCanSuccessfullyUsePortServer(self):
 
         with mock.patch.object(portpicker, '_pick_unused_port_without_server'):
@@ -75,8 +118,6 @@
             self.assertTrue(self.IsUnusedTCPPort(port))
             self.assertTrue(self.IsUnusedUDPPort(port))
 
-    @unittest.skipIf('PORTSERVER_ADDRESS' not in os.environ,
-                     'no port server to test against')
     def testPickUnusedCanSuccessfullyUsePortServerAddressKwarg(self):
 
         with mock.patch.object(portpicker, '_pick_unused_port_without_server'):
@@ -93,10 +134,8 @@
                 self.assertTrue(self.IsUnusedTCPPort(port))
                 self.assertTrue(self.IsUnusedUDPPort(port))
             finally:
-              os.environ['PORTSERVER_ADDRESS'] = addr
+                os.environ['PORTSERVER_ADDRESS'] = addr
 
-    @unittest.skipIf('PORTSERVER_ADDRESS' not in os.environ,
-                     'no port server to test against')
     def testGetPortFromPortServer(self):
         """Exercise the get_port_from_port_server() helper function."""
         for _ in range(10):
@@ -105,6 +144,16 @@
             self.assertTrue(self.IsUnusedTCPPort(port))
             self.assertTrue(self.IsUnusedUDPPort(port))
 
+
+class PickUnusedPortTest(CommonTestMixin, unittest.TestCase):
+
+    def testPickUnusedPortActuallyWorks(self):
+        """This test can be flaky."""
+        for _ in range(10):
+            port = portpicker.pick_unused_port()
+            self.assertTrue(self.IsUnusedTCPPort(port))
+            self.assertTrue(self.IsUnusedUDPPort(port))
+
     def testSendsPidToPortServer(self):
         with ExitStack() as stack:
             if _winapi:
@@ -253,12 +302,11 @@
             # Only successfully return a port if an OS-assigned port is
             # requested, or if we're checking that the last OS-assigned port
             # is unused on the other protocol.
-            if port == 0 or port == self.last_assigned_port:
+            if port in (0, self.last_assigned_port):
                 self.last_assigned_port = self._bind(port, socket_type,
                                                      socket_proto)
                 return self.last_assigned_port
-            else:
-                return None
+            return None
 
         with mock.patch.object(portpicker, 'bind', error_for_explicit_ports):
             # Without server, this can be little flaky, so check that it
@@ -295,7 +343,7 @@
 
         # Now test the second part, the fallback from above, which asks the
         # OS for a port.
-        def mock_port_free(port):
+        def mock_port_free(unused_port):
             return False
 
         with mock.patch.object(portpicker, 'is_port_free', mock_port_free):
@@ -386,5 +434,92 @@
                          portpicker.GetPortFromPortServer)
 
 
+def get_open_listen_tcp_ports():
+    netstat = subprocess.run(['netstat', '-lnt'], capture_output=True,
+                             encoding='utf-8')
+    if netstat.returncode != 0:
+        raise unittest.SkipTest('Unable to run netstat -lnt to list binds.')
+    rows = (line.split() for line in netstat.stdout.splitlines())
+    listen_addrs = (row[3] for row in rows if row[0].startswith('tcp'))
+    listen_ports = [int(addr.split(':')[-1]) for addr in listen_addrs]
+    return listen_ports
+
+
+@unittest.skipUnless((sys.executable and os.access(sys.executable, os.X_OK))
+                     or (os.environ.get('TEST_PORTPICKER_CLI') and
+                         os.access(os.environ['TEST_PORTPICKER_CLI'], 
os.X_OK)),
+                     'sys.executable portpicker.__file__ not launchable and '
+                     ' no TEST_PORTPICKER_CLI supplied.')
+class PortpickerCommandLineTests(unittest.TestCase):
+    def setUp(self):
+        self.main_py = portpicker.__file__
+
+    def _run_portpicker(self, pp_args, env_override=None):
+        env = dict(os.environ)
+        if env_override:
+            env.update(env_override)
+        if os.environ.get('TEST_PORTPICKER_CLI'):
+            pp_command = [os.environ['TEST_PORTPICKER_CLI']]
+        else:
+            pp_command = [sys.executable, '-m', 'portpicker']
+        return subprocess.run(pp_command + pp_args,
+                              capture_output=True,
+                              env=env,
+                              encoding='utf-8',
+                              check=False)
+
+    def test_command_line_help(self):
+        cmd = self._run_portpicker(['-h'])
+        self.assertNotEqual(0, cmd.returncode)
+        self.assertIn('usage', cmd.stdout)
+        self.assertIn('passed an arg', cmd.stdout)
+        cmd = self._run_portpicker(['--help'])
+        self.assertNotEqual(0, cmd.returncode)
+        self.assertIn('usage', cmd.stdout)
+        self.assertIn('passed an arg', cmd.stdout)
+
+    def test_command_line_help_text_dedented(self):
+        cmd = self._run_portpicker(['-h'])
+        self.assertNotEqual(0, cmd.returncode)
+        self.assertIn('\nIf passed an arg', cmd.stdout)
+        self.assertIn('\n  #!/bin/bash', cmd.stdout)
+        self.assertIn('\nOlder versions ', cmd.stdout)
+
+    def test_command_line_interface(self):
+        cmd = self._run_portpicker([str(os.getpid())])
+        cmd.check_returncode()
+        port = int(cmd.stdout)
+        self.assertNotEqual(0, port, msg=cmd)
+        listen_ports = sorted(get_open_listen_tcp_ports())
+        self.assertNotIn(port, listen_ports, msg='expected nothing to be bound 
to port.')
+
+    def test_command_line_interface_no_portserver(self):
+        cmd = self._run_portpicker([str(os.getpid())],
+                                   env_override={'PORTSERVER_ADDRESS': ''})
+        cmd.check_returncode()
+        port = int(cmd.stdout)
+        self.assertNotEqual(0, port, msg=cmd)
+        listen_ports = sorted(get_open_listen_tcp_ports())
+        self.assertNotIn(port, listen_ports, msg='expected nothing to be bound 
to port.')
+
+    def test_command_line_interface_no_portserver_bind_timeout(self):
+        # This test is timing sensitive and leaves that bind process hanging
+        # around consuming resources until it dies on its own unless the test
+        # runner kills the process group upon exit.
+        timeout = 9.5
+        before = time.monotonic()
+        cmd = self._run_portpicker([str(os.getpid()), str(timeout)],
+                                   env_override={'PORTSERVER_ADDRESS': ''})
+        self.assertEqual(0, cmd.returncode, msg=(cmd.stdout, cmd.stderr))
+        port = int(cmd.stdout)
+        self.assertNotEqual(0, port, msg=cmd)
+        if 'WARNING' in cmd.stderr:
+            raise unittest.SkipTest('bind timeout not supported on this 
platform.')
+        listen_ports = sorted(get_open_listen_tcp_ports())
+        self.assertIn(port, listen_ports, msg='expected port to be bound. '
+                      '%f seconds elapsed of %f bind timeout.' %
+                      (time.monotonic() - before, timeout))
+
+
 if __name__ == '__main__':
     unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/portpicker-1.5.2/src/tests/portserver_test.py 
new/portpicker-1.6.0/src/tests/portserver_test.py
--- old/portpicker-1.5.2/src/tests/portserver_test.py   2021-07-12 
03:38:42.000000000 +0200
+++ new/portpicker-1.6.0/src/tests/portserver_test.py   2022-06-18 
00:48:30.000000000 +0200
@@ -34,7 +34,12 @@
 if sys.platform == 'win32':
     sys.path.append(os.path.join(os.path.split(sys.executable)[0]))
 
-import portserver
+try:
+    import portserver
+except ImportError:
+    # Or if testing from a third_party/py/portpicker/ style installed
+    # package tree find it this way.
+    from portpicker import portserver
 
 
 def setUpModule():

Reply via email to