comparison env/lib/python3.9/site-packages/psutil/tests/test_unicode.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3
4 # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
5 # Use of this source code is governed by a BSD-style license that can be
6 # found in the LICENSE file.
7
8 """
9 Notes about unicode handling in psutil
10 ======================================
11
12 Starting from version 5.3.0 psutil adds unicode support, see:
13 https://github.com/giampaolo/psutil/issues/1040
14 The notes below apply to *any* API returning a string such as
15 process exe(), cwd() or username():
16
17 * all strings are encoded by using the OS filesystem encoding
18 (sys.getfilesystemencoding()) which varies depending on the platform
19 (e.g. "UTF-8" on macOS, "mbcs" on Win)
20 * no API call is supposed to crash with UnicodeDecodeError
21 * instead, in case of badly encoded data returned by the OS, the
22 following error handlers are used to replace the corrupted characters in
23 the string:
24 * Python 3: sys.getfilesystemencodeerrors() (PY 3.6+) or
25 "surrogatescape" on POSIX and "replace" on Windows
26 * Python 2: "replace"
27 * on Python 2 all APIs return bytes (str type), never unicode
28 * on Python 2, you can go back to unicode by doing:
29
30 >>> unicode(p.exe(), sys.getdefaultencoding(), errors="replace")
31
32 For a detailed explanation of how psutil handles unicode see #1040.
33
34 Tests
35 =====
36
37 List of APIs returning or dealing with a string:
38 ('not tested' means they are not tested to deal with non-ASCII strings):
39
40 * Process.cmdline()
41 * Process.connections('unix')
42 * Process.cwd()
43 * Process.environ()
44 * Process.exe()
45 * Process.memory_maps()
46 * Process.name()
47 * Process.open_files()
48 * Process.username() (not tested)
49
50 * disk_io_counters() (not tested)
51 * disk_partitions() (not tested)
52 * disk_usage(str)
53 * net_connections('unix')
54 * net_if_addrs() (not tested)
55 * net_if_stats() (not tested)
56 * net_io_counters() (not tested)
57 * sensors_fans() (not tested)
58 * sensors_temperatures() (not tested)
59 * users() (not tested)
60
61 * WindowsService.binpath() (not tested)
62 * WindowsService.description() (not tested)
63 * WindowsService.display_name() (not tested)
64 * WindowsService.name() (not tested)
65 * WindowsService.status() (not tested)
66 * WindowsService.username() (not tested)
67
68 In here we create a unicode path with a funky non-ASCII name and (where
69 possible) make psutil return it back (e.g. on name(), exe(), open_files(),
70 etc.) and make sure that:
71
72 * psutil never crashes with UnicodeDecodeError
73 * the returned path matches
74 """
75
76 import os
77 import shutil
78 import traceback
79 import warnings
80 from contextlib import closing
81
82 from psutil import BSD
83 from psutil import OPENBSD
84 from psutil import POSIX
85 from psutil import WINDOWS
86 from psutil._compat import PY3
87 from psutil._compat import u
88 from psutil.tests import APPVEYOR
89 from psutil.tests import ASCII_FS
90 from psutil.tests import bind_unix_socket
91 from psutil.tests import chdir
92 from psutil.tests import CI_TESTING
93 from psutil.tests import copyload_shared_lib
94 from psutil.tests import create_exe
95 from psutil.tests import get_testfn
96 from psutil.tests import HAS_CONNECTIONS_UNIX
97 from psutil.tests import HAS_ENVIRON
98 from psutil.tests import HAS_MEMORY_MAPS
99 from psutil.tests import INVALID_UNICODE_SUFFIX
100 from psutil.tests import PsutilTestCase
101 from psutil.tests import PYPY
102 from psutil.tests import safe_mkdir
103 from psutil.tests import safe_rmpath
104 from psutil.tests import serialrun
105 from psutil.tests import skip_on_access_denied
106 from psutil.tests import spawn_testproc
107 from psutil.tests import terminate
108 from psutil.tests import TESTFN_PREFIX
109 from psutil.tests import UNICODE_SUFFIX
110 from psutil.tests import unittest
111 import psutil
112
113
114 if APPVEYOR:
115 def safe_rmpath(path): # NOQA
116 # TODO - this is quite random and I'm not sure why it happens,
117 # nor I can reproduce it locally:
118 # https://ci.appveyor.com/project/giampaolo/psutil/build/job/
119 # jiq2cgd6stsbtn60
120 # safe_rmpath() happens after reap_children() so this is weird
121 # Perhaps wait_procs() on Windows is broken? Maybe because
122 # of STILL_ACTIVE?
123 # https://github.com/giampaolo/psutil/blob/
124 # 68c7a70728a31d8b8b58f4be6c4c0baa2f449eda/psutil/arch/
125 # windows/process_info.c#L146
126 from psutil.tests import safe_rmpath as rm
127 try:
128 return rm(path)
129 except WindowsError:
130 traceback.print_exc()
131
132
133 def try_unicode(suffix):
134 """Return True if both the fs and the subprocess module can
135 deal with a unicode file name.
136 """
137 sproc = None
138 testfn = get_testfn(suffix=suffix)
139 try:
140 safe_rmpath(testfn)
141 create_exe(testfn)
142 sproc = spawn_testproc(cmd=[testfn])
143 shutil.copyfile(testfn, testfn + '-2')
144 safe_rmpath(testfn + '-2')
145 except (UnicodeEncodeError, IOError):
146 return False
147 else:
148 return True
149 finally:
150 if sproc is not None:
151 terminate(sproc)
152 safe_rmpath(testfn)
153
154
155 # ===================================================================
156 # FS APIs
157 # ===================================================================
158
159
160 class BaseUnicodeTest(PsutilTestCase):
161 funky_suffix = None
162
163 def setUp(self):
164 if self.funky_suffix is not None:
165 if not try_unicode(self.funky_suffix):
166 raise self.skipTest("can't handle unicode str")
167
168
169 @serialrun
170 @unittest.skipIf(ASCII_FS, "ASCII fs")
171 @unittest.skipIf(PYPY and not PY3, "too much trouble on PYPY2")
172 class TestFSAPIs(BaseUnicodeTest):
173 """Test FS APIs with a funky, valid, UTF8 path name."""
174
175 funky_suffix = UNICODE_SUFFIX
176
177 @classmethod
178 def setUpClass(cls):
179 cls.funky_name = get_testfn(suffix=cls.funky_suffix)
180 create_exe(cls.funky_name)
181
182 @classmethod
183 def tearDownClass(cls):
184 safe_rmpath(cls.funky_name)
185
186 def expect_exact_path_match(self):
187 # Do not expect psutil to correctly handle unicode paths on
188 # Python 2 if os.listdir() is not able either.
189 here = '.' if isinstance(self.funky_name, str) else u('.')
190 with warnings.catch_warnings():
191 warnings.simplefilter("ignore")
192 return self.funky_name in os.listdir(here)
193
194 # ---
195
196 def test_proc_exe(self):
197 subp = self.spawn_testproc(cmd=[self.funky_name])
198 p = psutil.Process(subp.pid)
199 exe = p.exe()
200 self.assertIsInstance(exe, str)
201 if self.expect_exact_path_match():
202 self.assertEqual(os.path.normcase(exe),
203 os.path.normcase(self.funky_name))
204
205 def test_proc_name(self):
206 subp = self.spawn_testproc(cmd=[self.funky_name])
207 name = psutil.Process(subp.pid).name()
208 self.assertIsInstance(name, str)
209 if self.expect_exact_path_match():
210 self.assertEqual(name, os.path.basename(self.funky_name))
211
212 def test_proc_cmdline(self):
213 subp = self.spawn_testproc(cmd=[self.funky_name])
214 p = psutil.Process(subp.pid)
215 cmdline = p.cmdline()
216 for part in cmdline:
217 self.assertIsInstance(part, str)
218 if self.expect_exact_path_match():
219 self.assertEqual(cmdline, [self.funky_name])
220
221 def test_proc_cwd(self):
222 dname = self.funky_name + "2"
223 self.addCleanup(safe_rmpath, dname)
224 safe_mkdir(dname)
225 with chdir(dname):
226 p = psutil.Process()
227 cwd = p.cwd()
228 self.assertIsInstance(p.cwd(), str)
229 if self.expect_exact_path_match():
230 self.assertEqual(cwd, dname)
231
232 @unittest.skipIf(PYPY and WINDOWS, "fails on PYPY + WINDOWS")
233 def test_proc_open_files(self):
234 p = psutil.Process()
235 start = set(p.open_files())
236 with open(self.funky_name, 'rb'):
237 new = set(p.open_files())
238 path = (new - start).pop().path
239 self.assertIsInstance(path, str)
240 if BSD and not path:
241 # XXX - see https://github.com/giampaolo/psutil/issues/595
242 return self.skipTest("open_files on BSD is broken")
243 if self.expect_exact_path_match():
244 self.assertEqual(os.path.normcase(path),
245 os.path.normcase(self.funky_name))
246
247 @unittest.skipIf(not POSIX, "POSIX only")
248 def test_proc_connections(self):
249 name = self.get_testfn(suffix=self.funky_suffix)
250 try:
251 sock = bind_unix_socket(name)
252 except UnicodeEncodeError:
253 if PY3:
254 raise
255 else:
256 raise unittest.SkipTest("not supported")
257 with closing(sock):
258 conn = psutil.Process().connections('unix')[0]
259 self.assertIsInstance(conn.laddr, str)
260 # AF_UNIX addr not set on OpenBSD
261 if not OPENBSD: # XXX
262 self.assertEqual(conn.laddr, name)
263
264 @unittest.skipIf(not POSIX, "POSIX only")
265 @unittest.skipIf(not HAS_CONNECTIONS_UNIX, "can't list UNIX sockets")
266 @skip_on_access_denied()
267 def test_net_connections(self):
268 def find_sock(cons):
269 for conn in cons:
270 if os.path.basename(conn.laddr).startswith(TESTFN_PREFIX):
271 return conn
272 raise ValueError("connection not found")
273
274 name = self.get_testfn(suffix=self.funky_suffix)
275 try:
276 sock = bind_unix_socket(name)
277 except UnicodeEncodeError:
278 if PY3:
279 raise
280 else:
281 raise unittest.SkipTest("not supported")
282 with closing(sock):
283 cons = psutil.net_connections(kind='unix')
284 # AF_UNIX addr not set on OpenBSD
285 if not OPENBSD:
286 conn = find_sock(cons)
287 self.assertIsInstance(conn.laddr, str)
288 self.assertEqual(conn.laddr, name)
289
290 def test_disk_usage(self):
291 dname = self.funky_name + "2"
292 self.addCleanup(safe_rmpath, dname)
293 safe_mkdir(dname)
294 psutil.disk_usage(dname)
295
296 @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported")
297 @unittest.skipIf(not PY3, "ctypes does not support unicode on PY2")
298 @unittest.skipIf(PYPY, "unstable on PYPY")
299 def test_memory_maps(self):
300 # XXX: on Python 2, using ctypes.CDLL with a unicode path
301 # opens a message box which blocks the test run.
302 with copyload_shared_lib(suffix=self.funky_suffix) as funky_path:
303 def normpath(p):
304 return os.path.realpath(os.path.normcase(p))
305 libpaths = [normpath(x.path)
306 for x in psutil.Process().memory_maps()]
307 # ...just to have a clearer msg in case of failure
308 libpaths = [x for x in libpaths if TESTFN_PREFIX in x]
309 self.assertIn(normpath(funky_path), libpaths)
310 for path in libpaths:
311 self.assertIsInstance(path, str)
312
313
314 @unittest.skipIf(CI_TESTING, "unreliable on CI")
315 class TestFSAPIsWithInvalidPath(TestFSAPIs):
316 """Test FS APIs with a funky, invalid path name."""
317 funky_suffix = INVALID_UNICODE_SUFFIX
318
319 @classmethod
320 def expect_exact_path_match(cls):
321 # Invalid unicode names are supposed to work on Python 2.
322 return True
323
324
325 # ===================================================================
326 # Non fs APIs
327 # ===================================================================
328
329
330 class TestNonFSAPIS(BaseUnicodeTest):
331 """Unicode tests for non fs-related APIs."""
332 funky_suffix = UNICODE_SUFFIX if PY3 else 'è'
333
334 @unittest.skipIf(not HAS_ENVIRON, "not supported")
335 @unittest.skipIf(PYPY and WINDOWS, "segfaults on PYPY + WINDOWS")
336 def test_proc_environ(self):
337 # Note: differently from others, this test does not deal
338 # with fs paths. On Python 2 subprocess module is broken as
339 # it's not able to handle with non-ASCII env vars, so
340 # we use "è", which is part of the extended ASCII table
341 # (unicode point <= 255).
342 env = os.environ.copy()
343 env['FUNNY_ARG'] = self.funky_suffix
344 sproc = self.spawn_testproc(env=env)
345 p = psutil.Process(sproc.pid)
346 env = p.environ()
347 for k, v in env.items():
348 self.assertIsInstance(k, str)
349 self.assertIsInstance(v, str)
350 self.assertEqual(env['FUNNY_ARG'], self.funky_suffix)
351
352
353 if __name__ == '__main__':
354 from psutil.tests.runner import run_from_name
355 run_from_name(__file__)