diff --git a/debuggers/lldb/controllers/variable.cpp b/debuggers/lldb/controllers/variable.cpp --- a/debuggers/lldb/controllers/variable.cpp +++ b/debuggers/lldb/controllers/variable.cpp @@ -49,19 +49,18 @@ return; } - // update the value itself QPointer guarded_this(this); debugSession->addCommand(VarEvaluateExpression, varobj_, [guarded_this](const ResultRecord &r){ if (guarded_this && r.reason == "done" && r.hasField("value")) { - guarded_this->setValue(r["value"].literal()); + guarded_this->setValue(guarded_this->formatValue(r["value"].literal())); } }); // update children // remove all children first, this will cause some gliches in the UI, but there's no good way // that we can know if there's anything changed - if (isExpanded()) { + if (isExpanded() || !childCount()) { deleteChildren(); fetchMoreChildren(); } @@ -114,6 +113,9 @@ return Utils::quote(Utils::unquote(value, true)); } else if (value.startsWith('\'')) { return Utils::quote(Utils::unquote(value, true, '\''), '\''); + } else if (value.startsWith('b')) { + // this is a byte array, don't translate unicode, simply return without 'b' prefix + return value.mid(1); } return value; } diff --git a/debuggers/lldb/controllers/variablecontroller.cpp b/debuggers/lldb/controllers/variablecontroller.cpp --- a/debuggers/lldb/controllers/variablecontroller.cpp +++ b/debuggers/lldb/controllers/variablecontroller.cpp @@ -54,11 +54,11 @@ variableCollection()->watches()->reinstall(); } - if (autoUpdate() & UpdateLocals) { + if (autoUpdate() & UpdateLocals) { updateLocals(); - } + } - if ((autoUpdate() & UpdateLocals) || + if ((autoUpdate() & UpdateLocals) || ((autoUpdate() & UpdateWatches) && variableCollection()->watches()->childCount() > 0)) { debugSession()->updateAllVariables(); diff --git a/debuggers/lldb/formatters/helpers.py b/debuggers/lldb/formatters/helpers.py --- a/debuggers/lldb/formatters/helpers.py +++ b/debuggers/lldb/formatters/helpers.py @@ -21,7 +21,7 @@ # BEGIN: Utilities for wrapping differences of Python 2.x and Python 3 # Inspired by http://pythonhosted.org/six/ - +from __future__ import print_function import sys import lldb # Useful for very coarse version differentiation. @@ -38,11 +38,21 @@ return type(self).__next__(self) if PY3: unichr = chr + unicode = str else: unichr = unichr # END +def canonicalized_type_name(name): + """Canonicalize the type name for FindFirstType usage. + + 1 space between template arguments (after comma) + + no space before pointer * + otherwise FindFirstType returns None + """ + return name.replace(' ', '').replace(',', ', ') + + def quote(string, quote='"'): """Quote a string so it's suitable to be used in quote""" if isinstance(string, unicode): @@ -65,22 +75,25 @@ q=quote) -def unquote(string, quote='"'): +def unquote(data, quote='"'): """Unquote a string""" - if string.startswith(quote) and string.endswith(quote): - string = string.lstrip(quote).rstrip(quote) + if data.startswith(quote) and data.endswith(quote): + data = data[1:-1] ls = [] esc = False - for idx in range(0, len(string)): - ch = string[idx] - if ch == '\\': - if esc: - ls.append(ch) - esc = not esc - else: + for ch in data: + if esc: ls.append(ch) - string = ''.join(ls) - return string + esc = False + else: + if ch == '\\': + esc = True + else: + ls.append(ch) + if esc: + print('WARNING: unpaired escape') + data = ''.join(ls) + return data def invoke(val, method, args=''): @@ -105,8 +118,8 @@ # third, build expression expr = 'reinterpret_cast({})->{}({})'.format(ptype.GetName(), addr, method, args) res = frame.EvaluateExpression(expr) - #if not res.IsValid(): - #print 'Expr {} on value {} failed'.format(expr, val.GetName()) + # if not res.IsValid(): + # print 'Expr {} on value {} failed'.format(expr, val.GetName()) return res @@ -209,7 +222,7 @@ # it might be overwriten by others if we cache them. # child is a (name, expr) tuple in this case if len(child) != 2: - print 'error, const char[] value should be a tuple with two elements, it is', child + print('error, const char[] value should be a tuple with two elements, it is', child) return self.valobj.CreateValueFromExpression(*child) @staticmethod diff --git a/debuggers/lldb/formatters/qt.py b/debuggers/lldb/formatters/qt.py --- a/debuggers/lldb/formatters/qt.py +++ b/debuggers/lldb/formatters/qt.py @@ -18,16 +18,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function -import calendar import time import datetime as dt +import string from urlparse import urlsplit, urlunsplit import locale import lldb -from helpers import * +from helpers import (HiddenMemberProvider, quote, unquote, unichr, toSBPointer, Iterator, validAddr, + validPointer, invoke, rename, canonicalized_type_name) + def __lldb_init_module(debugger, unused): debugger.HandleCommand('type synthetic add QString -w kdevelop-qt -l qt.QStringFormatter') @@ -110,7 +113,6 @@ if isQt4: alloc += 1 - # some sanity check to see if we are dealing with garbage if size_val < 0 or size_val >= alloc: return None, 0, 0 @@ -120,7 +122,6 @@ tooLarge = u'...' size_val = HiddenMemberProvider._capping_size() - if isQt4: pointer = data.GetValueAsUnsigned(0) elif offset.IsValid(): @@ -143,8 +144,8 @@ # The QString object might be not yet initialized. In this case size is a bogus value, # and memory access may fail if error.Success(): - string = string_data.decode('utf-16') - return string + tooLarge, pointer, length + content = string_data.decode('utf-16') + return content + tooLarge, pointer, length except: pass return None, 0, 0 @@ -154,12 +155,14 @@ if valobj.IsValid(): content = valobj.GetChildMemberWithName('(content)') if content.IsValid(): - return content.GetSummary() - else: - # No synthetic provider installed, get the content by ourselves - printable, _, _ = printableQString(valobj) - if printable is not None: - return quote(printable) + summary = content.GetSummary() + if summary is not None: + return summary + # Something wrong with synthetic provider, or + # no synthetic provider installed, get the content by ourselves + printable, _, _ = printableQString(valobj) + if printable is not None: + return quote(printable) return '' @@ -189,7 +192,11 @@ def QCharSummaryProvider(valobj, internal_dict): if valobj.IsValid(): ucs = valobj.GetChildMemberWithName('ucs').GetValueAsUnsigned(0) - return unichr(ucs).__repr__().lstrip('u') + if ucs == 39: + # for '\'', python returns "'" rather than '\'' + return u"'\\''" + else: + return unichr(ucs).__repr__()[1:] return None @@ -234,14 +241,15 @@ if error.Success(): # replace non-ascii byte with a space and get a printable version ls = list(string_data) - for idx in range(0, length): - byte = ord(ls[idx]) - if byte >= 128 or byte < 0: - ls[idx] = hex(byte).replace('0', '\\', 1) - elif byte == 0: # specical handle for 0, as hex(0) returns '\\x0' - ls[idx] = '\\x00' - string = u''.join(ls) - return string + tooLarge, pointer, length + for idx in range(length): + if ls[idx] in string.printable: + if ls[idx] != "'": + # convert tab, nl, ..., and '\\' to r'\\' + ls[idx] = ls[idx].__repr__()[1:-1] + else: + ls[idx] = r'\x{:02x}'.format(ord(ls[idx])) + content = u''.join(ls) + return content + tooLarge, pointer, length except: pass return None, 0, 0 @@ -251,12 +259,17 @@ if valobj.IsValid(): content = valobj.GetChildMemberWithName('(content)') if content.IsValid(): - return content.GetSummary() - else: - # Our synthetic provider is not installed, get the content by ourselves - printable, _, _ = printableQByteArray(valobj) - if printable is not None: - return quote(printable) + summary = content.GetSummary() + if summary is not None: + # unlike QString, we quoted the (content) twice to preserve our own quotation, + # must undo the quotation done by GetSummary + return 'b' + unquote(summary) + # Something wrong with our synthetic provider, get the content by ourselves + printable, _, _ = printableQByteArray(valobj) + if printable is not None: + # first replace " to \", and suround by "", no need to escape other things which + # are handled in printableQByteArray. + return 'b"{}"'.format(printable.replace('"', '\\"')) return '' @@ -278,6 +291,12 @@ dataPointer + idx * self._char_size, self._char_type) self._addChild(var) + + # first replace " to \", and suround by "", no need to escape other things which + # are handled in printableQByteArray. + printable = '"{}"'.format(printable.replace('"', '\\"')) + # then we need to quote again, as the quoted_printable_expr is parsed by the lldb to + # produce the final content, which removes one level of quotation quoted_printable_expr = quote(printable) self._addChild(('(content)', quoted_printable_expr), hidden=True) @@ -294,20 +313,20 @@ pvoid_type = valobj.GetTarget().GetBasicType(lldb.eBasicTypeVoid).GetPointerType() self._pvoid_size = pvoid_type.GetByteSize() - #from QTypeInfo::isLarge + # from QTypeInfo::isLarge isLarge = self._item_type.GetByteSize() > self._pvoid_size - #unfortunately we can't use QTypeInfo::isStatic as it's all inlined, so use - #this list of types that use Q_DECLARE_TYPEINFO(T, Q_MOVABLE_TYPE) - #(obviously it won't work for custom types) + # unfortunately we can't use QTypeInfo::isStatic as it's all inlined, so use + # this list of types that use Q_DECLARE_TYPEINFO(T, Q_MOVABLE_TYPE) + # (obviously it won't work for custom types) movableTypes = ['QRect', 'QRectF', 'QString', 'QMargins', 'QLocale', 'QChar', 'QDate', 'QTime', 'QDateTime', 'QVector', 'QRegExpr', 'QPoint', 'QPointF', 'QByteArray', 'QSize', 'QSizeF', 'QBitArray', 'QLine', 'QLineF', 'QModelIndex', 'QPersitentModelIndex', 'QVariant', 'QFileInfo', 'QUrl', 'QXmlStreamAttribute', 'QXmlStreamNamespaceDeclaration', 'QXmlStreamNotationDeclaration', 'QXmlStreamEntityDeclaration', 'QPair'] movableTypes = [valobj.GetTarget().FindFirstType(t) for t in movableTypes] - #this list of types that use Q_DECLARE_TYPEINFO(T, Q_PRIMITIVE_TYPE) (from qglobal.h) + # this list of types that use Q_DECLARE_TYPEINFO(T, Q_PRIMITIVE_TYPE) (from qglobal.h) primitiveTypes = ['bool', 'char', 'signed char', 'unsigned char', 'short', 'unsigned short', 'int', 'unsigned int', 'long', 'unsigned long', 'long long', 'unsigned long long', 'float', 'double'] @@ -318,7 +337,7 @@ else: isStatic = not self._item_type.IsPointerType() - #see QList::Node::t() + # see QList::Node::t() self._externalStorage = isLarge or isStatic # If is external storage, then the node (a void*) is a pointer to item # else the item is stored inside the node @@ -400,7 +419,7 @@ if not toSBPointer(self.valobj, pArray, self._item_type).IsValid(): return - #self._num_children = d.GetChildMemberWithName('size').GetValueAsUnsigned(0) + # self._num_children = d.GetChildMemberWithName('size').GetValueAsUnsigned(0) self._num_children = d.GetChildMemberWithName('size').GetValueAsSigned(-1) if self._num_children < 0: return @@ -520,6 +539,7 @@ # the ' ' between two template arguments is significant, # otherwise FindFirstType returns None node_typename = 'QMapNode<{}, {}>'.format(key_type.GetName(), val_type.GetName()) + node_typename = canonicalized_type_name(node_typename) self._node_type = valobj.GetTarget().FindFirstType(node_typename) e = self.valobj.GetChildMemberWithName('e') @@ -697,10 +717,10 @@ self_type = valobj.GetType() self._key_type = self_type.GetTemplateArgumentType(0) self._val_type = self_type.GetTemplateArgumentType(1) - # the ' ' between two template arguments is significant, - # otherwise FindFirstType returns None node_typename = 'QHashNode<{}, {}>'.format(self._key_type.GetName(), self._val_type.GetName()) + node_typename = canonicalized_type_name(node_typename) + self._node_type = valobj.GetTarget().FindFirstType(node_typename) class _iterator(Iterator): @@ -768,6 +788,10 @@ idx = 0 for pnode in self._iterator(self.valobj, self._node_type.GetPointerType()): + if idx >= self._num_children: + self._members = [] + self._num_children = 0 + break # dereference node and change to a user friendly name name = '[{}]'.format(idx) idx += 1 @@ -797,6 +821,7 @@ self.valobj = self.actualobj.GetChildAtIndex(0) super(QMultiHashFormatter, self).update() + class QSetFormatter(HiddenMemberProvider): """lldb synthetic provider for QSet""" @@ -807,7 +832,6 @@ def num_children(self): return self._num_children - pass def _update(self): self._hash_formatter.valobj = self.valobj.GetChildMemberWithName('q_hash') @@ -840,76 +864,75 @@ if julianDay >= 2299161: # Gregorian calendar starting from October 15, 1582 # This algorithm is from Henry F. Fliegel and Thomas C. Van Flandern - ell = julianDay + 68569; - n = (4 * ell) / 146097; - ell = ell - (146097 * n + 3) / 4; - i = (4000 * (ell + 1)) / 1461001; - ell = ell - (1461 * i) / 4 + 31; - j = (80 * ell) / 2447; - d = ell - (2447 * j) / 80; - ell = j / 11; - m = j + 2 - (12 * ell); - y = 100 * (n - 49) + i + ell; + ell = julianDay + 68569 + n = (4 * ell) / 146097 + ell = ell - (146097 * n + 3) / 4 + i = (4000 * (ell + 1)) / 1461001 + ell = ell - (1461 * i) / 4 + 31 + j = (80 * ell) / 2447 + d = ell - (2447 * j) / 80 + ell = j / 11 + m = j + 2 - (12 * ell) + y = 100 * (n - 49) + i + ell else: # Julian calendar until October 4, 1582 # Algorithm from Frequently Asked Questions about Calendars by Claus Toendering - julianDay += 32082; - dd = (4 * julianDay + 3) / 1461; - ee = julianDay - (1461 * dd) / 4; - mm = ((5 * ee) + 2) / 153; - d = ee - (153 * mm + 2) / 5 + 1; - m = mm + 3 - 12 * (mm / 10); - y = dd - 4800 + (mm / 10); + julianDay += 32082 + dd = (4 * julianDay + 3) / 1461 + ee = julianDay - (1461 * dd) / 4 + mm = ((5 * ee) + 2) / 153 + d = ee - (153 * mm + 2) / 5 + 1 + m = mm + 3 - 12 * (mm / 10) + y = dd - 4800 + (mm / 10) if y <= 0: return None return dt.date(y, m, d) def _update(self): # FIXME: Calling functions returns incorrect SBValue for complex type in lldb - ## toString - #res = invoke(self.valobj, 'toString', '0') - #self._addChild(rename('toString', res)) + # # toString + # res = invoke(self.valobj, 'toString', '0') + # self._addChild(rename('toString', res)) # jd julianDay = self.valobj.GetChildMemberWithName('jd') self._addChild(julianDay) - pydate = self.parse(julianDay.GetValueAsUnsigned(0)) if pydate is None: return # (ISO) - iso_str = pydate.isoformat().decode().__repr__().lstrip("u'").rstrip("'") + iso_str = pydate.isoformat().decode().__repr__()[2:-1] self._addChild(('(ISO)', quote(iso_str))) # (Locale) locale_encoding = [locale.getlocale()[1]] if locale_encoding[0] is None: locale_encoding = [] - locale_str = pydate.strftime('%x').decode(*locale_encoding).__repr__().lstrip("u'").rstrip("'") + locale_str = pydate.strftime('%x').decode(*locale_encoding).__repr__()[2:-1] self._addChild(('(Locale)', quote(locale_str))) def QDateSummaryProvider(valobj, internal_dict): if valobj.IsValid(): content = valobj.GetChildMemberWithName('(Locale)') if content.IsValid(): - return content.GetSummary() - else: - # No synthetic provider installed, get the content by ourselves - pydate = QDateFormatter.parse(valobj.GetChildMemberWithName('jd').GetValueAsUnsigned(0)) - if pydate is not None: - content = pydate.isoformat().decode().__repr__().lstrip("u'").rstrip("'") - return quote(content) + summary = content.GetSummary() + if summary is not None: + return summary + # No synthetic provider installed, get the content by ourselves + pydate = QDateFormatter.parse(valobj.GetChildMemberWithName('jd').GetValueAsUnsigned(0)) + if pydate is not None: + return pydate.isoformat().decode().__repr__()[2:-1] return '' class QTimeFormatter(HiddenMemberProvider): """lldb synthetic provider for QTime""" def __init__(self, valobj, internal_dict): super(QTimeFormatter, self).__init__(valobj, internal_dict) self._add_original = False - + def has_children(self): return True @@ -923,15 +946,15 @@ hour = ds / MSECS_PER_HOUR minute = (ds % MSECS_PER_HOUR) / MSECS_PER_MIN - second = (ds / 1000)%SECS_PER_MIN + second = (ds / 1000) % SECS_PER_MIN msec = ds % 1000 return dt.time(hour, minute, second, msec) def _update(self): # FIXME: Calling functions returns incorrect SBValue for complex type in lldb - ## toString - #res = invoke(self.valobj, 'toString', '0') - #self._addChild(rename('toString', res)) + # # toString + # res = invoke(self.valobj, 'toString', '0') + # self._addChild(rename('toString', res)) # mds mds = self.valobj.GetChildMemberWithName('mds') @@ -941,36 +964,36 @@ if pytime is None: return # (ISO) - iso_str = pytime.isoformat().decode().__repr__().lstrip("u'").rstrip("'") + iso_str = pytime.isoformat().decode().__repr__()[2:-1] self._addChild(('(ISO)', quote(iso_str))) # (Locale) locale_encoding = [locale.getlocale()[1]] if locale_encoding[0] is None: locale_encoding = [] - locale_str = pytime.strftime('%X').decode(*locale_encoding).__repr__().lstrip("u'").rstrip("'") + locale_str = pytime.strftime('%X').decode(*locale_encoding).__repr__()[2:-1] self._addChild(('(Locale)', quote(locale_str))) def QTimeSummaryProvider(valobj, internal_dict): if valobj.IsValid(): content = valobj.GetChildMemberWithName('(Locale)') if content.IsValid(): - return content.GetSummary() - else: - # No synthetic provider installed, get the content by ourselves - pytime = QTimeFormatter.parse(valobj.GetChildMemberWithName('mds').GetValueAsUnsigned(0)) - if pytime is not None: - content = pytime.isoformat().decode().__repr__().lstrip("u'").rstrip("'") - return quote(content) + summary = content.GetSummary() + if summary is not None: + return summary + # No synthetic provider installed, get the content by ourselves + pytime = QTimeFormatter.parse(valobj.GetChildMemberWithName('mds').GetValueAsUnsigned(0)) + if pytime is not None: + return pytime.isoformat().decode().__repr__()[2:-1] return None class QDateTimeFormatter(HiddenMemberProvider): """lldb synthetic provider for QTime""" def __init__(self, valobj, internal_dict): super(QDateTimeFormatter, self).__init__(valobj, internal_dict) - + def has_children(self): return True @@ -1006,43 +1029,43 @@ utc_tt = self.parse(time_t.GetValueAsUnsigned(0), utc=True) # (ISO) - formatted = time.strftime('%Y-%m-%d %H:%M:%S', utc_tt).decode(*locale_encoding).__repr__().lstrip("u'").rstrip("'") + formatted = time.strftime('%Y-%m-%d %H:%M:%S', utc_tt).decode(*locale_encoding).__repr__() + formatted = formatted[2:-1] self._addChild(('(ISO)', quote(formatted))) def locale_fmt(name, tt): - formatted = time.strftime('%c', tt).decode(*locale_encoding).__repr__().lstrip("u'").rstrip("'") + formatted = time.strftime('%c', tt).decode(*locale_encoding).__repr__()[2:-1] self._addChild((name, quote(formatted))) # (Locale) locale_fmt('(Locale)', local_tt) # (UTC) locale_fmt('(UTC)', utc_tt) - + # FIXME: Calling functions returns incorrect SBValue for complex type in lldb - ## toString - #res = invoke(self.valobj, 'toString', '0') - #print 'tostring', res - #self._addChild(rename('toString', res)) + # # toString + # res = invoke(self.valobj, 'toString', '0') + # print 'tostring', res + # self._addChild(rename('toString', res)) - ## toLocalTime - #res = invoke(self.valobj, 'toTimeSpec', '0') # Qt::LocalTime == 0 - #print 'tolocaltime', res - #self._addChild(rename('toLocalTime', res)) + # # toLocalTime + # res = invoke(self.valobj, 'toTimeSpec', '0') # Qt::LocalTime == 0 + # print 'tolocaltime', res + # self._addChild(rename('toLocalTime', res)) def QDateTimeSummaryProvider(valobj, internal_dict): if valobj.IsValid(): content = valobj.GetChildMemberWithName('(Locale)') if content.IsValid(): - return content.GetSummary() - else: - # No synthetic provider installed, get the content by ourselves - pytime = QDateTimeFormatter.parse(QDateTimeFormatter.getdata(valobj).GetValueAsUnsigned(0)) - if pytime is not None: - #content = pytime.isoformat().decode().__repr__().lstrip("u'").rstrip("'") - #return quote(content) - pass + summary = content.GetSummary() + if summary is not None: + return summary + # No synthetic provider installed, get the content by ourselves + pytime = QDateTimeFormatter.parse(QDateTimeFormatter.getdata(valobj).GetValueAsUnsigned(0)) + if pytime is not None: + return pytime.isoformat().decode().__repr__()[2:-1] return None @@ -1169,51 +1192,64 @@ return (None,) * 9 return parseComponents(encoded) - def _update(self): - dataobj = self.valobj.GetChildMemberWithName('d') + @staticmethod + def try_parse(valobj): + dataobj = valobj.GetChildMemberWithName('d') # first try to access Qt4 data (encoded, port, scheme, username, password, host, path, query, fragment) = self.parseQt4Data(dataobj) if encoded is not None: - self._addChild(port) - self._addChild(scheme) - self._addChild(username) - self._addChild(password) - self._addChild(host) - self._addChild(path) - self._addChild(query) - self._addChild(fragment) - self._addChild(encoded, hidden=True) - return + return (encoded, port, scheme, username, password, host, path, query, fragment) # if this fails, maybe we deal with Qt5 (encoded, port, scheme, username, password, host, path, query, fragment) = self.parseQt5Data(dataobj) if encoded is not None: - self._addChild(port) - self._addChild(scheme) - self._addChild(username) - self._addChild(password) - self._addChild(host) - self._addChild(path) - self._addChild(query) - self._addChild(fragment) - self._addChild(encoded, hidden=True) - return + return (encoded, port, scheme, username, password, host, path, query, fragment) # if above fails, try to print directly. # But this might not work, and could lead to issues # (see http://sourceware-org.1504.n7.nabble.com/help-Calling-malloc-from-a-Python-pretty-printer-td284031.html) - res = invoke(self.valobj, 'toString', '(QUrl::FormattingOptions)0') # QUrl::PrettyDecoded == 0 + res = invoke(self.valobj, 'toString', '(QUrl::FormattingOptions)0') # QUrl::PrettyDecoded == 0 if res.IsValid(): - self._addChild(rename('(encoded)', res)) + return rename('(encoded)', res), None, None, None, None, None, None, None, None + return None, None, None, None, None, None, None, None, None + def _update(self): + (encoded, port, scheme, username, + password, host, path, query, fragment) = self.try_parse(self.valobj) + if encoded is not None: + self._addChild(encoded, hidden=True) + if port is not None: + self._addChild(port) + self._addChild(scheme) + self._addChild(username) + self._addChild(password) + self._addChild(host) + self._addChild(path) + self._addChild(query) + self._addChild(fragment) + return # if everything fails, we have no choice but to show the original member self._add_original = False self._addChild(self.valobj.GetChildMemberWithName('d')) +def QUrlSummaryProvider(valobj, internal_dict): + if valobj.IsValid(): + content = valobj.GetChildMemberWithName('(encoded)') + if content.IsValid(): + summary = content.GetSummary() + if summary is not None: + return summary + # No synthetic provider installed, get the content by ourselves + encoded = QUrlFormatter.try_parse(valobj)[0] + if encoded is not None: + return encoded + return None + + class QUuidFormatter(HiddenMemberProvider): """A lldb synthetic provider for QUuid""" def __init__(self, valobj, internal_dict): diff --git a/debuggers/lldb/unittests/debugees/debugeeqt.cpp b/debuggers/lldb/unittests/debugees/debugeeqt.cpp --- a/debuggers/lldb/unittests/debugees/debugeeqt.cpp +++ b/debuggers/lldb/unittests/debugees/debugeeqt.cpp @@ -27,6 +27,5 @@ x += QString::number(i); qDebug() << x; } - QString ustr = QString::fromUtf8("\u4f60\u597d\u4e16\u754c"); return 0; } diff --git a/debuggers/lldb/unittests/debugees/qbytearray.cpp b/debuggers/lldb/unittests/debugees/qbytearray.cpp --- a/debuggers/lldb/unittests/debugees/qbytearray.cpp +++ b/debuggers/lldb/unittests/debugees/qbytearray.cpp @@ -1,7 +1,7 @@ #include int main() { - QByteArray ba("test byte array"); + QByteArray ba("\xe6\x98\xaf'\"\\u6211"); ba.append("x"); return 0; } diff --git a/debuggers/lldb/unittests/debugees/qstring.cpp b/debuggers/lldb/unittests/debugees/qstring.cpp --- a/debuggers/lldb/unittests/debugees/qstring.cpp +++ b/debuggers/lldb/unittests/debugees/qstring.cpp @@ -1,7 +1,7 @@ #include int main() { - QString s("test string"); + QString s = QString::fromUtf8("test最后一个不是特殊字符'\"\\u6211"); s.append("x"); return 0; } diff --git a/debuggers/lldb/unittests/test_lldb.h b/debuggers/lldb/unittests/test_lldb.h --- a/debuggers/lldb/unittests/test_lldb.h +++ b/debuggers/lldb/unittests/test_lldb.h @@ -100,7 +100,6 @@ void testVariablesStartSecondSession(); void testVariablesSwitchFrame(); void testVariablesQuicklySwitchFrame(); - void testVariablesNonascii(); void testSwitchFrameLldbConsole(); void testSegfaultDebugee(); diff --git a/debuggers/lldb/unittests/test_lldb.cpp b/debuggers/lldb/unittests/test_lldb.cpp --- a/debuggers/lldb/unittests/test_lldb.cpp +++ b/debuggers/lldb/unittests/test_lldb.cpp @@ -1676,26 +1676,6 @@ WAIT_FOR_STATE(session, DebugSession::EndedState); } -void LldbTest::testVariablesNonascii() -{ - TestDebugSession *session = new TestDebugSession; - TestLaunchConfiguration cfg(findExecutable("lldb_debugeeqt")); - - session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); - - QString fileName = findSourceFile("debugeeqt.cpp"); - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 30); - - QVERIFY(session->startDebugging(&cfg, m_iface)); - WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - - QCOMPARE(session->currentLine(), 30); - COMPARE_DATA(localVariableIndexAt(0, 1), QString("\"\u4f60\u597d\u4e16\u754c\"")); - - session->run(); - WAIT_FOR_STATE(session, DebugSession::EndedState); -} - void LldbTest::testSwitchFrameLldbConsole() { TestDebugSession *session = new TestDebugSession; diff --git a/debuggers/lldb/unittests/test_lldbformatters.h b/debuggers/lldb/unittests/test_lldbformatters.h --- a/debuggers/lldb/unittests/test_lldbformatters.h +++ b/debuggers/lldb/unittests/test_lldbformatters.h @@ -49,10 +49,10 @@ void cleanup(); void testQString(); - /* void testQByteArray(); void testQListContainer_data(); void testQListContainer(); + /* void testQMapInt(); void testQMapString(); void testQMapStringBool(); @@ -73,13 +73,15 @@ private: // helpers - bool verifyQString(int index, const QString &name, const QString &expected, - const char *file, int line); + bool verifyVariable(int index, const QString &name, + const QString &expectedSummary, QStringList expectedChildren, + const char *file, int line, + bool isLocal = true, bool useRE = false, bool unordered = false); private: KDevelop::Breakpoint* addCodeBreakpoint(const QUrl& location, int line); KDevelop::VariableCollection *variableCollection(); - KDevMI::LLDB::LldbVariable *watchVariableAt(int i); + QModelIndex watchVariableIndexAt(int i, int col = 0); QModelIndex localVariableIndexAt(int i, int col = 0); KDevelop::TestCore *m_core; diff --git a/debuggers/lldb/unittests/test_lldbformatters.cpp b/debuggers/lldb/unittests/test_lldbformatters.cpp --- a/debuggers/lldb/unittests/test_lldbformatters.cpp +++ b/debuggers/lldb/unittests/test_lldbformatters.cpp @@ -40,18 +40,25 @@ #include #include +#include #include +#include #include #include +#include + #define WAIT_FOR_STATE(session, state) \ do { if (!KDevMI::LLDB::waitForState((session), (state), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR_STATE_AND_IDLE(session, state) \ do { if (!KDevMI::LLDB::waitForState((session), (state), __FILE__, __LINE__, true)) return; } while (0) -#define WAIT_FOR_A_WHILE(session, ms) \ - do { if (!KDevMI::LLDB::waitForAWhile((session), (ms), __FILE__, __LINE__)) return; } while (0) +#define WAIT_FOR_A_WHILE_AND_IDLE(session, ms) \ + do { if (!KDevMI::LLDB::waitForAWhile((session), (ms), __FILE__, __LINE__)) return; \ + if (!KDevMI::LLDB::waitForState((session), DebugSession::PausedState, __FILE__, __LINE__, true)) \ + return; \ + } while (0) #define WAIT_FOR(session, condition) \ do { \ @@ -62,8 +69,17 @@ #define COMPARE_DATA(index, expected) \ do { if (!KDevMI::LLDB::compareData((index), (expected), __FILE__, __LINE__)) return; } while (0) -#define VERIFY_QSTRING(row, name, expected) \ - do { if (!verifyQString((row), (name), (expected), __FILE__, __LINE__)) return; } while (0) +#define VERIFY_LOCAL(row, name, summary, children) \ + do { \ + if (!verifyVariable((row), (name), (summary), (children), __FILE__, __LINE__)) \ + return; \ + } while (0) + +#define VERIFY_WATCH(row, name, summary, children) \ + do { \ + if (!verifyVariable((row), (name), (summary), (children), __FILE__, __LINE__, false)) \ + return; \ + } while (0) #define findSourceFile(name) \ findSourceFile(__FILE__, (name)) @@ -120,19 +136,19 @@ return m_core->debugController()->variableCollection(); } -LldbVariable *LldbFormattersTest::watchVariableAt(int i) +QModelIndex LldbFormattersTest::watchVariableIndexAt(int i, int col) { auto watchRoot = variableCollection()->indexForItem(variableCollection()->watches(), 0); - auto idx = variableCollection()->index(i, 0, watchRoot); - return dynamic_cast(variableCollection()->itemForIndex(idx)); + return variableCollection()->index(i, col, watchRoot); } QModelIndex LldbFormattersTest::localVariableIndexAt(int i, int col) { auto localRoot = variableCollection()->indexForItem(variableCollection()->locals(), 0); return variableCollection()->index(i, col, localRoot); } +// Note: line is zero-based KDevelop::Breakpoint* LldbFormattersTest::addCodeBreakpoint(const QUrl& location, int line) { return m_core->debugController()->breakpointModel()->addCodeBreakpoint(location, line); @@ -168,7 +184,8 @@ m_core->debugController()->breakpointModel()->removeRows(0, count); while (variableCollection()->watches()->childCount() > 0) { - auto var = watchVariableAt(0); + auto idx = watchVariableIndexAt(0); + auto var = dynamic_cast(variableCollection()->itemForIndex(idx)); if (!var) break; var->die(); } @@ -185,42 +202,75 @@ m_session.clear(); } -bool LldbFormattersTest::verifyQString(int index, const QString &name, - const QString &expected, - const char *file, int line) +bool LldbFormattersTest::verifyVariable(int index, const QString &name, + const QString &expectedSummary, + QStringList expectedChildren, + const char *file, int line, + bool isLocal, bool useRE, bool unordered) { - auto varidx = localVariableIndexAt(index, 0); - auto var = variableCollection()->itemForIndex(varidx); + QModelIndex varIdx, summaryIdx; + if (isLocal) { + varIdx = localVariableIndexAt(index, 0); + summaryIdx = localVariableIndexAt(index, 1); + } else { + varIdx = watchVariableIndexAt(index, 0); + summaryIdx = watchVariableIndexAt(index, 1); + } - if (!compareData(varidx, name, file, line)) { + if (!compareData(varIdx, name, file, line)) { return false; } - if (!compareData(localVariableIndexAt(index, 1), Utils::quote(expected), file, line)) { + if (!compareData(summaryIdx, expectedSummary, file, line, useRE)) { return false; } // fetch all children + auto var = variableCollection()->itemForIndex(varIdx); auto childCount = 0; - while (childCount != variableCollection()->rowCount(varidx)) { - childCount = variableCollection()->rowCount(varidx); + while (childCount != variableCollection()->rowCount(varIdx)) { + childCount = variableCollection()->rowCount(varIdx); var->fetchMoreChildren(); if (!waitForAWhile(m_session, 50, file, line)) return false; } - if (childCount != expected.length()) { + if (childCount != expectedChildren.length()) { QTest::qFail(qPrintable(QString("'%0' didn't match expected '%1' in %2:%3") - .arg(childCount).arg(expected.length()).arg(file).arg(line)), + .arg(childCount).arg(expectedChildren.length()).arg(file).arg(line)), file, line); return false; } + QStringList actual; + for (int i = 0; i != childCount; ++i) { + auto index = variableCollection()->index(i, 1, varIdx); + actual << index.model()->data(index, Qt::DisplayRole).toString(); + } + if (unordered) { + qDebug() << "actual list sorted for unordered compare"; + actual.sort(); + expectedChildren.sort(); + qDebug() << "actual" << actual; + qDebug() << "expectedChildren" << expectedChildren; + } + for (int i = 0; i != childCount; ++i) { - if (!compareData(variableCollection()->index(i, 0, varidx), + if (!compareData(variableCollection()->index(i, 0, varIdx), QString("[%0]").arg(i), file, line)) { return false; } - if (!compareData(variableCollection()->index(i, 1, varidx), - QString("'%0'").arg(expected[i]), file, line)) { + + bool matched = true; + if (useRE) { + QRegularExpression re(expectedChildren[i]); + matched = re.match(actual[i]).hasMatch(); + } else { + matched = actual[i] == expectedChildren[i]; + } + if (!matched) { + QTest::qFail(qPrintable(QString("%0[%1] '%2' didn't match expected '%3' in %4:%5") + .arg(name).arg(i).arg(actual[i]).arg(expectedChildren[i]) + .arg(file).arg(line)), + file, line); return false; } } @@ -235,174 +285,265 @@ QVERIFY(m_session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + // Should be two rows ('auto', 'local') QCOMPARE(variableCollection()->rowCount(), 2); - VERIFY_QSTRING(0, "s", "test string"); + variableCollection()->expanded(localVariableIndexAt(0)); + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + QString expected = QString::fromUtf8("test最后一个不是特殊字符'\"\\u6211"); + QStringList children; + for (auto ch : expected) { + children << Utils::quote(ch, '\''); + } + + VERIFY_LOCAL(0, "s", Utils::quote(expected), children); m_session->stepOver(); WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); QCOMPARE(m_session->currentLine(), 5); - VERIFY_QSTRING(0, "s", "test stringx"); + expected.append("x"); + children << "'x'"; + + VERIFY_LOCAL(0, "s", Utils::quote(expected), children); m_session->run(); WAIT_FOR_STATE(m_session, DebugSession::EndedState); } -/* + void LldbFormattersTest::testQByteArray() { - LldbProcess lldb("qbytearray"); - lldb.execute("break qbytearray.cpp:5"); - lldb.execute("run"); - QByteArray out = lldb.execute("print ba"); - QVERIFY(out.contains("\"test byte array\"")); - QVERIFY(out.contains("[0] = 116 't'")); - QVERIFY(out.contains("[4] = 32 ' '")); - lldb.execute("next"); - QVERIFY(lldb.execute("print ba").contains("\"test byte arrayx\"")); + TestLaunchConfiguration cfg("lldb_qbytearray"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qbytearray.cpp")), 4); + + QVERIFY(m_session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + + // Should be two rows ('auto', 'local') + QCOMPARE(variableCollection()->rowCount(), 2); + + variableCollection()->expanded(localVariableIndexAt(0)); + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + QStringList charlist { + R"(-26 '\xe6')", + R"(-104 '\x98')", + R"(-81 '\xaf')", + R"(39 ''')", + R"(34 '"')", + R"(92 '\')", + R"(117 'u')", + R"(54 '6')", + R"(50 '2')", + R"(49 '1')", + R"(49 '1')", + }; + + VERIFY_LOCAL(0, "ba", R"("\xe6\x98\xaf'\"\\u6211")", charlist); + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + QCOMPARE(m_session->currentLine(), 5); + + charlist << "120 'x'"; + VERIFY_LOCAL(0, "ba", R"("\xe6\x98\xaf'\"\\u6211x")", charlist); + + m_session->run(); + WAIT_FOR_STATE(m_session, DebugSession::EndedState); } + void LldbFormattersTest::testQListContainer_data() { QTest::addColumn("container"); - - QTest::newRow("QList") << "QList"; - QTest::newRow("QQueue") << "QQueue"; - QTest::newRow("QVector") << "QVector"; - QTest::newRow("QStack") << "QStack"; - QTest::newRow("QLinkedList") << "QLinkedList"; - QTest::newRow("QSet") << "QSet"; + QTest::addColumn("unordered"); + + QTest::newRow("QList") << "QList" << false; + QTest::newRow("QQueue") << "QQueue" << false; + QTest::newRow("QVector") << "QVector" << false; + QTest::newRow("QStack") << "QStack" << false; + QTest::newRow("QLinkedList") << "QLinkedList" << false; + QTest::newRow("QSet") << "QSet" << true; } void LldbFormattersTest::testQListContainer() { QFETCH(QString, container); - LldbProcess lldb("qlistcontainer"); - lldb.execute("break main"); - lldb.execute("run"); - lldb.execute(QString("break 'doStuff<%1>()'").arg(container).toLocal8Bit()); - lldb.execute("cont"); - { // - lldb.execute("break qlistcontainer.cpp:34"); - lldb.execute("cont"); - QByteArray out = lldb.execute("print intList"); - QVERIFY(out.contains(QString("empty %1").arg(container).toLocal8Bit())); - lldb.execute("next"); - out = lldb.execute("print intList"); - QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); - if (container != "QSet") { - QVERIFY(out.contains("[0] = 10")); - QVERIFY(out.contains("[1] = 20")); - QVERIFY(!out.contains("[2] = 30")); - } else { // QSet order is undefined - QVERIFY(out.contains("] = 10")); - QVERIFY(out.contains("] = 20")); - QVERIFY(!out.contains("] = 30")); + QFETCH(bool, unordered); + + TestLaunchConfiguration cfg("lldb_qlistcontainer"); + cfg.config().writeEntry(KDevMI::Config::BreakOnStartEntry, true); + + auto watchRoot = variableCollection()->indexForItem(variableCollection()->watches(), 0); + variableCollection()->expanded(watchRoot); + variableCollection()->variableWidgetShown(); + + QVERIFY(m_session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + + m_session->addUserCommand(QString("break set --func doStuff<%1>()").arg(container)); + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + m_session->run(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + + // + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + auto var = variableCollection()->watches()->add("intList"); + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + if (!verifyVariable(0, "intList", "", QStringList{}, + __FILE__, __LINE__, false, false, unordered)) { + return; } - lldb.execute("next"); - out = lldb.execute("print intList"); - QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); - if (container != "QSet") { - QVERIFY(out.contains("[0] = 10")); - QVERIFY(out.contains("[1] = 20")); - QVERIFY(out.contains("[2] = 30")); - } else { // QSet order is undefined - QVERIFY(out.contains("] = 10")); - QVERIFY(out.contains("] = 20")); - QVERIFY(out.contains("] = 30")); + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + + variableCollection()->expanded(watchVariableIndexAt(0)); // expand this node for correct update. + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + if (!verifyVariable(0, "intList", "", QStringList{"10", "20"}, + __FILE__, __LINE__, false, false, unordered)) { + return; } + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + + if (!verifyVariable(0, "intList", "", QStringList{"10", "20", "30"}, + __FILE__, __LINE__, false, false, unordered)) { + return; } - { // - lldb.execute("next"); - QByteArray out = lldb.execute("print stringList"); - QVERIFY(out.contains(QString("empty %1").arg(container).toLocal8Bit())); - lldb.execute("next"); - out = lldb.execute("print stringList"); - QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); - if (container != "QSet") { - QVERIFY(out.contains("[0] = \"a\"")); - QVERIFY(out.contains("[1] = \"bc\"")); - QVERIFY(!out.contains("[2] = \"d\"")); - } else { // QSet order is undefined - QVERIFY(out.contains("] = \"a\"")); - QVERIFY(out.contains("] = \"bc\"")); - QVERIFY(!out.contains("] = \"d\"")); + var->die(); + + // + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + var = variableCollection()->watches()->add("stringList"); + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + if (!verifyVariable(0, "stringList", "", QStringList{}, + __FILE__, __LINE__, false, false, unordered)) { + return; } - lldb.execute("next"); - out = lldb.execute("print stringList"); - QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); - if (container != "QSet") { - QVERIFY(out.contains("[0] = \"a\"")); - QVERIFY(out.contains("[1] = \"bc\"")); - QVERIFY(out.contains("[2] = \"d\"")); - } else { // QSet order is undefined - QVERIFY(out.contains("] = \"a\"")); - QVERIFY(out.contains("] = \"bc\"")); - QVERIFY(out.contains("] = \"d\"")); + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + + variableCollection()->expanded(watchVariableIndexAt(0)); // expand this node for correct update. + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + + if (!verifyVariable(0, "stringList", "", QStringList{"\"a\"", "\"bc\""}, + __FILE__, __LINE__, false, false, unordered)) { + return; } + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + + if (!verifyVariable(0, "stringList", "", QStringList{"\"a\"", "\"bc\"", "\"d\""}, + __FILE__, __LINE__, false, false, unordered)) { + return; } - { // - lldb.execute("next"); - QByteArray out = lldb.execute("print structList"); - QVERIFY(out.contains(QString("empty %1").arg(container).toLocal8Bit())); - lldb.execute("next"); - out = lldb.execute("print structList"); - QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); - QVERIFY(out.contains("[0] = {")); - QVERIFY(out.contains("a = \"a\"")); - QVERIFY(out.contains("b = \"b\"")); - QVERIFY(out.contains("c = 100")); - QVERIFY(out.contains("d = -200")); - lldb.execute("next"); - out = lldb.execute("print structList"); - QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); - QVERIFY(out.contains("[1] = {")); + var->die(); + + // + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + var = variableCollection()->watches()->add("structList"); + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + if (!verifyVariable(0, "structList", "", QStringList{}, + __FILE__, __LINE__, false, false, unordered)) { + return; } - { // - lldb.execute("next"); - QByteArray out = lldb.execute("print pointerList"); - QVERIFY(out.contains(QString("empty %1").arg(container).toLocal8Bit())); - lldb.execute("next"); - out = lldb.execute("print pointerList"); - QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); - QVERIFY(out.contains("[0] = 0x")); - QVERIFY(out.contains("[1] = 0x")); - QVERIFY(!out.contains("[2] = 0x")); - lldb.execute("next"); - out = lldb.execute("print pointerList"); - QVERIFY(out.contains("[0] = 0x")); - QVERIFY(out.contains("[1] = 0x")); - QVERIFY(out.contains("[2] = 0x")); - lldb.execute("next"); + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + + variableCollection()->expanded(watchVariableIndexAt(0)); // expand this node for correct update. + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + if (!verifyVariable(0, "structList", "", QStringList{"{...}"}, + __FILE__, __LINE__, false, false, unordered)) { + return; } - { // > - lldb.execute("next"); - QByteArray out = lldb.execute("print pairList"); - QVERIFY(out.contains(QString("empty %1>").arg(container).toLocal8Bit())); - lldb.execute("next"); - out = lldb.execute("print pairList"); - QVERIFY(out.contains(QString("%1>").arg(container).toLocal8Bit())); - if (container != "QSet") { - QVERIFY(out.contains("[0] = {\n first = 1, \n second = 2\n }")); - QVERIFY(out.contains("[1] = {\n first = 2, \n second = 3\n }")); - } else { // order is undefined in QSet - QVERIFY(out.contains("] = {\n first = 1, \n second = 2\n }")); - QVERIFY(out.contains("] = {\n first = 2, \n second = 3\n }")); + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + + if (!verifyVariable(0, "structList", "", QStringList{"{...}", "{...}"}, + __FILE__, __LINE__, false, false, unordered)) { + return; } - QVERIFY(!out.contains("[2] = ")); - lldb.execute("next"); - out = lldb.execute("print pairList"); - if (container != "QSet") { - QVERIFY(out.contains("[0] = {\n first = 1, \n second = 2\n }")); - QVERIFY(out.contains("[1] = {\n first = 2, \n second = 3\n }")); - QVERIFY(out.contains("[2] = {\n first = 4, \n second = 5\n }")); - } else { // order is undefined in QSet - QVERIFY(out.contains("] = {\n first = 1, \n second = 2\n }")); - QVERIFY(out.contains("] = {\n first = 2, \n second = 3\n }")); - QVERIFY(out.contains("] = {\n first = 4, \n second = 5\n }")); + var->die(); + + // + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + var = variableCollection()->watches()->add("pointerList"); + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + if (!verifyVariable(0, "pointerList", "", QStringList{}, + __FILE__, __LINE__, false, false, unordered)) { + return; + } + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + + variableCollection()->expanded(watchVariableIndexAt(0)); // expand this node for correct update. + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + if (!verifyVariable(0, "pointerList", "", QStringList{"^0x[0-9A-Fa-f]+$", "^0x[0-9A-Fa-f]+$"}, + __FILE__, __LINE__, false, true, unordered)) { + return; + } + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + + if (!verifyVariable(0, "pointerList", "", QStringList{"^0x[0-9A-Fa-f]+$", "^0x[0-9A-Fa-f]+$", + "^0x[0-9A-Fa-f]+$"}, + __FILE__, __LINE__, false, true, unordered)) { + return; + } + var->die(); + m_session->stepOver(); // step over qDeleteAll + + // > + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + var = variableCollection()->watches()->add("pairList"); + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + VERIFY_WATCH(0, "pairList", "", QStringList{}); + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + + variableCollection()->expanded(watchVariableIndexAt(0)); // expand this node for correct update. + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + if (!verifyVariable(0, "pairList", "", QStringList{"{...}", "{...}"}, + __FILE__, __LINE__, false, false, unordered)) { + return; } + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + + if (!verifyVariable(0, "pairList", "", QStringList{"{...}", "{...}", "{...}"}, + __FILE__, __LINE__, false, false, unordered)) { + return; } + var->die(); } +/* void LldbFormattersTest::testQMapInt() { diff --git a/debuggers/lldb/unittests/testhelper.h b/debuggers/lldb/unittests/testhelper.h --- a/debuggers/lldb/unittests/testhelper.h +++ b/debuggers/lldb/unittests/testhelper.h @@ -40,7 +40,7 @@ QString findSourceFile(const char *file, const QString& name); bool isAttachForbidden(const char *file, int line); -bool compareData(QModelIndex index, QString expected, const char *file, int line); +bool compareData(QModelIndex index, QString expected, const char *file, int line, bool useRE = false); bool waitForState(MIDebugSession *session, KDevelop::IDebugSession::DebuggerState state, const char *file, int line, bool waitForIdle = false); diff --git a/debuggers/lldb/unittests/testhelper.cpp b/debuggers/lldb/unittests/testhelper.cpp --- a/debuggers/lldb/unittests/testhelper.cpp +++ b/debuggers/lldb/unittests/testhelper.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include namespace KDevMI { namespace LLDB { @@ -74,10 +75,17 @@ return false; } -bool compareData(QModelIndex index, QString expected, const char *file, int line) +bool compareData(QModelIndex index, QString expected, const char *file, int line, bool useRE) { QString s = index.model()->data(index, Qt::DisplayRole).toString(); - if (s != expected) { + bool matched = true; + if (useRE) { + QRegularExpression re(expected); + matched = re.match(s).hasMatch(); + } else { + matched = s == expected; + } + if (!matched) { QTest::qFail(qPrintable(QString("'%0' didn't match expected '%1' in %2:%3") .arg(s).arg(expected).arg(file).arg(line)), file, line);