source: trunk/src/allmydata/test/test_iputil.py

Last change on this file was 6a43465, checked in by Jean-Paul Calderone <exarkun@…>, at 2023-03-17T19:46:27Z

Fix the type annotations

  • Property mode set to 100644
File size: 4.8 KB
Line 
1"""
2Tests for allmydata.util.iputil.
3
4Ported to Python 3.
5"""
6
7from __future__ import annotations
8
9import os, socket
10import gc
11from functools import wraps
12
13from typing import TypeVar, Callable
14from testtools.matchers import (
15    MatchesAll,
16    IsInstance,
17    AllMatch,
18    MatchesPredicate,
19)
20
21from twisted.trial import unittest
22
23from foolscap.api import Tub
24
25from allmydata.util import iputil, gcutil
26
27from ..util.iputil import (
28    get_local_addresses_sync,
29)
30
31from .common import (
32    SyncTestCase,
33)
34
35T = TypeVar("T", contravariant=True)
36U = TypeVar("U", covariant=True)
37
38def retry(stop: Callable[[], bool]) -> Callable[[Callable[[T], U]], Callable[[T], U]]:
39    """
40    Call a function until the predicate says to stop or the function stops
41    raising an exception.
42
43    :param stop: A callable to call after the decorated function raises an
44        exception.  The decorated function will be called again if ``stop``
45        returns ``False``.
46
47    :return: A decorator function.
48    """
49    def decorate(f: Callable[[T], U]) -> Callable[[T], U]:
50        @wraps(f)
51        def decorator(self: T) -> U:
52            while True:
53                try:
54                    return f(self)
55                except Exception:
56                    if stop():
57                        raise
58        return decorator
59    return decorate
60
61def stop_after_attempt(limit: int) -> Callable[[], bool]:
62    """
63    Stop after ``limit`` calls.
64    """
65    counter = 0
66    def check():
67        nonlocal counter
68        counter += 1
69        return counter < limit
70    return check
71
72class ListenOnUsed(unittest.TestCase):
73    """Tests for listenOnUnused."""
74
75    def create_tub(self, basedir):
76        os.makedirs(basedir)
77        tubfile = os.path.join(basedir, "tub.pem")
78        tub = Tub(certFile=tubfile)
79        tub.setOption("expose-remote-exception-types", False)
80        tub.startService()
81        self.addCleanup(tub.stopService)
82        return tub
83
84    @retry(stop=stop_after_attempt(7))
85    def test_random_port(self):
86        """A random port is selected if none is given."""
87        tub = self.create_tub("utils/ListenOnUsed/test_randomport")
88        self.assertEqual(len(tub.getListeners()), 0)
89        portnum = iputil.listenOnUnused(tub)
90        # We can connect to this port:
91        s = socket.socket()
92        s.connect(("127.0.0.1", portnum))
93        s.close()
94        self.assertEqual(len(tub.getListeners()), 1)
95
96        # Listen on another port:
97        tub2 = self.create_tub("utils/ListenOnUsed/test_randomport_2")
98        portnum2 = iputil.listenOnUnused(tub2)
99        self.assertNotEqual(portnum, portnum2)
100
101    @retry(stop=stop_after_attempt(7))
102    def test_specific_port(self):
103        """The given port is used."""
104        tub = self.create_tub("utils/ListenOnUsed/test_givenport")
105        s = socket.socket()
106        s.bind(("127.0.0.1", 0))
107        port = s.getsockname()[1]
108        s.close()
109        port2 = iputil.listenOnUnused(tub, port)
110        self.assertEqual(port, port2)
111
112
113class GcUtil(unittest.TestCase):
114    """Tests for allmydata.util.gcutil, which is used only by listenOnUnused."""
115
116    def test_gc_after_allocations(self):
117        """The resource tracker triggers allocations every 26 allocations."""
118        tracker = gcutil._ResourceTracker()
119        collections = []
120        self.patch(gc, "collect", lambda: collections.append(1))
121        for _ in range(2):
122            for _ in range(25):
123                tracker.allocate()
124                self.assertEqual(len(collections), 0)
125            tracker.allocate()
126            self.assertEqual(len(collections), 1)
127            del collections[:]
128
129    def test_release_delays_gc(self):
130        """Releasing a file descriptor resource delays GC collection."""
131        tracker = gcutil._ResourceTracker()
132        collections = []
133        self.patch(gc, "collect", lambda: collections.append(1))
134        for _ in range(2):
135            tracker.allocate()
136        for _ in range(3):
137            tracker.release()
138        for _ in range(25):
139            tracker.allocate()
140            self.assertEqual(len(collections), 0)
141        tracker.allocate()
142        self.assertEqual(len(collections), 1)
143
144
145class GetLocalAddressesSyncTests(SyncTestCase):
146    """
147    Tests for ``get_local_addresses_sync``.
148    """
149    def test_some_ipv4_addresses(self):
150        """
151        ``get_local_addresses_sync`` returns a list of IPv4 addresses as native
152        strings.
153        """
154        self.assertThat(
155            get_local_addresses_sync(),
156            MatchesAll(
157                IsInstance(list),
158                AllMatch(
159                    MatchesAll(
160                        IsInstance(str),
161                        MatchesPredicate(
162                            lambda addr: socket.inet_pton(socket.AF_INET, addr),
163                            "%r is not an IPv4 address.",
164                        ),
165                    ),
166                ),
167            ),
168        )
Note: See TracBrowser for help on using the repository browser.