diff --git a/debuggers/lldb/TODO.txt b/debuggers/lldb/TODO.txt --- a/debuggers/lldb/TODO.txt +++ b/debuggers/lldb/TODO.txt @@ -46,7 +46,11 @@ + [DONE] KDevelop::Path + [DONE] KTextEditor::Cursor + [DONE] KTextEditor::Range + * lldb-mi doesn't properly quote string in most MI records, so can't use \\ or " in names - Finish unit tests for LLDB data formatters +- Qt data formatter cause hangs data size is too large + * [DONE] use dynamic caching + * invalid object detect - [DONE] Show application exit reason in the Debug View @@ -57,9 +61,6 @@ * disassembly widget * memory view -- Qt data formatter cause hangs data size is too large - * use dynamic caching - - [DONE] Find a way to avoid duplicate tool views for GDB and LLDB plugin - Polish debugger console 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/kde.py b/debuggers/lldb/formatters/kde.py --- a/debuggers/lldb/formatters/kde.py +++ b/debuggers/lldb/formatters/kde.py @@ -19,13 +19,10 @@ # along with this program. If not, see . # -import lldb - -from helpers import * def __lldb_init_module(debugger, unused): debugger.HandleCommand('type summary add KDevelop::Path -w kdevelop-kde -F kde.KDevPathSummaryProvider') - debugger.HandleCommand('type summary add KTextEditor::Cursor -w kdevelop-kde -F kde.KTextEditorCursorSummaryProvider') + debugger.HandleCommand('type summary add KTextEditor::Cursor -w kdevelop-kde -F kde.KTextEditorCursorSummaryProvider') # noqa: E501 debugger.HandleCommand('type summary add KTextEditor::Range -w kdevelop-kde -F kde.KTextEditorRangeSummaryProvider') debugger.HandleCommand('type category enable kdevelop-kde') 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') @@ -87,7 +90,7 @@ debugger.HandleCommand('type summary add -x QDateTime -w kdevelop-qt -e -F qt.QDateTimeSummaryProvider') debugger.HandleCommand('type synthetic add QUrl -w kdevelop-qt -l qt.QUrlFormatter') - debugger.HandleCommand('type summary add QUrl -w kdevelop-qt -e -s "${svar.(encoded)}"') + debugger.HandleCommand('type summary add QUrl -w kdevelop-qt -e -F qt.QUrlSummaryProvider') debugger.HandleCommand('type synthetic add QUuid -w kdevelop-qt -l qt.QUuidFormatter') debugger.HandleCommand('type summary add QUuid -w kdevelop-qt -F qt.QUuidSummaryProvider') @@ -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 @@ -506,7 +525,9 @@ key = valobj.GetChildMemberWithName('key') value = valobj.GetChildMemberWithName('value') - return '({}, {})'.format(key.GetSummary(), value.GetValue()) + key_summary = key.GetSummary() or key.GetValue() # show value if summary is empty or None + val_summary = value.GetSummary() or value.GetValue() # show value if summary is empty or None + return '({}, {})'.format(key_summary, val_summary) class BasicMapFormatter(HiddenMemberProvider): @@ -520,6 +541,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 +719,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 +790,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 +823,7 @@ self.valobj = self.actualobj.GetChildAtIndex(0) super(QMultiHashFormatter, self).update() + class QSetFormatter(HiddenMemberProvider): """lldb synthetic provider for QSet""" @@ -807,7 +834,6 @@ def num_children(self): return self._num_children - pass def _update(self): self._hash_formatter.valobj = self.valobj.GetChildMemberWithName('q_hash') @@ -840,76 +866,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 +948,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 +966,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 +1031,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 @@ -1065,10 +1090,11 @@ username_str = printableQString(username)[0] if username_str is not None: netloc += username_str - password_str = printableQString(password)[0] - if password_str is not None: - netloc += ':' + password_str - netloc += "@" + host_str + password_str = printableQString(password)[0] + if password_str is not None: + netloc += ':' + password_str + netloc += "@" + netloc += host_str port_num = port.GetValueAsSigned(-1) if port_num != -1: netloc += ":" + str(port_num) @@ -1169,51 +1195,63 @@ return (None,) * 9 return parseComponents(encoded) - def _update(self): + def try_parse(self): dataobj = self.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() + 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(valobj, internal_dict).try_parse()[0][1] + 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 @@ -23,8 +23,11 @@ #ifndef LLDBFORMATTERSTEST_H #define LLDBFORMATTERSTEST_H +#include #include +#include #include +#include class IExecutePlugin; @@ -48,38 +51,43 @@ void init(); void cleanup(); + void testQChar(); void testQString(); - /* void testQByteArray(); void testQListContainer_data(); void testQListContainer(); + void testQListPOD(); void testQMapInt(); void testQMapString(); void testQMapStringBool(); - void testQDate(); - void testQTime(); - void testQDateTime(); - void testQUrl(); void testQHashInt(); void testQHashString(); void testQSetInt(); void testQSetString(); - void testQChar(); - void testQListPOD(); + void testQDate(); + void testQTime(); + void testQDateTime(); + void testQUrl(); void testQUuid(); void testKTextEditorTypes(); void testKDevelopTypes(); - */ 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, QList> expectedChildren, + const char *file, int line, + bool isLocal = true, bool useRE = false, bool unordered = false); + + 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,415 +202,806 @@ 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) +{ + QList> childrenPairs; + childrenPairs.reserve(expectedChildren.size()); + if (unordered) { + qDebug() << "useRE set to true when unordered = true"; + useRE = true; + expectedChildren.sort(); + for (auto c : expectedChildren) { + childrenPairs << qMakePair(QStringLiteral(R"(^\[\d+\]$)"), c); + } + } else { + for (int i = 0; i != expectedChildren.size(); ++i) { + childrenPairs << qMakePair(QStringLiteral("[%0]").arg(i), expectedChildren[i]); + } + } + return verifyVariable(index, name, expectedSummary, childrenPairs, file, line, isLocal, useRE, unordered); +} + +bool LldbFormattersTest::verifyVariable(int index, const QString &name, + const QString &expectedSummary, + QList> 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; } + std::vector theOrder; + theOrder.reserve(childCount); + for (int i = 0; i != childCount; ++i) { + theOrder.push_back(i); + } + if (unordered) { + qDebug() << "actual list sorted for unordered compare"; + std::sort(theOrder.begin(), theOrder.end(), [&](int a, int b){ + auto indexA = variableCollection()->index(a, 1, varIdx); + auto indexB = variableCollection()->index(b, 1, varIdx); + return indexA.model()->data(indexA, Qt::DisplayRole).toString() + < indexB.model()->data(indexB, Qt::DisplayRole).toString(); + }); + std::sort(expectedChildren.begin(), expectedChildren.end(), + [](const QPair &a, const QPair &b){ + return a.second < b.second; + }); + qDebug() << "sorted actual order" << theOrder; + qDebug() << "sorted expectedChildren" << expectedChildren; + } + for (int i = 0; i != childCount; ++i) { - if (!compareData(variableCollection()->index(i, 0, varidx), - QString("[%0]").arg(i), file, line)) { + if (!compareData(variableCollection()->index(theOrder[i], 0, varIdx), + expectedChildren[i].first, file, line, useRE)) { return false; } - if (!compareData(variableCollection()->index(i, 1, varidx), - QString("'%0'").arg(expected[i]), file, line)) { + + if (!compareData(variableCollection()->index(theOrder[i], 1, varIdx), + expectedChildren[i].second, file, line, useRE)) { return false; } } return true; } +void LldbFormattersTest::testQChar() +{ + TestLaunchConfiguration cfg("lldb_qchar"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qchar.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); + + QList> children; + children << qMakePair(QStringLiteral("ucs"), QStringLiteral("107")); + + VERIFY_LOCAL(0, "c", "'k'", children); +} + void LldbFormattersTest::testQString() { TestLaunchConfiguration cfg("lldb_qstring"); addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qstring.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); - 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")); - } - 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")); - } - } - { // - 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\"")); - } - 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\"")); - } - } - { // - 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] = {")); - } - { // - 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"); - } - { // > - 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 }")); - } - 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 }")); + 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; } + + 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; + } + 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; + } + + 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; + } + 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; + } + + 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; + } + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + + if (!verifyVariable(0, "structList", "", QStringList{"{...}", "{...}"}, + __FILE__, __LINE__, false, false, unordered)) { + return; + } + 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::testQListPOD() +{ + TestLaunchConfiguration cfg("lldb_qlistpod"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qlistpod.cpp")), 30); + + 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); + + auto watchRoot = variableCollection()->indexForItem(variableCollection()->watches(), 0); + variableCollection()->expanded(watchRoot); + variableCollection()->variableWidgetShown(); + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + variableCollection()->watches()->add("b"); + variableCollection()->watches()->add("c"); + variableCollection()->watches()->add("uc"); + variableCollection()->watches()->add("s"); + variableCollection()->watches()->add("us"); + variableCollection()->watches()->add("i"); + variableCollection()->watches()->add("ui"); + variableCollection()->watches()->add("l"); + variableCollection()->watches()->add("ul"); + variableCollection()->watches()->add("i64"); + variableCollection()->watches()->add("ui64"); + variableCollection()->watches()->add("f"); + variableCollection()->watches()->add("d"); + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + VERIFY_WATCH(0, "b", "", (QStringList{"false"})); + VERIFY_WATCH(1, "c", "", (QStringList{"50 '2'"})); + VERIFY_WATCH(2, "uc", "", (QStringList{"50 '2'"})); + + VERIFY_WATCH(3, "s", "", (QStringList{"50"})); + VERIFY_WATCH(4, "us", "", (QStringList{"50"})); + + VERIFY_WATCH(5, "i", "", (QStringList{"50"})); + VERIFY_WATCH(6, "ui", "", (QStringList{"50"})); + + VERIFY_WATCH(7, "l", "", (QStringList{"50"})); + VERIFY_WATCH(8, "ul", "", (QStringList{"50"})); + + VERIFY_WATCH(9, "i64", "", (QStringList{"50"})); + VERIFY_WATCH(10, "ui64", "", (QStringList{"50"})); + + VERIFY_WATCH(11, "f", "", (QStringList{"50"})); + VERIFY_WATCH(12, "d", "", (QStringList{"50"})); } void LldbFormattersTest::testQMapInt() { - LldbProcess lldb("qmapint"); - lldb.execute("break qmapint.cpp:7"); - lldb.execute("run"); - QByteArray out = lldb.execute("print m"); - QVERIFY(out.contains("QMap")); - QVERIFY(out.contains("[10] = 100")); - QVERIFY(out.contains("[20] = 200")); - lldb.execute("next"); - out = lldb.execute("print m"); - QVERIFY(out.contains("[30] = 300")); + TestLaunchConfiguration cfg("lldb_qmapint"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qmapint.cpp")), 6); + + 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); + + VERIFY_LOCAL(0, "m", "", (QStringList{"(10, 100)", "(20, 200)"})); + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + QCOMPARE(m_session->currentLine(), 7); + + VERIFY_LOCAL(0, "m", "", (QStringList{"(10, 100)", "(20, 200)", "(30, 300)"})); } void LldbFormattersTest::testQMapString() { - LldbProcess lldb("qmapstring"); - lldb.execute("break qmapstring.cpp:8"); - lldb.execute("run"); - QByteArray out = lldb.execute("print m"); - QVERIFY(out.contains("QMap")); - QVERIFY(out.contains("[\"10\"] = \"100\"")); - QVERIFY(out.contains("[\"20\"] = \"200\"")); - lldb.execute("next"); - out = lldb.execute("print m"); - QVERIFY(out.contains("[\"30\"] = \"300\"")); + TestLaunchConfiguration cfg("lldb_qmapstring"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qmapstring.cpp")), 7); + + 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); + + VERIFY_LOCAL(0, "m", "", (QStringList{"(\"10\", \"100\")", "(\"20\", \"200\")"})); + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + QCOMPARE(m_session->currentLine(), 8); + + VERIFY_LOCAL(0, "m", "", + (QStringList{"(\"10\", \"100\")", "(\"20\", \"200\")", "(\"30\", \"300\")"})); } void LldbFormattersTest::testQMapStringBool() { - LldbProcess lldb("qmapstringbool"); - lldb.execute("break qmapstringbool.cpp:8"); - lldb.execute("run"); - QByteArray out = lldb.execute("print m"); - QVERIFY(out.contains("QMap")); - QVERIFY(out.contains("[\"10\"] = true")); - QVERIFY(out.contains("[\"20\"] = false")); - lldb.execute("next"); - out = lldb.execute("print m"); - QVERIFY(out.contains("[\"30\"] = true")); -} + TestLaunchConfiguration cfg("lldb_qmapstringbool"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qmapstringbool.cpp")), 7); + QVERIFY(m_session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); -void LldbFormattersTest::testQDate() -{ - LldbProcess lldb("qdate"); - lldb.execute("break qdate.cpp:6"); - lldb.execute("run"); - QByteArray out = lldb.execute("print d"); - QVERIFY(out.contains("2010-01-20")); -} + // Should be two rows ('auto', 'local') + QCOMPARE(variableCollection()->rowCount(), 2); -void LldbFormattersTest::testQTime() -{ - LldbProcess lldb("qtime"); - lldb.execute("break qtime.cpp:6"); - lldb.execute("run"); - QByteArray out = lldb.execute("print t"); - QVERIFY(out.contains("15:30:10.123")); -} + variableCollection()->expanded(localVariableIndexAt(0)); + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); -void LldbFormattersTest::testQDateTime() -{ - LldbProcess lldb("qdatetime"); - lldb.execute("break qdatetime.cpp:5"); - lldb.execute("run"); - QByteArray out = lldb.execute("print dt"); - QVERIFY(out.contains("Wed Jan 20 15:31:13 2010")); -} + VERIFY_LOCAL(0, "m", "", (QStringList{"(\"10\", true)", "(\"20\", false)"})); -void LldbFormattersTest::testQUrl() -{ - LldbProcess lldb("qurl"); - lldb.execute("break qurl.cpp:5"); - lldb.execute("run"); - QByteArray out = lldb.execute("print u"); - QVERIFY(out.contains("http://www.kdevelop.org/foo")); + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + QCOMPARE(m_session->currentLine(), 8); + + VERIFY_LOCAL(0, "m", "", (QStringList{"(\"10\", true)", "(\"20\", false)", "(\"30\", true)"})); } + void LldbFormattersTest::testQHashInt() { - LldbProcess lldb("qhashint"); - lldb.execute("break qhashint.cpp:7"); - lldb.execute("run"); - QByteArray out = lldb.execute("print h"); - QVERIFY(out.contains("[10] = 100")); - QVERIFY(out.contains("[20] = 200")); - lldb.execute("next"); - out = lldb.execute("print h"); - QVERIFY(out.contains("[30] = 300")); + TestLaunchConfiguration cfg("lldb_qhashint"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qhashint.cpp")), 6); + + 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); + + if (!verifyVariable(0, "h", "", {"(10, 100)", "(20, 200)"}, + __FILE__, __LINE__, true, false, true)) { + return; + } + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + QCOMPARE(m_session->currentLine(), 7); + + if (!verifyVariable(0, "h", "", {"(10, 100)", "(20, 200)", "(30, 300)"}, + __FILE__, __LINE__, true, false, true)) { + return; + } } void LldbFormattersTest::testQHashString() { - LldbProcess lldb("qhashstring"); - lldb.execute("break qhashstring.cpp:8"); - lldb.execute("run"); - QByteArray out = lldb.execute("print h"); - QVERIFY(out.contains("[\"10\"] = \"100\"")); - QVERIFY(out.contains("[\"20\"] = \"200\"")); - lldb.execute("next"); - out = lldb.execute("print h"); - QVERIFY(out.contains("[\"30\"] = \"300\"")); + TestLaunchConfiguration cfg("lldb_qhashstring"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qhashstring.cpp")), 7); + + 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); + + if (!verifyVariable(0, "h", "", {"(\"10\", \"100\")", "(\"20\", \"200\")"}, + __FILE__, __LINE__, true, false, true)) { + return; + } + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + QCOMPARE(m_session->currentLine(), 8); + + if (!verifyVariable(0, "h", "", + {"(\"10\", \"100\")", "(\"20\", \"200\")", "(\"30\", \"300\")"}, + __FILE__, __LINE__, true, false, true)) { + return; + } } void LldbFormattersTest::testQSetInt() { - LldbProcess lldb("qsetint"); - lldb.execute("break qsetint.cpp:7"); - lldb.execute("run"); - QByteArray out = lldb.execute("print s"); - QVERIFY(out.contains("] = 10")); - QVERIFY(out.contains("] = 20")); - lldb.execute("next"); - out = lldb.execute("print s"); - QVERIFY(out.contains("] = 30")); + TestLaunchConfiguration cfg("lldb_qsetint"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qsetint.cpp")), 6); + + 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); + + if (!verifyVariable(0, "s", "", {"10", "20"}, + __FILE__, __LINE__, true, false, true)) { + return; + } + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + QCOMPARE(m_session->currentLine(), 7); + + if (!verifyVariable(0, "s", "", {"10", "20", "30"}, + __FILE__, __LINE__, true, false, true)) { + return; + } } void LldbFormattersTest::testQSetString() { - LldbProcess lldb("qsetstring"); - lldb.execute("break qsetstring.cpp:8"); - lldb.execute("run"); - QByteArray out = lldb.execute("print s"); - QVERIFY(out.contains("] = \"10\"")); - QVERIFY(out.contains("] = \"20\"")); - lldb.execute("next"); - out = lldb.execute("print s"); - QVERIFY(out.contains("] = \"30\"")); + TestLaunchConfiguration cfg("lldb_qsetstring"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qsetstring.cpp")), 7); + + 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); + + if (!verifyVariable(0, "s", "", {"\"10\"", "\"20\""}, + __FILE__, __LINE__, true, false, true)) { + return; + } + + m_session->stepOver(); + WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); + QCOMPARE(m_session->currentLine(), 8); + + if (!verifyVariable(0, "s", "", + {"\"10\"", "\"20\"", "\"30\""}, + __FILE__, __LINE__, true, false, true)) { + return; + } } -void LldbFormattersTest::testQChar() +void LldbFormattersTest::testQDate() { - LldbProcess lldb("qchar"); - lldb.execute("break qchar.cpp:5"); - lldb.execute("run"); - QVERIFY(lldb.execute("print c").contains("\"k\"")); + TestLaunchConfiguration cfg("lldb_qdate"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qdate.cpp")), 5); + + 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); + + QList> children; + children.append({"jd", "2455217"}); + children.append({"(ISO)", "\"2010-01-20\""}); + children.append({"(Locale)", "\".+\""}); // (Locale) and summary are locale dependent + if (!verifyVariable(0, "d", ".+", children, + __FILE__, __LINE__, true, true, false)) { + return; + } } -void LldbFormattersTest::testQListPOD() +void LldbFormattersTest::testQTime() { - LldbProcess lldb("qlistpod"); - lldb.execute("break qlistpod.cpp:31"); - lldb.execute("run"); - QVERIFY(lldb.execute("print b").contains("false")); - QVERIFY(lldb.execute("print c").contains("50")); - QVERIFY(lldb.execute("print uc").contains("50")); - QVERIFY(lldb.execute("print s").contains("50")); - QVERIFY(lldb.execute("print us").contains("50")); - QVERIFY(lldb.execute("print i").contains("50")); - QVERIFY(lldb.execute("print ui").contains("50")); - QVERIFY(lldb.execute("print l").contains("50")); - QVERIFY(lldb.execute("print ul").contains("50")); - QVERIFY(lldb.execute("print i64").contains("50")); - QVERIFY(lldb.execute("print ui64").contains("50")); - QVERIFY(lldb.execute("print f").contains("50")); - QVERIFY(lldb.execute("print d").contains("50")); + TestLaunchConfiguration cfg("lldb_qtime"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qtime.cpp")), 5); + + 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); + + QList> children; + children.append({"mds", "55810123"}); + children.append({"(ISO)", "\"15:30:10.000123\""}); + children.append({"(Locale)", "\".+\""}); // (Locale) and summary are locale dependent + if (!verifyVariable(0, "t", ".+", children, + __FILE__, __LINE__, true, true, false)) { + return; + } +} + +void LldbFormattersTest::testQDateTime() +{ + TestLaunchConfiguration cfg("lldb_qdatetime"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qdatetime.cpp")), 5); + + 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); + + QList> children; + children.append({"toTime_t", "1264019473"}); + children.append({"(ISO)", "\"2010-01-20 20:31:13\""}); + children.append({"(Locale)", "\".+\""}); // (Locale), (UTC) and summary are locale dependent + children.append({"(UTC)", "\".+\""}); + if (!verifyVariable(0, "dt", ".+", children, + __FILE__, __LINE__, true, true, false)) { + return; + } +} + +void LldbFormattersTest::testQUrl() +{ + TestLaunchConfiguration cfg("lldb_qurl"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qurl.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); + + QList> children; + children.append({"(port)", "-1"}); + children.append({"(scheme)", "\"http\""}); + children.append({"(userName)", ""}); + children.append({"(password)", ""}); + children.append({"(host)", "\"www.kdevelop.org\""}); + children.append({"(path)", "\"/foo\""}); + children.append({"(query)", ""}); + children.append({"(fragment)", ""}); + VERIFY_LOCAL(0, "u", "\"http://www.kdevelop.org/foo\"", children); } void LldbFormattersTest::testQUuid() { - LldbProcess lldb("quuid"); - lldb.execute("break quuid.cpp:4"); - lldb.execute("run"); - QByteArray data = lldb.execute("print id"); - QVERIFY(data.contains("{9ec3b70b-d105-42bf-b3b4-656e44d2e223}")); + TestLaunchConfiguration cfg("lldb_quuid"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("quuid.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); + + VERIFY_LOCAL(0, "id", "QUuid({9ec3b70b-d105-42bf-b3b4-656e44d2e223})", (QStringList{})); } void LldbFormattersTest::testKTextEditorTypes() { - LldbProcess lldb("ktexteditortypes"); - lldb.execute("break ktexteditortypes.cpp:9"); - lldb.execute("run"); + TestLaunchConfiguration cfg("lldb_ktexteditortypes"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("ktexteditortypes.cpp")), 8); + + 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); + + auto watchRoot = variableCollection()->indexForItem(variableCollection()->watches(), 0); + variableCollection()->expanded(watchRoot); + variableCollection()->variableWidgetShown(); + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + variableCollection()->watches()->add("cursor"); + variableCollection()->watches()->add("range"); + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + QList> cursorChildren; + cursorChildren.append({"m_line", "1"}); + cursorChildren.append({"m_column", "1"}); - QByteArray data = lldb.execute("print cursor"); - QCOMPARE(data, QByteArray("$1 = [1, 1]")); - data = lldb.execute("print range"); - QCOMPARE(data, QByteArray("$2 = [(1, 1) -> (2, 2)]")); + QList> rangeChildren; + rangeChildren.append({"m_start", "(1, 1)"}); + rangeChildren.append({"m_end", "(2, 2)"}); + + VERIFY_WATCH(0, "cursor", "(1, 1)", cursorChildren); + VERIFY_WATCH(1, "range", "[(1, 1) -> (2, 2)]", rangeChildren); } void LldbFormattersTest::testKDevelopTypes() { - LldbProcess lldb("kdeveloptypes"); - lldb.execute("break kdeveloptypes.cpp:12"); - lldb.execute("run"); + TestLaunchConfiguration cfg("lldb_kdeveloptypes"); + addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("kdeveloptypes.cpp")), 11); + + 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); + + auto watchRoot = variableCollection()->indexForItem(variableCollection()->watches(), 0); + variableCollection()->expanded(watchRoot); + variableCollection()->variableWidgetShown(); + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + variableCollection()->watches()->add("path1"); + variableCollection()->watches()->add("path2"); + WAIT_FOR_A_WHILE_AND_IDLE(m_session, 50); + + QList> path1Children; + path1Children.append({"m_data", ""}); + + QList> path2Children; + path2Children.append({"m_data", ""}); - QVERIFY(lldb.execute("print path1").contains("(\"tmp\", \"foo\")")); - QVERIFY(lldb.execute("print path2").contains("(\"http://www.test.com\", \"tmp\", \"asdf.txt\")")); + VERIFY_WATCH(0, "path1", "(\"tmp\", \"foo\")", path1Children); + VERIFY_WATCH(1, "path2", "(\"http://www.test.com\", \"tmp\", \"asdf.txt\")", path2Children); } -*/ QTEST_MAIN(LldbFormattersTest) 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);