VirtualBox

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

最後變更 在這個檔案從70333是 69578,由 vboxsync 提交於 7 年 前

better processExists() implementations for windows.

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

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