VirtualBox

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

Last change on this file since 97672 was 97672, checked in by vboxsync, 2 years ago

Validation Kit: Solaris core dump name tweaking.

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