Ticket #4881: patch.txt

File patch.txt, 7.9 KB (added by lewq, 3 years ago)

This patch actually passes the regular tests as well as the new ones

Line 
1Index: twisted/internet/test/test_process.py
2===================================================================
3--- twisted/internet/test/test_process.py       (revision 30737)
4+++ twisted/internet/test/test_process.py       (working copy)
5@@ -20,9 +20,9 @@
6 from twisted.internet.defer import Deferred, succeed
7 from twisted.internet.protocol import ProcessProtocol
8 from twisted.internet.error import ProcessDone, ProcessTerminated
9+from twisted.python import runtime
10+from twisted.internet import process
11 
12-
13-
14 class _ShutdownCallbackProcessProtocol(ProcessProtocol):
15     """
16     An L{IProcessProtocol} which fires a Deferred when the process it is
17@@ -590,3 +590,135 @@
18             "Twisted 10.0.0: There is no longer any potential for zombie "
19             "process.")
20         self.assertEquals(len(warnings), 1)
21+
22+
23+class FakeResourceModule(object):
24+    RLIMIT_NOFILE = 1
25+    def getrlimit(self, no):
26+        return [0, 9223372036854775808]
27+        # TODO: Test that this gets rounded down
28+
29+class FDDetectorTest(TestCase):
30+    """
31+    Tests for _FDDetector class in twisted.internet.process, which detects
32+    which function to drop in place for the _listOpenFDs method.
33+    """
34+    skip = runtime.platform.getType() == 'win32'
35+    sane = False
36+    procfs = False
37+    devfs = False
38+    opened_file = False
39+    saved_resource_module = None
40+
41+    def getpid(self):
42+        return 123
43+
44+    def listdir(self, arg):
45+        if arg == '/proc/123/fd':
46+            if self.procfs:
47+                return ["0","1","2"]
48+            else:
49+                raise OSError
50+
51+        if arg == '/dev/fd':
52+            if self.devfs:
53+                if not self.sane:
54+                    # Always return the same thing
55+                    return ["0","1","2"]
56+                else:
57+                    if self.opened_file:
58+                        return ["0","1","2","3"]
59+                    else:
60+                        return ["0","1","2"]
61+            else:
62+                raise OSError
63+
64+    def openfile(self, fname, mode):
65+        self.opened_file = True
66+
67+    def save_resource_module(self):
68+        try:
69+            import resource
70+            self.saved_resource_module = resource
71+        except:
72+            self.saved_resource_module = None
73+
74+    def hide_resource_module(self):
75+        import sys
76+        sys.modules['resource'] = None
77+
78+    def reveal_resource_module(self):
79+        import sys
80+        sys.modules['resource'] = FakeResourceModule()
81+
82+    def replace_resource_module(self):
83+        sys.modules['resource'] = self.saved_resource_module
84+
85+    def setUp(self):
86+        self.detector = process._FDDetector()
87+        self.detector.listdir = self.listdir
88+        self.detector.getpid = self.getpid
89+        self.detector.openfile = self.openfile
90+
91+    def test_fddetector_sane_devfd(self):
92+        """
93+        e.g., FreeBSD with fdescfs mounted
94+        """
95+        self.procfs = False
96+        self.devfs = True
97+        self.sane = True
98+        self.assertIdentical(
99+                self.detector._getImplementation(),
100+                process._devFDImplementation
101+                )
102+
103+    def test_fddetector_insane_devfd(self):
104+        """
105+        e.g., FreeBSD without fdescfs mounted
106+        """
107+        self.procfs = False
108+        self.devfs = True
109+        self.sane = False
110+        self.assertIdentical(
111+                self.detector._getImplementation(),
112+                process._fallbackFDImplementation
113+                )
114+
115+    def test_fddetector_procfd(self):
116+        """
117+        e.g., Linux
118+        """
119+        self.devfs = False
120+        self.procfs = True
121+        self.assertIdentical(
122+                self.detector._getImplementation(),
123+                process._procFDImplementation
124+                )
125+
126+    def test_fddetector_resource_importable(self):
127+        """
128+        e.g., IRIX?
129+        """
130+        self.devfs = False
131+        self.procfs = False
132+        self.resource_importable = True
133+        self.reveal_resource_module()
134+        self.assertIdentical(
135+                self.detector._getImplementation(),
136+                process._resourceFDImplementation
137+                )
138+        self.replace_resource_module()
139+
140+    def test_fddetector_resource_unimportable(self):
141+        """
142+        e.g., who knows
143+        """
144+        self.devfs = False
145+        self.procfs = False
146+        self.hide_resource_module()
147+        self.assertIdentical(
148+                self.detector._getImplementation(),
149+                process._fallbackFDImplementation
150+                )
151+        self.replace_resource_module()
152+
153Index: twisted/internet/process.py
154===================================================================
155--- twisted/internet/process.py (revision 30737)
156+++ twisted/internet/process.py (working copy)
157@@ -466,35 +466,74 @@
158         return "<%s pid=%s status=%s>" % (self.__class__.__name__,
159                                           self.pid, self.status)
160 
161+def _listOpenFDs():
162+    return _setFDImplementation()
163 
164+def _setFDImplementation():
165+    detector = _FDDetector()
166+    _listOpenFDs = detector._getImplementation()
167+    return _listOpenFDs()
168 
169-def _listOpenFDs():
170-    """
171-    Return an iterable of potentially open file descriptors.
172+def _devFDImplementation():
173+    dname = "/dev/fd"
174+    result = [int(fd) for fd in os.listdir(dname)]
175+    return result
176 
177-    This function returns an iterable over the contents of /dev/fd or
178-    /proc/<pid>/fd, if they're available on the platform. If they're not, the
179-    returned value is the range [0, maxfds], where 'maxfds' is at least 256.
180-    """
181-    dname = "/dev/fd"
182-    try:
183-        return [int(fd) for fd in os.listdir(dname)]
184-    except:
185-        dname = "/proc/%d/fd" % (os.getpid(),)
186+def _procFDImplementation():
187+    dname = "/proc/%d/fd" % (os.getpid(),)
188+    return [int(fd) for fd in os.listdir(dname)]
189+
190+def _resourceFDImplementation():
191+    import resource
192+    maxfds = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + 1
193+    # OS-X reports 9223372036854775808. That's a lot of fds
194+    # to close
195+    if maxfds > 1024:
196+        maxfds = 1024
197+    return xrange(maxfds)
198+
199+def _fallbackFDImplementation():
200+    maxfds = 256
201+    return xrange(maxfds)
202+
203+class _FDDetector(object):
204+    # So that we can unit test this
205+    listdir = os.listdir
206+    getpid = os.getpid
207+    openfile = open
208+
209+    def _getImplementation(self):
210+        # Check if /dev/fd works, if so, use that
211+        # Otherwise, check if /proc/%d/fd exists, if so use that
212+        # Otherwise, ask resource.getrlimit, if that throws an exception, then
213+        # fallback to _fallbackFDImplementation
214         try:
215-            return [int(fd) for fd in os.listdir(dname)]
216+            self.listdir("/dev/fd")
217+            if self._checkDevFDSanity(): # FreeBSD support :-)
218+                return _devFDImplementation
219+            else:
220+                return _fallbackFDImplementation
221+
222         except:
223             try:
224-                import resource
225-                maxfds = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + 1
226-                # OS-X reports 9223372036854775808. That's a lot of fds
227-                # to close
228-                if maxfds > 1024:
229-                    maxfds = 1024
230+                self.listdir("/proc/%d/fd" % (self.getpid(),))
231+                return _procFDImplementation
232             except:
233-                maxfds = 256
234-            return xrange(maxfds)
235+                try:
236+                    _resourceFDImplementation() # Imports resource
237+                    return _resourceFDImplementation
238+                except:
239+                    return _fallbackFDImplementation
240 
241+    def _checkDevFDSanity(self):
242+        """
243+        Returns true iff opening a file modifies the fds visible
244+        in /dev/fd, as it should on a sane platform.
245+        """
246+        start = self.listdir("/dev/fd")
247+        fp = self.openfile("/dev/null", "r")
248+        end = self.listdir("/dev/fd")
249+        return start != end
250 
251 
252 class Process(_BaseProcess):