summaryrefslogtreecommitdiff
path: root/Tools/Scripts/webkitpy/common/system
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@digia.com>2013-09-13 12:51:20 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-09-19 20:50:05 +0200
commitd441d6f39bb846989d95bcf5caf387b42414718d (patch)
treee367e64a75991c554930278175d403c072de6bb8 /Tools/Scripts/webkitpy/common/system
parent0060b2994c07842f4c59de64b5e3e430525c4b90 (diff)
downloadqtwebkit-d441d6f39bb846989d95bcf5caf387b42414718d.tar.gz
Import Qt5x2 branch of QtWebkit for Qt 5.2
Importing a new snapshot of webkit. Change-Id: I2d01ad12cdc8af8cb015387641120a9d7ea5f10c Reviewed-by: Allan Sandfeld Jensen <allan.jensen@digia.com>
Diffstat (limited to 'Tools/Scripts/webkitpy/common/system')
-rw-r--r--[-rwxr-xr-x]Tools/Scripts/webkitpy/common/system/autoinstall.py55
-rw-r--r--Tools/Scripts/webkitpy/common/system/crashlogs.py41
-rw-r--r--Tools/Scripts/webkitpy/common/system/crashlogs_unittest.py222
-rw-r--r--Tools/Scripts/webkitpy/common/system/environment_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/common/system/executive.py109
-rw-r--r--Tools/Scripts/webkitpy/common/system/executive_mock.py11
-rw-r--r--Tools/Scripts/webkitpy/common/system/executive_unittest.py41
-rw-r--r--Tools/Scripts/webkitpy/common/system/file_lock.py1
-rw-r--r--Tools/Scripts/webkitpy/common/system/file_lock_integrationtest.py3
-rw-r--r--Tools/Scripts/webkitpy/common/system/file_lock_mock.py5
-rw-r--r--Tools/Scripts/webkitpy/common/system/filesystem_mock.py4
-rw-r--r--Tools/Scripts/webkitpy/common/system/filesystem_mock_unittest.py6
-rw-r--r--Tools/Scripts/webkitpy/common/system/filesystem_unittest.py14
-rw-r--r--Tools/Scripts/webkitpy/common/system/logtesting.py2
-rw-r--r--Tools/Scripts/webkitpy/common/system/logutils_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/common/system/outputcapture.py15
-rw-r--r--Tools/Scripts/webkitpy/common/system/outputcapture_unittest.py4
-rw-r--r--Tools/Scripts/webkitpy/common/system/outputtee_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/common/system/path_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/common/system/platforminfo.py2
-rw-r--r--Tools/Scripts/webkitpy/common/system/platforminfo_unittest.py16
-rw-r--r--Tools/Scripts/webkitpy/common/system/profiler.py143
-rw-r--r--Tools/Scripts/webkitpy/common/system/profiler_unittest.py28
-rw-r--r--Tools/Scripts/webkitpy/common/system/stack_utils_unittest.py6
-rw-r--r--Tools/Scripts/webkitpy/common/system/user_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/common/system/workspace.py2
-rw-r--r--Tools/Scripts/webkitpy/common/system/workspace_mock.py2
-rw-r--r--Tools/Scripts/webkitpy/common/system/workspace_unittest.py6
-rw-r--r--Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py6
29 files changed, 597 insertions, 157 deletions
diff --git a/Tools/Scripts/webkitpy/common/system/autoinstall.py b/Tools/Scripts/webkitpy/common/system/autoinstall.py
index 9d1f8cb2f..2e15887bb 100755..100644
--- a/Tools/Scripts/webkitpy/common/system/autoinstall.py
+++ b/Tools/Scripts/webkitpy/common/system/autoinstall.py
@@ -35,10 +35,11 @@ import codecs
import logging
import os
import shutil
+import stat
import sys
import tarfile
import tempfile
-import urllib
+import urllib2
import urlparse
import zipfile
@@ -173,7 +174,7 @@ class AutoInstaller(object):
return scratch_dir
def _url_downloaded_path(self, target_name):
- return os.path.join(self._target_dir, ".%s.url" % target_name)
+ return os.path.join(self._target_dir, ".%s.url" % target_name.replace('/', '_'))
def _is_downloaded(self, target_name, url):
version_path = self._url_downloaded_path(target_name)
@@ -283,17 +284,27 @@ class AutoInstaller(object):
return new_path
def _download_to_stream(self, url, stream):
- try:
- netstream = urllib.urlopen(url)
- except IOError, err:
- # Append existing Error message to new Error.
- message = ('Could not download Python modules from URL "%s".\n'
- " Make sure you are connected to the internet.\n"
- " You must be connected to the internet when "
- "downloading needed modules for the first time.\n"
- " --> Inner message: %s"
- % (url, err))
- raise IOError(message)
+ failures = 0
+ while True:
+ try:
+ netstream = urllib2.urlopen(url)
+ break
+ except IOError, err:
+ # Try multiple times
+ if failures < 5:
+ _log.warning("Failed to download %s, %s retrying" % (
+ url, err))
+ failures += 1
+ continue
+
+ # Append existing Error message to new Error.
+ message = ('Could not download Python modules from URL "%s".\n'
+ " Make sure you are connected to the internet.\n"
+ " You must be connected to the internet when "
+ "downloading needed modules for the first time.\n"
+ " --> Inner message: %s"
+ % (url, err))
+ raise IOError(message)
code = 200
if hasattr(netstream, "getcode"):
code = netstream.getcode()
@@ -319,8 +330,7 @@ class AutoInstaller(object):
return target_path
- def _install(self, scratch_dir, package_name, target_path, url,
- url_subpath):
+ def _install(self, scratch_dir, package_name, target_path, url, url_subpath, files_to_remove):
"""Install a python package from an URL.
This internal method overwrites the target path if the target
@@ -335,6 +345,13 @@ class AutoInstaller(object):
else:
source_path = os.path.join(path, url_subpath)
+ for filename in files_to_remove:
+ path = os.path.join(source_path, filename.replace('/', os.sep))
+ if os.path.exists(path):
+ # Pre-emptively change the permissions to #0777 to try and work around win32 permissions issues.
+ os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+ os.remove(path)
+
if os.path.exists(target_path):
if os.path.isdir(target_path):
shutil.rmtree(target_path, ignore_errors=True)
@@ -354,7 +371,7 @@ class AutoInstaller(object):
self._record_url_downloaded(package_name, url)
def install(self, url, should_refresh=False, target_name=None,
- url_subpath=None):
+ url_subpath=None, files_to_remove=None):
"""Install a python package from an URL.
Args:
@@ -382,10 +399,11 @@ class AutoInstaller(object):
url_subpath = os.path.normpath(url_subpath)
target_name = os.path.basename(url_subpath)
- target_path = os.path.join(self._target_dir, target_name)
+ target_path = os.path.join(self._target_dir, target_name.replace('/', os.sep))
if not should_refresh and self._is_downloaded(target_name, url):
return False
+ files_to_remove = files_to_remove or []
package_name = target_name.replace(os.sep, '.')
_log.info("Auto-installing package: %s" % package_name)
@@ -399,7 +417,8 @@ class AutoInstaller(object):
target_path=target_path,
scratch_dir=scratch_dir,
url=url,
- url_subpath=url_subpath)
+ url_subpath=url_subpath,
+ files_to_remove=files_to_remove)
except Exception, err:
# Append existing Error message to new Error.
message = ("Error auto-installing the %s package to:\n"
diff --git a/Tools/Scripts/webkitpy/common/system/crashlogs.py b/Tools/Scripts/webkitpy/common/system/crashlogs.py
index 270ca81ed..7ebe52241 100644
--- a/Tools/Scripts/webkitpy/common/system/crashlogs.py
+++ b/Tools/Scripts/webkitpy/common/system/crashlogs.py
@@ -26,16 +26,23 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+import codecs
import re
class CrashLogs(object):
- def __init__(self, host):
+
+ PID_LINE_REGEX = re.compile(r'\s+Global\s+PID:\s+\[(?P<pid>\d+)\]')
+
+ def __init__(self, host, results_directory=None):
self._host = host
+ self._results_directory = results_directory
def find_newest_log(self, process_name, pid=None, include_errors=False, newer_than=None):
if self._host.platform.is_mac():
return self._find_newest_log_darwin(process_name, pid, include_errors, newer_than)
+ elif self._host.platform.is_win():
+ return self._find_newest_log_win(process_name, pid, include_errors, newer_than)
return None
def _log_directory_darwin(self):
@@ -72,3 +79,35 @@ class CrashLogs(object):
if include_errors and errors:
return errors
return None
+
+ def _find_newest_log_win(self, process_name, pid, include_errors, newer_than):
+ def is_crash_log(fs, dirpath, basename):
+ return basename.startswith("CrashLog")
+
+ logs = self._host.filesystem.files_under(self._results_directory, file_filter=is_crash_log)
+ errors = ''
+ for path in reversed(sorted(logs)):
+ try:
+ if not newer_than or self._host.filesystem.mtime(path) > newer_than:
+ log_file = self._host.filesystem.read_binary_file(path).decode('utf8', 'ignore')
+ match = self.PID_LINE_REGEX.search(log_file)
+ if match is None:
+ continue
+ if int(match.group('pid')) == pid:
+ return errors + log_file
+ except IOError, e:
+ print "IOError %s" % str(e)
+ if include_errors:
+ errors += "ERROR: Failed to read '%s': %s\n" % (path, str(e))
+ except OSError, e:
+ print "OSError %s" % str(e)
+ if include_errors:
+ errors += "ERROR: Failed to read '%s': %s\n" % (path, str(e))
+ except UnicodeDecodeError, e:
+ print "UnicodeDecodeError %s" % str(e)
+ if include_errors:
+ errors += "ERROR: Failed to decode '%s' as utf8: %s\n" % (path, str(e))
+
+ if include_errors and errors:
+ return errors
+ return None
diff --git a/Tools/Scripts/webkitpy/common/system/crashlogs_unittest.py b/Tools/Scripts/webkitpy/common/system/crashlogs_unittest.py
index 1f5c40a09..48034e806 100644
--- a/Tools/Scripts/webkitpy/common/system/crashlogs_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/crashlogs_unittest.py
@@ -21,7 +21,7 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.crashlogs import CrashLogs
from webkitpy.common.system.filesystem_mock import MockFileSystem
@@ -29,6 +29,8 @@ from webkitpy.common.system.systemhost import SystemHost
from webkitpy.common.system.systemhost_mock import MockSystemHost
from webkitpy.thirdparty.mock import Mock
+# Needed to support Windows port tests
+from webkitpy.port.win import WinPort
def make_mock_crash_report_darwin(process_name, pid):
return """Process: {process_name} [{pid}]
@@ -68,14 +70,169 @@ PCI Card: NVIDIA GeForce GT 120, sppci_displaycontroller, MXM-Slot
Serial ATA Device: OPTIARC DVD RW AD-5670S
""".format(process_name=process_name, pid=pid)
-class CrashLogsTest(unittest.TestCase):
- def assertLinesEqual(self, a, b):
- if hasattr(self, 'assertMultiLineEqual'):
- self.assertMultiLineEqual(a, b)
- else:
- self.assertEqual(a.splitlines(), b.splitlines())
+def make_mock_crash_report_win(process_name, pid):
+ return """Opened log file 'C:\Projects\WebKit\OpenSource\WebKitBuild\Release\bin32\layout-test-results\CrashLog_1d58_2013-06-03_12-21-20-110.txt'
+0:000> .srcpath "C:\Projects\WebKit\OpenSource"
+Source search path is: C:\Projects\WebKit\OpenSource
+0:000> !analyze -vv
+*******************************************************************************
+* *
+* Exception Analysis *
+* *
+*******************************************************************************
+
+*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Projects\WebKit\OpenSource\WebKitBuild\Release\bin32\libdispatch.dll -
+*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\SYSTEM32\atiumdag.dll -
+
+FAULTING_IP:
+JavaScriptCore!JSC::JSActivation::getOwnPropertySlot+0 [c:\projects\webkit\opensource\source\javascriptcore\runtime\jsactivation.cpp @ 146]
+01e3d070 55 push ebp
+
+EXCEPTION_RECORD: 00092cc8 -- (.exr 0x92cc8)
+.exr 0x92cc8
+ExceptionAddress: 01e3d070 (JavaScriptCore!JSC::JSActivation::getOwnPropertySlot)
+ ExceptionCode: c00000fd (Stack overflow)
+ ExceptionFlags: 00000000
+NumberParameters: 2
+ Parameter[0]: 00000001
+ Parameter[1]: 00092ffc
+
+FAULTING_THREAD: 00000e68
+PROCESS_NAME: {process_name}
+ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%08lx referenced memory at 0x%08lx. The memory could not be %s.
+EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%08lx referenced memory at 0x%08lx. The memory could not be %s.
+EXCEPTION_CODE_STR: c0000005
+EXCEPTION_PARAMETER1: 00000000
+EXCEPTION_PARAMETER2: 00090000
+READ_ADDRESS: 00090000
+
+FOLLOWUP_IP:
+JavaScriptCore!JSC::JSActivation::getOwnPropertySlot+0 [c:\projects\webkit\opensource\source\javascriptcore\runtime\jsactivation.cpp @ 146]
+01e3d070 55 push ebp
+
+WATSON_BKT_PROCSTAMP: 51a8f979
+WATSON_BKT_MODULE: MSVCR100.dll
+WATSON_BKT_MODVER: 10.0.40219.325
+WATSON_BKT_MODSTAMP: 4df2be1e
+WATSON_BKT_MODOFFSET: 160d7
+MODULE_VER_PRODUCT: Microsoft(R) Visual Studio(R) 2010
+BUILD_VERSION_STRING: 6.2.9200.16384 (win8_rtm.120725-1247)
+NTGLOBALFLAG: 0
+APPLICATION_VERIFIER_FLAGS: 0
+APP: {process_name}
+
+ANALYSIS_SESSION_HOST: FULGBR-PC
+
+ANALYSIS_SESSION_TIME: 06-03-2013 12:21:20.0111
+
+CONTEXT: 00092d18 -- (.cxr 0x92d18)
+.cxr 0x92d18
+eax=01e3d070 ebx=000930bc ecx=7fe03ed0 edx=0751e168 esi=07a7ff98 edi=0791ff78
+eip=01e3d070 esp=00093000 ebp=0009306c iopl=0 nv up ei ng nz ac po cy
+cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00210293
+JavaScriptCore!JSC::JSActivation::getOwnPropertySlot:
+01e3d070 55 push ebp
+.cxr
+Resetting default scope
+
+RECURRING_STACK: From frames 0x14 to 0x1d
+
+THREAD_ATTRIBUTES:
+
+[ GLOBAL ]
+
+ Global PID: [{pid}]
+ Global Thread_Count: [19]
+ Global PageSize: [4096]
+ Global ModList_SHA1_Hash: [aacef4e7e83b9bddc9cd0cc094dac88d531ea4a3]
+ Global CommandLine: [C:\Projects\WebKit\OpenSource\WebKitBuild\Release\bin32\{process_name} -]
+ Global Desktop_Name: [Winsta0\Default]
+ Global ProcessName: [{process_name}]
+ Global Debugger_CPU_Architecture: [X86]
+ Global CPU_ProcessorCount: [24]
+ Global CPU_MHZ: [1596]
+ Global CPU_Architecture: [X86]
+ Global CPU_Family: [6]
+ Global CPU_Model: [12]
+ Global CPU_Stepping: [2]
+ Global CPU_VendorString: [GenuineIntel]
+ Global LoadedModule_Count: [82]
+ Global ProcessBeingDebugged
+ Global GFlags: [0]
+ Global Application_Verifer_Flags: [0]
+ Global FinalExh: [2012093943]
+ Global SystemUpTime: [3 days 23:52:56.000]
+ Global SystemUpTime: [345176]
+ Global ProcessUpTime: [0 days 0:00:00.000]
+ Global ProcessUpTime: [0]
+ Global CurrentTimeDate: [Mon Jun 3 12:21:20.000 2013 (UTC - 7:00)]
+ Global CurrentTimeDate: [1370287280]
+ Global ProductType: [1]
+ Global SuiteMask: [272]
+ Global ApplicationName: [{process_name}]
+ Global ASLR_Enabled
+ Global SafeSEH_Enabled
+
+FAULT_INSTR_CODE: 83ec8b55
+
+FAULTING_SOURCE_LINE: c:\projects\webkit\opensource\source\javascriptcore\runtime\jsactivation.cpp
+
+FAULTING_SOURCE_FILE: c:\projects\webkit\opensource\source\javascriptcore\runtime\jsactivation.cpp
+
+FAULTING_SOURCE_LINE_NUMBER: 146
+
+SYMBOL_STACK_INDEX: 0
+
+SYMBOL_NAME: javascriptcore!JSC::JSActivation::getOwnPropertySlot+92ffc
+
+FOLLOWUP_NAME: MachineOwner
+
+MODULE_NAME: JavaScriptCore
+
+IMAGE_NAME: JavaScriptCore.dll
+
+DEBUG_FLR_IMAGE_TIMESTAMP: 51ace473
+
+STACK_COMMAND: .cxr 00092D18 ; kb ; dps 93000 ; kb
+FAILURE_BUCKET_ID: STACK_OVERFLOW_c0000005_JavaScriptCore.dll!JSC::JSActivation::getOwnPropertySlot
+
+BUCKET_ID: APPLICATION_FAULT_STACK_OVERFLOW_INVALID_POINTER_READ_javascriptcore!JSC::JSActivation::getOwnPropertySlot+92ffc
+
+ANALYSIS_SESSION_ELAPSED_TIME: 18df
+
+Followup: MachineOwner
+---------
+
+0:000> ~*kpn
+
+. 0 Id: 18e0.e68 Suspend: 1 Teb: 7ffdd000 Unfrozen
+ # ChildEBP RetAddr
+00 00092a08 7261ece1 MSVCR100!_alloca_probe+0x27
+01 00092a4c 7261a5d0 MSVCR100!_write+0x95
+02 00092a6c 7261ef6b MSVCR100!_flush+0x3b
+03 00092a7c 7261ef1c MSVCR100!_fflush_nolock+0x1c
+04 00092ab4 1000f814 MSVCR100!fflush+0x30
+05 00092ac8 77c0084e DumpRenderTree_10000000!exceptionFilter(struct _EXCEPTION_POINTERS * __formal = 0x852ac807)+0x24 [c:\projects\webkit\opensource\tools\dumprendertree\win\dumprendertree.cpp @ 1281]
+06 00092b60 77e8bf2c KERNELBASE!UnhandledExceptionFilter+0x164
+07 00092b68 77e530b4 ntdll!__RtlUserThreadStart+0x57
+08 00092b7c 77e15246 ntdll!_EH4_CallFilterFunc+0x12
+09 00092ba4 77e151b1 ntdll!_except_handler4_common+0x8e
+0a 00092bc4 77e52e71 ntdll!_except_handler4+0x20
+0b 00092be8 77e52e43 ntdll!ExecuteHandler2+0x26
+0c 00092cb0 77e52cbb ntdll!ExecuteHandler+0x24
+0d 00092cb0 01e3d070 ntdll!KiUserExceptionDispatcher+0xf
+0e 00092ffc 01e67d25 JavaScriptCore!JSC::JSActivation::getOwnPropertySlot(class JSC::JSCell * cell = 0x07a7ff98, class JSC::ExecState * exec = 0x0751e168, class JSC::PropertyName propertyName = class JSC::PropertyName, class JSC::PropertySlot * slot = 0x000930bc) [c:\projects\webkit\opensource\source\javascriptcore\runtime\jsactivation.cpp @ 146]
+0f 0009306c 01e68837 JavaScriptCore!JSC::JSScope::resolveContainingScopeInternal<1,2>(class JSC::ExecState * callFrame = 0x0751e168, class JSC::Identifier * identifier = 0x7fe0ebc0, class JSC::PropertySlot * slot = 0x7fe03ed0, class WTF::Vector<JSC::ResolveOperation,0,WTF::CrashOnOverflow> * operations = 0x7fda16c0, struct JSC::PutToBaseOperation * putToBaseOperation = 0x00000000, bool __formal = false)+0x205 [c:\projects\webkit\opensource\source\javascriptcore\runtime\jsscope.cpp @ 247]
+10 00093090 01e65860 JavaScriptCore!JSC::JSScope::resolveContainingScope<1>(class JSC::ExecState * callFrame = 0x0751e168, class JSC::Identifier * identifier = 0x7fe0ebc0, class JSC::PropertySlot * slot = 0x000930bc, class WTF::Vector<JSC::ResolveOperation,0,WTF::CrashOnOverflow> * operations = 0x7fda16c0, struct JSC::PutToBaseOperation * putToBaseOperation = 0x00000000, bool isStrict = false)+0x27 [c:\projects\webkit\opensource\source\javascriptcore\runtime\jsscope.cpp @ 427]
+11 00093104 01dceeff JavaScriptCore!JSC::JSScope::resolve(class JSC::ExecState * callFrame = 0x0751e168, class JSC::Identifier * identifier = 0x7fe0ebc0, class WTF::Vector<JSC::ResolveOperation,0,WTF::CrashOnOverflow> * operations = 0x7fda16c0)+0xc0 [c:\projects\webkit\opensource\source\javascriptcore\runtime\jsscope.cpp @ 447]
+
+0:000> q
+quit:
+""".format(process_name=process_name, pid=pid)
+
+class CrashLogsTest(unittest.TestCase):
def test_find_log_darwin(self):
if not SystemHost().platform.is_mac():
return
@@ -95,15 +252,15 @@ class CrashLogsTest(unittest.TestCase):
filesystem = MockFileSystem(files)
crash_logs = CrashLogs(MockSystemHost(filesystem=filesystem))
log = crash_logs.find_newest_log("DumpRenderTree")
- self.assertLinesEqual(log, newer_mock_crash_report)
+ self.assertMultiLineEqual(log, newer_mock_crash_report)
log = crash_logs.find_newest_log("DumpRenderTree", 28529)
- self.assertLinesEqual(log, newer_mock_crash_report)
+ self.assertMultiLineEqual(log, newer_mock_crash_report)
log = crash_logs.find_newest_log("DumpRenderTree", 28530)
- self.assertLinesEqual(log, mock_crash_report)
+ self.assertMultiLineEqual(log, mock_crash_report)
log = crash_logs.find_newest_log("DumpRenderTree", 28531)
- self.assertEqual(log, None)
+ self.assertIsNone(log)
log = crash_logs.find_newest_log("DumpRenderTree", newer_than=1.0)
- self.assertEqual(log, None)
+ self.assertIsNone(log)
def bad_read(path):
raise IOError('IOError: No such file or directory')
@@ -113,10 +270,47 @@ class CrashLogsTest(unittest.TestCase):
filesystem.read_text_file = bad_read
log = crash_logs.find_newest_log("DumpRenderTree", 28531, include_errors=True)
- self.assertTrue('IOError: No such file or directory' in log)
+ self.assertIn('IOError: No such file or directory', log)
filesystem = MockFileSystem(files)
crash_logs = CrashLogs(MockSystemHost(filesystem=filesystem))
filesystem.mtime = bad_mtime
log = crash_logs.find_newest_log("DumpRenderTree", newer_than=1.0, include_errors=True)
- self.assertTrue('OSError: No such file or directory' in log)
+ self.assertIn('OSError: No such file or directory', log)
+
+ def test_find_log_win(self):
+ if not SystemHost().platform.is_win():
+ return
+
+ older_mock_crash_report = make_mock_crash_report_win('DumpRenderTree', 28528)
+ mock_crash_report = make_mock_crash_report_win('DumpRenderTree', 28530)
+ newer_mock_crash_report = make_mock_crash_report_win('DumpRenderTree', 28529)
+ other_process_mock_crash_report = make_mock_crash_report_win('FooProcess', 28527)
+ misformatted_mock_crash_report = 'Junk that should not appear in a crash report' + make_mock_crash_report_win('DumpRenderTree', 28526)[200:]
+ files = {}
+ files['~/CrashLog_1d58_2013-06-03_12-21-20-110.txt'] = older_mock_crash_report
+ files['~/CrashLog_abcd_2013-06-03_12-22-19-129.txt'] = mock_crash_report
+ files['~/CrashLog_2eff_2013-06-03_12-23-20-150.txt'] = newer_mock_crash_report
+ files['~/CrashLog_31a0_2013-06-03_12-24-22-119.txt'] = None
+ files['~/CrashLog_01a3_2013-06-03_12-25-23-120.txt'] = other_process_mock_crash_report
+ files['~/CrashLog_aadd_2013-06-03_12-26-24-121.txt'] = misformatted_mock_crash_report
+ filesystem = MockFileSystem(files)
+ mock_host = MockSystemHost(os_name='win', filesystem=filesystem)
+ crash_logs = CrashLogs(mock_host, "~")
+
+ log = crash_logs.find_newest_log("DumpRenderTree", 28529)
+ self.assertMultiLineEqual(log, newer_mock_crash_report)
+ log = crash_logs.find_newest_log("DumpRenderTree", 28530)
+ self.assertMultiLineEqual(log, mock_crash_report)
+ log = crash_logs.find_newest_log("DumpRenderTree", 28531)
+ self.assertIsNone(log)
+ log = crash_logs.find_newest_log("DumpRenderTree", newer_than=1.0)
+ self.assertIsNone(log)
+
+ def bad_read(path):
+ raise IOError('IOError: No such file or directory')
+
+ filesystem.read_text_file = bad_read
+ filesystem.read_binary_file = bad_read
+ log = crash_logs.find_newest_log("DumpRenderTree", 28531, include_errors=True)
+ self.assertIn('IOError: No such file or directory', log)
diff --git a/Tools/Scripts/webkitpy/common/system/environment_unittest.py b/Tools/Scripts/webkitpy/common/system/environment_unittest.py
index 6558b51df..2868a65d2 100644
--- a/Tools/Scripts/webkitpy/common/system/environment_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/environment_unittest.py
@@ -26,7 +26,7 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-import unittest
+import unittest2 as unittest
from .environment import Environment
diff --git a/Tools/Scripts/webkitpy/common/system/executive.py b/Tools/Scripts/webkitpy/common/system/executive.py
index 42a8122d3..ca45f2f35 100644
--- a/Tools/Scripts/webkitpy/common/system/executive.py
+++ b/Tools/Scripts/webkitpy/common/system/executive.py
@@ -46,14 +46,6 @@ _log = logging.getLogger(__name__)
class ScriptError(Exception):
- # This is a custom List.__str__ implementation to allow size limiting.
- def _string_from_args(self, args, limit=100):
- args_string = unicode(args)
- # We could make this much fancier, but for now this is OK.
- if len(args_string) > limit:
- return args_string[:limit - 3] + "..."
- return args_string
-
def __init__(self,
message=None,
script_args=None,
@@ -61,7 +53,7 @@ class ScriptError(Exception):
output=None,
cwd=None):
if not message:
- message = 'Failed to run "%s"' % self._string_from_args(script_args)
+ message = 'Failed to run "%s"' % repr(script_args)
if exit_code:
message += " exit_code: %d" % exit_code
if cwd:
@@ -92,6 +84,9 @@ class Executive(object):
PIPE = subprocess.PIPE
STDOUT = subprocess.STDOUT
+ def __init__(self):
+ self.pid_to_system_pid = {}
+
def _should_close_fds(self):
# We need to pass close_fds=True to work around Python bug #2320
# (otherwise we can hang when we kill DumpRenderTree when we are running
@@ -101,9 +96,6 @@ class Executive(object):
return sys.platform not in ('win32', 'cygwin')
def _run_command_with_teed_output(self, args, teed_output, **kwargs):
- args = map(unicode, args) # Popen will throw an exception if args are non-strings (like int())
- args = map(self._encode_argument_if_needed, args)
-
child_process = self.popen(args,
stdout=self.PIPE,
stderr=self.STDOUT,
@@ -153,6 +145,12 @@ class Executive(object):
return child_output
def cpu_count(self):
+ try:
+ cpus = int(os.environ.get('NUMBER_OF_PROCESSORS'))
+ if cpus > 0:
+ return cpus
+ except (ValueError, TypeError):
+ pass
return multiprocessing.cpu_count()
@staticmethod
@@ -272,26 +270,37 @@ class Executive(object):
return False
def running_pids(self, process_name_filter=None):
+ if sys.platform == "win32":
+ # FIXME: running_pids isn't implemented on native Windows yet...
+ return []
+
if not process_name_filter:
process_name_filter = lambda process_name: True
running_pids = []
-
- if sys.platform in ("win32", "cygwin"):
- # FIXME: running_pids isn't implemented on Windows yet...
- return []
-
- ps_process = self.popen(['ps', '-eo', 'pid,comm'], stdout=self.PIPE, stderr=self.PIPE)
- stdout, _ = ps_process.communicate()
- for line in stdout.splitlines():
- try:
- # In some cases the line can contain one or more
- # leading white-spaces, so strip it before split.
- pid, process_name = line.strip().split(' ', 1)
- if process_name_filter(process_name):
- running_pids.append(int(pid))
- except ValueError, e:
- pass
+ if sys.platform in ("cygwin"):
+ ps_process = self.run_command(['ps', '-e'], error_handler=Executive.ignore_error)
+ for line in ps_process.splitlines():
+ tokens = line.strip().split()
+ try:
+ pid, ppid, pgid, winpid, tty, uid, stime, process_name = tokens
+ if process_name_filter(process_name):
+ running_pids.append(int(pid))
+ self.pid_to_system_pid[int(pid)] = int(winpid)
+ except ValueError, e:
+ pass
+ else:
+ ps_process = self.popen(['ps', '-eo', 'pid,comm'], stdout=self.PIPE, stderr=self.PIPE)
+ stdout, _ = ps_process.communicate()
+ for line in stdout.splitlines():
+ try:
+ # In some cases the line can contain one or more
+ # leading white-spaces, so strip it before split.
+ pid, process_name = line.strip().split(' ', 1)
+ if process_name_filter(process_name):
+ running_pids.append(int(pid))
+ except ValueError, e:
+ pass
return sorted(running_pids)
@@ -307,6 +316,13 @@ class Executive(object):
while self.check_running_pid(pid):
time.sleep(0.25)
+ def wait_limited(self, pid, limit_in_seconds=None, check_frequency_in_seconds=None):
+ seconds_left = limit_in_seconds or 10
+ sleep_length = check_frequency_in_seconds or 1
+ while seconds_left > 0 and self.check_running_pid(pid):
+ seconds_left -= sleep_length
+ time.sleep(sleep_length)
+
def _windows_image_name(self, process_name):
name, extension = os.path.splitext(process_name)
if not extension:
@@ -315,6 +331,17 @@ class Executive(object):
process_name = "%s.exe" % name
return process_name
+ def interrupt(self, pid):
+ interrupt_signal = signal.SIGINT
+ # FIXME: The python docs seem to imply that platform == 'win32' may need to use signal.CTRL_C_EVENT
+ # http://docs.python.org/2/library/signal.html
+ try:
+ os.kill(pid, interrupt_signal)
+ except OSError:
+ # Silently ignore when the pid doesn't exist.
+ # It's impossible for callers to avoid race conditions with process shutdown.
+ pass
+
def kill_all(self, process_name):
"""Attempts to kill processes matching process_name.
Will fail silently if no process are found."""
@@ -365,9 +392,10 @@ class Executive(object):
input = input.encode(self._child_process_encoding())
return (self.PIPE, input)
- def _command_for_printing(self, args):
+ def command_for_printing(self, args):
"""Returns a print-ready string representing command args.
The string should be copy/paste ready for execution in a shell."""
+ args = self._stringify_args(args)
escaped_args = []
for arg in args:
if isinstance(arg, unicode):
@@ -390,8 +418,6 @@ class Executive(object):
"""Popen wrapper for convenience and to work around python bugs."""
assert(isinstance(args, list) or isinstance(args, tuple))
start_time = time.time()
- args = map(unicode, args) # Popen will throw an exception if args are non-strings (like int())
- args = map(self._encode_argument_if_needed, args)
stdin, string_to_communicate = self._compute_stdin(input)
stderr = self.STDOUT if return_stderr else None
@@ -413,7 +439,7 @@ class Executive(object):
# http://bugs.python.org/issue1731717
exit_code = process.wait()
- _log.debug('"%s" took %.2fs' % (self._command_for_printing(args), time.time() - start_time))
+ _log.debug('"%s" took %.2fs' % (self.command_for_printing(args), time.time() - start_time))
if return_exit_code:
return exit_code
@@ -457,8 +483,23 @@ class Executive(object):
return argument
return argument.encode(self._child_process_encoding())
- def popen(self, *args, **kwargs):
- return subprocess.Popen(*args, **kwargs)
+ def _stringify_args(self, args):
+ # Popen will throw an exception if args are non-strings (like int())
+ string_args = map(unicode, args)
+ # The Windows implementation of Popen cannot handle unicode strings. :(
+ return map(self._encode_argument_if_needed, string_args)
+
+ # The only required arugment to popen is named "args", the rest are optional keyword arguments.
+ def popen(self, args, **kwargs):
+ # FIXME: We should always be stringifying the args, but callers who pass shell=True
+ # expect that the exact bytes passed will get passed to the shell (even if they're wrongly encoded).
+ # shell=True is wrong for many other reasons, and we should remove this
+ # hack as soon as we can fix all callers to not use shell=True.
+ if kwargs.get('shell') == True:
+ string_args = args
+ else:
+ string_args = self._stringify_args(args)
+ return subprocess.Popen(string_args, **kwargs)
def run_in_parallel(self, command_lines_and_cwds, processes=None):
"""Runs a list of (cmd_line list, cwd string) tuples in parallel and returns a list of (retcode, stdout, stderr) tuples."""
diff --git a/Tools/Scripts/webkitpy/common/system/executive_mock.py b/Tools/Scripts/webkitpy/common/system/executive_mock.py
index a83f5b245..a3870b131 100644
--- a/Tools/Scripts/webkitpy/common/system/executive_mock.py
+++ b/Tools/Scripts/webkitpy/common/system/executive_mock.py
@@ -63,6 +63,7 @@ class MockExecutive(object):
self._running_pids = {'test-webkitpy': os.getpid()}
self._proc = None
self.calls = []
+ self.pid_to_system_pid = {}
def check_running_pid(self, pid):
return pid in self._running_pids.values()
@@ -86,6 +87,10 @@ class MockExecutive(object):
raise ScriptError("Exception for %s" % args, output="MOCK command output")
return "MOCK output of child process"
+ def command_for_printing(self, args):
+ string_args = map(unicode, args)
+ return " ".join(string_args)
+
def run_command(self,
args,
cwd=None,
@@ -108,6 +113,10 @@ class MockExecutive(object):
input_string = ", input=%s" % input
_log.info("MOCK run_command: %s, cwd=%s%s%s" % (args, cwd, env_string, input_string))
output = "MOCK output of child process"
+
+ if self._should_throw_when_run.intersection(args):
+ raise ScriptError("Exception for %s" % args, output="MOCK command output")
+
if self._should_throw:
raise ScriptError("MOCK ScriptError", output=output)
return output
@@ -170,7 +179,7 @@ class MockExecutive2(MockExecutive):
self.calls.append(args)
assert(isinstance(args, list) or isinstance(args, tuple))
if self._exception:
- raise self._exception # pylint: disable-msg=E0702
+ raise self._exception # pylint: disable=E0702
if self._run_command_fn:
return self._run_command_fn(args)
if return_exit_code:
diff --git a/Tools/Scripts/webkitpy/common/system/executive_unittest.py b/Tools/Scripts/webkitpy/common/system/executive_unittest.py
index 755955d34..f71201a04 100644
--- a/Tools/Scripts/webkitpy/common/system/executive_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/executive_unittest.py
@@ -33,34 +33,36 @@ import signal
import subprocess
import sys
import time
-import unittest
# Since we execute this script directly as part of the unit tests, we need to ensure
# that Tools/Scripts is in sys.path for the next imports to work correctly.
script_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
if script_dir not in sys.path:
sys.path.append(script_dir)
+third_party_py = os.path.join(script_dir, "webkitpy", "thirdparty", "autoinstalled")
+if third_party_py not in sys.path:
+ sys.path.append(third_party_py)
+
+import unittest2 as unittest
from webkitpy.common.system.executive import Executive, ScriptError
from webkitpy.common.system.filesystem_mock import MockFileSystem
class ScriptErrorTest(unittest.TestCase):
- def test_string_from_args(self):
- error = ScriptError()
- self.assertEqual(error._string_from_args(None), 'None')
- self.assertEqual(error._string_from_args([]), '[]')
- self.assertEqual(error._string_from_args(map(str, range(30))), "['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17'...")
-
def test_message_with_output(self):
error = ScriptError('My custom message!', '', -1)
self.assertEqual(error.message_with_output(), 'My custom message!')
error = ScriptError('My custom message!', '', -1, 'My output.')
self.assertEqual(error.message_with_output(), 'My custom message!\n\nMy output.')
error = ScriptError('', 'my_command!', -1, 'My output.', '/Users/username/blah')
- self.assertEqual(error.message_with_output(), 'Failed to run "my_command!" exit_code: -1 cwd: /Users/username/blah\n\nMy output.')
+ self.assertEqual(error.message_with_output(), 'Failed to run "\'my_command!\'" exit_code: -1 cwd: /Users/username/blah\n\nMy output.')
error = ScriptError('', 'my_command!', -1, 'ab' + '1' * 499)
- self.assertEqual(error.message_with_output(), 'Failed to run "my_command!" exit_code: -1\n\nLast 500 characters of output:\nb' + '1' * 499)
+ self.assertEqual(error.message_with_output(), 'Failed to run "\'my_command!\'" exit_code: -1\n\nLast 500 characters of output:\nb' + '1' * 499)
+
+ def test_message_with_tuple(self):
+ error = ScriptError('', ('my', 'command'), -1, 'My output.', '/Users/username/blah')
+ self.assertEqual(error.message_with_output(), 'Failed to run "(\'my\', \'command\')" exit_code: -1 cwd: /Users/username/blah\n\nMy output.')
def never_ending_command():
"""Arguments for a command that will never end (useful for testing process
@@ -113,6 +115,17 @@ class ExecutiveTest(unittest.TestCase):
executive.run_command(command_line('echo', 'foo'))
executive.run_command(tuple(command_line('echo', 'foo')))
+ def test_auto_stringify_args(self):
+ executive = Executive()
+ executive.run_command(command_line('echo', 1))
+ executive.popen(command_line('echo', 1), stdout=executive.PIPE).wait()
+ self.assertEqual('echo 1', executive.command_for_printing(['echo', 1]))
+
+ def test_popen_args(self):
+ executive = Executive()
+ # Explicitly naming the 'args' argument should not thow an exception.
+ executive.popen(args=command_line('echo', 1), stdout=executive.PIPE).wait()
+
def test_run_command_with_unicode(self):
"""Validate that it is safe to pass unicode() objects
to Executive.run* methods, and they will return unicode()
@@ -161,11 +174,11 @@ class ExecutiveTest(unittest.TestCase):
if sys.platform == "win32":
# FIXME: https://bugs.webkit.org/show_bug.cgi?id=54790
# We seem to get either 0 or 1 here for some reason.
- self.assertTrue(process.wait() in (0, 1))
+ self.assertIn(process.wait(), (0, 1))
elif sys.platform == "cygwin":
# FIXME: https://bugs.webkit.org/show_bug.cgi?id=98196
# cygwin seems to give us either SIGABRT or SIGKILL
- self.assertTrue(process.wait() in (-signal.SIGABRT, -signal.SIGKILL))
+ self.assertIn(process.wait(), (-signal.SIGABRT, -signal.SIGKILL))
else:
expected_exit_code = -signal.SIGKILL
self.assertEqual(process.wait(), expected_exit_code)
@@ -176,7 +189,7 @@ class ExecutiveTest(unittest.TestCase):
def serial_test_kill_all(self):
executive = Executive()
process = subprocess.Popen(never_ending_command(), stdout=subprocess.PIPE)
- self.assertEqual(process.poll(), None) # Process is running
+ self.assertIsNone(process.poll()) # Process is running
executive.kill_all(never_ending_command()[0])
# Note: Can't use a ternary since signal.SIGTERM is undefined for sys.platform == "win32"
if sys.platform == "cygwin":
@@ -185,7 +198,7 @@ class ExecutiveTest(unittest.TestCase):
elif sys.platform == "win32":
# FIXME: https://bugs.webkit.org/show_bug.cgi?id=54790
# We seem to get either 0 or 1 here for some reason.
- self.assertTrue(process.wait() in (0, 1))
+ self.assertIn(process.wait(), (0, 1))
else:
expected_exit_code = -signal.SIGTERM
self.assertEqual(process.wait(), expected_exit_code)
@@ -218,7 +231,7 @@ class ExecutiveTest(unittest.TestCase):
executive = Executive()
pids = executive.running_pids()
- self.assertTrue(os.getpid() in pids)
+ self.assertIn(os.getpid(), pids)
def serial_test_run_in_parallel(self):
# We run this test serially to avoid overloading the machine and throwing off the timing.
diff --git a/Tools/Scripts/webkitpy/common/system/file_lock.py b/Tools/Scripts/webkitpy/common/system/file_lock.py
index c542777f2..3ca8b3cba 100644
--- a/Tools/Scripts/webkitpy/common/system/file_lock.py
+++ b/Tools/Scripts/webkitpy/common/system/file_lock.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
# Copyright (C) 2010 Gabor Rapcsanyi (rgabor@inf.u-szeged.hu), University of Szeged
#
# All rights reserved.
diff --git a/Tools/Scripts/webkitpy/common/system/file_lock_integrationtest.py b/Tools/Scripts/webkitpy/common/system/file_lock_integrationtest.py
index 5cd27d11d..7b1b42695 100644
--- a/Tools/Scripts/webkitpy/common/system/file_lock_integrationtest.py
+++ b/Tools/Scripts/webkitpy/common/system/file_lock_integrationtest.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
# Copyright (C) 2010 Gabor Rapcsanyi (rgabor@inf.u-szeged.hu), University of Szeged
#
# All rights reserved.
@@ -26,7 +25,7 @@
import os
import tempfile
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.file_lock import FileLock
diff --git a/Tools/Scripts/webkitpy/common/system/file_lock_mock.py b/Tools/Scripts/webkitpy/common/system/file_lock_mock.py
index e2c1d5cdf..f53081d1c 100644
--- a/Tools/Scripts/webkitpy/common/system/file_lock_mock.py
+++ b/Tools/Scripts/webkitpy/common/system/file_lock_mock.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
# Copyright (c) 2012 Google Inc. All rights reserved.
#
# All rights reserved.
@@ -30,7 +29,7 @@ class MockFileLock(object):
pass
def acquire_lock(self):
- pass
+ return True
def release_lock(self):
- pass
+ return True
diff --git a/Tools/Scripts/webkitpy/common/system/filesystem_mock.py b/Tools/Scripts/webkitpy/common/system/filesystem_mock.py
index 16e9fadaa..ee0664ea0 100644
--- a/Tools/Scripts/webkitpy/common/system/filesystem_mock.py
+++ b/Tools/Scripts/webkitpy/common/system/filesystem_mock.py
@@ -395,7 +395,7 @@ class MockFileSystem(object):
def splitext(self, path):
idx = path.rfind('.')
if idx == -1:
- idx = 0
+ idx = len(path)
return (path[0:idx], path[idx:])
@@ -452,7 +452,7 @@ class ReadableBinaryFileObject(object):
class ReadableTextFileObject(ReadableBinaryFileObject):
def __init__(self, fs, path, data):
- super(ReadableTextFileObject, self).__init__(fs, path, StringIO.StringIO(data))
+ super(ReadableTextFileObject, self).__init__(fs, path, StringIO.StringIO(data.decode("utf-8")))
def close(self):
self.data.close()
diff --git a/Tools/Scripts/webkitpy/common/system/filesystem_mock_unittest.py b/Tools/Scripts/webkitpy/common/system/filesystem_mock_unittest.py
index 391c1d954..a5983320a 100644
--- a/Tools/Scripts/webkitpy/common/system/filesystem_mock_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/filesystem_mock_unittest.py
@@ -28,7 +28,7 @@
import os
import re
-import unittest
+import unittest2 as unittest
from webkitpy.common.system import filesystem_mock
@@ -82,7 +82,3 @@ class MockFileSystemTest(unittest.TestCase, filesystem_unittest.GenericFileSyste
'foo/../bar',
'foo/../bar/baz',
'../foo')
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py b/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py
index d656b2580..cd4ad6e4a 100644
--- a/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py
@@ -35,7 +35,7 @@ import os
import stat
import sys
import tempfile
-import unittest
+import unittest2 as unittest
from filesystem import FileSystem
@@ -209,6 +209,8 @@ class RealFileSystemTest(unittest.TestCase, GenericFileSystemTests):
unicode_text_string = u'\u016An\u012Dc\u014Dde\u033D'
hex_equivalent = '\xC5\xAA\x6E\xC4\xAD\x63\xC5\x8D\x64\x65\xCC\xBD'
+ malformed_text_hex = '\x4D\x69\x63\x72\x6F\x73\x6F\x66\x74\xAE\x20\x56\x69\x73\x75\x61\x6C\x20\x53\x74\x75\x64\x69\x6F\xAE\x20\x32\x30\x31\x30\x0D\x0A'
+ malformed_ignored_text_hex = '\x4D\x69\x63\x72\x6F\x73\x6F\x66\x74\x20\x56\x69\x73\x75\x61\x6C\x20\x53\x74\x75\x64\x69\x6F\x20\x32\x30\x31\x30\x0D\x0A'
try:
text_path = tempfile.mktemp(prefix='tree_unittest_')
binary_path = tempfile.mktemp(prefix='tree_unittest_')
@@ -219,6 +221,12 @@ class RealFileSystemTest(unittest.TestCase, GenericFileSystemTests):
fs.write_binary_file(binary_path, hex_equivalent)
text_contents = fs.read_text_file(binary_path)
self.assertEqual(text_contents, unicode_text_string)
+
+ self.assertRaises(ValueError, fs.write_text_file, binary_path, malformed_text_hex)
+ fs.write_binary_file(binary_path, malformed_text_hex)
+ self.assertRaises(ValueError, fs.read_text_file, binary_path)
+ text_contents = fs.read_binary_file(binary_path).decode('utf8', 'ignore')
+ self.assertEquals(text_contents, malformed_ignored_text_hex)
finally:
if text_path and fs.isfile(text_path):
os.remove(text_path)
@@ -254,7 +262,3 @@ class RealFileSystemTest(unittest.TestCase, GenericFileSystemTests):
self.assertEqual(fs.sep, os.sep)
self.assertEqual(fs.join("foo", "bar"),
os.path.join("foo", "bar"))
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/Tools/Scripts/webkitpy/common/system/logtesting.py b/Tools/Scripts/webkitpy/common/system/logtesting.py
index 0cfa6cb0a..1aba1726a 100644
--- a/Tools/Scripts/webkitpy/common/system/logtesting.py
+++ b/Tools/Scripts/webkitpy/common/system/logtesting.py
@@ -32,7 +32,7 @@ see the TestLogStream class, and perhaps also the LogTesting class.
"""
import logging
-import unittest
+import unittest2 as unittest
class TestLogStream(object):
diff --git a/Tools/Scripts/webkitpy/common/system/logutils_unittest.py b/Tools/Scripts/webkitpy/common/system/logutils_unittest.py
index 6d7cc4da4..252ebf4cc 100644
--- a/Tools/Scripts/webkitpy/common/system/logutils_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/logutils_unittest.py
@@ -24,7 +24,7 @@
import logging
import os
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.logtesting import LogTesting
from webkitpy.common.system.logtesting import TestLogStream
diff --git a/Tools/Scripts/webkitpy/common/system/outputcapture.py b/Tools/Scripts/webkitpy/common/system/outputcapture.py
index 26670d214..893b5e528 100644
--- a/Tools/Scripts/webkitpy/common/system/outputcapture.py
+++ b/Tools/Scripts/webkitpy/common/system/outputcapture.py
@@ -29,8 +29,8 @@
# Class for unittest support. Used for capturing stderr/stdout.
import logging
+import unittest # Don't use unittest2 here as the autoinstaller may not have it yet.
import sys
-import unittest
from StringIO import StringIO
@@ -94,15 +94,22 @@ class OutputCapture(object):
finally:
(stdout_string, stderr_string, logs_string) = self.restore_output()
- testcase.assertEqual(stdout_string, expected_stdout)
- testcase.assertEqual(stderr_string, expected_stderr)
+ if hasattr(testcase, 'assertMultiLineEqual'):
+ testassert = testcase.assertMultiLineEqual
+ else:
+ testassert = testcase.assertEqual
+
+ testassert(stdout_string, expected_stdout)
+ testassert(stderr_string, expected_stderr)
if expected_logs is not None:
- testcase.assertEqual(logs_string, expected_logs)
+ testassert(logs_string, expected_logs)
# This is a little strange, but I don't know where else to return this information.
return return_value
class OutputCaptureTestCaseBase(unittest.TestCase):
+ maxDiff = None
+
def setUp(self):
unittest.TestCase.setUp(self)
self.output_capture = OutputCapture()
diff --git a/Tools/Scripts/webkitpy/common/system/outputcapture_unittest.py b/Tools/Scripts/webkitpy/common/system/outputcapture_unittest.py
index da4347c8d..7ef2e247a 100644
--- a/Tools/Scripts/webkitpy/common/system/outputcapture_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/outputcapture_unittest.py
@@ -21,7 +21,7 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import logging
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
@@ -43,7 +43,7 @@ class OutputCaptureTest(unittest.TestCase):
actual_stdout, actual_stderr, actual_logs = self.output.restore_output()
self.assertEqual('', actual_stdout)
self.assertEqual('', actual_stderr)
- self.assertEqual(expected_logs, actual_logs)
+ self.assertMultiLineEqual(expected_logs, actual_logs)
def test_initial_log_level(self):
self.output.capture_output()
diff --git a/Tools/Scripts/webkitpy/common/system/outputtee_unittest.py b/Tools/Scripts/webkitpy/common/system/outputtee_unittest.py
index 6a509f0c2..8d06916f8 100644
--- a/Tools/Scripts/webkitpy/common/system/outputtee_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/outputtee_unittest.py
@@ -27,7 +27,7 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import StringIO
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.outputtee import Tee, OutputTee
diff --git a/Tools/Scripts/webkitpy/common/system/path_unittest.py b/Tools/Scripts/webkitpy/common/system/path_unittest.py
index 7a719584d..118546e68 100644
--- a/Tools/Scripts/webkitpy/common/system/path_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/path_unittest.py
@@ -26,7 +26,7 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-import unittest
+import unittest2 as unittest
import sys
from webkitpy.common.system.systemhost import SystemHost
diff --git a/Tools/Scripts/webkitpy/common/system/platforminfo.py b/Tools/Scripts/webkitpy/common/system/platforminfo.py
index b2451f5f9..582e1996f 100644
--- a/Tools/Scripts/webkitpy/common/system/platforminfo.py
+++ b/Tools/Scripts/webkitpy/common/system/platforminfo.py
@@ -155,7 +155,7 @@ class PlatformInfo(object):
def _win_version_tuple_from_cmd(self):
# Note that this should only ever be called on windows, so this should always work.
- ver_output = self._executive.run_command(['cmd', '/c', 'ver'])
+ ver_output = self._executive.run_command(['cmd', '/c', 'ver'], decode_output=False)
match_object = re.search(r'(?P<major>\d)\.(?P<minor>\d)\.(?P<build>\d+)', ver_output)
assert match_object, 'cmd returned an unexpected version string: ' + ver_output
return tuple(map(int, match_object.groups()))
diff --git a/Tools/Scripts/webkitpy/common/system/platforminfo_unittest.py b/Tools/Scripts/webkitpy/common/system/platforminfo_unittest.py
index 327229eb9..bdb0f8661 100644
--- a/Tools/Scripts/webkitpy/common/system/platforminfo_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/platforminfo_unittest.py
@@ -28,7 +28,7 @@
import platform
import sys
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.executive import Executive
from webkitpy.common.system.executive_mock import MockExecutive, MockExecutive2
@@ -79,12 +79,12 @@ class TestPlatformInfo(unittest.TestCase):
self.assertNotEquals(info.os_version, '')
self.assertNotEquals(info.display_name(), '')
self.assertTrue(info.is_mac() or info.is_win() or info.is_linux() or info.is_freebsd())
- self.assertNotEquals(info.terminal_width(), None)
+ self.assertIsNotNone(info.terminal_width())
if info.is_mac():
self.assertTrue(info.total_bytes_memory() > 0)
else:
- self.assertEqual(info.total_bytes_memory(), None)
+ self.assertIsNone(info.total_bytes_memory())
def test_os_name_and_wrappers(self):
info = self.make_info(fake_sys('linux2'))
@@ -172,14 +172,10 @@ class TestPlatformInfo(unittest.TestCase):
self.assertEqual(info.total_bytes_memory(), 1234)
info = self.make_info(fake_sys('win32', tuple([6, 1, 7600])))
- self.assertEqual(info.total_bytes_memory(), None)
+ self.assertIsNone(info.total_bytes_memory())
info = self.make_info(fake_sys('linux2'))
- self.assertEqual(info.total_bytes_memory(), None)
+ self.assertIsNone(info.total_bytes_memory())
info = self.make_info(fake_sys('freebsd9'))
- self.assertEqual(info.total_bytes_memory(), None)
-
-
-if __name__ == '__main__':
- unittest.main()
+ self.assertIsNone(info.total_bytes_memory())
diff --git a/Tools/Scripts/webkitpy/common/system/profiler.py b/Tools/Scripts/webkitpy/common/system/profiler.py
index 264a4e238..0208cf898 100644
--- a/Tools/Scripts/webkitpy/common/system/profiler.py
+++ b/Tools/Scripts/webkitpy/common/system/profiler.py
@@ -28,19 +28,44 @@
import logging
import re
+import itertools
_log = logging.getLogger(__name__)
class ProfilerFactory(object):
@classmethod
- def create_profiler(cls, host, executable_path, output_dir, identifier=None):
- if host.platform.is_mac():
- return Instruments(host, executable_path, output_dir, identifier)
- return GooglePProf(host, executable_path, output_dir, identifier)
+ def create_profiler(cls, host, executable_path, output_dir, profiler_name=None, identifier=None):
+ profilers = cls.profilers_for_platform(host.platform)
+ if not profilers:
+ return None
+ profiler_name = profiler_name or cls.default_profiler_name(host.platform)
+ profiler_class = next(itertools.ifilter(lambda profiler: profiler.name == profiler_name, profilers), None)
+ if not profiler_class:
+ return None
+ return profilers[0](host, executable_path, output_dir, identifier)
+
+ @classmethod
+ def default_profiler_name(cls, platform):
+ profilers = cls.profilers_for_platform(platform)
+ return profilers[0].name if profilers else None
+
+ @classmethod
+ def profilers_for_platform(cls, platform):
+ # GooglePProf requires TCMalloc/google-perftools, but is available everywhere.
+ profilers_by_os_name = {
+ 'mac': [IProfiler, Sample, GooglePProf],
+ 'linux': [Perf, GooglePProf],
+ # Note: freebsd, win32 have no profilers defined yet, thus --profile will be ignored
+ # by default, but a profiler can be selected with --profiler=PROFILER explicitly.
+ }
+ return profilers_by_os_name.get(platform.os_name, [])
class Profiler(object):
+ # Used by ProfilerFactory to lookup a profiler from the --profiler=NAME option.
+ name = None
+
def __init__(self, host, executable_path, output_dir, identifier=None):
self._host = host
self._executable_path = executable_path
@@ -61,10 +86,14 @@ class Profiler(object):
class SingleFileOutputProfiler(Profiler):
def __init__(self, host, executable_path, output_dir, output_suffix, identifier=None):
super(SingleFileOutputProfiler, self).__init__(host, executable_path, output_dir, identifier)
- self._output_path = self._host.workspace.find_unused_filename(self._output_dir, self._identifier, output_suffix)
+ # FIXME: Currently all reports are kept as test.*, until we fix that, search up to 1000 names before giving up.
+ self._output_path = self._host.workspace.find_unused_filename(self._output_dir, self._identifier, output_suffix, search_limit=1000)
+ assert(self._output_path)
class GooglePProf(SingleFileOutputProfiler):
+ name = 'pprof'
+
def __init__(self, host, executable_path, output_dir, identifier=None):
super(GooglePProf, self).__init__(host, executable_path, output_dir, "pprof", identifier)
@@ -76,24 +105,106 @@ class GooglePProf(SingleFileOutputProfiler):
match = re.search("^Total:[^\n]*\n((?:[^\n]*\n){0,10})", pprof_output, re.MULTILINE)
return match.group(1) if match else None
- def profile_after_exit(self):
+ def _pprof_path(self):
# FIXME: We should have code to find the right google-pprof executable, some Googlers have
# google-pprof installed as "pprof" on their machines for them.
- # FIXME: Similarly we should find the right perl!
- pprof_args = ['/usr/bin/perl', '/usr/bin/google-pprof', '--text', self._executable_path, self._output_path]
+ return '/usr/bin/google-pprof'
+
+ def profile_after_exit(self):
+ # google-pprof doesn't check its arguments, so we have to.
+ if not (self._host.filesystem.exists(self._output_path)):
+ print "Failed to gather profile, %s does not exist." % self._output_path
+ return
+
+ pprof_args = [self._pprof_path(), '--text', self._executable_path, self._output_path]
profile_text = self._host.executive.run_command(pprof_args)
+ print "First 10 lines of pprof --text:"
print self._first_ten_lines_of_profile(profile_text)
+ print "http://google-perftools.googlecode.com/svn/trunk/doc/cpuprofile.html documents output."
+ print
+ print "To interact with the the full profile, including produce graphs:"
+ print ' '.join([self._pprof_path(), self._executable_path, self._output_path])
+
+class Perf(SingleFileOutputProfiler):
+ name = 'perf'
-# FIXME: iprofile is a newer commandline interface to replace /usr/bin/instruments.
-class Instruments(SingleFileOutputProfiler):
def __init__(self, host, executable_path, output_dir, identifier=None):
- super(Instruments, self).__init__(host, executable_path, output_dir, "trace", identifier)
+ super(Perf, self).__init__(host, executable_path, output_dir, "data", identifier)
+ self._perf_process = None
+ self._pid_being_profiled = None
- # FIXME: We may need a way to find this tracetemplate on the disk
- _time_profile = "/Applications/Xcode.app/Contents/Applications/Instruments.app/Contents/Resources/templates/Time Profiler.tracetemplate"
+ def _perf_path(self):
+ # FIXME: We may need to support finding the perf binary in other locations.
+ return 'perf'
def attach_to_pid(self, pid):
- cmd = ["instruments", "-t", self._time_profile, "-D", self._output_path, "-p", pid]
- cmd = map(unicode, cmd)
- self._host.executive.popen(cmd)
+ assert(not self._perf_process and not self._pid_being_profiled)
+ self._pid_being_profiled = pid
+ cmd = [self._perf_path(), "record", "--call-graph", "--pid", pid, "--output", self._output_path]
+ self._perf_process = self._host.executive.popen(cmd)
+
+ def _first_ten_lines_of_profile(self, perf_output):
+ match = re.search("^#[^\n]*\n((?: [^\n]*\n){1,10})", perf_output, re.MULTILINE)
+ return match.group(1) if match else None
+
+ def profile_after_exit(self):
+ # Perf doesn't automatically watch the attached pid for death notifications,
+ # so we have to do it for it, and then tell it its time to stop sampling. :(
+ self._host.executive.wait_limited(self._pid_being_profiled, limit_in_seconds=10)
+ perf_exitcode = self._perf_process.poll()
+ if perf_exitcode is None: # This should always be the case, unless perf error'd out early.
+ self._host.executive.interrupt(self._perf_process.pid)
+
+ perf_exitcode = self._perf_process.wait()
+ if perf_exitcode not in (0, -2): # The exit code should always be -2, as we're always interrupting perf.
+ print "'perf record' failed (exit code: %i), can't process results:" % perf_exitcode
+ return
+
+ perf_args = [self._perf_path(), 'report', '--call-graph', 'none', '--input', self._output_path]
+ print "First 10 lines of 'perf report --call-graph=none':"
+
+ print " ".join(perf_args)
+ perf_output = self._host.executive.run_command(perf_args)
+ print self._first_ten_lines_of_profile(perf_output)
+
+ print "To view the full profile, run:"
+ print ' '.join([self._perf_path(), 'report', '-i', self._output_path])
+ print # An extra line between tests looks nicer.
+
+
+class Sample(SingleFileOutputProfiler):
+ name = 'sample'
+
+ def __init__(self, host, executable_path, output_dir, identifier=None):
+ super(Sample, self).__init__(host, executable_path, output_dir, "txt", identifier)
+ self._profiler_process = None
+
+ def attach_to_pid(self, pid):
+ cmd = ["sample", pid, "-mayDie", "-file", self._output_path]
+ self._profiler_process = self._host.executive.popen(cmd)
+
+ def profile_after_exit(self):
+ self._profiler_process.wait()
+
+
+class IProfiler(SingleFileOutputProfiler):
+ name = 'iprofiler'
+
+ def __init__(self, host, executable_path, output_dir, identifier=None):
+ super(IProfiler, self).__init__(host, executable_path, output_dir, "dtps", identifier)
+ self._profiler_process = None
+
+ def attach_to_pid(self, pid):
+ # FIXME: iprofiler requires us to pass the directory separately
+ # from the basename of the file, with no control over the extension.
+ fs = self._host.filesystem
+ cmd = ["iprofiler", "-timeprofiler", "-a", pid,
+ "-d", fs.dirname(self._output_path), "-o", fs.splitext(fs.basename(self._output_path))[0]]
+ # FIXME: Consider capturing instead of letting instruments spam to stderr directly.
+ self._profiler_process = self._host.executive.popen(cmd)
+
+ def profile_after_exit(self):
+ # It seems like a nicer user experiance to wait on the profiler to exit to prevent
+ # it from spewing to stderr at odd times.
+ self._profiler_process.wait()
diff --git a/Tools/Scripts/webkitpy/common/system/profiler_unittest.py b/Tools/Scripts/webkitpy/common/system/profiler_unittest.py
index 059b7cfa1..22bc2df6e 100644
--- a/Tools/Scripts/webkitpy/common/system/profiler_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/profiler_unittest.py
@@ -26,25 +26,41 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-import unittest
+import unittest2 as unittest
+from webkitpy.common.system.platforminfo_mock import MockPlatformInfo
from webkitpy.common.system.systemhost_mock import MockSystemHost
-from .profiler import ProfilerFactory, Instruments, GooglePProf
+from .profiler import ProfilerFactory, GooglePProf
class ProfilerFactoryTest(unittest.TestCase):
- def test_basic(self):
+ def _assert_default_profiler_name(self, os_name, expected_profiler_name):
+ profiler_name = ProfilerFactory.default_profiler_name(MockPlatformInfo(os_name))
+ self.assertEqual(profiler_name, expected_profiler_name)
+
+ def test_default_profilers(self):
+ self._assert_default_profiler_name('mac', 'iprofiler')
+ self._assert_default_profiler_name('linux', 'perf')
+ self._assert_default_profiler_name('win32', None)
+ self._assert_default_profiler_name('freebsd', None)
+
+ def test_default_profiler_output(self):
host = MockSystemHost()
self.assertFalse(host.filesystem.exists("/tmp/output"))
+
+ # Default mocks are Mac, so iprofile should be default.
profiler = ProfilerFactory.create_profiler(host, '/bin/executable', '/tmp/output')
self.assertTrue(host.filesystem.exists("/tmp/output"))
- self.assertEquals(profiler._output_path, "/tmp/output/test.trace")
+ self.assertEqual(profiler._output_path, "/tmp/output/test.dtps")
+ # Linux defaults to perf.
host.platform.os_name = 'linux'
profiler = ProfilerFactory.create_profiler(host, '/bin/executable', '/tmp/output')
- self.assertEquals(profiler._output_path, "/tmp/output/test.pprof")
+ self.assertEqual(profiler._output_path, "/tmp/output/test.data")
+
+class GooglePProfTest(unittest.TestCase):
def test_pprof_output_regexp(self):
pprof_output = """
sometimes
@@ -84,4 +100,4 @@ Total: 3770 samples
"""
host = MockSystemHost()
profiler = GooglePProf(host, '/bin/executable', '/tmp/output')
- self.assertEquals(profiler._first_ten_lines_of_profile(pprof_output), expected_first_ten_lines)
+ self.assertEqual(profiler._first_ten_lines_of_profile(pprof_output), expected_first_ten_lines)
diff --git a/Tools/Scripts/webkitpy/common/system/stack_utils_unittest.py b/Tools/Scripts/webkitpy/common/system/stack_utils_unittest.py
index 625acf2b3..3050adc99 100644
--- a/Tools/Scripts/webkitpy/common/system/stack_utils_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/stack_utils_unittest.py
@@ -27,7 +27,7 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import sys
-import unittest
+import unittest2 as unittest
from webkitpy.common.system import outputcapture
from webkitpy.common.system import stack_utils
@@ -42,11 +42,11 @@ class StackUtilsTest(unittest.TestCase):
def test_find_thread_stack_found(self):
thread_id = current_thread_id()
found_stack = stack_utils._find_thread_stack(thread_id)
- self.assertNotEqual(found_stack, None)
+ self.assertIsNotNone(found_stack)
def test_find_thread_stack_not_found(self):
found_stack = stack_utils._find_thread_stack(0)
- self.assertEqual(found_stack, None)
+ self.assertIsNone(found_stack)
def test_log_thread_state(self):
msgs = []
diff --git a/Tools/Scripts/webkitpy/common/system/user_unittest.py b/Tools/Scripts/webkitpy/common/system/user_unittest.py
index bd86d228f..49810b2e0 100644
--- a/Tools/Scripts/webkitpy/common/system/user_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/user_unittest.py
@@ -26,7 +26,7 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.common.system.user import User
diff --git a/Tools/Scripts/webkitpy/common/system/workspace.py b/Tools/Scripts/webkitpy/common/system/workspace.py
index 686837619..1d92aca13 100644
--- a/Tools/Scripts/webkitpy/common/system/workspace.py
+++ b/Tools/Scripts/webkitpy/common/system/workspace.py
@@ -67,7 +67,7 @@ class Workspace(object):
try:
self._executive.run_command(['zip', '-9', '-r', zip_path, '.'], cwd=source_path)
except ScriptError, e:
- _log.error("Workspace.create_zip failed:\n%s" % e.message_with_output())
+ _log.error("Workspace.create_zip failed in %s:\n%s" % (source_path, e.message_with_output()))
return None
return zip_class(zip_path)
diff --git a/Tools/Scripts/webkitpy/common/system/workspace_mock.py b/Tools/Scripts/webkitpy/common/system/workspace_mock.py
index 005f86cf3..02a5f4c29 100644
--- a/Tools/Scripts/webkitpy/common/system/workspace_mock.py
+++ b/Tools/Scripts/webkitpy/common/system/workspace_mock.py
@@ -32,4 +32,6 @@ class MockWorkspace(object):
return "%s/%s.%s" % (directory, name, extension)
def create_zip(self, zip_path, source_path):
+ self.zip_path = zip_path
+ self.source_path = source_path
return object() # Something that is not None
diff --git a/Tools/Scripts/webkitpy/common/system/workspace_unittest.py b/Tools/Scripts/webkitpy/common/system/workspace_unittest.py
index eca386ac3..8262f6cf1 100644
--- a/Tools/Scripts/webkitpy/common/system/workspace_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/workspace_unittest.py
@@ -26,7 +26,7 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.filesystem_mock import MockFileSystem
from webkitpy.common.system.outputcapture import OutputCapture
@@ -60,7 +60,7 @@ class WorkspaceTest(unittest.TestCase):
def test_create_zip_exception(self):
workspace = Workspace(None, MockExecutive(should_log=True, should_throw=True))
expected_logs = """MOCK run_command: ['zip', '-9', '-r', '/zip/path', '.'], cwd=/source/path
-Workspace.create_zip failed:
+Workspace.create_zip failed in /source/path:
MOCK ScriptError
MOCK output of child process
@@ -69,4 +69,4 @@ MOCK output of child process
def __init__(self, path):
self.filename = path
archive = OutputCapture().assert_outputs(self, workspace.create_zip, ["/zip/path", "/source/path", MockZipFile], expected_logs=expected_logs)
- self.assertEqual(archive, None)
+ self.assertIsNone(archive)
diff --git a/Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py b/Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py
index 22ba72082..1a0603c9e 100644
--- a/Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py
@@ -23,7 +23,7 @@
import shutil
import tempfile
-import unittest
+import unittest2 as unittest
import zipfile
from webkitpy.common.system.filesystem_mock import MockFileSystem
@@ -92,7 +92,3 @@ class ZipFileSetTest(unittest.TestCase):
def test_namelist(self):
self.assertTrue('some-file' in self._zip.namelist())
-
-
-if __name__ == '__main__':
- unittest.main()