VirtualBox

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

Last change on this file since 70558 was 70518, checked in by vboxsync, 7 years ago

common/utils.py: Python 3 adjustments.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 67.2 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: utils.py 70518 2018-01-10 14:14:56Z vboxsync $
3# pylint: disable=C0302
4
5"""
6Common Utility Functions.
7"""
8
9__copyright__ = \
10"""
11Copyright (C) 2012-2017 Oracle Corporation
12
13This file is part of VirtualBox Open Source Edition (OSE), as
14available from http://www.virtualbox.org. This file is free software;
15you can redistribute it and/or modify it under the terms of the GNU
16General Public License (GPL) as published by the Free Software
17Foundation, in version 2 as it comes in the "COPYING" file of the
18VirtualBox OSE distribution. VirtualBox OSE is distributed in the
19hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
20
21The contents of this file may alternatively be used under the terms
22of the Common Development and Distribution License Version 1.0
23(CDDL) only, as it comes in the "COPYING.CDDL" file of the
24VirtualBox OSE distribution, in which case the provisions of the
25CDDL are applicable instead of those of the GPL.
26
27You may elect to license modified versions of this file under the
28terms and conditions of either the GPL or the CDDL or both.
29"""
30__version__ = "$Revision: 70518 $"
31
32
33# Standard Python imports.
34import datetime;
35import os;
36import platform;
37import re;
38import stat;
39import subprocess;
40import sys;
41import time;
42import traceback;
43import unittest;
44
45if sys.platform == 'win32':
46 import ctypes;
47 import msvcrt; # pylint: disable=import-error
48 import win32api; # pylint: disable=import-error
49 import win32con; # pylint: disable=import-error
50 import win32console; # pylint: disable=import-error
51 import win32file; # pylint: disable=import-error
52 import win32process; # pylint: disable=import-error
53 import winerror; # pylint: disable=import-error
54 import pywintypes; # pylint: disable=import-error
55else:
56 import signal;
57
58# Python 3 hacks:
59if sys.version_info[0] >= 3:
60 unicode = str; # pylint: disable=redefined-builtin,invalid-name
61 xrange = range; # pylint: disable=redefined-builtin,invalid-name
62 long = int; # pylint: disable=redefined-builtin,invalid-name
63
64
65#
66# Host OS and CPU.
67#
68
69def getHostOs():
70 """
71 Gets the host OS name (short).
72
73 See the KBUILD_OSES variable in kBuild/header.kmk for possible return values.
74 """
75 sPlatform = platform.system();
76 if sPlatform in ('Linux', 'Darwin', 'Solaris', 'FreeBSD', 'NetBSD', 'OpenBSD'):
77 sPlatform = sPlatform.lower();
78 elif sPlatform == 'Windows':
79 sPlatform = 'win';
80 elif sPlatform == 'SunOS':
81 sPlatform = 'solaris';
82 else:
83 raise Exception('Unsupported platform "%s"' % (sPlatform,));
84 return sPlatform;
85
86g_sHostArch = None;
87
88def getHostArch():
89 """
90 Gets the host CPU architecture.
91
92 See the KBUILD_ARCHES variable in kBuild/header.kmk for possible return values.
93 """
94 global g_sHostArch;
95 if g_sHostArch is None:
96 sArch = platform.machine();
97 if sArch in ('i386', 'i486', 'i586', 'i686', 'i786', 'i886', 'x86'):
98 sArch = 'x86';
99 elif sArch in ('AMD64', 'amd64', 'x86_64'):
100 sArch = 'amd64';
101 elif sArch == 'i86pc': # SunOS
102 if platform.architecture()[0] == '64bit':
103 sArch = 'amd64';
104 else:
105 try:
106 sArch = processOutputChecked(['/usr/bin/isainfo', '-n',]);
107 except:
108 pass;
109 sArch = sArch.strip();
110 if sArch != 'amd64':
111 sArch = 'x86';
112 else:
113 raise Exception('Unsupported architecture/machine "%s"' % (sArch,));
114 g_sHostArch = sArch;
115 return g_sHostArch;
116
117
118def getHostOsDotArch():
119 """
120 Gets the 'os.arch' for the host.
121 """
122 return '%s.%s' % (getHostOs(), getHostArch());
123
124
125def isValidOs(sOs):
126 """
127 Validates the OS name.
128 """
129 if sOs in ('darwin', 'dos', 'dragonfly', 'freebsd', 'haiku', 'l4', 'linux', 'netbsd', 'nt', 'openbsd', \
130 'os2', 'solaris', 'win', 'os-agnostic'):
131 return True;
132 return False;
133
134
135def isValidArch(sArch):
136 """
137 Validates the CPU architecture name.
138 """
139 if sArch in ('x86', 'amd64', 'sparc32', 'sparc64', 's390', 's390x', 'ppc32', 'ppc64', \
140 'mips32', 'mips64', 'ia64', 'hppa32', 'hppa64', 'arm', 'alpha'):
141 return True;
142 return False;
143
144def isValidOsDotArch(sOsDotArch):
145 """
146 Validates the 'os.arch' string.
147 """
148
149 asParts = sOsDotArch.split('.');
150 if asParts.length() != 2:
151 return False;
152 return isValidOs(asParts[0]) \
153 and isValidArch(asParts[1]);
154
155def getHostOsVersion():
156 """
157 Returns the host OS version. This is platform.release with additional
158 distro indicator on linux.
159 """
160 sVersion = platform.release();
161 sOs = getHostOs();
162 if sOs == 'linux':
163 sDist = '';
164 try:
165 # try /etc/lsb-release first to distinguish between Debian and Ubuntu
166 oFile = open('/etc/lsb-release');
167 for sLine in oFile:
168 oMatch = re.search(r'(?:DISTRIB_DESCRIPTION\s*=)\s*"*(.*)"', sLine);
169 if oMatch is not None:
170 sDist = oMatch.group(1).strip();
171 except:
172 pass;
173 if sDist:
174 sVersion += ' / ' + sDist;
175 else:
176 asFiles = \
177 [
178 [ '/etc/debian_version', 'Debian v'],
179 [ '/etc/gentoo-release', '' ],
180 [ '/etc/oracle-release', '' ],
181 [ '/etc/redhat-release', '' ],
182 [ '/etc/SuSE-release', '' ],
183 ];
184 for sFile, sPrefix in asFiles:
185 if os.path.isfile(sFile):
186 try:
187 oFile = open(sFile);
188 sLine = oFile.readline();
189 oFile.close();
190 except:
191 continue;
192 sLine = sLine.strip()
193 if sLine:
194 sVersion += ' / ' + sPrefix + sLine;
195 break;
196
197 elif sOs == 'solaris':
198 sVersion = platform.version();
199 if os.path.isfile('/etc/release'):
200 try:
201 oFile = open('/etc/release');
202 sLast = oFile.readlines()[-1];
203 oFile.close();
204 sLast = sLast.strip();
205 if sLast:
206 sVersion += ' (' + sLast + ')';
207 except:
208 pass;
209
210 elif sOs == 'darwin':
211 sOsxVersion = platform.mac_ver()[0];
212 codenames = {"4": "Tiger",
213 "5": "Leopard",
214 "6": "Snow Leopard",
215 "7": "Lion",
216 "8": "Mountain Lion",
217 "9": "Mavericks",
218 "10": "Yosemite",
219 "11": "El Capitan",
220 "12": "Sierra",
221 "13": "High Sierra",
222 "14": "Unknown 14", }
223 sVersion += ' / OS X ' + sOsxVersion + ' (' + codenames[sOsxVersion.split('.')[1]] + ')'
224
225 elif sOs == 'win':
226 class OSVersionInfoEx(ctypes.Structure):
227 """ OSVERSIONEX """
228 kaFields = [
229 ('dwOSVersionInfoSize', ctypes.c_ulong),
230 ('dwMajorVersion', ctypes.c_ulong),
231 ('dwMinorVersion', ctypes.c_ulong),
232 ('dwBuildNumber', ctypes.c_ulong),
233 ('dwPlatformId', ctypes.c_ulong),
234 ('szCSDVersion', ctypes.c_wchar*128),
235 ('wServicePackMajor', ctypes.c_ushort),
236 ('wServicePackMinor', ctypes.c_ushort),
237 ('wSuiteMask', ctypes.c_ushort),
238 ('wProductType', ctypes.c_byte),
239 ('wReserved', ctypes.c_byte)]
240 _fields_ = kaFields # pylint: disable=invalid-name
241
242 def __init__(self):
243 super(OSVersionInfoEx, self).__init__()
244 self.dwOSVersionInfoSize = ctypes.sizeof(self)
245
246 oOsVersion = OSVersionInfoEx()
247 rc = ctypes.windll.Ntdll.RtlGetVersion(ctypes.byref(oOsVersion))
248 if rc == 0:
249 # Python platform.release() is not reliable for newer server releases
250 if oOsVersion.wProductType != 1:
251 if oOsVersion.dwMajorVersion == 10 and oOsVersion.dwMinorVersion == 0:
252 sVersion = '2016Server';
253 elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 3:
254 sVersion = '2012ServerR2';
255 elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 2:
256 sVersion = '2012Server';
257 elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 1:
258 sVersion = '2008ServerR2';
259 elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 0:
260 sVersion = '2008Server';
261 elif oOsVersion.dwMajorVersion == 5 and oOsVersion.dwMinorVersion == 2:
262 sVersion = '2003Server';
263 sVersion += ' build ' + str(oOsVersion.dwBuildNumber)
264 if oOsVersion.wServicePackMajor:
265 sVersion += ' SP' + str(oOsVersion.wServicePackMajor)
266 if oOsVersion.wServicePackMinor:
267 sVersion += '.' + str(oOsVersion.wServicePackMinor)
268
269 return sVersion;
270
271#
272# File system.
273#
274
275def openNoInherit(sFile, sMode = 'r'):
276 """
277 Wrapper around open() that tries it's best to make sure the file isn't
278 inherited by child processes.
279
280 This is a best effort thing at the moment as it doesn't synchronizes with
281 child process spawning in any way. Thus it can be subject to races in
282 multithreaded programs.
283 """
284
285 # Python 3.4 and later automatically creates non-inherit handles. See PEP-0446.
286 uPythonVer = (sys.version_info[0] << 16) | (sys.version_info[1] & 0xffff);
287 if uPythonVer >= ((3 << 16) | 4):
288 oFile = open(sFile, sMode);
289 else:
290 try:
291 from fcntl import FD_CLOEXEC, F_GETFD, F_SETFD, fcntl; # pylint: disable=F0401
292 except:
293 # On windows, we can use the 'N' flag introduced in Visual C++ 7.0 or 7.1 with python 2.x.
294 if getHostOs() == 'win':
295 if uPythonVer < (3 << 16):
296 offComma = sMode.find(',');
297 if offComma < 0:
298 return open(sFile, sMode + 'N');
299 return open(sFile, sMode[:offComma] + 'N' + sMode[offComma:]);
300
301 # Just in case.
302 return open(sFile, sMode);
303
304 oFile = open(sFile, sMode);
305 #try:
306 fcntl(oFile, F_SETFD, fcntl(oFile, F_GETFD) | FD_CLOEXEC);
307 #except:
308 # pass;
309 return oFile;
310
311def openNoDenyDeleteNoInherit(sFile, sMode = 'r'):
312 """
313 Wrapper around open() that tries it's best to make sure the file isn't
314 inherited by child processes.
315
316 This is a best effort thing at the moment as it doesn't synchronizes with
317 child process spawning in any way. Thus it can be subject to races in
318 multithreaded programs.
319 """
320
321 if getHostOs() == 'win':
322 # Need to use CreateFile directly to open the file so we can feed it FILE_SHARE_DELETE.
323 fAccess = 0;
324 fDisposition = win32file.OPEN_EXISTING; # pylint: disable=no-member
325 if 'r' in sMode or '+' in sMode:
326 fAccess |= win32file.GENERIC_READ; # pylint: disable=no-member
327 if 'a' in sMode:
328 fAccess |= win32file.GENERIC_WRITE; # pylint: disable=no-member
329 fDisposition = win32file.OPEN_ALWAYS; # pylint: disable=no-member
330 elif 'w' in sMode:
331 fAccess = win32file.GENERIC_WRITE; # pylint: disable=no-member
332 if '+' in sMode:
333 fDisposition = win32file.OPEN_ALWAYS; # pylint: disable=no-member
334 fAccess |= win32file.GENERIC_READ; # pylint: disable=no-member
335 else:
336 fDisposition = win32file.CREATE_ALWAYS; # pylint: disable=no-member
337 if not fAccess:
338 fAccess |= win32file.GENERIC_READ; # pylint: disable=no-member
339 fSharing = (win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE # pylint: disable=no-member
340 | win32file.FILE_SHARE_DELETE); # pylint: disable=no-member
341 hFile = win32file.CreateFile(sFile, fAccess, fSharing, None, fDisposition, 0, None); # pylint: disable=no-member
342 if 'a' in sMode:
343 win32file.SetFilePointer(hFile, 0, win32file.FILE_END); # pylint: disable=no-member
344
345 # Turn the NT handle into a CRT file descriptor.
346 hDetachedFile = hFile.Detach();
347 if fAccess == win32file.GENERIC_READ: # pylint: disable=no-member
348 fOpen = os.O_RDONLY;
349 elif fAccess == win32file.GENERIC_WRITE: # pylint: disable=no-member
350 fOpen = os.O_WRONLY;
351 else:
352 fOpen = os.O_RDWR;
353 if 'a' in sMode:
354 fOpen |= os.O_APPEND;
355 if 'b' in sMode or 't' in sMode:
356 fOpen |= os.O_TEXT; # pylint: disable=no-member
357 fdFile = msvcrt.open_osfhandle(hDetachedFile, fOpen);
358
359 # Tell python to use this handle.
360 oFile = os.fdopen(fdFile, sMode);
361 else:
362 oFile = open(sFile, sMode);
363
364 # Python 3.4 and later automatically creates non-inherit handles. See PEP-0446.
365 uPythonVer = (sys.version_info[0] << 16) | (sys.version_info[1] & 0xffff);
366 if uPythonVer < ((3 << 16) | 4):
367 try:
368 from fcntl import FD_CLOEXEC, F_GETFD, F_SETFD, fcntl; # pylint: disable=F0401
369 except:
370 pass;
371 else:
372 fcntl(oFile, F_SETFD, fcntl(oFile, F_GETFD) | FD_CLOEXEC);
373 return oFile;
374
375def noxcptReadLink(sPath, sXcptRet):
376 """
377 No exceptions os.readlink wrapper.
378 """
379 try:
380 sRet = os.readlink(sPath); # pylint: disable=E1101
381 except:
382 sRet = sXcptRet;
383 return sRet;
384
385def readFile(sFile, sMode = 'rb'):
386 """
387 Reads the entire file.
388 """
389 oFile = open(sFile, sMode);
390 sRet = oFile.read();
391 oFile.close();
392 return sRet;
393
394def noxcptReadFile(sFile, sXcptRet, sMode = 'rb'):
395 """
396 No exceptions common.readFile wrapper.
397 """
398 try:
399 sRet = readFile(sFile, sMode);
400 except:
401 sRet = sXcptRet;
402 return sRet;
403
404def noxcptRmDir(sDir, oXcptRet = False):
405 """
406 No exceptions os.rmdir wrapper.
407 """
408 oRet = True;
409 try:
410 os.rmdir(sDir);
411 except:
412 oRet = oXcptRet;
413 return oRet;
414
415def noxcptDeleteFile(sFile, oXcptRet = False):
416 """
417 No exceptions os.remove wrapper.
418 """
419 oRet = True;
420 try:
421 os.remove(sFile);
422 except:
423 oRet = oXcptRet;
424 return oRet;
425
426
427def dirEnumerateTree(sDir, fnCallback, fIgnoreExceptions = True):
428 # type: (string, (string, stat) -> bool) -> bool
429 """
430 Recursively walks a directory tree, calling fnCallback for each.
431
432 fnCallback takes a full path and stat object (can be None). It
433 returns a boolean value, False stops walking and returns immediately.
434
435 Returns True or False depending on fnCallback.
436 Returns None fIgnoreExceptions is True and an exception was raised by listdir.
437 """
438 def __worker(sCurDir):
439 """ Worker for """
440 try:
441 asNames = os.listdir(sCurDir);
442 except:
443 if not fIgnoreExceptions:
444 raise;
445 return None;
446 rc = True;
447 for sName in asNames:
448 if sName not in [ '.', '..' ]:
449 sFullName = os.path.join(sCurDir, sName);
450 try: oStat = os.lstat(sFullName);
451 except: oStat = None;
452 if fnCallback(sFullName, oStat) is False:
453 return False;
454 if oStat is not None and stat.S_ISDIR(oStat.st_mode):
455 rc = __worker(sFullName);
456 if rc is False:
457 break;
458 return rc;
459
460 # Ensure unicode path here so listdir also returns unicode on windows.
461 ## @todo figure out unicode stuff on non-windows.
462 if sys.platform == 'win32':
463 sDir = unicode(sDir);
464 return __worker(sDir);
465
466
467
468def formatFileMode(uMode):
469 # type: (int) -> string
470 """
471 Format a st_mode value 'ls -la' fasion.
472 Returns string.
473 """
474 if stat.S_ISDIR(uMode): sMode = 'd';
475 elif stat.S_ISREG(uMode): sMode = '-';
476 elif stat.S_ISLNK(uMode): sMode = 'l';
477 elif stat.S_ISFIFO(uMode): sMode = 'p';
478 elif stat.S_ISCHR(uMode): sMode = 'c';
479 elif stat.S_ISBLK(uMode): sMode = 'b';
480 elif stat.S_ISSOCK(uMode): sMode = 's';
481 else: sMode = '?';
482 ## @todo sticky bits.
483 sMode += 'r' if uMode & stat.S_IRUSR else '-';
484 sMode += 'w' if uMode & stat.S_IWUSR else '-';
485 sMode += 'x' if uMode & stat.S_IXUSR else '-';
486 sMode += 'r' if uMode & stat.S_IRGRP else '-';
487 sMode += 'w' if uMode & stat.S_IWGRP else '-';
488 sMode += 'x' if uMode & stat.S_IXGRP else '-';
489 sMode += 'r' if uMode & stat.S_IROTH else '-';
490 sMode += 'w' if uMode & stat.S_IWOTH else '-';
491 sMode += 'x' if uMode & stat.S_IXOTH else '-';
492 sMode += ' ';
493 return sMode;
494
495
496def formatFileStat(oStat):
497 # type: (stat) -> string
498 """
499 Format a stat result 'ls -la' fasion (numeric IDs).
500 Returns string.
501 """
502 return '%s %3s %4s %4s %10s %s' \
503 % (formatFileMode(oStat.st_mode), oStat.st_nlink, oStat.st_uid, oStat.st_gid, oStat.st_size,
504 time.strftime('%Y-%m-%d %H:%M', time.localtime(oStat.st_mtime)), );
505
506## Good buffer for file operations.
507g_cbGoodBufferSize = 256*1024;
508
509## The original shutil.copyfileobj.
510g_fnOriginalShCopyFileObj = None;
511
512def __myshutilcopyfileobj(fsrc, fdst, length = g_cbGoodBufferSize):
513 """ shutil.copyfileobj with different length default value (16384 is slow with python 2.7 on windows). """
514 return g_fnOriginalShCopyFileObj(fsrc, fdst, length);
515
516def __installShUtilHacks(shutil):
517 """ Installs the shutil buffer size hacks. """
518 global g_fnOriginalShCopyFileObj;
519 if g_fnOriginalShCopyFileObj is None:
520 g_fnOriginalShCopyFileObj = shutil.copyfileobj;
521 shutil.copyfileobj = __myshutilcopyfileobj;
522 return True;
523
524
525def copyFileSimple(sFileSrc, sFileDst):
526 """
527 Wrapper around shutil.copyfile that simply copies the data of a regular file.
528 Raises exception on failure.
529 Return True for show.
530 """
531 import shutil;
532 __installShUtilHacks(shutil);
533 return shutil.copyfile(sFileSrc, sFileDst);
534
535#
536# SubProcess.
537#
538
539def _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs):
540 """
541 If the "executable" is a python script, insert the python interpreter at
542 the head of the argument list so that it will work on systems which doesn't
543 support hash-bang scripts.
544 """
545
546 asArgs = dKeywordArgs.get('args');
547 if asArgs is None:
548 asArgs = aPositionalArgs[0];
549
550 if asArgs[0].endswith('.py'):
551 if sys.executable:
552 asArgs.insert(0, sys.executable);
553 else:
554 asArgs.insert(0, 'python');
555
556 # paranoia...
557 if dKeywordArgs.get('args') is not None:
558 dKeywordArgs['args'] = asArgs;
559 else:
560 aPositionalArgs = (asArgs,) + aPositionalArgs[1:];
561 return None;
562
563def processPopenSafe(*aPositionalArgs, **dKeywordArgs):
564 """
565 Wrapper for subprocess.Popen that's Ctrl-C safe on windows.
566 """
567 if getHostOs() == 'win':
568 if dKeywordArgs.get('creationflags', 0) == 0:
569 dKeywordArgs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP;
570 return subprocess.Popen(*aPositionalArgs, **dKeywordArgs);
571
572def processCall(*aPositionalArgs, **dKeywordArgs):
573 """
574 Wrapper around subprocess.call to deal with its absence in older
575 python versions.
576 Returns process exit code (see subprocess.poll).
577 """
578 assert dKeywordArgs.get('stdout') is None;
579 assert dKeywordArgs.get('stderr') is None;
580 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
581 oProcess = processPopenSafe(*aPositionalArgs, **dKeywordArgs);
582 return oProcess.wait();
583
584def processOutputChecked(*aPositionalArgs, **dKeywordArgs):
585 """
586 Wrapper around subprocess.check_output to deal with its absense in older
587 python versions.
588 """
589 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
590 oProcess = processPopenSafe(stdout=subprocess.PIPE, *aPositionalArgs, **dKeywordArgs);
591
592 sOutput, _ = oProcess.communicate();
593 iExitCode = oProcess.poll();
594
595 if iExitCode is not 0:
596 asArgs = dKeywordArgs.get('args');
597 if asArgs is None:
598 asArgs = aPositionalArgs[0];
599 print(sOutput);
600 raise subprocess.CalledProcessError(iExitCode, asArgs);
601
602 return str(sOutput); # str() make pylint happy.
603
604g_fOldSudo = None;
605def _sudoFixArguments(aPositionalArgs, dKeywordArgs, fInitialEnv = True):
606 """
607 Adds 'sudo' (or similar) to the args parameter, whereever it is.
608 """
609
610 # Are we root?
611 fIsRoot = True;
612 try:
613 fIsRoot = os.getuid() == 0; # pylint: disable=E1101
614 except:
615 pass;
616
617 # If not, prepend sudo (non-interactive, simulate initial login).
618 if fIsRoot is not True:
619 asArgs = dKeywordArgs.get('args');
620 if asArgs is None:
621 asArgs = aPositionalArgs[0];
622
623 # Detect old sudo.
624 global g_fOldSudo;
625 if g_fOldSudo is None:
626 try:
627 sVersion = processOutputChecked(['sudo', '-V']);
628 except:
629 sVersion = '1.7.0';
630 sVersion = sVersion.strip().split('\n')[0];
631 sVersion = sVersion.replace('Sudo version', '').strip();
632 g_fOldSudo = len(sVersion) >= 4 \
633 and sVersion[0] == '1' \
634 and sVersion[1] == '.' \
635 and sVersion[2] <= '6' \
636 and sVersion[3] == '.';
637
638 asArgs.insert(0, 'sudo');
639 if not g_fOldSudo:
640 asArgs.insert(1, '-n');
641 if fInitialEnv and not g_fOldSudo:
642 asArgs.insert(1, '-i');
643
644 # paranoia...
645 if dKeywordArgs.get('args') is not None:
646 dKeywordArgs['args'] = asArgs;
647 else:
648 aPositionalArgs = (asArgs,) + aPositionalArgs[1:];
649 return None;
650
651
652def sudoProcessCall(*aPositionalArgs, **dKeywordArgs):
653 """
654 sudo (or similar) + subprocess.call
655 """
656 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
657 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
658 return processCall(*aPositionalArgs, **dKeywordArgs);
659
660def sudoProcessOutputChecked(*aPositionalArgs, **dKeywordArgs):
661 """
662 sudo (or similar) + subprocess.check_output.
663 """
664 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
665 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
666 return processOutputChecked(*aPositionalArgs, **dKeywordArgs);
667
668def sudoProcessOutputCheckedNoI(*aPositionalArgs, **dKeywordArgs):
669 """
670 sudo (or similar) + subprocess.check_output, except '-i' isn't used.
671 """
672 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
673 _sudoFixArguments(aPositionalArgs, dKeywordArgs, False);
674 return processOutputChecked(*aPositionalArgs, **dKeywordArgs);
675
676def sudoProcessPopen(*aPositionalArgs, **dKeywordArgs):
677 """
678 sudo (or similar) + processPopenSafe.
679 """
680 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
681 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
682 return processPopenSafe(*aPositionalArgs, **dKeywordArgs);
683
684
685#
686# Generic process stuff.
687#
688
689def processInterrupt(uPid):
690 """
691 Sends a SIGINT or equivalent to interrupt the specified process.
692 Returns True on success, False on failure.
693
694 On Windows hosts this may not work unless the process happens to be a
695 process group leader.
696 """
697 if sys.platform == 'win32':
698 try:
699 win32console.GenerateConsoleCtrlEvent(win32con.CTRL_BREAK_EVENT, uPid); # pylint: disable=no-member
700 fRc = True;
701 except:
702 fRc = False;
703 else:
704 try:
705 os.kill(uPid, signal.SIGINT);
706 fRc = True;
707 except:
708 fRc = False;
709 return fRc;
710
711def sendUserSignal1(uPid):
712 """
713 Sends a SIGUSR1 or equivalent to nudge the process into shutting down
714 (VBoxSVC) or something.
715 Returns True on success, False on failure or if not supported (win).
716
717 On Windows hosts this may not work unless the process happens to be a
718 process group leader.
719 """
720 if sys.platform == 'win32':
721 fRc = False;
722 else:
723 try:
724 os.kill(uPid, signal.SIGUSR1); # pylint: disable=E1101
725 fRc = True;
726 except:
727 fRc = False;
728 return fRc;
729
730def processTerminate(uPid):
731 """
732 Terminates the process in a nice manner (SIGTERM or equivalent).
733 Returns True on success, False on failure.
734 """
735 fRc = False;
736 if sys.platform == 'win32':
737 try:
738 hProcess = win32api.OpenProcess(win32con.PROCESS_TERMINATE, False, uPid); # pylint: disable=no-member
739 except:
740 pass;
741 else:
742 try:
743 win32process.TerminateProcess(hProcess, 0x40010004); # DBG_TERMINATE_PROCESS # pylint: disable=no-member
744 fRc = True;
745 except:
746 pass;
747 hProcess.Close(); #win32api.CloseHandle(hProcess)
748 else:
749 try:
750 os.kill(uPid, signal.SIGTERM);
751 fRc = True;
752 except:
753 pass;
754 return fRc;
755
756def processKill(uPid):
757 """
758 Terminates the process with extreme prejudice (SIGKILL).
759 Returns True on success, False on failure.
760 """
761 if sys.platform == 'win32':
762 fRc = processTerminate(uPid);
763 else:
764 try:
765 os.kill(uPid, signal.SIGKILL); # pylint: disable=E1101
766 fRc = True;
767 except:
768 fRc = False;
769 return fRc;
770
771def processKillWithNameCheck(uPid, sName):
772 """
773 Like processKill(), but checks if the process name matches before killing
774 it. This is intended for killing using potentially stale pid values.
775
776 Returns True on success, False on failure.
777 """
778
779 if processCheckPidAndName(uPid, sName) is not True:
780 return False;
781 return processKill(uPid);
782
783
784def processExists(uPid):
785 """
786 Checks if the specified process exits.
787 This will only work if we can signal/open the process.
788
789 Returns True if it positively exists, False otherwise.
790 """
791 if sys.platform == 'win32':
792 fRc = False;
793 # We try open the process for waiting since this is generally only forbidden in a very few cases.
794 try:
795 hProcess = win32api.OpenProcess(win32con.SYNCHRONIZE, False, uPid); # pylint: disable=no-member
796 except pywintypes.error as oXcpt: # pylint: disable=no-member
797 if oXcpt.winerror == winerror.ERROR_ACCESS_DENIED:
798 fRc = True;
799 except Exception as oXcpt:
800 pass;
801 else:
802 hProcess.Close();
803 fRc = True;
804 else:
805 try:
806 os.kill(uPid, 0);
807 fRc = True;
808 except: ## @todo check error code.
809 fRc = False;
810 return fRc;
811
812def processCheckPidAndName(uPid, sName):
813 """
814 Checks if a process PID and NAME matches.
815 """
816 fRc = processExists(uPid);
817 if fRc is not True:
818 return False;
819
820 if sys.platform == 'win32':
821 try:
822 from win32com.client import GetObject; # pylint: disable=F0401
823 oWmi = GetObject('winmgmts:');
824 aoProcesses = oWmi.InstancesOf('Win32_Process');
825 for oProcess in aoProcesses:
826 if long(oProcess.Properties_("ProcessId").Value) == uPid:
827 sCurName = oProcess.Properties_("Name").Value;
828 #reporter.log2('uPid=%s sName=%s sCurName=%s' % (uPid, sName, sCurName));
829 sName = sName.lower();
830 sCurName = sCurName.lower();
831 if os.path.basename(sName) == sName:
832 sCurName = os.path.basename(sCurName);
833
834 if sCurName == sName \
835 or sCurName + '.exe' == sName \
836 or sCurName == sName + '.exe':
837 fRc = True;
838 break;
839 except:
840 #reporter.logXcpt('uPid=%s sName=%s' % (uPid, sName));
841 pass;
842 else:
843 if sys.platform in ('linux2', ):
844 asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname='];
845 elif sys.platform in ('sunos5',):
846 asPsCmd = ['/usr/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname='];
847 elif sys.platform in ('darwin',):
848 asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'ucomm='];
849 else:
850 asPsCmd = None;
851
852 if asPsCmd is not None:
853 try:
854 oPs = subprocess.Popen(asPsCmd, stdout=subprocess.PIPE);
855 sCurName = oPs.communicate()[0];
856 iExitCode = oPs.wait();
857 except:
858 #reporter.logXcpt();
859 return False;
860
861 # ps fails with non-zero exit code if the pid wasn't found.
862 if iExitCode is not 0:
863 return False;
864 if sCurName is None:
865 return False;
866 sCurName = sCurName.strip();
867 if not sCurName:
868 return False;
869
870 if os.path.basename(sName) == sName:
871 sCurName = os.path.basename(sCurName);
872 elif os.path.basename(sCurName) == sCurName:
873 sName = os.path.basename(sName);
874
875 if sCurName != sName:
876 return False;
877
878 fRc = True;
879 return fRc;
880
881def processGetInfo(uPid, fSudo = False):
882 """
883 Tries to acquire state information of the given process.
884
885 Returns a string with the information on success or None on failure or
886 if the host is not supported.
887
888 Note that the format of the information is host system dependent and will
889 likely differ much between different hosts.
890 """
891 fRc = processExists(uPid);
892 if fRc is not True:
893 return None;
894
895 sHostOs = getHostOs();
896 if sHostOs in [ 'linux',]:
897 sGdb = '/usr/bin/gdb';
898 if not os.path.isfile(sGdb): sGdb = '/usr/local/bin/gdb';
899 if not os.path.isfile(sGdb): sGdb = 'gdb';
900 aasCmd = [
901 [ sGdb, '-batch',
902 '-ex', 'set pagination off',
903 '-ex', 'thread apply all bt',
904 '-ex', 'info proc mapping',
905 '-ex', 'info sharedlibrary',
906 '-p', '%u' % (uPid,), ],
907 ];
908 elif sHostOs == 'darwin':
909 # LLDB doesn't work in batch mode when attaching to a process, at least
910 # with macOS Sierra (10.12). GDB might not be installed. Use the sample
911 # tool instead with a 1 second duration and 1000ms sampling interval to
912 # get one stack trace. For the process mappings use vmmap.
913 aasCmd = [
914 [ '/usr/bin/sample', '-mayDie', '%u' % (uPid,), '1', '1000', ],
915 [ '/usr/bin/vmmap', '%u' % (uPid,), ],
916 ];
917 elif sHostOs == 'solaris':
918 aasCmd = [
919 [ '/usr/bin/pstack', '%u' % (uPid,), ],
920 [ '/usr/bin/pmap', '%u' % (uPid,), ],
921 ];
922 else:
923 aasCmd = [];
924
925 sInfo = '';
926 for asCmd in aasCmd:
927 try:
928 if fSudo:
929 sThisInfo = sudoProcessOutputChecked(asCmd);
930 else:
931 sThisInfo = processOutputChecked(asCmd);
932 if sThisInfo is not None:
933 sInfo += sThisInfo;
934 except:
935 pass;
936 if not sInfo:
937 sInfo = None;
938
939 return sInfo;
940
941
942class ProcessInfo(object):
943 """Process info."""
944 def __init__(self, iPid):
945 self.iPid = iPid;
946 self.iParentPid = None;
947 self.sImage = None;
948 self.sName = None;
949 self.asArgs = None;
950 self.sCwd = None;
951 self.iGid = None;
952 self.iUid = None;
953 self.iProcGroup = None;
954 self.iSessionId = None;
955
956 def loadAll(self):
957 """Load all the info."""
958 sOs = getHostOs();
959 if sOs == 'linux':
960 sProc = '/proc/%s/' % (self.iPid,);
961 if self.sImage is None: self.sImage = noxcptReadLink(sProc + 'exe', None);
962 if self.sCwd is None: self.sCwd = noxcptReadLink(sProc + 'cwd', None);
963 if self.asArgs is None: self.asArgs = noxcptReadFile(sProc + 'cmdline', '').split('\x00');
964 #elif sOs == 'solaris': - doesn't work for root processes, suid proces, and other stuff.
965 # sProc = '/proc/%s/' % (self.iPid,);
966 # if self.sImage is None: self.sImage = noxcptReadLink(sProc + 'path/a.out', None);
967 # if self.sCwd is None: self.sCwd = noxcptReadLink(sProc + 'path/cwd', None);
968 else:
969 pass;
970 if self.sName is None and self.sImage is not None:
971 self.sName = self.sImage;
972
973 def windowsGrabProcessInfo(self, oProcess):
974 """Windows specific loadAll."""
975 try: self.sName = oProcess.Properties_("Name").Value;
976 except: pass;
977 try: self.sImage = oProcess.Properties_("ExecutablePath").Value;
978 except: pass;
979 try: self.asArgs = [oProcess.Properties_("CommandLine").Value]; ## @todo split it.
980 except: pass;
981 try: self.iParentPid = oProcess.Properties_("ParentProcessId").Value;
982 except: pass;
983 try: self.iSessionId = oProcess.Properties_("SessionId").Value;
984 except: pass;
985 if self.sName is None and self.sImage is not None:
986 self.sName = self.sImage;
987
988 def getBaseImageName(self):
989 """
990 Gets the base image name if available, use the process name if not available.
991 Returns image/process base name or None.
992 """
993 sRet = self.sImage if self.sName is None else self.sName;
994 if sRet is None:
995 self.loadAll();
996 sRet = self.sImage if self.sName is None else self.sName;
997 if sRet is None:
998 if not self.asArgs:
999 return None;
1000 sRet = self.asArgs[0];
1001 if not sRet:
1002 return None;
1003 return os.path.basename(sRet);
1004
1005 def getBaseImageNameNoExeSuff(self):
1006 """
1007 Same as getBaseImageName, except any '.exe' or similar suffix is stripped.
1008 """
1009 sRet = self.getBaseImageName();
1010 if sRet is not None and len(sRet) > 4 and sRet[-4] == '.':
1011 if (sRet[-4:]).lower() in [ '.exe', '.com', '.msc', '.vbs', '.cmd', '.bat' ]:
1012 sRet = sRet[:-4];
1013 return sRet;
1014
1015
1016def processListAll(): # pylint: disable=R0914
1017 """
1018 Return a list of ProcessInfo objects for all the processes in the system
1019 that the current user can see.
1020 """
1021 asProcesses = [];
1022
1023 sOs = getHostOs();
1024 if sOs == 'win':
1025 from win32com.client import GetObject; # pylint: disable=F0401
1026 oWmi = GetObject('winmgmts:');
1027 aoProcesses = oWmi.InstancesOf('Win32_Process');
1028 for oProcess in aoProcesses:
1029 try:
1030 iPid = int(oProcess.Properties_("ProcessId").Value);
1031 except:
1032 continue;
1033 oMyInfo = ProcessInfo(iPid);
1034 oMyInfo.windowsGrabProcessInfo(oProcess);
1035 asProcesses.append(oMyInfo);
1036 return asProcesses;
1037
1038 if sOs in [ 'linux', ]: # Not solaris, ps gets more info than /proc/.
1039 try:
1040 asDirs = os.listdir('/proc');
1041 except:
1042 asDirs = [];
1043 for sDir in asDirs:
1044 if sDir.isdigit():
1045 asProcesses.append(ProcessInfo(int(sDir),));
1046 return asProcesses;
1047
1048 #
1049 # The other OSes parses the output from the 'ps' utility.
1050 #
1051 asPsCmd = [
1052 '/bin/ps', # 0
1053 '-A', # 1
1054 '-o', 'pid=', # 2,3
1055 '-o', 'ppid=', # 4,5
1056 '-o', 'pgid=', # 6,7
1057 '-o', 'sid=', # 8,9
1058 '-o', 'uid=', # 10,11
1059 '-o', 'gid=', # 12,13
1060 '-o', 'comm=' # 14,15
1061 ];
1062
1063 if sOs == 'darwin':
1064 assert asPsCmd[9] == 'sid=';
1065 asPsCmd[9] = 'sess=';
1066 elif sOs == 'solaris':
1067 asPsCmd[0] = '/usr/bin/ps';
1068
1069 try:
1070 sRaw = processOutputChecked(asPsCmd);
1071 except:
1072 return asProcesses;
1073
1074 for sLine in sRaw.split('\n'):
1075 sLine = sLine.lstrip();
1076 if len(sLine) < 7 or not sLine[0].isdigit():
1077 continue;
1078
1079 iField = 0;
1080 off = 0;
1081 aoFields = [None, None, None, None, None, None, None];
1082 while iField < 7:
1083 # Eat whitespace.
1084 while off < len(sLine) and (sLine[off] == ' ' or sLine[off] == '\t'):
1085 off += 1;
1086
1087 # Final field / EOL.
1088 if iField == 6:
1089 aoFields[6] = sLine[off:];
1090 break;
1091 if off >= len(sLine):
1092 break;
1093
1094 # Generic field parsing.
1095 offStart = off;
1096 off += 1;
1097 while off < len(sLine) and sLine[off] != ' ' and sLine[off] != '\t':
1098 off += 1;
1099 try:
1100 if iField != 3:
1101 aoFields[iField] = int(sLine[offStart:off]);
1102 else:
1103 aoFields[iField] = long(sLine[offStart:off], 16); # sess is a hex address.
1104 except:
1105 pass;
1106 iField += 1;
1107
1108 if aoFields[0] is not None:
1109 oMyInfo = ProcessInfo(aoFields[0]);
1110 oMyInfo.iParentPid = aoFields[1];
1111 oMyInfo.iProcGroup = aoFields[2];
1112 oMyInfo.iSessionId = aoFields[3];
1113 oMyInfo.iUid = aoFields[4];
1114 oMyInfo.iGid = aoFields[5];
1115 oMyInfo.sName = aoFields[6];
1116 asProcesses.append(oMyInfo);
1117
1118 return asProcesses;
1119
1120
1121def processCollectCrashInfo(uPid, fnLog, fnCrashFile):
1122 """
1123 Looks for information regarding the demise of the given process.
1124 """
1125 sOs = getHostOs();
1126 if sOs == 'darwin':
1127 #
1128 # On darwin we look for crash and diagnostic reports.
1129 #
1130 asLogDirs = [
1131 u'/Library/Logs/DiagnosticReports/',
1132 u'/Library/Logs/CrashReporter/',
1133 u'~/Library/Logs/DiagnosticReports/',
1134 u'~/Library/Logs/CrashReporter/',
1135 ];
1136 for sDir in asLogDirs:
1137 sDir = os.path.expanduser(sDir);
1138 if not os.path.isdir(sDir):
1139 continue;
1140 try:
1141 asDirEntries = os.listdir(sDir);
1142 except:
1143 continue;
1144 for sEntry in asDirEntries:
1145 # Only interested in .crash files.
1146 _, sSuff = os.path.splitext(sEntry);
1147 if sSuff != '.crash':
1148 continue;
1149
1150 # The pid can be found at the end of the first line.
1151 sFull = os.path.join(sDir, sEntry);
1152 try:
1153 oFile = open(sFull, 'r');
1154 sFirstLine = oFile.readline();
1155 oFile.close();
1156 except:
1157 continue;
1158 if len(sFirstLine) <= 4 or sFirstLine[-2] != ']':
1159 continue;
1160 offPid = len(sFirstLine) - 3;
1161 while offPid > 1 and sFirstLine[offPid - 1].isdigit():
1162 offPid -= 1;
1163 try: uReportPid = int(sFirstLine[offPid:-2]);
1164 except: continue;
1165
1166 # Does the pid we found match?
1167 if uReportPid == uPid:
1168 fnLog('Found crash report for %u: %s' % (uPid, sFull,));
1169 fnCrashFile(sFull, False);
1170 elif sOs == 'win':
1171 #
1172 # Getting WER reports would be great, however we have trouble match the
1173 # PID to those as they seems not to mention it in the brief reports.
1174 # Instead we'll just look for crash dumps in C:\CrashDumps (our custom
1175 # location - see the windows readme for the testbox script) and what
1176 # the MSDN article lists for now.
1177 #
1178 # It's been observed on Windows server 2012 that the dump files takes
1179 # the form: <processimage>.<decimal-pid>.dmp
1180 #
1181 asDmpDirs = [
1182 u'%SystemDrive%/CrashDumps/', # Testboxes.
1183 u'%LOCALAPPDATA%/CrashDumps/', # MSDN example.
1184 u'%WINDIR%/ServiceProfiles/LocalServices/', # Local and network service.
1185 u'%WINDIR%/ServiceProfiles/NetworkSerices/',
1186 u'%WINDIR%/ServiceProfiles/',
1187 u'%WINDIR%/System32/Config/SystemProfile/', # System services.
1188 ];
1189 sMatchSuffix = '.%u.dmp' % (uPid,);
1190
1191 for sDir in asDmpDirs:
1192 sDir = os.path.expandvars(sDir);
1193 if not os.path.isdir(sDir):
1194 continue;
1195 try:
1196 asDirEntries = os.listdir(sDir);
1197 except:
1198 continue;
1199 for sEntry in asDirEntries:
1200 if sEntry.endswith(sMatchSuffix):
1201 sFull = os.path.join(sDir, sEntry);
1202 fnLog('Found crash dump for %u: %s' % (uPid, sFull,));
1203 fnCrashFile(sFull, True);
1204
1205 else:
1206 pass; ## TODO
1207 return None;
1208
1209
1210#
1211# Time.
1212#
1213
1214#
1215# The following test case shows how time.time() only have ~ms resolution
1216# on Windows (tested W10) and why it therefore makes sense to try use
1217# performance counters.
1218#
1219# Note! We cannot use time.clock() as the timestamp must be portable across
1220# processes. See timeout testcase problem on win hosts (no logs).
1221#
1222#import sys;
1223#import time;
1224#from common import utils;
1225#
1226#atSeries = [];
1227#for i in xrange(1,160):
1228# if i == 159: time.sleep(10);
1229# atSeries.append((utils.timestampNano(), long(time.clock() * 1000000000), long(time.time() * 1000000000)));
1230#
1231#tPrev = atSeries[0]
1232#for tCur in atSeries:
1233# print 't1=%+22u, %u' % (tCur[0], tCur[0] - tPrev[0]);
1234# print 't2=%+22u, %u' % (tCur[1], tCur[1] - tPrev[1]);
1235# print 't3=%+22u, %u' % (tCur[2], tCur[2] - tPrev[2]);
1236# print '';
1237# tPrev = tCur
1238#
1239#print 't1=%u' % (atSeries[-1][0] - atSeries[0][0]);
1240#print 't2=%u' % (atSeries[-1][1] - atSeries[0][1]);
1241#print 't3=%u' % (atSeries[-1][2] - atSeries[0][2]);
1242
1243g_fWinUseWinPerfCounter = sys.platform == 'win32';
1244g_fpWinPerfCounterFreq = None;
1245g_oFuncwinQueryPerformanceCounter = None;
1246
1247def _winInitPerfCounter():
1248 """ Initializes the use of performance counters. """
1249 global g_fWinUseWinPerfCounter, g_fpWinPerfCounterFreq, g_oFuncwinQueryPerformanceCounter
1250
1251 uFrequency = ctypes.c_ulonglong(0);
1252 if ctypes.windll.kernel32.QueryPerformanceFrequency(ctypes.byref(uFrequency)):
1253 if uFrequency.value >= 1000:
1254 #print 'uFrequency = %s' % (uFrequency,);
1255 #print 'type(uFrequency) = %s' % (type(uFrequency),);
1256 g_fpWinPerfCounterFreq = float(uFrequency.value);
1257
1258 # Check that querying the counter works too.
1259 global g_oFuncwinQueryPerformanceCounter
1260 g_oFuncwinQueryPerformanceCounter = ctypes.windll.kernel32.QueryPerformanceCounter;
1261 uCurValue = ctypes.c_ulonglong(0);
1262 if g_oFuncwinQueryPerformanceCounter(ctypes.byref(uCurValue)):
1263 if uCurValue.value > 0:
1264 return True;
1265 g_fWinUseWinPerfCounter = False;
1266 return False;
1267
1268def _winFloatTime():
1269 """ Gets floating point time on windows. """
1270 if g_fpWinPerfCounterFreq is not None or _winInitPerfCounter():
1271 uCurValue = ctypes.c_ulonglong(0);
1272 if g_oFuncwinQueryPerformanceCounter(ctypes.byref(uCurValue)):
1273 return float(uCurValue.value) / g_fpWinPerfCounterFreq;
1274 return time.time();
1275
1276
1277def timestampNano():
1278 """
1279 Gets a nanosecond timestamp.
1280 """
1281 if g_fWinUseWinPerfCounter is True:
1282 return long(_winFloatTime() * 1000000000);
1283 return long(time.time() * 1000000000);
1284
1285def timestampMilli():
1286 """
1287 Gets a millisecond timestamp.
1288 """
1289 if g_fWinUseWinPerfCounter is True:
1290 return long(_winFloatTime() * 1000);
1291 return long(time.time() * 1000);
1292
1293def timestampSecond():
1294 """
1295 Gets a second timestamp.
1296 """
1297 if g_fWinUseWinPerfCounter is True:
1298 return long(_winFloatTime());
1299 return long(time.time());
1300
1301def getTimePrefix():
1302 """
1303 Returns a timestamp prefix, typically used for logging. UTC.
1304 """
1305 try:
1306 oNow = datetime.datetime.utcnow();
1307 sTs = '%02u:%02u:%02u.%06u' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond);
1308 except:
1309 sTs = 'getTimePrefix-exception';
1310 return sTs;
1311
1312def getTimePrefixAndIsoTimestamp():
1313 """
1314 Returns current UTC as log prefix and iso timestamp.
1315 """
1316 try:
1317 oNow = datetime.datetime.utcnow();
1318 sTsPrf = '%02u:%02u:%02u.%06u' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond);
1319 sTsIso = formatIsoTimestamp(oNow);
1320 except:
1321 sTsPrf = sTsIso = 'getTimePrefix-exception';
1322 return (sTsPrf, sTsIso);
1323
1324def formatIsoTimestamp(oNow):
1325 """Formats the datetime object as an ISO timestamp."""
1326 assert oNow.tzinfo is None;
1327 sTs = '%s.%09uZ' % (oNow.strftime('%Y-%m-%dT%H:%M:%S'), oNow.microsecond * 1000);
1328 return sTs;
1329
1330def getIsoTimestamp():
1331 """Returns the current UTC timestamp as a string."""
1332 return formatIsoTimestamp(datetime.datetime.utcnow());
1333
1334
1335def getLocalHourOfWeek():
1336 """ Local hour of week (0 based). """
1337 oNow = datetime.datetime.now();
1338 return (oNow.isoweekday() - 1) * 24 + oNow.hour;
1339
1340
1341def formatIntervalSeconds(cSeconds):
1342 """ Format a seconds interval into a nice 01h 00m 22s string """
1343 # Two simple special cases.
1344 if cSeconds < 60:
1345 return '%ss' % (cSeconds,);
1346 if cSeconds < 3600:
1347 cMins = cSeconds / 60;
1348 cSecs = cSeconds % 60;
1349 if cSecs == 0:
1350 return '%sm' % (cMins,);
1351 return '%sm %ss' % (cMins, cSecs,);
1352
1353 # Generic and a bit slower.
1354 cDays = cSeconds / 86400;
1355 cSeconds %= 86400;
1356 cHours = cSeconds / 3600;
1357 cSeconds %= 3600;
1358 cMins = cSeconds / 60;
1359 cSecs = cSeconds % 60;
1360 sRet = '';
1361 if cDays > 0:
1362 sRet = '%sd ' % (cDays,);
1363 if cHours > 0:
1364 sRet += '%sh ' % (cHours,);
1365 if cMins > 0:
1366 sRet += '%sm ' % (cMins,);
1367 if cSecs > 0:
1368 sRet += '%ss ' % (cSecs,);
1369 assert sRet; assert sRet[-1] == ' ';
1370 return sRet[:-1];
1371
1372def formatIntervalSeconds2(oSeconds):
1373 """
1374 Flexible input version of formatIntervalSeconds for use in WUI forms where
1375 data is usually already string form.
1376 """
1377 if isinstance(oSeconds, (int, long)):
1378 return formatIntervalSeconds(oSeconds);
1379 if not isString(oSeconds):
1380 try:
1381 lSeconds = long(oSeconds);
1382 except:
1383 pass;
1384 else:
1385 if lSeconds >= 0:
1386 return formatIntervalSeconds2(lSeconds);
1387 return oSeconds;
1388
1389def parseIntervalSeconds(sString):
1390 """
1391 Reverse of formatIntervalSeconds.
1392
1393 Returns (cSeconds, sError), where sError is None on success.
1394 """
1395
1396 # We might given non-strings, just return them without any fuss.
1397 if not isString(sString):
1398 if isinstance(sString, (int, long)) or sString is None:
1399 return (sString, None);
1400 ## @todo time/date objects?
1401 return (int(sString), None);
1402
1403 # Strip it and make sure it's not empty.
1404 sString = sString.strip();
1405 if not sString:
1406 return (0, 'Empty interval string.');
1407
1408 #
1409 # Split up the input into a list of 'valueN, unitN, ...'.
1410 #
1411 # Don't want to spend too much time trying to make re.split do exactly what
1412 # I need here, so please forgive the extra pass I'm making here.
1413 #
1414 asRawParts = re.split(r'\s*([0-9]+)\s*([^0-9,;]*)[\s,;]*', sString);
1415 asParts = [];
1416 for sPart in asRawParts:
1417 sPart = sPart.strip();
1418 if sPart:
1419 asParts.append(sPart);
1420 if not asParts:
1421 return (0, 'Empty interval string or something?');
1422
1423 #
1424 # Process them one or two at the time.
1425 #
1426 cSeconds = 0;
1427 asErrors = [];
1428 i = 0;
1429 while i < len(asParts):
1430 sNumber = asParts[i];
1431 i += 1;
1432 if sNumber.isdigit():
1433 iNumber = int(sNumber);
1434
1435 sUnit = 's';
1436 if i < len(asParts) and not asParts[i].isdigit():
1437 sUnit = asParts[i];
1438 i += 1;
1439
1440 sUnitLower = sUnit.lower();
1441 if sUnitLower in [ 's', 'se', 'sec', 'second', 'seconds' ]:
1442 pass;
1443 elif sUnitLower in [ 'm', 'mi', 'min', 'minute', 'minutes' ]:
1444 iNumber *= 60;
1445 elif sUnitLower in [ 'h', 'ho', 'hou', 'hour', 'hours' ]:
1446 iNumber *= 3600;
1447 elif sUnitLower in [ 'd', 'da', 'day', 'days' ]:
1448 iNumber *= 86400;
1449 elif sUnitLower in [ 'w', 'week', 'weeks' ]:
1450 iNumber *= 7 * 86400;
1451 else:
1452 asErrors.append('Unknown unit "%s".' % (sUnit,));
1453 cSeconds += iNumber;
1454 else:
1455 asErrors.append('Bad number "%s".' % (sNumber,));
1456 return (cSeconds, None if not asErrors else ' '.join(asErrors));
1457
1458def formatIntervalHours(cHours):
1459 """ Format a hours interval into a nice 1w 2d 1h string. """
1460 # Simple special cases.
1461 if cHours < 24:
1462 return '%sh' % (cHours,);
1463
1464 # Generic and a bit slower.
1465 cWeeks = cHours / (7 * 24);
1466 cHours %= 7 * 24;
1467 cDays = cHours / 24;
1468 cHours %= 24;
1469 sRet = '';
1470 if cWeeks > 0:
1471 sRet = '%sw ' % (cWeeks,);
1472 if cDays > 0:
1473 sRet = '%sd ' % (cDays,);
1474 if cHours > 0:
1475 sRet += '%sh ' % (cHours,);
1476 assert sRet; assert sRet[-1] == ' ';
1477 return sRet[:-1];
1478
1479def parseIntervalHours(sString):
1480 """
1481 Reverse of formatIntervalHours.
1482
1483 Returns (cHours, sError), where sError is None on success.
1484 """
1485
1486 # We might given non-strings, just return them without any fuss.
1487 if not isString(sString):
1488 if isinstance(sString, (int, long)) or sString is None:
1489 return (sString, None);
1490 ## @todo time/date objects?
1491 return (int(sString), None);
1492
1493 # Strip it and make sure it's not empty.
1494 sString = sString.strip();
1495 if not sString:
1496 return (0, 'Empty interval string.');
1497
1498 #
1499 # Split up the input into a list of 'valueN, unitN, ...'.
1500 #
1501 # Don't want to spend too much time trying to make re.split do exactly what
1502 # I need here, so please forgive the extra pass I'm making here.
1503 #
1504 asRawParts = re.split(r'\s*([0-9]+)\s*([^0-9,;]*)[\s,;]*', sString);
1505 asParts = [];
1506 for sPart in asRawParts:
1507 sPart = sPart.strip();
1508 if sPart:
1509 asParts.append(sPart);
1510 if not asParts:
1511 return (0, 'Empty interval string or something?');
1512
1513 #
1514 # Process them one or two at the time.
1515 #
1516 cHours = 0;
1517 asErrors = [];
1518 i = 0;
1519 while i < len(asParts):
1520 sNumber = asParts[i];
1521 i += 1;
1522 if sNumber.isdigit():
1523 iNumber = int(sNumber);
1524
1525 sUnit = 'h';
1526 if i < len(asParts) and not asParts[i].isdigit():
1527 sUnit = asParts[i];
1528 i += 1;
1529
1530 sUnitLower = sUnit.lower();
1531 if sUnitLower in [ 'h', 'ho', 'hou', 'hour', 'hours' ]:
1532 pass;
1533 elif sUnitLower in [ 'd', 'da', 'day', 'days' ]:
1534 iNumber *= 24;
1535 elif sUnitLower in [ 'w', 'week', 'weeks' ]:
1536 iNumber *= 7 * 24;
1537 else:
1538 asErrors.append('Unknown unit "%s".' % (sUnit,));
1539 cHours += iNumber;
1540 else:
1541 asErrors.append('Bad number "%s".' % (sNumber,));
1542 return (cHours, None if not asErrors else ' '.join(asErrors));
1543
1544
1545#
1546# Introspection.
1547#
1548
1549def getCallerName(oFrame=None, iFrame=2):
1550 """
1551 Returns the name of the caller's caller.
1552 """
1553 if oFrame is None:
1554 try:
1555 raise Exception();
1556 except:
1557 oFrame = sys.exc_info()[2].tb_frame.f_back;
1558 while iFrame > 1:
1559 if oFrame is not None:
1560 oFrame = oFrame.f_back;
1561 iFrame = iFrame - 1;
1562 if oFrame is not None:
1563 sName = '%s:%u' % (oFrame.f_code.co_name, oFrame.f_lineno);
1564 return sName;
1565 return "unknown";
1566
1567
1568def getXcptInfo(cFrames = 1):
1569 """
1570 Gets text detailing the exception. (Good for logging.)
1571 Returns list of info strings.
1572 """
1573
1574 #
1575 # Try get exception info.
1576 #
1577 try:
1578 oType, oValue, oTraceback = sys.exc_info();
1579 except:
1580 oType = oValue = oTraceback = None;
1581 if oType is not None:
1582
1583 #
1584 # Try format the info
1585 #
1586 asRet = [];
1587 try:
1588 try:
1589 asRet = asRet + traceback.format_exception_only(oType, oValue);
1590 asTraceBack = traceback.format_tb(oTraceback);
1591 if cFrames is not None and cFrames <= 1:
1592 asRet.append(asTraceBack[-1]);
1593 else:
1594 asRet.append('Traceback:')
1595 for iFrame in range(min(cFrames, len(asTraceBack))):
1596 asRet.append(asTraceBack[-iFrame - 1]);
1597 asRet.append('Stack:')
1598 asRet = asRet + traceback.format_stack(oTraceback.tb_frame.f_back, cFrames);
1599 except:
1600 asRet.append('internal-error: Hit exception #2! %s' % (traceback.format_exc(),));
1601
1602 if not asRet:
1603 asRet.append('No exception info...');
1604 except:
1605 asRet.append('internal-error: Hit exception! %s' % (traceback.format_exc(),));
1606 else:
1607 asRet = ['Couldn\'t find exception traceback.'];
1608 return asRet;
1609
1610
1611def getObjectTypeName(oObject):
1612 """
1613 Get the type name of the given object.
1614 """
1615 if oObject is None:
1616 return 'None';
1617
1618 # Get the type object.
1619 try:
1620 oType = type(oObject);
1621 except:
1622 return 'type-throws-exception';
1623
1624 # Python 2.x only: Handle old-style object wrappers.
1625 if sys.version_info[0] < 3:
1626 try:
1627 from types import InstanceType;
1628 if oType == InstanceType:
1629 oType = oObject.__class__;
1630 except:
1631 pass;
1632
1633 # Get the name.
1634 try:
1635 return oType.__name__;
1636 except:
1637 return '__type__-throws-exception';
1638
1639
1640#
1641# TestSuite stuff.
1642#
1643
1644def isRunningFromCheckout(cScriptDepth = 1):
1645 """
1646 Checks if we're running from the SVN checkout or not.
1647 """
1648
1649 try:
1650 sFile = __file__;
1651 cScriptDepth = 1;
1652 except:
1653 sFile = sys.argv[0];
1654
1655 sDir = os.path.abspath(sFile);
1656 while cScriptDepth >= 0:
1657 sDir = os.path.dirname(sDir);
1658 if os.path.exists(os.path.join(sDir, 'Makefile.kmk')) \
1659 or os.path.exists(os.path.join(sDir, 'Makefile.kup')):
1660 return True;
1661 cScriptDepth -= 1;
1662
1663 return False;
1664
1665
1666#
1667# Bourne shell argument fun.
1668#
1669
1670
1671def argsSplit(sCmdLine):
1672 """
1673 Given a bourne shell command line invocation, split it up into arguments
1674 assuming IFS is space.
1675 Returns None on syntax error.
1676 """
1677 ## @todo bourne shell argument parsing!
1678 return sCmdLine.split(' ');
1679
1680def argsGetFirst(sCmdLine):
1681 """
1682 Given a bourne shell command line invocation, get return the first argument
1683 assuming IFS is space.
1684 Returns None on invalid syntax, otherwise the parsed and unescaped argv[0] string.
1685 """
1686 asArgs = argsSplit(sCmdLine);
1687 if not asArgs:
1688 return None;
1689
1690 return asArgs[0];
1691
1692#
1693# String helpers.
1694#
1695
1696def stricmp(sFirst, sSecond):
1697 """
1698 Compares to strings in an case insensitive fashion.
1699
1700 Python doesn't seem to have any way of doing the correctly, so this is just
1701 an approximation using lower.
1702 """
1703 if sFirst == sSecond:
1704 return 0;
1705 sLower1 = sFirst.lower();
1706 sLower2 = sSecond.lower();
1707 if sLower1 == sLower2:
1708 return 0;
1709 if sLower1 < sLower2:
1710 return -1;
1711 return 1;
1712
1713
1714#
1715# Misc.
1716#
1717
1718def versionCompare(sVer1, sVer2):
1719 """
1720 Compares to version strings in a fashion similar to RTStrVersionCompare.
1721 """
1722
1723 ## @todo implement me!!
1724
1725 if sVer1 == sVer2:
1726 return 0;
1727 if sVer1 < sVer2:
1728 return -1;
1729 return 1;
1730
1731
1732def formatNumber(lNum, sThousandSep = ' '):
1733 """
1734 Formats a decimal number with pretty separators.
1735 """
1736 sNum = str(lNum);
1737 sRet = sNum[-3:];
1738 off = len(sNum) - 3;
1739 while off > 0:
1740 off -= 3;
1741 sRet = sNum[(off if off >= 0 else 0):(off + 3)] + sThousandSep + sRet;
1742 return sRet;
1743
1744
1745def formatNumberNbsp(lNum):
1746 """
1747 Formats a decimal number with pretty separators.
1748 """
1749 sRet = formatNumber(lNum);
1750 return unicode(sRet).replace(' ', u'\u00a0');
1751
1752
1753def isString(oString):
1754 """
1755 Checks if the object is a string object, hiding difference between python 2 and 3.
1756
1757 Returns True if it's a string of some kind.
1758 Returns False if not.
1759 """
1760 if sys.version_info[0] >= 3:
1761 return isinstance(oString, str);
1762 return isinstance(oString, basestring);
1763
1764
1765def hasNonAsciiCharacters(sText):
1766 """
1767 Returns True is specified string has non-ASCII characters, False if ASCII only.
1768 """
1769 sTmp = unicode(sText, errors='ignore') if isinstance(sText, str) else sText;
1770 return not all(ord(ch) < 128 for ch in sTmp);
1771
1772
1773def chmodPlusX(sFile):
1774 """
1775 Makes the specified file or directory executable.
1776 Returns success indicator, no exceptions.
1777
1778 Note! Symbolic links are followed and the target will be changed.
1779 """
1780 try:
1781 oStat = os.stat(sFile);
1782 except:
1783 return False;
1784 try:
1785 os.chmod(sFile, oStat.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH);
1786 except:
1787 return False;
1788 return True;
1789
1790
1791def unpackZipFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None):
1792 # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string]
1793 """
1794 Worker for unpackFile that deals with ZIP files, same function signature.
1795 """
1796 import zipfile
1797 if fnError is None:
1798 fnError = fnLog;
1799
1800 fnLog('Unzipping "%s" to "%s"...' % (sArchive, sDstDir));
1801
1802 # Open it.
1803 try: oZipFile = zipfile.ZipFile(sArchive, 'r')
1804 except Exception as oXcpt:
1805 fnError('Error opening "%s" for unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt,));
1806 return None;
1807
1808 # Extract all members.
1809 asMembers = [];
1810 try:
1811 for sMember in oZipFile.namelist():
1812 if fnFilter is None or fnFilter(sMember) is not False:
1813 if sMember.endswith('/'):
1814 os.makedirs(os.path.join(sDstDir, sMember.replace('/', os.path.sep)), 0x1fd); # octal: 0775 (python 3/2)
1815 else:
1816 oZipFile.extract(sMember, sDstDir);
1817 asMembers.append(os.path.join(sDstDir, sMember.replace('/', os.path.sep)));
1818 except Exception as oXcpt:
1819 fnError('Error unpacking "%s" into "%s": %s' % (sArchive, sDstDir, oXcpt));
1820 asMembers = None;
1821
1822 # close it.
1823 try: oZipFile.close();
1824 except Exception as oXcpt:
1825 fnError('Error closing "%s" after unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt));
1826 asMembers = None;
1827
1828 return asMembers;
1829
1830
1831## Set if we've replaced tarfile.copyfileobj with __mytarfilecopyfileobj already.
1832g_fTarCopyFileObjOverriddend = False;
1833
1834def __mytarfilecopyfileobj(src, dst, length = None, exception = OSError):
1835 """ tarfile.copyfileobj with different buffer size (16384 is slow on windows). """
1836 if length is None:
1837 __myshutilcopyfileobj(src, dst, g_cbGoodBufferSize);
1838 elif length > 0:
1839 cFull, cbRemainder = divmod(length, g_cbGoodBufferSize);
1840 for _ in xrange(cFull):
1841 abBuffer = src.read(g_cbGoodBufferSize);
1842 dst.write(abBuffer);
1843 if len(abBuffer) != g_cbGoodBufferSize:
1844 raise exception('unexpected end of source file');
1845 if cbRemainder > 0:
1846 abBuffer = src.read(cbRemainder);
1847 dst.write(abBuffer);
1848 if len(abBuffer) != cbRemainder:
1849 raise exception('unexpected end of source file');
1850
1851
1852def unpackTarFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None):
1853 # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string]
1854 """
1855 Worker for unpackFile that deals with tarballs, same function signature.
1856 """
1857 import shutil;
1858 import tarfile;
1859 if fnError is None:
1860 fnError = fnLog;
1861
1862 fnLog('Untarring "%s" to "%s"...' % (sArchive, sDstDir));
1863
1864 #
1865 # Default buffer sizes of 16384 bytes is causing too many syscalls on Windows.
1866 # 60%+ speedup for python 2.7 and 50%+ speedup for python 3.5, both on windows with PDBs.
1867 # 20%+ speedup for python 2.7 and 15%+ speedup for python 3.5, both on windows skipping PDBs.
1868 #
1869 if True is True:
1870 __installShUtilHacks(shutil);
1871 global g_fTarCopyFileObjOverriddend;
1872 if g_fTarCopyFileObjOverriddend is False:
1873 g_fTarCopyFileObjOverriddend = True;
1874 tarfile.copyfileobj = __mytarfilecopyfileobj;
1875
1876 #
1877 # Open it.
1878 #
1879 # Note! We not using 'r:*' because we cannot allow seeking compressed files!
1880 # That's how we got a 13 min unpack time for VBoxAll on windows (hardlinked pdb).
1881 #
1882 try: oTarFile = tarfile.open(sArchive, 'r|*', bufsize = g_cbGoodBufferSize);
1883 except Exception as oXcpt:
1884 fnError('Error opening "%s" for unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt,));
1885 return None;
1886
1887 # Extract all members.
1888 asMembers = [];
1889 try:
1890 for oTarInfo in oTarFile:
1891 try:
1892 if fnFilter is None or fnFilter(oTarInfo.name) is not False:
1893 if oTarInfo.islnk():
1894 # Links are trouble, especially on Windows. We must avoid the falling that will end up seeking
1895 # in the compressed tar stream. So, fall back on shutil.copy2 instead.
1896 sLinkFile = os.path.join(sDstDir, oTarInfo.name.rstrip('/').replace('/', os.path.sep));
1897 sLinkTarget = os.path.join(sDstDir, oTarInfo.linkname.rstrip('/').replace('/', os.path.sep));
1898 sParentDir = os.path.dirname(sLinkFile);
1899 try: os.unlink(sLinkFile);
1900 except: pass;
1901 if sParentDir and not os.path.exists(sParentDir):
1902 os.makedirs(sParentDir);
1903 try: os.link(sLinkTarget, sLinkFile);
1904 except: shutil.copy2(sLinkTarget, sLinkFile);
1905 else:
1906 if oTarInfo.isdir():
1907 # Just make sure the user (we) got full access to dirs. Don't bother getting it 100% right.
1908 oTarInfo.mode |= 0x1c0; # (octal: 0700)
1909 oTarFile.extract(oTarInfo, sDstDir);
1910 asMembers.append(os.path.join(sDstDir, oTarInfo.name.replace('/', os.path.sep)));
1911 except Exception as oXcpt:
1912 fnError('Error unpacking "%s" member "%s" into "%s": %s' % (sArchive, oTarInfo.name, sDstDir, oXcpt));
1913 for sAttr in [ 'name', 'linkname', 'type', 'mode', 'size', 'mtime', 'uid', 'uname', 'gid', 'gname' ]:
1914 fnError('Info: %8s=%s' % (sAttr, getattr(oTarInfo, sAttr),));
1915 for sFn in [ 'isdir', 'isfile', 'islnk', 'issym' ]:
1916 fnError('Info: %8s=%s' % (sFn, getattr(oTarInfo, sFn)(),));
1917 asMembers = None;
1918 break;
1919 except Exception as oXcpt:
1920 fnError('Error unpacking "%s" into "%s": %s' % (sArchive, sDstDir, oXcpt));
1921 asMembers = None;
1922
1923 #
1924 # Finally, close it.
1925 #
1926 try: oTarFile.close();
1927 except Exception as oXcpt:
1928 fnError('Error closing "%s" after unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt));
1929 asMembers = None;
1930
1931 return asMembers;
1932
1933
1934def unpackFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None):
1935 # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string]
1936 """
1937 Unpacks the given file if it has a know archive extension, otherwise do
1938 nothing.
1939
1940 fnLog & fnError both take a string parameter.
1941
1942 fnFilter takes a member name (string) and returns True if it's included
1943 and False if excluded.
1944
1945 Returns list of the extracted files (full path) on success.
1946 Returns empty list if not a supported archive format.
1947 Returns None on failure. Raises no exceptions.
1948 """
1949 sBaseNameLower = os.path.basename(sArchive).lower();
1950
1951 #
1952 # Zip file?
1953 #
1954 if sBaseNameLower.endswith('.zip'):
1955 return unpackZipFile(sArchive, sDstDir, fnLog, fnError, fnFilter);
1956
1957 #
1958 # Tarball?
1959 #
1960 if sBaseNameLower.endswith('.tar') \
1961 or sBaseNameLower.endswith('.tar.gz') \
1962 or sBaseNameLower.endswith('.tgz') \
1963 or sBaseNameLower.endswith('.tar.bz2'):
1964 return unpackTarFile(sArchive, sDstDir, fnLog, fnError, fnFilter);
1965
1966 #
1967 # Cannot classify it from the name, so just return that to the caller.
1968 #
1969 fnLog('Not unpacking "%s".' % (sArchive,));
1970 return [];
1971
1972
1973def getDiskUsage(sPath):
1974 """
1975 Get free space of a partition that corresponds to specified sPath in MB.
1976
1977 Returns partition free space value in MB.
1978 """
1979 if platform.system() == 'Windows':
1980 oCTypeFreeSpace = ctypes.c_ulonglong(0);
1981 ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(sPath), None, None,
1982 ctypes.pointer(oCTypeFreeSpace));
1983 cbFreeSpace = oCTypeFreeSpace.value;
1984 else:
1985 oStats = os.statvfs(sPath); # pylint: disable=E1101
1986 cbFreeSpace = long(oStats.f_frsize) * oStats.f_bfree;
1987
1988 # Convert to MB
1989 cMbFreeSpace = long(cbFreeSpace) / (1024 * 1024);
1990
1991 return cMbFreeSpace;
1992
1993
1994#
1995# Unit testing.
1996#
1997
1998# pylint: disable=C0111
1999class BuildCategoryDataTestCase(unittest.TestCase):
2000 def testIntervalSeconds(self):
2001 self.assertEqual(parseIntervalSeconds(formatIntervalSeconds(3600)), (3600, None));
2002 self.assertEqual(parseIntervalSeconds(formatIntervalSeconds(1209438593)), (1209438593, None));
2003 self.assertEqual(parseIntervalSeconds('123'), (123, None));
2004 self.assertEqual(parseIntervalSeconds(123), (123, None));
2005 self.assertEqual(parseIntervalSeconds(99999999999), (99999999999, None));
2006 self.assertEqual(parseIntervalSeconds(''), (0, 'Empty interval string.'));
2007 self.assertEqual(parseIntervalSeconds('1X2'), (3, 'Unknown unit "X".'));
2008 self.assertEqual(parseIntervalSeconds('1 Y3'), (4, 'Unknown unit "Y".'));
2009 self.assertEqual(parseIntervalSeconds('1 Z 4'), (5, 'Unknown unit "Z".'));
2010 self.assertEqual(parseIntervalSeconds('1 hour 2m 5second'), (3725, None));
2011 self.assertEqual(parseIntervalSeconds('1 hour,2m ; 5second'), (3725, None));
2012
2013 def testHasNonAsciiChars(self):
2014 self.assertEqual(hasNonAsciiCharacters(''), False);
2015 self.assertEqual(hasNonAsciiCharacters('asdfgebASDFKJ@#$)(!@#UNASDFKHB*&$%&)@#(!)@(#!(#$&*#$&%*Y@#$IQWN---00;'), False);
2016 self.assertEqual(hasNonAsciiCharacters(u'12039889y!@#$%^&*()0-0asjdkfhoiuyweasdfASDFnvV'), False);
2017 self.assertEqual(hasNonAsciiCharacters(u'\u0079'), False);
2018 self.assertEqual(hasNonAsciiCharacters(u'\u0080'), True);
2019 self.assertEqual(hasNonAsciiCharacters(u'\u0081 \u0100'), True);
2020
2021if __name__ == '__main__':
2022 unittest.main();
2023 # not reached.
2024
Note: See TracBrowser for help on using the repository browser.

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