VirtualBox

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

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

common/utils.py: Added getObjectTypeName method.

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