VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/common/utils.py@ 103724

Last change on this file since 103724 was 103724, checked in by vboxsync, 8 months ago

ValidationKit: Suppress pylint warnings which appear with Python 3.12, bugref:10617.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 89.5 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: utils.py 103724 2024-03-07 10:22:33Z vboxsync $
3# pylint: disable=too-many-lines
4
5"""
6Common Utility Functions.
7"""
8
9from __future__ import print_function;
10
11__copyright__ = \
12"""
13Copyright (C) 2012-2023 Oracle and/or its affiliates.
14
15This file is part of VirtualBox base platform packages, as
16available from https://www.virtualbox.org.
17
18This program is free software; you can redistribute it and/or
19modify it under the terms of the GNU General Public License
20as published by the Free Software Foundation, in version 3 of the
21License.
22
23This program is distributed in the hope that it will be useful, but
24WITHOUT ANY WARRANTY; without even the implied warranty of
25MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26General Public License for more details.
27
28You should have received a copy of the GNU General Public License
29along with this program; if not, see <https://www.gnu.org/licenses>.
30
31The contents of this file may alternatively be used under the terms
32of the Common Development and Distribution License Version 1.0
33(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
34in the VirtualBox distribution, in which case the provisions of the
35CDDL are applicable instead of those of the GPL.
36
37You may elect to license modified versions of this file under the
38terms and conditions of either the GPL or the CDDL or both.
39
40SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
41"""
42__version__ = "$Revision: 103724 $"
43
44
45# Standard Python imports.
46import datetime;
47import errno;
48import os;
49import platform;
50import re;
51import stat;
52import subprocess;
53import sys;
54import time;
55import traceback;
56import unittest;
57
58if sys.platform == 'win32':
59 import ctypes;
60 import msvcrt; # pylint: disable=import-error
61 import win32api; # pylint: disable=import-error
62 import win32con; # pylint: disable=import-error
63 import win32console; # pylint: disable=import-error
64 import win32file; # pylint: disable=import-error
65 import win32process; # pylint: disable=import-error
66 import winerror; # pylint: disable=import-error
67 import pywintypes; # pylint: disable=import-error
68else:
69 import signal;
70
71# Python 3 hacks:
72if sys.version_info[0] >= 3:
73 unicode = str; # pylint: disable=redefined-builtin,invalid-name
74 xrange = range; # pylint: disable=redefined-builtin,invalid-name
75 long = int; # pylint: disable=redefined-builtin,invalid-name
76
77
78#
79# Python 2/3 glue.
80#
81
82if sys.version_info[0] >= 3:
83 def iteritems(dDict):
84 """
85 Wrapper around dict.items() / dict.iteritems().
86 """
87 return iter(dDict.items());
88else:
89 def iteritems(dDict):
90 """
91 Wrapper around dict.items() / dict.iteritems().
92 """
93 return dDict.iteritems();
94
95
96#
97# Strings.
98#
99
100def toUnicode(sString, encoding = None, errors = 'strict'):
101 """
102 A little like the python 2 unicode() function.
103 """
104 if sys.version_info[0] >= 3:
105 if isinstance(sString, bytes):
106 return str(sString, encoding if encoding else 'utf-8', errors);
107 else:
108 if not isinstance(sString, unicode):
109 return unicode(sString, encoding if encoding else 'utf-8', errors);
110 return sString;
111
112
113
114#
115# Output.
116#
117
118def printOut(sString):
119 """
120 Outputs a string to standard output, dealing with python 2.x encoding stupidity.
121 """
122 sStreamEncoding = sys.stdout.encoding;
123 if sStreamEncoding is None: # Files, pipes and such on 2.x. (pylint is confused here)
124 sStreamEncoding = 'US-ASCII'; # pylint: disable=redefined-variable-type
125 if sStreamEncoding == 'UTF-8' or not isinstance(sString, unicode):
126 print(sString);
127 else:
128 print(sString.encode(sStreamEncoding, 'backslashreplace').decode(sStreamEncoding));
129
130def printErr(sString):
131 """
132 Outputs a string to standard error, dealing with python 2.x encoding stupidity.
133 """
134 sStreamEncoding = sys.stderr.encoding;
135 if sStreamEncoding is None: # Files, pipes and such on 2.x. (pylint is confused here)
136 sStreamEncoding = 'US-ASCII'; # pylint: disable=redefined-variable-type
137 if sStreamEncoding == 'UTF-8' or not isinstance(sString, unicode):
138 print(sString, file = sys.stderr);
139 else:
140 print(sString.encode(sStreamEncoding, 'backslashreplace').decode(sStreamEncoding), file = sys.stderr);
141
142
143#
144# Host OS and CPU.
145#
146
147def getHostOs():
148 """
149 Gets the host OS name (short).
150
151 See the KBUILD_OSES variable in kBuild/header.kmk for possible return values.
152 """
153 sPlatform = platform.system();
154 if sPlatform in ('Linux', 'Darwin', 'Solaris', 'FreeBSD', 'NetBSD', 'OpenBSD'):
155 sPlatform = sPlatform.lower();
156 elif sPlatform == 'Windows':
157 sPlatform = 'win';
158 elif sPlatform == 'SunOS':
159 sPlatform = 'solaris';
160 else:
161 raise Exception('Unsupported platform "%s"' % (sPlatform,));
162 return sPlatform;
163
164g_sHostArch = None;
165
166def getHostArch():
167 """
168 Gets the host CPU architecture.
169
170 See the KBUILD_ARCHES variable in kBuild/header.kmk for possible return values.
171 """
172 global g_sHostArch;
173 if g_sHostArch is None:
174 sArch = platform.machine();
175 if sArch in ('i386', 'i486', 'i586', 'i686', 'i786', 'i886', 'x86'):
176 sArch = 'x86';
177 elif sArch in ('AMD64', 'amd64', 'x86_64'):
178 sArch = 'amd64';
179 elif sArch == 'i86pc': # SunOS
180 if platform.architecture()[0] == '64bit':
181 sArch = 'amd64';
182 else:
183 try:
184 sArch = str(processOutputChecked(['/usr/bin/isainfo', '-n',]));
185 except:
186 pass;
187 sArch = sArch.strip();
188 if sArch != 'amd64':
189 sArch = 'x86';
190 elif sArch in ('arm64', 'ARM64', 'aarch64'):
191 sArch = 'arm64';
192 else:
193 raise Exception('Unsupported architecture/machine "%s"' % (sArch,));
194 g_sHostArch = sArch;
195 return g_sHostArch;
196
197
198def getHostOsDotArch():
199 """
200 Gets the 'os.arch' for the host.
201 """
202 return '%s.%s' % (getHostOs(), getHostArch());
203
204
205def isValidOs(sOs):
206 """
207 Validates the OS name.
208 """
209 if sOs in ('darwin', 'dos', 'dragonfly', 'freebsd', 'haiku', 'l4', 'linux', 'netbsd', 'nt', 'openbsd', \
210 'os2', 'solaris', 'win', 'os-agnostic'):
211 return True;
212 return False;
213
214
215def isValidArch(sArch):
216 """
217 Validates the CPU architecture name.
218 """
219 if sArch in ('x86', 'amd64', 'sparc32', 'sparc64', 's390', 's390x', 'ppc32', 'ppc64', \
220 'mips32', 'mips64', 'ia64', 'hppa32', 'hppa64', 'arm', 'arm64', 'alpha'):
221 return True;
222 return False;
223
224def isValidOsDotArch(sOsDotArch):
225 """
226 Validates the 'os.arch' string.
227 """
228
229 asParts = sOsDotArch.split('.');
230 if asParts.length() != 2:
231 return False;
232 return isValidOs(asParts[0]) \
233 and isValidArch(asParts[1]);
234
235def getHostOsVersion():
236 """
237 Returns the host OS version. This is platform.release with additional
238 distro indicator on linux.
239 """
240 sVersion = platform.release();
241 sOs = getHostOs();
242 if sOs == 'linux':
243 sDist = '';
244 try:
245 # try /etc/lsb-release first to distinguish between Debian and Ubuntu
246 with open('/etc/lsb-release') as oFile: # pylint: disable=unspecified-encoding
247 for sLine in oFile:
248 oMatch = re.search(r'(?:DISTRIB_DESCRIPTION\s*=)\s*"*(.*)"', sLine);
249 if oMatch is not None:
250 sDist = oMatch.group(1).strip();
251 except:
252 pass;
253 if sDist:
254 sVersion += ' / ' + sDist;
255 else:
256 asFiles = \
257 [
258 [ '/etc/debian_version', 'Debian v'],
259 [ '/etc/gentoo-release', '' ],
260 [ '/etc/oracle-release', '' ],
261 [ '/etc/redhat-release', '' ],
262 [ '/etc/SuSE-release', '' ],
263 ];
264 for sFile, sPrefix in asFiles:
265 if os.path.isfile(sFile):
266 try:
267 with open(sFile) as oFile: # pylint: disable=unspecified-encoding
268 sLine = oFile.readline();
269 except:
270 continue;
271 sLine = sLine.strip()
272 if sLine:
273 sVersion += ' / ' + sPrefix + sLine;
274 break;
275
276 elif sOs == 'solaris':
277 sVersion = platform.version();
278 if os.path.isfile('/etc/release'):
279 try:
280 with open('/etc/release') as oFile: # pylint: disable=unspecified-encoding
281 sLast = oFile.readlines()[-1];
282 sLast = sLast.strip();
283 if sLast:
284 sVersion += ' (' + sLast + ')';
285 except:
286 pass;
287
288 elif sOs == 'darwin':
289 def getMacVersionName(sVersion):
290 """
291 Figures out the Mac OS X/macOS code name from the numeric version.
292 """
293 aOsVersion = sVersion.split('.') # example: ('10','15','7')
294 codenames = {"4": "Tiger",
295 "5": "Leopard",
296 "6": "Snow Leopard",
297 "7": "Lion",
298 "8": "Mountain Lion",
299 "9": "Mavericks",
300 "10": "Yosemite",
301 "11": "El Capitan",
302 "12": "Sierra",
303 "13": "High Sierra",
304 "14": "Mojave",
305 "15": "Catalina",
306 "16": "Wrong version",
307 }
308 codenames_afterCatalina = {"11": "Big Sur",
309 "12": "Monterey",
310 "13": "Ventura",
311 "14": "Sonoma",
312 "15": "Unknown 15",
313 "16": "Unknown 16"}
314
315 if aOsVersion[0] == '10':
316 sResult = codenames[aOsVersion[1]]
317 else:
318 sResult = codenames_afterCatalina[aOsVersion[0]]
319 return sResult
320
321 sOsxVersion = platform.mac_ver()[0]
322 sVersion += ' / OS X ' + sOsxVersion + ' (' + getMacVersionName(sOsxVersion) + ')'
323
324 elif sOs == 'win':
325 class OSVersionInfoEx(ctypes.Structure): # pylint: disable=used-before-assignment
326 """ OSVERSIONEX """
327 kaFields = [
328 ('dwOSVersionInfoSize', ctypes.c_ulong),
329 ('dwMajorVersion', ctypes.c_ulong),
330 ('dwMinorVersion', ctypes.c_ulong),
331 ('dwBuildNumber', ctypes.c_ulong),
332 ('dwPlatformId', ctypes.c_ulong),
333 ('szCSDVersion', ctypes.c_wchar*128),
334 ('wServicePackMajor', ctypes.c_ushort),
335 ('wServicePackMinor', ctypes.c_ushort),
336 ('wSuiteMask', ctypes.c_ushort),
337 ('wProductType', ctypes.c_byte),
338 ('wReserved', ctypes.c_byte)]
339 _fields_ = kaFields # pylint: disable=invalid-name
340
341 def __init__(self):
342 super(OSVersionInfoEx, self).__init__()
343 self.dwOSVersionInfoSize = ctypes.sizeof(self)
344
345 oOsVersion = OSVersionInfoEx()
346 rc = ctypes.windll.Ntdll.RtlGetVersion(ctypes.byref(oOsVersion))
347 if rc == 0:
348 # Python platform.release() is not reliable for newer server releases
349 if oOsVersion.wProductType != 1:
350 if oOsVersion.dwMajorVersion == 10 and oOsVersion.dwMinorVersion == 0:
351 sVersion = '2016Server';
352 elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 3:
353 sVersion = '2012ServerR2';
354 elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 2:
355 sVersion = '2012Server';
356 elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 1:
357 sVersion = '2008ServerR2';
358 elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 0:
359 sVersion = '2008Server';
360 elif oOsVersion.dwMajorVersion == 5 and oOsVersion.dwMinorVersion == 2:
361 sVersion = '2003Server';
362 sVersion += ' build ' + str(oOsVersion.dwBuildNumber)
363 if oOsVersion.wServicePackMajor:
364 sVersion += ' SP' + str(oOsVersion.wServicePackMajor)
365 if oOsVersion.wServicePackMinor:
366 sVersion += '.' + str(oOsVersion.wServicePackMinor)
367
368 return sVersion;
369
370def getPresentCpuCount():
371 """
372 Gets the number of CPUs present in the system.
373
374 This differs from multiprocessor.cpu_count() and os.cpu_count() on windows in
375 that we return the active count rather than the maximum count. If we don't,
376 we will end up thinking testboxmem1 has 512 CPU threads, which it doesn't and
377 never will have.
378
379 @todo This is probably not exactly what we get on non-windows...
380 """
381
382 if getHostOs() == 'win':
383 fnGetActiveProcessorCount = getattr(ctypes.windll.kernel32, 'GetActiveProcessorCount', None);
384 if fnGetActiveProcessorCount:
385 cCpus = fnGetActiveProcessorCount(ctypes.c_ushort(0xffff));
386 if cCpus > 0:
387 return cCpus;
388
389 import multiprocessing
390 return multiprocessing.cpu_count();
391
392
393#
394# File system.
395#
396
397def openNoInherit(sFile, sMode = 'r'):
398 """
399 Wrapper around open() that tries it's best to make sure the file isn't
400 inherited by child processes.
401
402 This is a best effort thing at the moment as it doesn't synchronizes with
403 child process spawning in any way. Thus it can be subject to races in
404 multithreaded programs.
405 """
406
407 # Python 3.4 and later automatically creates non-inherit handles. See PEP-0446.
408 uPythonVer = (sys.version_info[0] << 16) | (sys.version_info[1] & 0xffff);
409 if uPythonVer >= ((3 << 16) | 4):
410 oFile = open(sFile, sMode); # pylint: disable=consider-using-with,unspecified-encoding
411 else:
412 try:
413 from fcntl import FD_CLOEXEC, F_GETFD, F_SETFD, fcntl; # pylint: disable=import-error
414 except:
415 # On windows, we can use the 'N' flag introduced in Visual C++ 7.0 or 7.1 with python 2.x.
416 if getHostOs() == 'win':
417 if uPythonVer < (3 << 16):
418 offComma = sMode.find(',');
419 if offComma < 0:
420 return open(sFile, sMode + 'N'); # pylint: disable=consider-using-with,unspecified-encoding
421 return open(sFile, # pylint: disable=consider-using-with,unspecified-encoding,bad-open-mode
422 sMode[:offComma] + 'N' + sMode[offComma:]);
423
424 # Just in case.
425 return open(sFile, sMode); # pylint: disable=consider-using-with,unspecified-encoding
426
427 oFile = open(sFile, sMode); # pylint: disable=consider-using-with,unspecified-encoding
428 #try:
429 fcntl(oFile, F_SETFD, fcntl(oFile, F_GETFD) | FD_CLOEXEC);
430 #except:
431 # pass;
432 return oFile;
433
434def openNoDenyDeleteNoInherit(sFile, sMode = 'r'):
435 """
436 Wrapper around open() that tries it's best to make sure the file isn't
437 inherited by child processes.
438
439 This is a best effort thing at the moment as it doesn't synchronizes with
440 child process spawning in any way. Thus it can be subject to races in
441 multithreaded programs.
442 """
443
444 if getHostOs() == 'win':
445 # Need to use CreateFile directly to open the file so we can feed it FILE_SHARE_DELETE.
446 # pylint: disable=no-member,c-extension-no-member
447 fAccess = 0;
448 fDisposition = win32file.OPEN_EXISTING; # pylint: disable=used-before-assignment
449 if 'r' in sMode or '+' in sMode:
450 fAccess |= win32file.GENERIC_READ;
451 if 'a' in sMode:
452 fAccess |= win32file.GENERIC_WRITE;
453 fDisposition = win32file.OPEN_ALWAYS;
454 elif 'w' in sMode:
455 fAccess = win32file.GENERIC_WRITE;
456 if '+' in sMode:
457 fDisposition = win32file.OPEN_ALWAYS;
458 fAccess |= win32file.GENERIC_READ;
459 else:
460 fDisposition = win32file.CREATE_ALWAYS;
461 if not fAccess:
462 fAccess |= win32file.GENERIC_READ;
463 fSharing = (win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE
464 | win32file.FILE_SHARE_DELETE);
465 hFile = win32file.CreateFile(sFile, fAccess, fSharing, None, fDisposition, 0, None);
466 if 'a' in sMode:
467 win32file.SetFilePointer(hFile, 0, win32file.FILE_END);
468
469 # Turn the NT handle into a CRT file descriptor.
470 hDetachedFile = hFile.Detach();
471 if fAccess == win32file.GENERIC_READ:
472 fOpen = os.O_RDONLY;
473 elif fAccess == win32file.GENERIC_WRITE:
474 fOpen = os.O_WRONLY;
475 else:
476 fOpen = os.O_RDWR;
477 # pulint: enable=no-member,c-extension-no-member
478 if 'a' in sMode:
479 fOpen |= os.O_APPEND;
480 if 'b' in sMode or 't' in sMode:
481 fOpen |= os.O_TEXT; # pylint: disable=no-member
482 fdFile = msvcrt.open_osfhandle(hDetachedFile, fOpen); # pylint: disable=used-before-assignment
483
484 # Tell python to use this handle.
485 oFile = os.fdopen(fdFile, sMode);
486 else:
487 oFile = open(sFile, sMode); # pylint: disable=consider-using-with,unspecified-encoding
488
489 # Python 3.4 and later automatically creates non-inherit handles. See PEP-0446.
490 uPythonVer = (sys.version_info[0] << 16) | (sys.version_info[1] & 0xffff);
491 if uPythonVer < ((3 << 16) | 4):
492 try:
493 from fcntl import FD_CLOEXEC, F_GETFD, F_SETFD, fcntl; # pylint: disable=import-error
494 except:
495 pass;
496 else:
497 fcntl(oFile, F_SETFD, fcntl(oFile, F_GETFD) | FD_CLOEXEC);
498 return oFile;
499
500def noxcptReadLink(sPath, sXcptRet, sEncoding = 'utf-8'):
501 """
502 No exceptions os.readlink wrapper.
503 """
504 try:
505 sRet = os.readlink(sPath); # pylint: disable=no-member
506 except:
507 return sXcptRet;
508 if hasattr(sRet, 'decode'):
509 sRet = sRet.decode(sEncoding, 'ignore');
510 return sRet;
511
512def readFile(sFile, sMode = 'rb'):
513 """
514 Reads the entire file.
515 """
516 with open(sFile, sMode) as oFile: # pylint: disable=unspecified-encoding
517 sRet = oFile.read();
518 return sRet;
519
520def noxcptReadFile(sFile, sXcptRet, sMode = 'rb', sEncoding = 'utf-8'):
521 """
522 No exceptions common.readFile wrapper.
523 """
524 try:
525 sRet = readFile(sFile, sMode);
526 except:
527 sRet = sXcptRet;
528 if sEncoding is not None and hasattr(sRet, 'decode'):
529 sRet = sRet.decode(sEncoding, 'ignore');
530 return sRet;
531
532def noxcptRmDir(sDir, oXcptRet = False):
533 """
534 No exceptions os.rmdir wrapper.
535 """
536 oRet = True;
537 try:
538 os.rmdir(sDir);
539 except:
540 oRet = oXcptRet;
541 return oRet;
542
543def noxcptDeleteFile(sFile, oXcptRet = False):
544 """
545 No exceptions os.remove wrapper.
546 """
547 oRet = True;
548 try:
549 os.remove(sFile);
550 except:
551 oRet = oXcptRet;
552 return oRet;
553
554
555def dirEnumerateTree(sDir, fnCallback, fIgnoreExceptions = True):
556 # type: (string, (string, stat) -> bool) -> bool
557 """
558 Recursively walks a directory tree, calling fnCallback for each.
559
560 fnCallback takes a full path and stat object (can be None). It
561 returns a boolean value, False stops walking and returns immediately.
562
563 Returns True or False depending on fnCallback.
564 Returns None fIgnoreExceptions is True and an exception was raised by listdir.
565 """
566 def __worker(sCurDir):
567 """ Worker for """
568 try:
569 asNames = os.listdir(sCurDir);
570 except:
571 if not fIgnoreExceptions:
572 raise;
573 return None;
574 rc = True;
575 for sName in asNames:
576 if sName not in [ '.', '..' ]:
577 sFullName = os.path.join(sCurDir, sName);
578 try: oStat = os.lstat(sFullName);
579 except: oStat = None;
580 if fnCallback(sFullName, oStat) is False:
581 return False;
582 if oStat is not None and stat.S_ISDIR(oStat.st_mode):
583 rc = __worker(sFullName);
584 if rc is False:
585 break;
586 return rc;
587
588 # Ensure unicode path here so listdir also returns unicode on windows.
589 ## @todo figure out unicode stuff on non-windows.
590 if sys.platform == 'win32':
591 sDir = unicode(sDir);
592 return __worker(sDir);
593
594
595
596def formatFileMode(uMode):
597 # type: (int) -> string
598 """
599 Format a st_mode value 'ls -la' fasion.
600 Returns string.
601 """
602 if stat.S_ISDIR(uMode): sMode = 'd';
603 elif stat.S_ISREG(uMode): sMode = '-';
604 elif stat.S_ISLNK(uMode): sMode = 'l';
605 elif stat.S_ISFIFO(uMode): sMode = 'p';
606 elif stat.S_ISCHR(uMode): sMode = 'c';
607 elif stat.S_ISBLK(uMode): sMode = 'b';
608 elif stat.S_ISSOCK(uMode): sMode = 's';
609 else: sMode = '?';
610 ## @todo sticky bits.
611 sMode += 'r' if uMode & stat.S_IRUSR else '-';
612 sMode += 'w' if uMode & stat.S_IWUSR else '-';
613 sMode += 'x' if uMode & stat.S_IXUSR else '-';
614 sMode += 'r' if uMode & stat.S_IRGRP else '-';
615 sMode += 'w' if uMode & stat.S_IWGRP else '-';
616 sMode += 'x' if uMode & stat.S_IXGRP else '-';
617 sMode += 'r' if uMode & stat.S_IROTH else '-';
618 sMode += 'w' if uMode & stat.S_IWOTH else '-';
619 sMode += 'x' if uMode & stat.S_IXOTH else '-';
620 sMode += ' ';
621 return sMode;
622
623
624def formatFileStat(oStat):
625 # type: (stat) -> string
626 """
627 Format a stat result 'ls -la' fasion (numeric IDs).
628 Returns string.
629 """
630 return '%s %3s %4s %4s %10s %s' \
631 % (formatFileMode(oStat.st_mode), oStat.st_nlink, oStat.st_uid, oStat.st_gid, oStat.st_size,
632 time.strftime('%Y-%m-%d %H:%M', time.localtime(oStat.st_mtime)), );
633
634## Good buffer for file operations.
635g_cbGoodBufferSize = 256*1024;
636
637## The original shutil.copyfileobj.
638g_fnOriginalShCopyFileObj = None;
639
640def __myshutilcopyfileobj(fsrc, fdst, length = g_cbGoodBufferSize):
641 """ shutil.copyfileobj with different length default value (16384 is slow with python 2.7 on windows). """
642 return g_fnOriginalShCopyFileObj(fsrc, fdst, length);
643
644def __installShUtilHacks(shutil):
645 """ Installs the shutil buffer size hacks. """
646 global g_fnOriginalShCopyFileObj;
647 if g_fnOriginalShCopyFileObj is None:
648 g_fnOriginalShCopyFileObj = shutil.copyfileobj;
649 shutil.copyfileobj = __myshutilcopyfileobj;
650 return True;
651
652
653def copyFileSimple(sFileSrc, sFileDst):
654 """
655 Wrapper around shutil.copyfile that simply copies the data of a regular file.
656 Raises exception on failure.
657 Return True for show.
658 """
659 import shutil;
660 __installShUtilHacks(shutil);
661 return shutil.copyfile(sFileSrc, sFileDst);
662
663
664def getDiskUsage(sPath):
665 """
666 Get free space of a partition that corresponds to specified sPath in MB.
667
668 Returns partition free space value in MB.
669 """
670 if platform.system() == 'Windows':
671 oCTypeFreeSpace = ctypes.c_ulonglong(0);
672 ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(sPath), None, None,
673 ctypes.pointer(oCTypeFreeSpace));
674 cbFreeSpace = oCTypeFreeSpace.value;
675 else:
676 oStats = os.statvfs(sPath); # pylint: disable=no-member
677 cbFreeSpace = long(oStats.f_frsize) * oStats.f_bfree;
678
679 # Convert to MB
680 cMbFreeSpace = long(cbFreeSpace) / (1024 * 1024);
681
682 return cMbFreeSpace;
683
684
685
686#
687# SubProcess.
688#
689
690def _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs):
691 """
692 If the "executable" is a python script, insert the python interpreter at
693 the head of the argument list so that it will work on systems which doesn't
694 support hash-bang scripts.
695 """
696
697 asArgs = dKeywordArgs.get('args');
698 if asArgs is None:
699 asArgs = aPositionalArgs[0];
700
701 if asArgs[0].endswith('.py'):
702 if sys.executable:
703 asArgs.insert(0, sys.executable);
704 else:
705 asArgs.insert(0, 'python');
706
707 # paranoia...
708 if dKeywordArgs.get('args') is not None:
709 dKeywordArgs['args'] = asArgs;
710 else:
711 aPositionalArgs = (asArgs,) + aPositionalArgs[1:];
712 return None;
713
714def processPopenSafe(*aPositionalArgs, **dKeywordArgs):
715 """
716 Wrapper for subprocess.Popen that's Ctrl-C safe on windows.
717 """
718 if getHostOs() == 'win':
719 if dKeywordArgs.get('creationflags', 0) == 0:
720 dKeywordArgs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP;
721 return subprocess.Popen(*aPositionalArgs, **dKeywordArgs); # pylint: disable=consider-using-with
722
723def processStart(*aPositionalArgs, **dKeywordArgs):
724 """
725 Wrapper around subprocess.Popen to deal with its absence in older
726 python versions.
727 Returns process object on success which can be worked on.
728 """
729 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
730 return processPopenSafe(*aPositionalArgs, **dKeywordArgs);
731
732def processCall(*aPositionalArgs, **dKeywordArgs):
733 """
734 Wrapper around subprocess.call to deal with its absence in older
735 python versions.
736 Returns process exit code (see subprocess.poll).
737 """
738 assert dKeywordArgs.get('stdout') is None;
739 assert dKeywordArgs.get('stderr') is None;
740 oProcess = processStart(*aPositionalArgs, **dKeywordArgs);
741 return oProcess.wait();
742
743def processOutputChecked(*aPositionalArgs, **dKeywordArgs):
744 """
745 Wrapper around subprocess.check_output to deal with its absense in older
746 python versions.
747
748 Extra keywords for specifying now output is to be decoded:
749 sEncoding='utf-8
750 fIgnoreEncoding=True/False
751 """
752 sEncoding = dKeywordArgs.get('sEncoding');
753 if sEncoding is not None: del dKeywordArgs['sEncoding'];
754 else: sEncoding = 'utf-8';
755
756 fIgnoreEncoding = dKeywordArgs.get('fIgnoreEncoding');
757 if fIgnoreEncoding is not None: del dKeywordArgs['fIgnoreEncoding'];
758 else: fIgnoreEncoding = True;
759
760 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
761 oProcess = processPopenSafe(stdout=subprocess.PIPE, *aPositionalArgs, **dKeywordArgs);
762
763 sOutput, _ = oProcess.communicate();
764 iExitCode = oProcess.poll();
765
766 if iExitCode != 0:
767 asArgs = dKeywordArgs.get('args');
768 if asArgs is None:
769 asArgs = aPositionalArgs[0];
770 print(sOutput);
771 raise subprocess.CalledProcessError(iExitCode, asArgs);
772
773 if hasattr(sOutput, 'decode'):
774 sOutput = sOutput.decode(sEncoding, 'ignore' if fIgnoreEncoding else 'strict');
775 return sOutput;
776
777def processOutputUnchecked(*aPositionalArgs, **dKeywordArgs):
778 """
779 Similar to processOutputChecked, but returns status code and both stdout
780 and stderr results.
781
782 Extra keywords for specifying now output is to be decoded:
783 sEncoding='utf-8
784 fIgnoreEncoding=True/False
785 """
786 sEncoding = dKeywordArgs.get('sEncoding');
787 if sEncoding is not None: del dKeywordArgs['sEncoding'];
788 else: sEncoding = 'utf-8';
789
790 fIgnoreEncoding = dKeywordArgs.get('fIgnoreEncoding');
791 if fIgnoreEncoding is not None: del dKeywordArgs['fIgnoreEncoding'];
792 else: fIgnoreEncoding = True;
793
794 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
795 oProcess = processPopenSafe(stdout = subprocess.PIPE, stderr = subprocess.PIPE, *aPositionalArgs, **dKeywordArgs);
796
797 sOutput, sError = oProcess.communicate();
798 iExitCode = oProcess.poll();
799
800 if hasattr(sOutput, 'decode'):
801 sOutput = sOutput.decode(sEncoding, 'ignore' if fIgnoreEncoding else 'strict');
802 if hasattr(sError, 'decode'):
803 sError = sError.decode(sEncoding, 'ignore' if fIgnoreEncoding else 'strict');
804 return (iExitCode, sOutput, sError);
805
806g_fOldSudo = None;
807def _sudoFixArguments(aPositionalArgs, dKeywordArgs, fInitialEnv = True):
808 """
809 Adds 'sudo' (or similar) to the args parameter, whereever it is.
810 """
811
812 # Are we root?
813 fIsRoot = True;
814 try:
815 fIsRoot = os.getuid() == 0; # pylint: disable=no-member
816 except:
817 pass;
818
819 # If not, prepend sudo (non-interactive, simulate initial login).
820 if fIsRoot is not True:
821 asArgs = dKeywordArgs.get('args');
822 if asArgs is None:
823 asArgs = aPositionalArgs[0];
824
825 # Detect old sudo.
826 global g_fOldSudo;
827 if g_fOldSudo is None:
828 try:
829 sVersion = str(processOutputChecked(['sudo', '-V']));
830 except:
831 sVersion = '1.7.0';
832 sVersion = sVersion.strip().split('\n', 1)[0];
833 sVersion = sVersion.replace('Sudo version', '').strip();
834 g_fOldSudo = len(sVersion) >= 4 \
835 and sVersion[0] == '1' \
836 and sVersion[1] == '.' \
837 and sVersion[2] <= '6' \
838 and sVersion[3] == '.';
839
840 asArgs.insert(0, 'sudo');
841 if not g_fOldSudo:
842 asArgs.insert(1, '-n');
843 if fInitialEnv and not g_fOldSudo:
844 asArgs.insert(1, '-i');
845
846 # paranoia...
847 if dKeywordArgs.get('args') is not None:
848 dKeywordArgs['args'] = asArgs;
849 else:
850 aPositionalArgs = (asArgs,) + aPositionalArgs[1:];
851 return None;
852
853
854def sudoProcessStart(*aPositionalArgs, **dKeywordArgs):
855 """
856 sudo (or similar) + subprocess.Popen,
857 returning the process object on success.
858 """
859 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
860 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
861 return processStart(*aPositionalArgs, **dKeywordArgs);
862
863def sudoProcessCall(*aPositionalArgs, **dKeywordArgs):
864 """
865 sudo (or similar) + subprocess.call
866 """
867 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
868 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
869 return processCall(*aPositionalArgs, **dKeywordArgs);
870
871def sudoProcessOutputChecked(*aPositionalArgs, **dKeywordArgs):
872 """
873 sudo (or similar) + subprocess.check_output.
874 """
875 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
876 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
877 return processOutputChecked(*aPositionalArgs, **dKeywordArgs);
878
879def sudoProcessOutputCheckedNoI(*aPositionalArgs, **dKeywordArgs):
880 """
881 sudo (or similar) + subprocess.check_output, except '-i' isn't used.
882 """
883 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
884 _sudoFixArguments(aPositionalArgs, dKeywordArgs, False);
885 return processOutputChecked(*aPositionalArgs, **dKeywordArgs);
886
887def sudoProcessPopen(*aPositionalArgs, **dKeywordArgs):
888 """
889 sudo (or similar) + processPopenSafe.
890 """
891 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
892 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
893 return processPopenSafe(*aPositionalArgs, **dKeywordArgs);
894
895
896def whichProgram(sName, sPath = None):
897 """
898 Works similar to the 'which' utility on unix.
899
900 Returns path to the given program if found.
901 Returns None if not found.
902 """
903 sHost = getHostOs();
904 sSep = ';' if sHost in [ 'win', 'os2' ] else ':';
905
906 if sPath is None:
907 if sHost == 'win':
908 sPath = os.environ.get('Path', None);
909 else:
910 sPath = os.environ.get('PATH', None);
911 if sPath is None:
912 return None;
913
914 for sDir in sPath.split(sSep):
915 if sDir.strip() != '':
916 sTest = os.path.abspath(os.path.join(sDir, sName));
917 else:
918 sTest = os.path.abspath(sName);
919 if os.path.exists(sTest):
920 return sTest;
921
922 return None;
923
924#
925# Generic process stuff.
926#
927
928def processInterrupt(uPid):
929 """
930 Sends a SIGINT or equivalent to interrupt the specified process.
931 Returns True on success, False on failure.
932
933 On Windows hosts this may not work unless the process happens to be a
934 process group leader.
935 """
936 if sys.platform == 'win32':
937 try:
938 win32console.GenerateConsoleCtrlEvent(win32con.CTRL_BREAK_EVENT, # pylint: disable=no-member,c-extension-no-member
939 uPid);
940 fRc = True;
941 except:
942 fRc = False;
943 else:
944 try:
945 os.kill(uPid, signal.SIGINT);
946 fRc = True;
947 except:
948 fRc = False;
949 return fRc;
950
951def sendUserSignal1(uPid):
952 """
953 Sends a SIGUSR1 or equivalent to nudge the process into shutting down
954 (VBoxSVC) or something.
955 Returns True on success, False on failure or if not supported (win).
956
957 On Windows hosts this may not work unless the process happens to be a
958 process group leader.
959 """
960 if sys.platform == 'win32':
961 fRc = False;
962 else:
963 try:
964 os.kill(uPid, signal.SIGUSR1); # pylint: disable=no-member
965 fRc = True;
966 except:
967 fRc = False;
968 return fRc;
969
970def processTerminate(uPid):
971 """
972 Terminates the process in a nice manner (SIGTERM or equivalent).
973 Returns True on success, False on failure.
974 """
975 fRc = False;
976 if sys.platform == 'win32':
977 try:
978 hProcess = win32api.OpenProcess(win32con.PROCESS_TERMINATE, # pylint: disable=no-member,c-extension-no-member
979 False, uPid);
980 except:
981 pass;
982 else:
983 try:
984 win32process.TerminateProcess(hProcess, # pylint: disable=no-member,c-extension-no-member
985 0x40010004); # DBG_TERMINATE_PROCESS
986 fRc = True;
987 except:
988 pass;
989 hProcess.Close(); #win32api.CloseHandle(hProcess)
990 else:
991 try:
992 os.kill(uPid, signal.SIGTERM);
993 fRc = True;
994 except:
995 pass;
996 return fRc;
997
998def processKill(uPid):
999 """
1000 Terminates the process with extreme prejudice (SIGKILL).
1001 Returns True on success, False on failure.
1002 """
1003 if sys.platform == 'win32':
1004 fRc = processTerminate(uPid);
1005 else:
1006 try:
1007 os.kill(uPid, signal.SIGKILL); # pylint: disable=no-member
1008 fRc = True;
1009 except:
1010 fRc = False;
1011 return fRc;
1012
1013def processKillWithNameCheck(uPid, sName):
1014 """
1015 Like processKill(), but checks if the process name matches before killing
1016 it. This is intended for killing using potentially stale pid values.
1017
1018 Returns True on success, False on failure.
1019 """
1020
1021 if processCheckPidAndName(uPid, sName) is not True:
1022 return False;
1023 return processKill(uPid);
1024
1025
1026def processExists(uPid):
1027 """
1028 Checks if the specified process exits.
1029 This will only work if we can signal/open the process.
1030
1031 Returns True if it positively exists, False otherwise.
1032 """
1033 sHostOs = getHostOs();
1034 if sHostOs == 'win':
1035 fRc = False;
1036 # We try open the process for waiting since this is generally only forbidden in a very few cases.
1037 try:
1038 hProcess = win32api.OpenProcess(win32con.SYNCHRONIZE, # pylint: disable=no-member,c-extension-no-member
1039 False, uPid);
1040 except pywintypes.error as oXcpt: # pylint: disable=no-member,used-before-assignment
1041 if oXcpt.winerror == winerror.ERROR_ACCESS_DENIED: # pylint: disable=used-before-assignment
1042 fRc = True;
1043 except:
1044 pass;
1045 else:
1046 hProcess.Close();
1047 fRc = True;
1048 else:
1049 fRc = False;
1050 try:
1051 os.kill(uPid, 0);
1052 fRc = True;
1053 except OSError as oXcpt:
1054 if oXcpt.errno == errno.EPERM:
1055 fRc = True;
1056 except:
1057 pass;
1058 return fRc;
1059
1060def processCheckPidAndName(uPid, sName):
1061 """
1062 Checks if a process PID and NAME matches.
1063 """
1064 fRc = processExists(uPid);
1065 if fRc is not True:
1066 return False;
1067
1068 if sys.platform == 'win32':
1069 try:
1070 from win32com.client import GetObject; # pylint: disable=import-error
1071 oWmi = GetObject('winmgmts:');
1072 aoProcesses = oWmi.InstancesOf('Win32_Process');
1073 for oProcess in aoProcesses:
1074 if long(oProcess.Properties_("ProcessId").Value) == uPid:
1075 sCurName = oProcess.Properties_("Name").Value;
1076 #reporter.log2('uPid=%s sName=%s sCurName=%s' % (uPid, sName, sCurName));
1077 sName = sName.lower();
1078 sCurName = sCurName.lower();
1079 if os.path.basename(sName) == sName:
1080 sCurName = os.path.basename(sCurName);
1081
1082 if sCurName == sName \
1083 or sCurName + '.exe' == sName \
1084 or sCurName == sName + '.exe':
1085 fRc = True;
1086 break;
1087 except:
1088 #reporter.logXcpt('uPid=%s sName=%s' % (uPid, sName));
1089 pass;
1090 else:
1091 if sys.platform in ('linux2', 'linux', 'linux3', 'linux4', 'linux5', 'linux6'):
1092 asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname='];
1093 elif sys.platform in ('sunos5',):
1094 asPsCmd = ['/usr/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname='];
1095 elif sys.platform in ('darwin',):
1096 asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'ucomm='];
1097 else:
1098 asPsCmd = None;
1099
1100 if asPsCmd is not None:
1101 try:
1102 oPs = subprocess.Popen(asPsCmd, stdout=subprocess.PIPE); # pylint: disable=consider-using-with
1103 sCurName = oPs.communicate()[0];
1104 iExitCode = oPs.wait();
1105 except:
1106 #reporter.logXcpt();
1107 return False;
1108
1109 # ps fails with non-zero exit code if the pid wasn't found.
1110 if iExitCode != 0:
1111 return False;
1112 if sCurName is None:
1113 return False;
1114 sCurName = sCurName.strip();
1115 if not sCurName:
1116 return False;
1117
1118 if os.path.basename(sName) == sName:
1119 sCurName = os.path.basename(sCurName);
1120 elif os.path.basename(sCurName) == sCurName:
1121 sName = os.path.basename(sName);
1122
1123 if sCurName != sName:
1124 return False;
1125
1126 fRc = True;
1127 return fRc;
1128
1129def processGetInfo(uPid, fSudo = False):
1130 """
1131 Tries to acquire state information of the given process.
1132
1133 Returns a string with the information on success or None on failure or
1134 if the host is not supported.
1135
1136 Note that the format of the information is host system dependent and will
1137 likely differ much between different hosts.
1138 """
1139 fRc = processExists(uPid);
1140 if fRc is not True:
1141 return None;
1142
1143 sHostOs = getHostOs();
1144 if sHostOs in [ 'linux',]:
1145 sGdb = '/usr/bin/gdb';
1146 if not os.path.isfile(sGdb): sGdb = '/usr/local/bin/gdb';
1147 if not os.path.isfile(sGdb): sGdb = 'gdb';
1148 aasCmd = [
1149 [ sGdb, '-batch',
1150 '-ex', 'set pagination off',
1151 '-ex', 'thread apply all bt',
1152 '-ex', 'info proc mapping',
1153 '-ex', 'info sharedlibrary',
1154 '-p', '%u' % (uPid,), ],
1155 ];
1156 elif sHostOs == 'darwin':
1157 # LLDB doesn't work in batch mode when attaching to a process, at least
1158 # with macOS Sierra (10.12). GDB might not be installed. Use the sample
1159 # tool instead with a 1 second duration and 1000ms sampling interval to
1160 # get one stack trace. For the process mappings use vmmap.
1161 aasCmd = [
1162 [ '/usr/bin/sample', '-mayDie', '%u' % (uPid,), '1', '1000', ],
1163 [ '/usr/bin/vmmap', '%u' % (uPid,), ],
1164 ];
1165 elif sHostOs == 'solaris':
1166 aasCmd = [
1167 [ '/usr/bin/pstack', '%u' % (uPid,), ],
1168 [ '/usr/bin/pmap', '%u' % (uPid,), ],
1169 ];
1170 else:
1171 aasCmd = [];
1172
1173 sInfo = '';
1174 for asCmd in aasCmd:
1175 try:
1176 if fSudo:
1177 sThisInfo = sudoProcessOutputChecked(asCmd);
1178 else:
1179 sThisInfo = processOutputChecked(asCmd);
1180 if sThisInfo is not None:
1181 sInfo += sThisInfo;
1182 except:
1183 pass;
1184 if not sInfo:
1185 sInfo = None;
1186
1187 return sInfo;
1188
1189
1190class ProcessInfo(object):
1191 """Process info."""
1192 def __init__(self, iPid):
1193 self.iPid = iPid;
1194 self.iParentPid = None;
1195 self.sImage = None;
1196 self.sName = None;
1197 self.asArgs = None;
1198 self.sCwd = None;
1199 self.iGid = None;
1200 self.iUid = None;
1201 self.iProcGroup = None;
1202 self.iSessionId = None;
1203
1204 def loadAll(self):
1205 """Load all the info."""
1206 sOs = getHostOs();
1207 if sOs == 'linux':
1208 sProc = '/proc/%s/' % (self.iPid,);
1209 if self.sImage is None: self.sImage = noxcptReadLink(sProc + 'exe', None);
1210 if self.sImage is None:
1211 self.sImage = noxcptReadFile(sProc + 'comm', None);
1212 if self.sImage: self.sImage = self.sImage.strip();
1213 if self.sCwd is None: self.sCwd = noxcptReadLink(sProc + 'cwd', None);
1214 if self.asArgs is None: self.asArgs = noxcptReadFile(sProc + 'cmdline', '').split('\x00');
1215 #elif sOs == 'solaris': - doesn't work for root processes, suid proces, and other stuff.
1216 # sProc = '/proc/%s/' % (self.iPid,);
1217 # if self.sImage is None: self.sImage = noxcptReadLink(sProc + 'path/a.out', None);
1218 # if self.sCwd is None: self.sCwd = noxcptReadLink(sProc + 'path/cwd', None);
1219 else:
1220 pass;
1221 if self.sName is None and self.sImage is not None:
1222 self.sName = self.sImage;
1223
1224 def windowsGrabProcessInfo(self, oProcess):
1225 """Windows specific loadAll."""
1226 try: self.sName = oProcess.Properties_("Name").Value;
1227 except: pass;
1228 try: self.sImage = oProcess.Properties_("ExecutablePath").Value;
1229 except: pass;
1230 try: self.asArgs = [oProcess.Properties_("CommandLine").Value]; ## @todo split it.
1231 except: pass;
1232 try: self.iParentPid = oProcess.Properties_("ParentProcessId").Value;
1233 except: pass;
1234 try: self.iSessionId = oProcess.Properties_("SessionId").Value;
1235 except: pass;
1236 if self.sName is None and self.sImage is not None:
1237 self.sName = self.sImage;
1238
1239 def getBaseImageName(self):
1240 """
1241 Gets the base image name if available, use the process name if not available.
1242 Returns image/process base name or None.
1243 """
1244 sRet = self.sImage if self.sName is None else self.sName;
1245 if sRet is None:
1246 self.loadAll();
1247 sRet = self.sImage if self.sName is None else self.sName;
1248 if sRet is None:
1249 if not self.asArgs:
1250 return None;
1251 sRet = self.asArgs[0];
1252 if not sRet:
1253 return None;
1254 return os.path.basename(sRet);
1255
1256 def getBaseImageNameNoExeSuff(self):
1257 """
1258 Same as getBaseImageName, except any '.exe' or similar suffix is stripped.
1259 """
1260 sRet = self.getBaseImageName();
1261 if sRet is not None and len(sRet) > 4 and sRet[-4] == '.':
1262 if (sRet[-4:]).lower() in [ '.exe', '.com', '.msc', '.vbs', '.cmd', '.bat' ]:
1263 sRet = sRet[:-4];
1264 return sRet;
1265
1266
1267def processListAll():
1268 """
1269 Return a list of ProcessInfo objects for all the processes in the system
1270 that the current user can see.
1271 """
1272 asProcesses = [];
1273
1274 sOs = getHostOs();
1275 if sOs == 'win':
1276 from win32com.client import GetObject; # pylint: disable=import-error
1277 oWmi = GetObject('winmgmts:');
1278 aoProcesses = oWmi.InstancesOf('Win32_Process');
1279 for oProcess in aoProcesses:
1280 try:
1281 iPid = int(oProcess.Properties_("ProcessId").Value);
1282 except:
1283 continue;
1284 oMyInfo = ProcessInfo(iPid);
1285 oMyInfo.windowsGrabProcessInfo(oProcess);
1286 asProcesses.append(oMyInfo);
1287 return asProcesses;
1288
1289 if sOs in [ 'linux', ]: # Not solaris, ps gets more info than /proc/.
1290 try:
1291 asDirs = os.listdir('/proc');
1292 except:
1293 asDirs = [];
1294 for sDir in asDirs:
1295 if sDir.isdigit():
1296 asProcesses.append(ProcessInfo(int(sDir),));
1297 return asProcesses;
1298
1299 #
1300 # The other OSes parses the output from the 'ps' utility.
1301 #
1302 asPsCmd = [
1303 '/bin/ps', # 0
1304 '-A', # 1
1305 '-o', 'pid=', # 2,3
1306 '-o', 'ppid=', # 4,5
1307 '-o', 'pgid=', # 6,7
1308 '-o', 'sid=', # 8,9
1309 '-o', 'uid=', # 10,11
1310 '-o', 'gid=', # 12,13
1311 '-o', 'comm=' # 14,15
1312 ];
1313
1314 if sOs == 'darwin':
1315 assert asPsCmd[9] == 'sid=';
1316 asPsCmd[9] = 'sess=';
1317 elif sOs == 'solaris':
1318 asPsCmd[0] = '/usr/bin/ps';
1319
1320 try:
1321 sRaw = processOutputChecked(asPsCmd);
1322 except:
1323 return asProcesses;
1324
1325 for sLine in sRaw.split('\n'):
1326 sLine = sLine.lstrip();
1327 if len(sLine) < 7 or not sLine[0].isdigit():
1328 continue;
1329
1330 iField = 0;
1331 off = 0;
1332 aoFields = [None, None, None, None, None, None, None];
1333 while iField < 7:
1334 # Eat whitespace.
1335 while off < len(sLine) and (sLine[off] == ' ' or sLine[off] == '\t'):
1336 off += 1;
1337
1338 # Final field / EOL.
1339 if iField == 6:
1340 aoFields[6] = sLine[off:];
1341 break;
1342 if off >= len(sLine):
1343 break;
1344
1345 # Generic field parsing.
1346 offStart = off;
1347 off += 1;
1348 while off < len(sLine) and sLine[off] != ' ' and sLine[off] != '\t':
1349 off += 1;
1350 try:
1351 if iField != 3:
1352 aoFields[iField] = int(sLine[offStart:off]);
1353 else:
1354 aoFields[iField] = long(sLine[offStart:off], 16); # sess is a hex address.
1355 except:
1356 pass;
1357 iField += 1;
1358
1359 if aoFields[0] is not None:
1360 oMyInfo = ProcessInfo(aoFields[0]);
1361 oMyInfo.iParentPid = aoFields[1];
1362 oMyInfo.iProcGroup = aoFields[2];
1363 oMyInfo.iSessionId = aoFields[3];
1364 oMyInfo.iUid = aoFields[4];
1365 oMyInfo.iGid = aoFields[5];
1366 oMyInfo.sName = aoFields[6];
1367 asProcesses.append(oMyInfo);
1368
1369 return asProcesses;
1370
1371
1372def processCollectCrashInfo(uPid, fnLog, fnCrashFile):
1373 """
1374 Looks for information regarding the demise of the given process.
1375 """
1376 sOs = getHostOs();
1377 if sOs == 'darwin':
1378 #
1379 # On darwin we look for crash and diagnostic reports.
1380 #
1381 asLogDirs = [
1382 u'/Library/Logs/DiagnosticReports/',
1383 u'/Library/Logs/CrashReporter/',
1384 u'~/Library/Logs/DiagnosticReports/',
1385 u'~/Library/Logs/CrashReporter/',
1386 ];
1387 for sDir in asLogDirs:
1388 sDir = os.path.expanduser(sDir);
1389 if not os.path.isdir(sDir):
1390 continue;
1391 try:
1392 asDirEntries = os.listdir(sDir);
1393 except:
1394 continue;
1395 for sEntry in asDirEntries:
1396 # Only interested in .crash files.
1397 _, sSuff = os.path.splitext(sEntry);
1398 if sSuff != '.crash':
1399 continue;
1400
1401 # The pid can be found at the end of the first line.
1402 sFull = os.path.join(sDir, sEntry);
1403 try:
1404 with open(sFull, 'r') as oFile: # pylint: disable=unspecified-encoding
1405 sFirstLine = oFile.readline();
1406 except:
1407 continue;
1408 if len(sFirstLine) <= 4 or sFirstLine[-2] != ']':
1409 continue;
1410 offPid = len(sFirstLine) - 3;
1411 while offPid > 1 and sFirstLine[offPid - 1].isdigit():
1412 offPid -= 1;
1413 try: uReportPid = int(sFirstLine[offPid:-2]);
1414 except: continue;
1415
1416 # Does the pid we found match?
1417 if uReportPid == uPid:
1418 fnLog('Found crash report for %u: %s' % (uPid, sFull,));
1419 fnCrashFile(sFull, False);
1420 elif sOs == 'win':
1421 #
1422 # Getting WER reports would be great, however we have trouble match the
1423 # PID to those as they seems not to mention it in the brief reports.
1424 # Instead we'll just look for crash dumps in C:\CrashDumps (our custom
1425 # location - see the windows readme for the testbox script) and what
1426 # the MSDN article lists for now.
1427 #
1428 # It's been observed on Windows server 2012 that the dump files takes
1429 # the form: <processimage>.<decimal-pid>.dmp
1430 #
1431 asDmpDirs = [
1432 u'%SystemDrive%/CrashDumps/', # Testboxes.
1433 u'%LOCALAPPDATA%/CrashDumps/', # MSDN example.
1434 u'%WINDIR%/ServiceProfiles/LocalServices/', # Local and network service.
1435 u'%WINDIR%/ServiceProfiles/NetworkSerices/',
1436 u'%WINDIR%/ServiceProfiles/',
1437 u'%WINDIR%/System32/Config/SystemProfile/', # System services.
1438 ];
1439 sMatchSuffix = '.%u.dmp' % (uPid,);
1440
1441 for sDir in asDmpDirs:
1442 sDir = os.path.expandvars(sDir);
1443 if not os.path.isdir(sDir):
1444 continue;
1445 try:
1446 asDirEntries = os.listdir(sDir);
1447 except:
1448 continue;
1449 for sEntry in asDirEntries:
1450 if sEntry.endswith(sMatchSuffix):
1451 sFull = os.path.join(sDir, sEntry);
1452 fnLog('Found crash dump for %u: %s' % (uPid, sFull,));
1453 fnCrashFile(sFull, True);
1454 elif sOs == 'solaris':
1455 asDmpDirs = [];
1456 try:
1457 sScratchPath = os.environ.get('TESTBOX_PATH_SCRATCH', None);
1458 asDmpDirs.extend([ sScratchPath ]);
1459 except:
1460 pass;
1461 # Some other useful locations as fallback.
1462 asDmpDirs.extend([
1463 u'/var/cores/'
1464 ]);
1465 #
1466 # Solaris by default creates a core file in the directory of the crashing process with the name 'core'.
1467 #
1468 # As we need to distinguish the core files correlating to their PIDs and have a persistent storage location,
1469 # the host needs to be tweaked via:
1470 #
1471 # ```coreadm -g /var/cores/core.%f.%p```
1472 #
1473 sMatchRegEx = r'core\..*\.%u' % (uPid);
1474 for sDir in asDmpDirs:
1475 sDir = os.path.expandvars(sDir);
1476 if not os.path.isdir(sDir):
1477 continue;
1478 try:
1479 asDirEntries = os.listdir(sDir);
1480 except:
1481 continue;
1482 for sEntry in asDirEntries:
1483 fnLog('Entry: %s' % (os.path.join(sDir, sEntry)));
1484 if re.search(sMatchRegEx, sEntry):
1485 sFull = os.path.join(sDir, sEntry);
1486 fnLog('Found crash dump for %u: %s' % (uPid, sFull,));
1487 fnCrashFile(sFull, True);
1488 else:
1489 pass; ## TODO
1490 return None;
1491
1492
1493#
1494# Time.
1495#
1496
1497#
1498# The following test case shows how time.time() only have ~ms resolution
1499# on Windows (tested W10) and why it therefore makes sense to try use
1500# performance counters.
1501#
1502# Note! We cannot use time.clock() as the timestamp must be portable across
1503# processes. See timeout testcase problem on win hosts (no logs).
1504# Also, time.clock() was axed in python 3.8 (https://bugs.python.org/issue31803).
1505#
1506#import sys;
1507#import time;
1508#from common import utils;
1509#
1510#atSeries = [];
1511#for i in xrange(1,160):
1512# if i == 159: time.sleep(10);
1513# atSeries.append((utils.timestampNano(), long(time.clock() * 1000000000), long(time.time() * 1000000000)));
1514#
1515#tPrev = atSeries[0]
1516#for tCur in atSeries:
1517# print 't1=%+22u, %u' % (tCur[0], tCur[0] - tPrev[0]);
1518# print 't2=%+22u, %u' % (tCur[1], tCur[1] - tPrev[1]);
1519# print 't3=%+22u, %u' % (tCur[2], tCur[2] - tPrev[2]);
1520# print '';
1521# tPrev = tCur
1522#
1523#print 't1=%u' % (atSeries[-1][0] - atSeries[0][0]);
1524#print 't2=%u' % (atSeries[-1][1] - atSeries[0][1]);
1525#print 't3=%u' % (atSeries[-1][2] - atSeries[0][2]);
1526
1527g_fWinUseWinPerfCounter = sys.platform == 'win32';
1528g_fpWinPerfCounterFreq = None;
1529g_oFuncwinQueryPerformanceCounter = None;
1530
1531def _winInitPerfCounter():
1532 """ Initializes the use of performance counters. """
1533 global g_fWinUseWinPerfCounter, g_fpWinPerfCounterFreq, g_oFuncwinQueryPerformanceCounter
1534
1535 uFrequency = ctypes.c_ulonglong(0);
1536 if ctypes.windll.kernel32.QueryPerformanceFrequency(ctypes.byref(uFrequency)):
1537 if uFrequency.value >= 1000:
1538 #print 'uFrequency = %s' % (uFrequency,);
1539 #print 'type(uFrequency) = %s' % (type(uFrequency),);
1540 g_fpWinPerfCounterFreq = float(uFrequency.value);
1541
1542 # Check that querying the counter works too.
1543 global g_oFuncwinQueryPerformanceCounter
1544 g_oFuncwinQueryPerformanceCounter = ctypes.windll.kernel32.QueryPerformanceCounter;
1545 uCurValue = ctypes.c_ulonglong(0);
1546 if g_oFuncwinQueryPerformanceCounter(ctypes.byref(uCurValue)):
1547 if uCurValue.value > 0:
1548 return True;
1549 g_fWinUseWinPerfCounter = False;
1550 return False;
1551
1552def _winFloatTime():
1553 """ Gets floating point time on windows. """
1554 if g_fpWinPerfCounterFreq is not None or _winInitPerfCounter():
1555 uCurValue = ctypes.c_ulonglong(0);
1556 if g_oFuncwinQueryPerformanceCounter(ctypes.byref(uCurValue)):
1557 return float(uCurValue.value) / g_fpWinPerfCounterFreq;
1558 return time.time();
1559
1560def timestampNano():
1561 """
1562 Gets a nanosecond timestamp.
1563 """
1564 if g_fWinUseWinPerfCounter is True:
1565 return long(_winFloatTime() * 1000000000);
1566 return long(time.time() * 1000000000);
1567
1568def timestampMilli():
1569 """
1570 Gets a millisecond timestamp.
1571 """
1572 if g_fWinUseWinPerfCounter is True:
1573 return long(_winFloatTime() * 1000);
1574 return long(time.time() * 1000);
1575
1576def timestampSecond():
1577 """
1578 Gets a second timestamp.
1579 """
1580 if g_fWinUseWinPerfCounter is True:
1581 return long(_winFloatTime());
1582 return long(time.time());
1583
1584def secondsSinceUnixEpoch():
1585 """
1586 Returns unix time, floating point second count since 1970-01-01T00:00:00Z
1587 """
1588 ## ASSUMES This returns unix epoch time on all systems we care about...
1589 return time.time();
1590
1591def getTimePrefix():
1592 """
1593 Returns a timestamp prefix, typically used for logging. UTC.
1594 """
1595 try:
1596 oNow = datetime.datetime.utcnow();
1597 sTs = '%02u:%02u:%02u.%06u' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond);
1598 except:
1599 sTs = 'getTimePrefix-exception';
1600 return sTs;
1601
1602def getTimePrefixAndIsoTimestamp():
1603 """
1604 Returns current UTC as log prefix and iso timestamp.
1605 """
1606 try:
1607 oNow = datetime.datetime.utcnow();
1608 sTsPrf = '%02u:%02u:%02u.%06u' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond);
1609 sTsIso = formatIsoTimestamp(oNow);
1610 except:
1611 sTsPrf = sTsIso = 'getTimePrefix-exception';
1612 return (sTsPrf, sTsIso);
1613
1614class UtcTzInfo(datetime.tzinfo):
1615 """UTC TZ Info Class"""
1616 def utcoffset(self, _):
1617 return datetime.timedelta(0);
1618 def tzname(self, _):
1619 return "UTC";
1620 def dst(self, _):
1621 return datetime.timedelta(0);
1622
1623class GenTzInfo(datetime.tzinfo):
1624 """Generic TZ Info Class"""
1625 def __init__(self, offInMin):
1626 datetime.tzinfo.__init__(self);
1627 self.offInMin = offInMin;
1628 def utcoffset(self, _):
1629 return datetime.timedelta(minutes = self.offInMin);
1630 def tzname(self, _):
1631 if self.offInMin >= 0:
1632 return "+%02d%02d" % (self.offInMin // 60, self.offInMin % 60);
1633 return "-%02d%02d" % (-self.offInMin // 60, -self.offInMin % 60);
1634 def dst(self, _):
1635 return datetime.timedelta(0);
1636
1637def formatIsoTimestamp(oNow):
1638 """Formats the datetime object as an ISO timestamp."""
1639 assert oNow.tzinfo is None or isinstance(oNow.tzinfo, UtcTzInfo);
1640 sTs = '%s.%09uZ' % (oNow.strftime('%Y-%m-%dT%H:%M:%S'), oNow.microsecond * 1000);
1641 return sTs;
1642
1643def getIsoTimestamp():
1644 """Returns the current UTC timestamp as a string."""
1645 return formatIsoTimestamp(datetime.datetime.utcnow());
1646
1647def formatShortIsoTimestamp(oNow):
1648 """Formats the datetime object as an ISO timestamp, but w/o microseconds."""
1649 assert oNow.tzinfo is None or isinstance(oNow.tzinfo, UtcTzInfo);
1650 return oNow.strftime('%Y-%m-%dT%H:%M:%SZ');
1651
1652def getShortIsoTimestamp():
1653 """Returns the current UTC timestamp as a string, but w/o microseconds."""
1654 return formatShortIsoTimestamp(datetime.datetime.utcnow());
1655
1656def convertDateTimeToZulu(oDateTime):
1657 """ Converts oDateTime to zulu time if it has timezone info. """
1658 if oDateTime.tzinfo is not None:
1659 oDateTime = oDateTime.astimezone(UtcTzInfo());
1660 else:
1661 oDateTime = oDateTime.replace(tzinfo = UtcTzInfo());
1662 return oDateTime;
1663
1664def parseIsoTimestamp(sTs):
1665 """
1666 Parses a typical ISO timestamp, returing a datetime object, reasonably
1667 forgiving, but will throw weird indexing/conversion errors if the input
1668 is malformed.
1669 """
1670 # YYYY-MM-DD
1671 iYear = int(sTs[0:4]);
1672 assert(sTs[4] == '-');
1673 iMonth = int(sTs[5:7]);
1674 assert(sTs[7] == '-');
1675 iDay = int(sTs[8:10]);
1676
1677 # Skip separator
1678 sTime = sTs[10:];
1679 while sTime[0] in 'Tt \t\n\r':
1680 sTime = sTime[1:];
1681
1682 # HH:MM[:SS]
1683 iHour = int(sTime[0:2]);
1684 assert(sTime[2] == ':');
1685 iMin = int(sTime[3:5]);
1686 if sTime[5] == ':':
1687 iSec = int(sTime[6:8]);
1688
1689 # Fraction?
1690 offTime = 8;
1691 iMicroseconds = 0;
1692 if offTime < len(sTime) and sTime[offTime] in '.,':
1693 offTime += 1;
1694 cchFraction = 0;
1695 while offTime + cchFraction < len(sTime) and sTime[offTime + cchFraction] in '0123456789':
1696 cchFraction += 1;
1697 if cchFraction > 0:
1698 iMicroseconds = int(sTime[offTime : (offTime + cchFraction)]);
1699 offTime += cchFraction;
1700 while cchFraction < 6:
1701 iMicroseconds *= 10;
1702 cchFraction += 1;
1703 while cchFraction > 6:
1704 iMicroseconds = iMicroseconds // 10;
1705 cchFraction -= 1;
1706
1707 else:
1708 iSec = 0;
1709 iMicroseconds = 0;
1710 offTime = 5;
1711
1712 # Naive?
1713 if offTime >= len(sTime):
1714 return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds);
1715
1716 # Zulu?
1717 if offTime >= len(sTime) or sTime[offTime] in 'Zz':
1718 return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds, tzinfo = UtcTzInfo());
1719
1720 # Some kind of offset afterwards, and strptime is useless. sigh.
1721 if sTime[offTime] in '+-':
1722 chSign = sTime[offTime];
1723 offTime += 1;
1724 cMinTz = int(sTime[offTime : (offTime + 2)]) * 60;
1725 offTime += 2;
1726 if offTime < len(sTime) and sTime[offTime] in ':':
1727 offTime += 1;
1728 if offTime + 2 <= len(sTime):
1729 cMinTz += int(sTime[offTime : (offTime + 2)]);
1730 offTime += 2;
1731 assert offTime == len(sTime);
1732 if chSign == '-':
1733 cMinTz = -cMinTz;
1734 return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds, tzinfo = GenTzInfo(cMinTz));
1735 assert False, sTs;
1736 return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds);
1737
1738def normalizeIsoTimestampToZulu(sTs):
1739 """
1740 Takes a iso timestamp string and normalizes it (basically parseIsoTimestamp
1741 + convertDateTimeToZulu + formatIsoTimestamp).
1742 Returns ISO tiemstamp string.
1743 """
1744 return formatIsoTimestamp(convertDateTimeToZulu(parseIsoTimestamp(sTs)));
1745
1746def getLocalHourOfWeek():
1747 """ Local hour of week (0 based). """
1748 oNow = datetime.datetime.now();
1749 return (oNow.isoweekday() - 1) * 24 + oNow.hour;
1750
1751
1752def formatIntervalSeconds(cSeconds):
1753 """ Format a seconds interval into a nice 01h 00m 22s string """
1754 # Two simple special cases.
1755 if cSeconds < 60:
1756 return '%ss' % (cSeconds,);
1757 if cSeconds < 3600:
1758 cMins = cSeconds // 60;
1759 cSecs = cSeconds % 60;
1760 if cSecs == 0:
1761 return '%sm' % (cMins,);
1762 return '%sm %ss' % (cMins, cSecs,);
1763
1764 # Generic and a bit slower.
1765 cDays = cSeconds // 86400;
1766 cSeconds %= 86400;
1767 cHours = cSeconds // 3600;
1768 cSeconds %= 3600;
1769 cMins = cSeconds // 60;
1770 cSecs = cSeconds % 60;
1771 sRet = '';
1772 if cDays > 0:
1773 sRet = '%sd ' % (cDays,);
1774 if cHours > 0:
1775 sRet += '%sh ' % (cHours,);
1776 if cMins > 0:
1777 sRet += '%sm ' % (cMins,);
1778 if cSecs > 0:
1779 sRet += '%ss ' % (cSecs,);
1780 assert sRet; assert sRet[-1] == ' ';
1781 return sRet[:-1];
1782
1783def formatIntervalSeconds2(oSeconds):
1784 """
1785 Flexible input version of formatIntervalSeconds for use in WUI forms where
1786 data is usually already string form.
1787 """
1788 if isinstance(oSeconds, (int, long)):
1789 return formatIntervalSeconds(oSeconds);
1790 if not isString(oSeconds):
1791 try:
1792 lSeconds = long(oSeconds);
1793 except:
1794 pass;
1795 else:
1796 if lSeconds >= 0:
1797 return formatIntervalSeconds2(lSeconds);
1798 return oSeconds;
1799
1800def parseIntervalSeconds(sString):
1801 """
1802 Reverse of formatIntervalSeconds.
1803
1804 Returns (cSeconds, sError), where sError is None on success.
1805 """
1806
1807 # We might given non-strings, just return them without any fuss.
1808 if not isString(sString):
1809 if isinstance(sString, (int, long)) or sString is None:
1810 return (sString, None);
1811 ## @todo time/date objects?
1812 return (int(sString), None);
1813
1814 # Strip it and make sure it's not empty.
1815 sString = sString.strip();
1816 if not sString:
1817 return (0, 'Empty interval string.');
1818
1819 #
1820 # Split up the input into a list of 'valueN, unitN, ...'.
1821 #
1822 # Don't want to spend too much time trying to make re.split do exactly what
1823 # I need here, so please forgive the extra pass I'm making here.
1824 #
1825 asRawParts = re.split(r'\s*([0-9]+)\s*([^0-9,;]*)[\s,;]*', sString);
1826 asParts = [];
1827 for sPart in asRawParts:
1828 sPart = sPart.strip();
1829 if sPart:
1830 asParts.append(sPart);
1831 if not asParts:
1832 return (0, 'Empty interval string or something?');
1833
1834 #
1835 # Process them one or two at the time.
1836 #
1837 cSeconds = 0;
1838 asErrors = [];
1839 i = 0;
1840 while i < len(asParts):
1841 sNumber = asParts[i];
1842 i += 1;
1843 if sNumber.isdigit():
1844 iNumber = int(sNumber);
1845
1846 sUnit = 's';
1847 if i < len(asParts) and not asParts[i].isdigit():
1848 sUnit = asParts[i];
1849 i += 1;
1850
1851 sUnitLower = sUnit.lower();
1852 if sUnitLower in [ 's', 'se', 'sec', 'second', 'seconds' ]:
1853 pass;
1854 elif sUnitLower in [ 'm', 'mi', 'min', 'minute', 'minutes' ]:
1855 iNumber *= 60;
1856 elif sUnitLower in [ 'h', 'ho', 'hou', 'hour', 'hours' ]:
1857 iNumber *= 3600;
1858 elif sUnitLower in [ 'd', 'da', 'day', 'days' ]:
1859 iNumber *= 86400;
1860 elif sUnitLower in [ 'w', 'week', 'weeks' ]:
1861 iNumber *= 7 * 86400;
1862 else:
1863 asErrors.append('Unknown unit "%s".' % (sUnit,));
1864 cSeconds += iNumber;
1865 else:
1866 asErrors.append('Bad number "%s".' % (sNumber,));
1867 return (cSeconds, None if not asErrors else ' '.join(asErrors));
1868
1869def formatIntervalHours(cHours):
1870 """ Format a hours interval into a nice 1w 2d 1h string. """
1871 # Simple special cases.
1872 if cHours < 24:
1873 return '%sh' % (cHours,);
1874
1875 # Generic and a bit slower.
1876 cWeeks = cHours / (7 * 24);
1877 cHours %= 7 * 24;
1878 cDays = cHours / 24;
1879 cHours %= 24;
1880 sRet = '';
1881 if cWeeks > 0:
1882 sRet = '%sw ' % (cWeeks,);
1883 if cDays > 0:
1884 sRet = '%sd ' % (cDays,);
1885 if cHours > 0:
1886 sRet += '%sh ' % (cHours,);
1887 assert sRet; assert sRet[-1] == ' ';
1888 return sRet[:-1];
1889
1890def parseIntervalHours(sString):
1891 """
1892 Reverse of formatIntervalHours.
1893
1894 Returns (cHours, sError), where sError is None on success.
1895 """
1896
1897 # We might given non-strings, just return them without any fuss.
1898 if not isString(sString):
1899 if isinstance(sString, (int, long)) or sString is None:
1900 return (sString, None);
1901 ## @todo time/date objects?
1902 return (int(sString), None);
1903
1904 # Strip it and make sure it's not empty.
1905 sString = sString.strip();
1906 if not sString:
1907 return (0, 'Empty interval string.');
1908
1909 #
1910 # Split up the input into a list of 'valueN, unitN, ...'.
1911 #
1912 # Don't want to spend too much time trying to make re.split do exactly what
1913 # I need here, so please forgive the extra pass I'm making here.
1914 #
1915 asRawParts = re.split(r'\s*([0-9]+)\s*([^0-9,;]*)[\s,;]*', sString);
1916 asParts = [];
1917 for sPart in asRawParts:
1918 sPart = sPart.strip();
1919 if sPart:
1920 asParts.append(sPart);
1921 if not asParts:
1922 return (0, 'Empty interval string or something?');
1923
1924 #
1925 # Process them one or two at the time.
1926 #
1927 cHours = 0;
1928 asErrors = [];
1929 i = 0;
1930 while i < len(asParts):
1931 sNumber = asParts[i];
1932 i += 1;
1933 if sNumber.isdigit():
1934 iNumber = int(sNumber);
1935
1936 sUnit = 'h';
1937 if i < len(asParts) and not asParts[i].isdigit():
1938 sUnit = asParts[i];
1939 i += 1;
1940
1941 sUnitLower = sUnit.lower();
1942 if sUnitLower in [ 'h', 'ho', 'hou', 'hour', 'hours' ]:
1943 pass;
1944 elif sUnitLower in [ 'd', 'da', 'day', 'days' ]:
1945 iNumber *= 24;
1946 elif sUnitLower in [ 'w', 'week', 'weeks' ]:
1947 iNumber *= 7 * 24;
1948 else:
1949 asErrors.append('Unknown unit "%s".' % (sUnit,));
1950 cHours += iNumber;
1951 else:
1952 asErrors.append('Bad number "%s".' % (sNumber,));
1953 return (cHours, None if not asErrors else ' '.join(asErrors));
1954
1955
1956#
1957# Introspection.
1958#
1959
1960def getCallerName(oFrame=None, iFrame=2):
1961 """
1962 Returns the name of the caller's caller.
1963 """
1964 if oFrame is None:
1965 try:
1966 raise Exception();
1967 except:
1968 oFrame = sys.exc_info()[2].tb_frame.f_back;
1969 while iFrame > 1:
1970 if oFrame is not None:
1971 oFrame = oFrame.f_back;
1972 iFrame = iFrame - 1;
1973 if oFrame is not None:
1974 sName = '%s:%u' % (oFrame.f_code.co_name, oFrame.f_lineno);
1975 return sName;
1976 return "unknown";
1977
1978
1979def getXcptInfo(cFrames = 1):
1980 """
1981 Gets text detailing the exception. (Good for logging.)
1982 Returns list of info strings.
1983 """
1984
1985 #
1986 # Try get exception info.
1987 #
1988 try:
1989 oType, oValue, oTraceback = sys.exc_info();
1990 except:
1991 oType = oValue = oTraceback = None;
1992 if oType is not None:
1993
1994 #
1995 # Try format the info
1996 #
1997 asRet = [];
1998 try:
1999 try:
2000 asRet = asRet + traceback.format_exception_only(oType, oValue);
2001 asTraceBack = traceback.format_tb(oTraceback);
2002 if cFrames is not None and cFrames <= 1:
2003 asRet.append(asTraceBack[-1]);
2004 else:
2005 asRet.append('Traceback:')
2006 for iFrame in range(min(cFrames, len(asTraceBack))):
2007 asRet.append(asTraceBack[-iFrame - 1]);
2008 asRet.append('Stack:')
2009 asRet = asRet + traceback.format_stack(oTraceback.tb_frame.f_back, cFrames);
2010 except:
2011 asRet.append('internal-error: Hit exception #2! %s' % (traceback.format_exc(),));
2012
2013 if not asRet:
2014 asRet.append('No exception info...');
2015 except:
2016 asRet.append('internal-error: Hit exception! %s' % (traceback.format_exc(),));
2017 else:
2018 asRet = ['Couldn\'t find exception traceback.'];
2019 return asRet;
2020
2021
2022def getObjectTypeName(oObject):
2023 """
2024 Get the type name of the given object.
2025 """
2026 if oObject is None:
2027 return 'None';
2028
2029 # Get the type object.
2030 try:
2031 oType = type(oObject);
2032 except:
2033 return 'type-throws-exception';
2034
2035 # Python 2.x only: Handle old-style object wrappers.
2036 if sys.version_info[0] < 3:
2037 try:
2038 from types import InstanceType; # pylint: disable=no-name-in-module
2039 if oType == InstanceType:
2040 oType = oObject.__class__;
2041 except:
2042 pass;
2043
2044 # Get the name.
2045 try:
2046 return oType.__name__;
2047 except:
2048 return '__type__-throws-exception';
2049
2050
2051def chmodPlusX(sFile):
2052 """
2053 Makes the specified file or directory executable.
2054 Returns success indicator, no exceptions.
2055
2056 Note! Symbolic links are followed and the target will be changed.
2057 """
2058 try:
2059 oStat = os.stat(sFile);
2060 except:
2061 return False;
2062 try:
2063 os.chmod(sFile, oStat.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH);
2064 except:
2065 return False;
2066 return True;
2067
2068
2069#
2070# TestSuite stuff.
2071#
2072
2073def isRunningFromCheckout(cScriptDepth = 1):
2074 """
2075 Checks if we're running from the SVN checkout or not.
2076 """
2077
2078 try:
2079 sFile = __file__;
2080 cScriptDepth = 1;
2081 except:
2082 sFile = sys.argv[0];
2083
2084 sDir = os.path.abspath(sFile);
2085 while cScriptDepth >= 0:
2086 sDir = os.path.dirname(sDir);
2087 if os.path.exists(os.path.join(sDir, 'Makefile.kmk')) \
2088 or os.path.exists(os.path.join(sDir, 'Makefile.kup')):
2089 return True;
2090 cScriptDepth -= 1;
2091
2092 return False;
2093
2094
2095#
2096# Bourne shell argument fun.
2097#
2098
2099
2100def argsSplit(sCmdLine):
2101 """
2102 Given a bourne shell command line invocation, split it up into arguments
2103 assuming IFS is space.
2104 Returns None on syntax error.
2105 """
2106 ## @todo bourne shell argument parsing!
2107 return sCmdLine.split(' ');
2108
2109def argsGetFirst(sCmdLine):
2110 """
2111 Given a bourne shell command line invocation, get return the first argument
2112 assuming IFS is space.
2113 Returns None on invalid syntax, otherwise the parsed and unescaped argv[0] string.
2114 """
2115 asArgs = argsSplit(sCmdLine);
2116 if not asArgs:
2117 return None;
2118
2119 return asArgs[0];
2120
2121#
2122# String helpers.
2123#
2124
2125def stricmp(sFirst, sSecond):
2126 """
2127 Compares to strings in an case insensitive fashion.
2128
2129 Python doesn't seem to have any way of doing the correctly, so this is just
2130 an approximation using lower.
2131 """
2132 if sFirst == sSecond:
2133 return 0;
2134 sLower1 = sFirst.lower();
2135 sLower2 = sSecond.lower();
2136 if sLower1 == sLower2:
2137 return 0;
2138 if sLower1 < sLower2:
2139 return -1;
2140 return 1;
2141
2142
2143def versionCompare(sVer1, sVer2):
2144 """
2145 Compares to version strings in a fashion similar to RTStrVersionCompare.
2146 """
2147
2148 ## @todo implement me!!
2149
2150 if sVer1 == sVer2:
2151 return 0;
2152 if sVer1 < sVer2:
2153 return -1;
2154 return 1;
2155
2156
2157def formatNumber(lNum, sThousandSep = ' '):
2158 """
2159 Formats a decimal number with pretty separators.
2160 """
2161 sNum = str(lNum);
2162 sRet = sNum[-3:];
2163 off = len(sNum) - 3;
2164 while off > 0:
2165 off -= 3;
2166 sRet = sNum[(off if off >= 0 else 0):(off + 3)] + sThousandSep + sRet;
2167 return sRet;
2168
2169
2170def formatNumberNbsp(lNum):
2171 """
2172 Formats a decimal number with pretty separators.
2173 """
2174 sRet = formatNumber(lNum);
2175 return unicode(sRet).replace(' ', u'\u00a0');
2176
2177
2178def isString(oString):
2179 """
2180 Checks if the object is a string object, hiding difference between python 2 and 3.
2181
2182 Returns True if it's a string of some kind.
2183 Returns False if not.
2184 """
2185 if sys.version_info[0] >= 3:
2186 return isinstance(oString, str);
2187 return isinstance(oString, basestring); # pylint: disable=undefined-variable
2188
2189
2190def hasNonAsciiCharacters(sText):
2191 """
2192 Returns True is specified string has non-ASCII characters, False if ASCII only.
2193 """
2194 if isString(sText):
2195 for ch in sText:
2196 if ord(ch) >= 128:
2197 return True;
2198 else:
2199 # Probably byte array or some such thing.
2200 for ch in sText:
2201 if ch >= 128 or ch < 0:
2202 return True;
2203 return False;
2204
2205
2206#
2207# Unpacking.
2208#
2209
2210def unpackZipFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None):
2211 # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string]
2212 """
2213 Worker for unpackFile that deals with ZIP files, same function signature.
2214 """
2215 import zipfile
2216 if fnError is None:
2217 fnError = fnLog;
2218
2219 fnLog('Unzipping "%s" to "%s"...' % (sArchive, sDstDir));
2220
2221 # Open it.
2222 try: oZipFile = zipfile.ZipFile(sArchive, 'r'); # pylint: disable=consider-using-with
2223 except Exception as oXcpt:
2224 fnError('Error opening "%s" for unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt,));
2225 return None;
2226
2227 # Extract all members.
2228 asMembers = [];
2229 try:
2230 for sMember in oZipFile.namelist():
2231 if fnFilter is None or fnFilter(sMember) is not False:
2232 if sMember.endswith('/'):
2233 os.makedirs(os.path.join(sDstDir, sMember.replace('/', os.path.sep)), 0x1fd); # octal: 0775 (python 3/2)
2234 else:
2235 oZipFile.extract(sMember, sDstDir);
2236 asMembers.append(os.path.join(sDstDir, sMember.replace('/', os.path.sep)));
2237 except Exception as oXcpt:
2238 fnError('Error unpacking "%s" into "%s": %s' % (sArchive, sDstDir, oXcpt));
2239 asMembers = None;
2240
2241 # close it.
2242 try: oZipFile.close();
2243 except Exception as oXcpt:
2244 fnError('Error closing "%s" after unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt));
2245 asMembers = None;
2246
2247 return asMembers;
2248
2249
2250## Set if we've replaced tarfile.copyfileobj with __mytarfilecopyfileobj already.
2251g_fTarCopyFileObjOverriddend = False;
2252
2253def __mytarfilecopyfileobj(src, dst, length = None, exception = OSError, bufsize = None):
2254 """ tarfile.copyfileobj with different buffer size (16384 is slow on windows). """
2255 _ = bufsize;
2256 if length is None:
2257 __myshutilcopyfileobj(src, dst, g_cbGoodBufferSize);
2258 elif length > 0:
2259 cFull, cbRemainder = divmod(length, g_cbGoodBufferSize);
2260 for _ in xrange(cFull):
2261 abBuffer = src.read(g_cbGoodBufferSize);
2262 dst.write(abBuffer);
2263 if len(abBuffer) != g_cbGoodBufferSize:
2264 raise exception('unexpected end of source file');
2265 if cbRemainder > 0:
2266 abBuffer = src.read(cbRemainder);
2267 dst.write(abBuffer);
2268 if len(abBuffer) != cbRemainder:
2269 raise exception('unexpected end of source file');
2270
2271
2272def unpackTarFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None):
2273 # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string]
2274 """
2275 Worker for unpackFile that deals with tarballs, same function signature.
2276 """
2277 import shutil;
2278 import tarfile;
2279 if fnError is None:
2280 fnError = fnLog;
2281
2282 fnLog('Untarring "%s" to "%s"...' % (sArchive, sDstDir));
2283
2284 #
2285 # Default buffer sizes of 16384 bytes is causing too many syscalls on Windows.
2286 # 60%+ speedup for python 2.7 and 50%+ speedup for python 3.5, both on windows with PDBs.
2287 # 20%+ speedup for python 2.7 and 15%+ speedup for python 3.5, both on windows skipping PDBs.
2288 #
2289 if True is True: # pylint: disable=comparison-with-itself,comparison-of-constants
2290 __installShUtilHacks(shutil);
2291 global g_fTarCopyFileObjOverriddend;
2292 if g_fTarCopyFileObjOverriddend is False:
2293 g_fTarCopyFileObjOverriddend = True;
2294 #if sys.hexversion < 0x03060000:
2295 tarfile.copyfileobj = __mytarfilecopyfileobj;
2296
2297 #
2298 # Open it.
2299 #
2300 # Note! We not using 'r:*' because we cannot allow seeking compressed files!
2301 # That's how we got a 13 min unpack time for VBoxAll on windows (hardlinked pdb).
2302 #
2303 try:
2304 if sys.hexversion >= 0x03060000:
2305 oTarFile = tarfile.open(sArchive, 'r|*', # pylint: disable=consider-using-with
2306 bufsize = g_cbGoodBufferSize, copybufsize = g_cbGoodBufferSize);
2307 else:
2308 oTarFile = tarfile.open(sArchive, 'r|*', bufsize = g_cbGoodBufferSize); # pylint: disable=consider-using-with
2309 except Exception as oXcpt:
2310 fnError('Error opening "%s" for unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt,));
2311 return None;
2312
2313 # Extract all members.
2314 asMembers = [];
2315 try:
2316 for oTarInfo in oTarFile:
2317 try:
2318 if fnFilter is None or fnFilter(oTarInfo.name) is not False:
2319 if oTarInfo.islnk():
2320 # Links are trouble, especially on Windows. We must avoid the falling that will end up seeking
2321 # in the compressed tar stream. So, fall back on shutil.copy2 instead.
2322 sLinkFile = os.path.join(sDstDir, oTarInfo.name.rstrip('/').replace('/', os.path.sep));
2323 sLinkTarget = os.path.join(sDstDir, oTarInfo.linkname.rstrip('/').replace('/', os.path.sep));
2324 sParentDir = os.path.dirname(sLinkFile);
2325 try: os.unlink(sLinkFile);
2326 except: pass;
2327 if sParentDir and not os.path.exists(sParentDir):
2328 os.makedirs(sParentDir);
2329 try: os.link(sLinkTarget, sLinkFile);
2330 except: shutil.copy2(sLinkTarget, sLinkFile);
2331 else:
2332 if oTarInfo.isdir():
2333 # Just make sure the user (we) got full access to dirs. Don't bother getting it 100% right.
2334 oTarInfo.mode |= 0x1c0; # (octal: 0700)
2335 oTarFile.extract(oTarInfo, sDstDir);
2336 asMembers.append(os.path.join(sDstDir, oTarInfo.name.replace('/', os.path.sep)));
2337 except Exception as oXcpt:
2338 fnError('Error unpacking "%s" member "%s" into "%s": %s' % (sArchive, oTarInfo.name, sDstDir, oXcpt));
2339 for sAttr in [ 'name', 'linkname', 'type', 'mode', 'size', 'mtime', 'uid', 'uname', 'gid', 'gname' ]:
2340 fnError('Info: %8s=%s' % (sAttr, getattr(oTarInfo, sAttr),));
2341 for sFn in [ 'isdir', 'isfile', 'islnk', 'issym' ]:
2342 fnError('Info: %8s=%s' % (sFn, getattr(oTarInfo, sFn)(),));
2343 asMembers = None;
2344 break;
2345 except Exception as oXcpt:
2346 fnError('Error unpacking "%s" into "%s": %s' % (sArchive, sDstDir, oXcpt));
2347 asMembers = None;
2348
2349 #
2350 # Finally, close it.
2351 #
2352 try: oTarFile.close();
2353 except Exception as oXcpt:
2354 fnError('Error closing "%s" after unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt));
2355 asMembers = None;
2356
2357 return asMembers;
2358
2359
2360def unpackFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None):
2361 # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string]
2362 """
2363 Unpacks the given file if it has a know archive extension, otherwise do
2364 nothing.
2365
2366 fnLog & fnError both take a string parameter.
2367
2368 fnFilter takes a member name (string) and returns True if it's included
2369 and False if excluded.
2370
2371 Returns list of the extracted files (full path) on success.
2372 Returns empty list if not a supported archive format.
2373 Returns None on failure. Raises no exceptions.
2374 """
2375 sBaseNameLower = os.path.basename(sArchive).lower();
2376
2377 #
2378 # Zip file?
2379 #
2380 if sBaseNameLower.endswith('.zip'):
2381 return unpackZipFile(sArchive, sDstDir, fnLog, fnError, fnFilter);
2382
2383 #
2384 # Tarball?
2385 #
2386 if sBaseNameLower.endswith('.tar') \
2387 or sBaseNameLower.endswith('.tar.gz') \
2388 or sBaseNameLower.endswith('.tgz') \
2389 or sBaseNameLower.endswith('.tar.bz2'):
2390 return unpackTarFile(sArchive, sDstDir, fnLog, fnError, fnFilter);
2391
2392 #
2393 # Cannot classify it from the name, so just return that to the caller.
2394 #
2395 fnLog('Not unpacking "%s".' % (sArchive,));
2396 return [];
2397
2398
2399#
2400# Misc.
2401#
2402def areBytesEqual(oLeft, oRight):
2403 """
2404 Compares two byte arrays, strings or whatnot.
2405
2406 returns true / false accordingly.
2407 """
2408
2409 # If both are None, consider them equal (bogus?):
2410 if oLeft is None and oRight is None:
2411 return True;
2412
2413 # If just one is None, they can't match:
2414 if oLeft is None or oRight is None:
2415 return False;
2416
2417 # If both have the same type, use the compare operator of the class:
2418 if type(oLeft) is type(oRight):
2419 #print('same type: %s' % (oLeft == oRight,));
2420 return oLeft == oRight;
2421
2422 # On the offchance that they're both strings, but of different types.
2423 if isString(oLeft) and isString(oRight):
2424 #print('string compare: %s' % (oLeft == oRight,));
2425 return oLeft == oRight;
2426
2427 #
2428 # See if byte/buffer stuff that can be compared directory. If not convert
2429 # strings to bytes.
2430 #
2431 # Note! For 2.x, we must convert both sides to the buffer type or the
2432 # comparison may fail despite it working okay in test cases.
2433 #
2434 if sys.version_info[0] >= 3:
2435 if isinstance(oLeft, (bytearray, memoryview, bytes)) and isinstance(oRight, (bytearray, memoryview, bytes)): # pylint: disable=undefined-variable
2436 return oLeft == oRight;
2437
2438 if isString(oLeft):
2439 try: oLeft = bytes(oLeft, 'utf-8');
2440 except: pass;
2441 if isString(oRight):
2442 try: oRight = bytes(oRight, 'utf-8');
2443 except: pass;
2444 else:
2445 if isinstance(oLeft, (bytearray, buffer)) and isinstance(oRight, (bytearray, buffer)): # pylint: disable=undefined-variable
2446 if isinstance(oLeft, bytearray):
2447 oLeft = buffer(oLeft); # pylint: disable=redefined-variable-type,undefined-variable
2448 else:
2449 oRight = buffer(oRight); # pylint: disable=redefined-variable-type,undefined-variable
2450 #print('buf/byte #1 compare: %s (%s vs %s)' % (oLeft == oRight, type(oLeft), type(oRight),));
2451 return oLeft == oRight;
2452
2453 if isString(oLeft):
2454 try: oLeft = bytearray(oLeft, 'utf-8'); # pylint: disable=redefined-variable-type
2455 except: pass;
2456 if isString(oRight):
2457 try: oRight = bytearray(oRight, 'utf-8'); # pylint: disable=redefined-variable-type
2458 except: pass;
2459
2460 # Check if we now have the same type for both:
2461 if type(oLeft) is type(oRight):
2462 #print('same type now: %s' % (oLeft == oRight,));
2463 return oLeft == oRight;
2464
2465 # Check if we now have buffer/memoryview vs bytes/bytesarray again.
2466 if sys.version_info[0] >= 3:
2467 if isinstance(oLeft, (bytearray, memoryview, bytes)) and isinstance(oRight, (bytearray, memoryview, bytes)): # pylint: disable=undefined-variable
2468 return oLeft == oRight;
2469 else:
2470 if isinstance(oLeft, (bytearray, buffer)) and isinstance(oRight, (bytearray, buffer)): # pylint: disable=undefined-variable
2471 if isinstance(oLeft, bytearray):
2472 oLeft = buffer(oLeft); # pylint: disable=redefined-variable-type,undefined-variable
2473 else:
2474 oRight = buffer(oRight); # pylint: disable=redefined-variable-type,undefined-variable
2475 #print('buf/byte #2 compare: %s (%s vs %s)' % (oLeft == oRight, type(oLeft), type(oRight),));
2476 return oLeft == oRight;
2477
2478 # Do item by item comparison:
2479 if len(oLeft) != len(oRight):
2480 #print('different length: %s vs %s' % (len(oLeft), len(oRight)));
2481 return False;
2482 i = len(oLeft);
2483 while i > 0:
2484 i = i - 1;
2485
2486 iElmLeft = oLeft[i];
2487 if not isinstance(iElmLeft, int) and not isinstance(iElmLeft, long):
2488 iElmLeft = ord(iElmLeft);
2489
2490 iElmRight = oRight[i];
2491 if not isinstance(iElmRight, int) and not isinstance(iElmRight, long):
2492 iElmRight = ord(iElmRight);
2493
2494 if iElmLeft != iElmRight:
2495 #print('element %d differs: %x %x' % (i, iElmLeft, iElmRight,));
2496 return False;
2497 return True;
2498
2499
2500def calcCrc32OfFile(sFile):
2501 """
2502 Simple helper for calculating the CRC32 of a file.
2503
2504 Throws stuff if the file cannot be opened or read successfully.
2505 """
2506 import zlib;
2507
2508 uCrc32 = 0;
2509 with open(sFile, 'rb') as oFile: # pylint: disable=unspecified-encoding
2510 while True:
2511 oBuf = oFile.read(1024 * 1024);
2512 if not oBuf:
2513 break
2514 uCrc32 = zlib.crc32(oBuf, uCrc32);
2515
2516 return uCrc32 % 2**32;
2517
2518
2519#
2520# Unit testing.
2521#
2522
2523# pylint: disable=missing-docstring
2524# pylint: disable=undefined-variable
2525class BuildCategoryDataTestCase(unittest.TestCase):
2526 def testIntervalSeconds(self):
2527 self.assertEqual(parseIntervalSeconds(formatIntervalSeconds(3600)), (3600, None));
2528 self.assertEqual(parseIntervalSeconds(formatIntervalSeconds(1209438593)), (1209438593, None));
2529 self.assertEqual(parseIntervalSeconds('123'), (123, None));
2530 self.assertEqual(parseIntervalSeconds(123), (123, None));
2531 self.assertEqual(parseIntervalSeconds(99999999999), (99999999999, None));
2532 self.assertEqual(parseIntervalSeconds(''), (0, 'Empty interval string.'));
2533 self.assertEqual(parseIntervalSeconds('1X2'), (3, 'Unknown unit "X".'));
2534 self.assertEqual(parseIntervalSeconds('1 Y3'), (4, 'Unknown unit "Y".'));
2535 self.assertEqual(parseIntervalSeconds('1 Z 4'), (5, 'Unknown unit "Z".'));
2536 self.assertEqual(parseIntervalSeconds('1 hour 2m 5second'), (3725, None));
2537 self.assertEqual(parseIntervalSeconds('1 hour,2m ; 5second'), (3725, None));
2538
2539 def testZuluNormalization(self):
2540 self.assertEqual(normalizeIsoTimestampToZulu('2011-01-02T03:34:25.000000000Z'), '2011-01-02T03:34:25.000000000Z');
2541 self.assertEqual(normalizeIsoTimestampToZulu('2011-01-02T03:04:25-0030'), '2011-01-02T03:34:25.000000000Z');
2542 self.assertEqual(normalizeIsoTimestampToZulu('2011-01-02T03:04:25+0030'), '2011-01-02T02:34:25.000000000Z');
2543 self.assertEqual(normalizeIsoTimestampToZulu('2020-03-20T20:47:39,832312863+01:00'), '2020-03-20T19:47:39.832312000Z');
2544 self.assertEqual(normalizeIsoTimestampToZulu('2020-03-20T20:47:39,832312863-02:00'), '2020-03-20T22:47:39.832312000Z');
2545
2546 def testHasNonAsciiChars(self):
2547 self.assertEqual(hasNonAsciiCharacters(''), False);
2548 self.assertEqual(hasNonAsciiCharacters('asdfgebASDFKJ@#$)(!@#UNASDFKHB*&$%&)@#(!)@(#!(#$&*#$&%*Y@#$IQWN---00;'), False);
2549 self.assertEqual(hasNonAsciiCharacters('\x80 '), True);
2550 self.assertEqual(hasNonAsciiCharacters('\x79 '), False);
2551 self.assertEqual(hasNonAsciiCharacters(u'12039889y!@#$%^&*()0-0asjdkfhoiuyweasdfASDFnvV'), False);
2552 self.assertEqual(hasNonAsciiCharacters(u'\u0079'), False);
2553 self.assertEqual(hasNonAsciiCharacters(u'\u0080'), True);
2554 self.assertEqual(hasNonAsciiCharacters(u'\u0081 \u0100'), True);
2555 self.assertEqual(hasNonAsciiCharacters(b'\x20\x20\x20'), False);
2556 self.assertEqual(hasNonAsciiCharacters(b'\x20\x81\x20'), True);
2557
2558 def testAreBytesEqual(self):
2559 self.assertEqual(areBytesEqual(None, None), True);
2560 self.assertEqual(areBytesEqual(None, ''), False);
2561 self.assertEqual(areBytesEqual('', ''), True);
2562 self.assertEqual(areBytesEqual('1', '1'), True);
2563 self.assertEqual(areBytesEqual('12345', '1234'), False);
2564 self.assertEqual(areBytesEqual('1234', '1234'), True);
2565 self.assertEqual(areBytesEqual('1234', b'1234'), True);
2566 self.assertEqual(areBytesEqual(b'1234', b'1234'), True);
2567 self.assertEqual(areBytesEqual(b'1234', '1234'), True);
2568 self.assertEqual(areBytesEqual(b'1234', bytearray([0x31,0x32,0x33,0x34])), True);
2569 self.assertEqual(areBytesEqual('1234', bytearray([0x31,0x32,0x33,0x34])), True);
2570 self.assertEqual(areBytesEqual(u'1234', bytearray([0x31,0x32,0x33,0x34])), True);
2571 self.assertEqual(areBytesEqual(bytearray([0x31,0x32,0x33,0x34]), bytearray([0x31,0x32,0x33,0x34])), True);
2572 self.assertEqual(areBytesEqual(bytearray([0x31,0x32,0x33,0x34]), '1224'), False);
2573 self.assertEqual(areBytesEqual(bytearray([0x31,0x32,0x33,0x34]), bytearray([0x31,0x32,0x32,0x34])), False);
2574 if sys.version_info[0] >= 3:
2575 pass;
2576 else:
2577 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1),
2578 bytearray([0x31,0x32,0x33,0x34])), True);
2579 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1),
2580 bytearray([0x99,0x32,0x32,0x34])), False);
2581 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1),
2582 buffer(bytearray([0x31,0x32,0x33,0x34,0x34]), 0, 4)), True);
2583 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1),
2584 buffer(bytearray([0x99,0x32,0x33,0x34,0x34]), 0, 4)), False);
2585 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), b'1234'), True);
2586 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), '1234'), True);
2587 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), u'1234'), True);
2588
2589if __name__ == '__main__':
2590 unittest.main();
2591 # not reached.
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette