2012-02-10 16:25:08 +01:00
|
|
|
import urllib2
|
|
|
|
import re
|
|
|
|
|
|
|
|
JIRA_URL='https://bugreports.qt-project.org/browse'
|
|
|
|
|
|
|
|
class JIRA:
|
|
|
|
__instance__ = None
|
|
|
|
|
|
|
|
# Helper class
|
|
|
|
class Bug:
|
|
|
|
CREATOR = 'QTCREATORBUG'
|
|
|
|
SIMULATOR = 'QTSIM'
|
|
|
|
SDK = 'QTSDK'
|
|
|
|
QT = 'QTBUG'
|
|
|
|
QT_QUICKCOMPONENTS = 'QTCOMPONENTS'
|
|
|
|
|
|
|
|
# constructor of JIRA
|
|
|
|
def __init__(self, number, bugType=Bug.CREATOR):
|
|
|
|
if JIRA.__instance__ == None:
|
|
|
|
JIRA.__instance__ = JIRA.__impl(number, bugType)
|
|
|
|
JIRA.__dict__['_JIRA__instance__'] = JIRA.__instance__
|
|
|
|
else:
|
|
|
|
JIRA.__instance__._bugType = bugType
|
|
|
|
JIRA.__instance__._number = number
|
2013-04-22 13:52:53 +02:00
|
|
|
JIRA.__instance__.__fetchResolutionFromJira__()
|
2012-02-10 16:25:08 +01:00
|
|
|
|
|
|
|
# overriden to make it possible to use JIRA just like the
|
|
|
|
# underlying implementation (__impl)
|
|
|
|
def __getattr__(self, attr):
|
|
|
|
return getattr(self.__instance__, attr)
|
|
|
|
|
|
|
|
# overriden to make it possible to use JIRA just like the
|
|
|
|
# underlying implementation (__impl)
|
|
|
|
def __setattr__(self, attr, value):
|
|
|
|
return setattr(self.__instance__, attr, value)
|
|
|
|
|
|
|
|
# function to get an instance of the singleton
|
|
|
|
@staticmethod
|
|
|
|
def getInstance():
|
|
|
|
if '_JIRA__instance__' in JIRA.__dict__:
|
|
|
|
return JIRA.__instance__
|
|
|
|
else:
|
|
|
|
return JIRA.__impl(0, Bug.CREATOR)
|
|
|
|
|
|
|
|
# function to check if the given bug is open or not
|
|
|
|
@staticmethod
|
|
|
|
def isBugStillOpen(number, bugType=Bug.CREATOR):
|
|
|
|
tmpJIRA = JIRA(number, bugType)
|
|
|
|
return tmpJIRA.isOpen()
|
|
|
|
|
|
|
|
# function similar to performWorkaroundForBug - but it will execute the
|
|
|
|
# workaround (function) only if the bug is still open
|
|
|
|
# returns True if the workaround function has been executed, False otherwise
|
|
|
|
@staticmethod
|
|
|
|
def performWorkaroundIfStillOpen(number, bugType=Bug.CREATOR, *args):
|
|
|
|
if JIRA.isBugStillOpen(number, bugType):
|
|
|
|
return JIRA.performWorkaroundForBug(number, bugType, *args)
|
|
|
|
else:
|
2013-04-22 13:52:53 +02:00
|
|
|
test.warning("Bug %s-%d is closed... skipping workaround!" % (bugType, number),
|
2012-02-10 16:25:08 +01:00
|
|
|
"You should remove potential code inside performWorkaroundForBug()")
|
|
|
|
return False
|
|
|
|
|
|
|
|
# function that performs the workaround (function) for the given bug
|
|
|
|
# if the function needs additional arguments pass them as 3rd parameter
|
|
|
|
@staticmethod
|
|
|
|
def performWorkaroundForBug(number, bugType=Bug.CREATOR, *args):
|
|
|
|
functionToCall = JIRA.getInstance().__bugs__.get("%s-%d" % (bugType, number), None)
|
|
|
|
if functionToCall:
|
|
|
|
test.warning("Using workaround for %s-%d" % (bugType, number))
|
|
|
|
functionToCall(*args)
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
JIRA.getInstance()._exitFatal_(bugType, number)
|
|
|
|
return False
|
|
|
|
|
|
|
|
# implementation of JIRA singleton
|
|
|
|
class __impl:
|
|
|
|
# constructor of __impl
|
|
|
|
def __init__(self, number, bugType):
|
|
|
|
self._number = number
|
|
|
|
self._bugType = bugType
|
|
|
|
self._localOnly = os.getenv("SYSTEST_JIRA_NO_LOOKUP")=="1"
|
|
|
|
self.__initBugDict__()
|
2012-02-24 16:13:46 +01:00
|
|
|
self._fetchResults_ = {}
|
2013-04-22 13:52:53 +02:00
|
|
|
self.__fetchResolutionFromJira__()
|
2012-02-10 16:25:08 +01:00
|
|
|
|
|
|
|
# this function checks the resolution of the given bug
|
|
|
|
# and returns True if the bug can still be assumed as 'Open' and False otherwise
|
|
|
|
def isOpen(self):
|
|
|
|
# handle special cases
|
|
|
|
if self._resolution == None:
|
|
|
|
return True
|
|
|
|
if self._resolution in ('Duplicate', 'Moved', 'Incomplete', 'Cannot Reproduce', 'Invalid'):
|
|
|
|
test.warning("Resolution of bug is '%s' - assuming 'Open' for now." % self._resolution,
|
|
|
|
"Please check the bugreport manually and update this test.")
|
|
|
|
return True
|
|
|
|
return self._resolution != 'Done'
|
|
|
|
|
2013-04-22 13:52:53 +02:00
|
|
|
# this function tries to fetch the resolution from JIRA for the given bug
|
2012-02-10 16:25:08 +01:00
|
|
|
# if this isn't possible or the lookup is disabled it does only check the internal
|
|
|
|
# dict whether a function for the given bug is deposited or not
|
2013-04-22 13:52:53 +02:00
|
|
|
def __fetchResolutionFromJira__(self):
|
2012-02-10 16:25:08 +01:00
|
|
|
global JIRA_URL
|
2012-02-24 16:13:46 +01:00
|
|
|
bug = "%s-%d" % (self._bugType, self._number)
|
|
|
|
if bug in self._fetchResults_:
|
|
|
|
result = self._fetchResults_[bug]
|
2013-04-22 13:52:53 +02:00
|
|
|
self._resolution = result
|
2012-02-24 16:13:46 +01:00
|
|
|
return
|
2012-02-10 16:25:08 +01:00
|
|
|
data = None
|
2012-03-06 14:21:20 +01:00
|
|
|
proxy = os.getenv("SYSTEST_PROXY", None)
|
2012-02-10 16:25:08 +01:00
|
|
|
if not self._localOnly:
|
|
|
|
try:
|
2012-03-06 14:21:20 +01:00
|
|
|
if proxy:
|
|
|
|
proxy = urllib2.ProxyHandler({'https': proxy})
|
|
|
|
opener = urllib2.build_opener(proxy)
|
|
|
|
urllib2.install_opener(opener)
|
2012-02-24 16:13:46 +01:00
|
|
|
bugReport = urllib2.urlopen('%s/%s' % (JIRA_URL, bug))
|
2012-02-10 16:25:08 +01:00
|
|
|
data = bugReport.read()
|
|
|
|
except:
|
2012-03-06 14:21:20 +01:00
|
|
|
data = self.__tryExternalTools__(proxy)
|
2012-02-10 16:25:08 +01:00
|
|
|
if data == None:
|
|
|
|
test.warning("Sorry, ssl module missing - cannot fetch data via HTTPS",
|
|
|
|
"Try to install the ssl module by yourself, or set the python "
|
|
|
|
"path inside SQUISHDIR/etc/paths.ini to use a python version with "
|
|
|
|
"ssl support OR install wget or curl to get rid of this warning!")
|
|
|
|
self._localOnly = True
|
|
|
|
if data == None:
|
2012-02-24 16:13:46 +01:00
|
|
|
if bug in self.__bugs__:
|
2012-02-10 16:25:08 +01:00
|
|
|
test.warning("Using internal dict - bug status could have changed already",
|
|
|
|
"Please check manually!")
|
|
|
|
self._resolution = None
|
|
|
|
else:
|
2012-02-24 16:13:46 +01:00
|
|
|
test.fatal("No workaround function deposited for %s" % bug)
|
2012-02-10 16:25:08 +01:00
|
|
|
self._resolution = 'Done'
|
|
|
|
else:
|
|
|
|
data = data.replace("\r", "").replace("\n", "")
|
|
|
|
resPattern = re.compile('<span\s+id="resolution-val".*?>(?P<resolution>.*?)</span>')
|
|
|
|
resolution = resPattern.search(data)
|
2012-02-24 16:13:46 +01:00
|
|
|
if resolution:
|
|
|
|
self._resolution = resolution.group("resolution").strip()
|
|
|
|
else:
|
|
|
|
test.fatal("FATAL: Cannot get resolution of bugreport %s" % bug,
|
|
|
|
"Looks like JIRA has changed.... Please verify!")
|
|
|
|
self._resolution = None
|
2013-04-22 13:52:53 +02:00
|
|
|
if self._resolution == None:
|
2012-03-06 13:42:13 +01:00
|
|
|
self.__cropAndLog__(data)
|
2013-04-22 13:52:53 +02:00
|
|
|
self._fetchResults_.update({bug:self._resolution})
|
2012-02-10 16:25:08 +01:00
|
|
|
|
|
|
|
# simple helper function - used as fallback if python has no ssl support
|
|
|
|
# tries to find curl or wget in PATH and fetches data with it instead of
|
|
|
|
# using urllib2
|
2012-03-06 14:21:20 +01:00
|
|
|
def __tryExternalTools__(self, proxy=None):
|
2012-02-10 16:25:08 +01:00
|
|
|
global JIRA_URL
|
2012-03-06 14:21:20 +01:00
|
|
|
if proxy:
|
|
|
|
cmdAndArgs = { 'curl':'-k --proxy %s' % proxy,
|
|
|
|
'wget':'-qO-'}
|
|
|
|
else:
|
|
|
|
cmdAndArgs = { 'curl':'-k', 'wget':'-qO-' }
|
2012-02-10 16:25:08 +01:00
|
|
|
for call in cmdAndArgs:
|
|
|
|
prog = which(call)
|
|
|
|
if prog:
|
2012-03-06 14:21:20 +01:00
|
|
|
if call == 'wget' and proxy and os.getenv("https_proxy", None) == None:
|
|
|
|
test.warning("Missing environment variable https_proxy for using wget with proxy!")
|
2012-02-27 16:56:55 +01:00
|
|
|
return getOutputFromCmdline('"%s" %s %s/%s-%d' % (prog, cmdAndArgs[call], JIRA_URL, self._bugType, self._number))
|
2012-02-10 16:25:08 +01:00
|
|
|
return None
|
|
|
|
|
2012-03-06 13:42:13 +01:00
|
|
|
# this function crops multiple whitespaces from fetched and searches for expected
|
|
|
|
# ids without using regex
|
|
|
|
def __cropAndLog__(self, fetched):
|
2012-04-10 14:13:07 +02:00
|
|
|
if fetched == None:
|
|
|
|
test.log("None passed to __cropAndLog__()")
|
|
|
|
return
|
2012-03-06 13:42:13 +01:00
|
|
|
fetched = " ".join(fetched.split())
|
|
|
|
resoInd = fetched.find('resolution-val')
|
|
|
|
statInd = fetched.find('status-val')
|
|
|
|
if resoInd == statInd == -1:
|
|
|
|
test.log("Neither resolution nor status found inside fetched data.",
|
|
|
|
"%s[...]" % fetched[:200])
|
|
|
|
else:
|
|
|
|
if resoInd == -1:
|
|
|
|
test.log("Fetched and cropped data: [...]%s[...]" % fetched[statInd-20:statInd+800])
|
|
|
|
elif statInd == -1:
|
|
|
|
test.log("Fetched and cropped data: [...]%s[...]" % fetched[resoInd-720:resoInd+100])
|
|
|
|
else:
|
|
|
|
test.log("Fetched and cropped data (status): [...]%s[...]" % fetched[statInd-20:statInd+300],
|
|
|
|
"Fetched and cropped data (resolution): [...]%s[...]" % fetched[resoInd-20:resoInd+100])
|
|
|
|
|
2012-02-10 16:25:08 +01:00
|
|
|
# this function initializes the bug dict for localOnly usage and
|
|
|
|
# for later lookup which function to call for which bug
|
|
|
|
# ALWAYS update this dict when adding a new function for a workaround!
|
|
|
|
def __initBugDict__(self):
|
|
|
|
self.__bugs__= {
|
2012-02-15 18:18:52 +01:00
|
|
|
'QTCREATORBUG-6853':self._workaroundCreator6853_,
|
2013-04-02 20:03:33 +02:00
|
|
|
'QTCREATORBUG-8735':self._workaroundCreator_MacEditorFocus_
|
2012-02-10 16:25:08 +01:00
|
|
|
}
|
|
|
|
# helper function - will be called if no workaround for the requested bug is deposited
|
|
|
|
def _exitFatal_(self, bugType, number):
|
|
|
|
test.fatal("No workaround found for bug %s-%d" % (bugType, number))
|
|
|
|
|
|
|
|
############### functions that hold workarounds #################################
|
|
|
|
|
2012-02-15 18:18:52 +01:00
|
|
|
def _workaroundCreator6853_(self, *args):
|
|
|
|
if "Release" in args[0] and platform.system() == "Linux":
|
2012-02-24 16:13:46 +01:00
|
|
|
snooze(2)
|
2012-02-15 18:18:52 +01:00
|
|
|
|
2012-02-10 16:25:08 +01:00
|
|
|
def _workaroundCreator_MacEditorFocus_(self, *args):
|
|
|
|
editor = args[0]
|
|
|
|
nativeMouseClick(editor.mapToGlobal(QPoint(50, 50)).x, editor.mapToGlobal(QPoint(50, 50)).y, Qt.LeftButton)
|