comparison planemo/lib/python3.7/site-packages/psutil/tests/test_unicode.py @ 1:56ad4e20f292 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:32:28 -0400
parents
children
comparison
equal deleted inserted replaced
0:d30785e31577 1:56ad4e20f292
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 CIRRUS
94 from psutil.tests import copyload_shared_lib
95 from psutil.tests import create_exe
96 from psutil.tests import get_testfn
97 from psutil.tests import HAS_CONNECTIONS_UNIX
98 from psutil.tests import HAS_ENVIRON
99 from psutil.tests import HAS_MEMORY_MAPS
100 from psutil.tests import INVALID_UNICODE_SUFFIX
101 from psutil.tests import PsutilTestCase
102 from psutil.tests import PYPY
103 from psutil.tests import safe_mkdir
104 from psutil.tests import safe_rmpath
105 from psutil.tests import serialrun
106 from psutil.tests import skip_on_access_denied
107 from psutil.tests import spawn_testproc
108 from psutil.tests import terminate
109 from psutil.tests import TESTFN_PREFIX
110 from psutil.tests import UNICODE_SUFFIX
111 from psutil.tests import unittest
112 import psutil
113
114
115 if APPVEYOR:
116 def safe_rmpath(path): # NOQA
117 # TODO - this is quite random and I'm not sure why it happens,
118 # nor I can reproduce it locally:
119 # https://ci.appveyor.com/project/giampaolo/psutil/build/job/
120 # jiq2cgd6stsbtn60
121 # safe_rmpath() happens after reap_children() so this is weird
122 # Perhaps wait_procs() on Windows is broken? Maybe because
123 # of STILL_ACTIVE?
124 # https://github.com/giampaolo/psutil/blob/
125 # 68c7a70728a31d8b8b58f4be6c4c0baa2f449eda/psutil/arch/
126 # windows/process_info.c#L146
127 from psutil.tests import safe_rmpath as _rm
128 try:
129 return _rm(path)
130 except WindowsError:
131 traceback.print_exc()
132
133
134 def try_unicode(suffix):
135 """Return True if both the fs and the subprocess module can
136 deal with a unicode file name.
137 """
138 if PY3:
139 return True
140 sproc = None
141 testfn = get_testfn(suffix=suffix)
142 try:
143 safe_rmpath(testfn)
144 create_exe(testfn)
145 sproc = spawn_testproc(cmd=[testfn])
146 shutil.copyfile(testfn, testfn + '-2')
147 safe_rmpath(testfn + '-2')
148 except (UnicodeEncodeError, IOError):
149 return False
150 else:
151 return True
152 finally:
153 if sproc is not None:
154 terminate(sproc)
155 safe_rmpath(testfn)
156
157
158 # ===================================================================
159 # FS APIs
160 # ===================================================================
161
162
163 @serialrun
164 @unittest.skipIf(ASCII_FS, "ASCII fs")
165 @unittest.skipIf(PYPY and not PY3, "too much trouble on PYPY2")
166 class _BaseFSAPIsTests(object):
167 funky_suffix = None
168
169 @classmethod
170 def setUpClass(cls):
171 cls.funky_name = get_testfn(suffix=cls.funky_suffix)
172 create_exe(cls.funky_name)
173
174 @classmethod
175 def tearDownClass(cls):
176 safe_rmpath(cls.funky_name)
177
178 def expect_exact_path_match(self):
179 raise NotImplementedError("must be implemented in subclass")
180
181 # ---
182
183 def test_proc_exe(self):
184 subp = self.spawn_testproc(cmd=[self.funky_name])
185 p = psutil.Process(subp.pid)
186 exe = p.exe()
187 self.assertIsInstance(exe, str)
188 if self.expect_exact_path_match():
189 self.assertEqual(os.path.normcase(exe),
190 os.path.normcase(self.funky_name))
191
192 def test_proc_name(self):
193 subp = self.spawn_testproc(cmd=[self.funky_name])
194 name = psutil.Process(subp.pid).name()
195 self.assertIsInstance(name, str)
196 if self.expect_exact_path_match():
197 self.assertEqual(name, os.path.basename(self.funky_name))
198
199 def test_proc_cmdline(self):
200 subp = self.spawn_testproc(cmd=[self.funky_name])
201 p = psutil.Process(subp.pid)
202 cmdline = p.cmdline()
203 for part in cmdline:
204 self.assertIsInstance(part, str)
205 if self.expect_exact_path_match():
206 self.assertEqual(cmdline, [self.funky_name])
207
208 def test_proc_cwd(self):
209 dname = self.funky_name + "2"
210 self.addCleanup(safe_rmpath, dname)
211 safe_mkdir(dname)
212 with chdir(dname):
213 p = psutil.Process()
214 cwd = p.cwd()
215 self.assertIsInstance(p.cwd(), str)
216 if self.expect_exact_path_match():
217 self.assertEqual(cwd, dname)
218
219 @unittest.skipIf(PYPY and WINDOWS, "fails on PYPY + WINDOWS")
220 def test_proc_open_files(self):
221 p = psutil.Process()
222 start = set(p.open_files())
223 with open(self.funky_name, 'rb'):
224 new = set(p.open_files())
225 path = (new - start).pop().path
226 self.assertIsInstance(path, str)
227 if BSD and not path:
228 # XXX - see https://github.com/giampaolo/psutil/issues/595
229 return self.skipTest("open_files on BSD is broken")
230 if self.expect_exact_path_match():
231 self.assertEqual(os.path.normcase(path),
232 os.path.normcase(self.funky_name))
233
234 @unittest.skipIf(not POSIX, "POSIX only")
235 def test_proc_connections(self):
236 name = self.get_testfn(suffix=self.funky_suffix)
237 try:
238 sock = bind_unix_socket(name)
239 except UnicodeEncodeError:
240 if PY3:
241 raise
242 else:
243 raise unittest.SkipTest("not supported")
244 with closing(sock):
245 conn = psutil.Process().connections('unix')[0]
246 self.assertIsInstance(conn.laddr, str)
247 # AF_UNIX addr not set on OpenBSD
248 if not OPENBSD and not CIRRUS: # XXX
249 self.assertEqual(conn.laddr, name)
250
251 @unittest.skipIf(not POSIX, "POSIX only")
252 @unittest.skipIf(not HAS_CONNECTIONS_UNIX, "can't list UNIX sockets")
253 @skip_on_access_denied()
254 def test_net_connections(self):
255 def find_sock(cons):
256 for conn in cons:
257 if os.path.basename(conn.laddr).startswith(TESTFN_PREFIX):
258 return conn
259 raise ValueError("connection not found")
260
261 name = self.get_testfn(suffix=self.funky_suffix)
262 try:
263 sock = bind_unix_socket(name)
264 except UnicodeEncodeError:
265 if PY3:
266 raise
267 else:
268 raise unittest.SkipTest("not supported")
269 with closing(sock):
270 cons = psutil.net_connections(kind='unix')
271 # AF_UNIX addr not set on OpenBSD
272 if not OPENBSD:
273 conn = find_sock(cons)
274 self.assertIsInstance(conn.laddr, str)
275 self.assertEqual(conn.laddr, name)
276
277 def test_disk_usage(self):
278 dname = self.funky_name + "2"
279 self.addCleanup(safe_rmpath, dname)
280 safe_mkdir(dname)
281 psutil.disk_usage(dname)
282
283 @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported")
284 @unittest.skipIf(not PY3, "ctypes does not support unicode on PY2")
285 @unittest.skipIf(PYPY and WINDOWS,
286 "copyload_shared_lib() unsupported on PYPY + WINDOWS")
287 def test_memory_maps(self):
288 # XXX: on Python 2, using ctypes.CDLL with a unicode path
289 # opens a message box which blocks the test run.
290 with copyload_shared_lib(suffix=self.funky_suffix) as funky_path:
291 def normpath(p):
292 return os.path.realpath(os.path.normcase(p))
293 libpaths = [normpath(x.path)
294 for x in psutil.Process().memory_maps()]
295 # ...just to have a clearer msg in case of failure
296 libpaths = [x for x in libpaths if TESTFN_PREFIX in x]
297 self.assertIn(normpath(funky_path), libpaths)
298 for path in libpaths:
299 self.assertIsInstance(path, str)
300
301
302 # https://travis-ci.org/giampaolo/psutil/jobs/440073249
303 # @unittest.skipIf(PYPY and TRAVIS, "unreliable on PYPY + TRAVIS")
304 # @unittest.skipIf(MACOS and TRAVIS, "unreliable on TRAVIS") # TODO
305 @unittest.skipIf(not try_unicode(UNICODE_SUFFIX),
306 "can't deal with unicode str")
307 class TestFSAPIs(_BaseFSAPIsTests, PsutilTestCase):
308 """Test FS APIs with a funky, valid, UTF8 path name."""
309 funky_suffix = UNICODE_SUFFIX
310
311 def expect_exact_path_match(self):
312 # Do not expect psutil to correctly handle unicode paths on
313 # Python 2 if os.listdir() is not able either.
314 here = '.' if isinstance(self.funky_name, str) else u('.')
315 with warnings.catch_warnings():
316 warnings.simplefilter("ignore")
317 return self.funky_name in os.listdir(here)
318
319
320 @unittest.skipIf(CI_TESTING, "unreliable on CI")
321 @unittest.skipIf(not try_unicode(INVALID_UNICODE_SUFFIX),
322 "can't deal with invalid unicode str")
323 class TestFSAPIsWithInvalidPath(_BaseFSAPIsTests, PsutilTestCase):
324 """Test FS APIs with a funky, invalid path name."""
325 funky_suffix = INVALID_UNICODE_SUFFIX
326
327 @classmethod
328 def expect_exact_path_match(cls):
329 # Invalid unicode names are supposed to work on Python 2.
330 return True
331
332
333 # ===================================================================
334 # Non fs APIs
335 # ===================================================================
336
337
338 class TestNonFSAPIS(PsutilTestCase):
339 """Unicode tests for non fs-related APIs."""
340
341 @unittest.skipIf(not HAS_ENVIRON, "not supported")
342 @unittest.skipIf(PYPY and WINDOWS, "segfaults on PYPY + WINDOWS")
343 def test_proc_environ(self):
344 # Note: differently from others, this test does not deal
345 # with fs paths. On Python 2 subprocess module is broken as
346 # it's not able to handle with non-ASCII env vars, so
347 # we use "è", which is part of the extended ASCII table
348 # (unicode point <= 255).
349 env = os.environ.copy()
350 funky_str = UNICODE_SUFFIX if PY3 else 'è'
351 env['FUNNY_ARG'] = funky_str
352 sproc = self.spawn_testproc(env=env)
353 p = psutil.Process(sproc.pid)
354 env = p.environ()
355 for k, v in env.items():
356 self.assertIsInstance(k, str)
357 self.assertIsInstance(v, str)
358 self.assertEqual(env['FUNNY_ARG'], funky_str)
359
360
361 if __name__ == '__main__':
362 from psutil.tests.runner import run_from_name
363 run_from_name(__file__)