tst_dumpers.cpp 189 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
Bill King's avatar
Bill King committed
2
**
3
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
Bill King's avatar
Bill King committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
Bill King's avatar
Bill King committed
7
**
hjk's avatar
hjk committed
8
9
10
11
12
13
14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
Bill King's avatar
Bill King committed
15
16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
18
19
20
21
22
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
Bill King's avatar
Bill King committed
23
**
hjk's avatar
hjk committed
24
25
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
Bill King's avatar
Bill King committed
26
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
Bill King's avatar
Bill King committed
29

hjk's avatar
hjk committed
30
#include "debuggerprotocol.h"
31
32
#include "watchdata.h"
#include "watchutils.h"
33

34
#include <cplusplus/CppRewriter.h>
35
#include <utils/environment.h>
36
37
38
#include <utils/qtcprocess.h>
#include <utils/fileutils.h>
#include <utils/synchronousprocess.h>
39

40
#include "temporarydir.h"
41

42
#include <QtTest>
43
#include <math.h>
44

45
46
47
48
49
50
#if  QT_VERSION >= 0x050000
#define MSKIP_SINGLE(x) QSKIP(x)
#else
#define MSKIP_SINGLE(x) QSKIP(x, SkipSingle)
#endif

51
using namespace Debugger;
52
using namespace Utils;
hjk's avatar
hjk committed
53
using namespace Internal;
54

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Copied from abstractmsvctoolchain.cpp to avoid plugin dependency.

static bool generateEnvironmentSettings(Utils::Environment &env,
                                        const QString &batchFile,
                                        const QString &batchArgs,
                                        QMap<QString, QString> &envPairs)
{
    // Create a temporary file name for the output. Use a temporary file here
    // as I don't know another way to do this in Qt...
    // Note, can't just use a QTemporaryFile all the way through as it remains open
    // internally so it can't be streamed to later.
    QString tempOutFile;
    QTemporaryFile* pVarsTempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/XXXXXX.txt"));
    pVarsTempFile->setAutoRemove(false);
    pVarsTempFile->open();
    pVarsTempFile->close();
    tempOutFile = pVarsTempFile->fileName();
    delete pVarsTempFile;

    // Create a batch file to create and save the env settings
    Utils::TempFileSaver saver(QDir::tempPath() + QLatin1String("/XXXXXX.bat"));

    QByteArray call = "call ";
    call += Utils::QtcProcess::quoteArg(batchFile).toLocal8Bit();
    if (!batchArgs.isEmpty()) {
        call += ' ';
        call += batchArgs.toLocal8Bit();
    }
    saver.write(call + "\r\n");

    const QByteArray redirect = "set > " + Utils::QtcProcess::quoteArg(
                                    QDir::toNativeSeparators(tempOutFile)).toLocal8Bit() + "\r\n";
    saver.write(redirect);
    if (!saver.finalize()) {
        qWarning("%s: %s", Q_FUNC_INFO, qPrintable(saver.errorString()));
        return false;
    }

    Utils::QtcProcess run;
    // As of WinSDK 7.1, there is logic preventing the path from being set
    // correctly if "ORIGINALPATH" is already set. That can cause problems
    // if Creator is launched within a session set up by setenv.cmd.
    env.unset(QLatin1String("ORIGINALPATH"));
    run.setEnvironment(env);
    const QString cmdPath = QString::fromLocal8Bit(qgetenv("COMSPEC"));
    // Windows SDK setup scripts require command line switches for environment expansion.
    QString cmdArguments = QLatin1String(" /E:ON /V:ON /c \"");
    cmdArguments += QDir::toNativeSeparators(saver.fileName());
    cmdArguments += QLatin1Char('"');
    run.setCommand(cmdPath, cmdArguments);
    run.start();

    if (!run.waitForStarted()) {
        qWarning("%s: Unable to run '%s': %s", Q_FUNC_INFO, qPrintable(batchFile),
                 qPrintable(run.errorString()));
        return false;
    }
    if (!run.waitForFinished()) {
        qWarning("%s: Timeout running '%s'", Q_FUNC_INFO, qPrintable(batchFile));
        Utils::SynchronousProcess::stopProcess(run);
        return false;
    }
    // The SDK/MSVC scripts do not return exit codes != 0. Check on stdout.
    const QByteArray stdOut = run.readAllStandardOutput();
    if (!stdOut.isEmpty() && (stdOut.contains("Unknown") || stdOut.contains("Error")))
        qWarning("%s: '%s' reports:\n%s", Q_FUNC_INFO, call.constData(), stdOut.constData());

    //
    // Now parse the file to get the environment settings
    QFile varsFile(tempOutFile);
    if (!varsFile.open(QIODevice::ReadOnly))
        return false;

    QRegExp regexp(QLatin1String("(\\w*)=(.*)"));
    while (!varsFile.atEnd()) {
        const QString line = QString::fromLocal8Bit(varsFile.readLine()).trimmed();
        if (regexp.exactMatch(line)) {
            const QString varName = regexp.cap(1);
            const QString varValue = regexp.cap(2);

            if (!varValue.isEmpty())
                envPairs.insert(varName, varValue);
        }
    }

    // Tidy up and remove the file
    varsFile.close();
    varsFile.remove();

    return true;
}

147
148
static QByteArray noValue = "\001";

149
150
151
152
153
154
155
156
157
158
static QString toHex(const QString &str)
{
    QString encoded;
    foreach (const QChar c, str) {
        encoded += QString::fromLatin1("%1")
            .arg(c.unicode(), 2, 16, QLatin1Char('0'));
    }
    return encoded;
}

hjk's avatar
hjk committed
159
160
161
162
163
164
165
166
167

struct Context
{
    Context() : qtVersion(0) {}

    QByteArray nameSpace;
    int qtVersion;
};

hjk's avatar
hjk committed
168
169
struct Name
{
hjk's avatar
hjk committed
170
171
172
173
174
    Name() : name(noValue) {}
    Name(const char *str) : name(str) {}
    Name(const QByteArray &ba) : name(ba) {}

    bool matches(const QByteArray &actualName0, const Context &context) const
hjk's avatar
hjk committed
175
176
177
    {
        QByteArray actualName = actualName0;
        QByteArray expectedName = name;
hjk's avatar
hjk committed
178
        expectedName.replace("@Q", context.nameSpace + 'Q');
hjk's avatar
hjk committed
179
180
181
182
183
184
185
        return actualName == expectedName;
    }

    QByteArray name;
};

static Name nameFromIName(const QByteArray &iname)
hjk's avatar
hjk committed
186
187
{
    int pos = iname.lastIndexOf('.');
hjk's avatar
hjk committed
188
    return Name(pos == -1 ? iname : iname.mid(pos + 1));
hjk's avatar
hjk committed
189
190
191
192
193
194
195
196
}

static QByteArray parentIName(const QByteArray &iname)
{
    int pos = iname.lastIndexOf('.');
    return pos == -1 ? QByteArray() : iname.left(pos);
}

hjk's avatar
hjk committed
197
struct ValueBase
198
{
199
    ValueBase() : hasPtrSuffix(false), isFloatValue(false), version(0) {}
hjk's avatar
hjk committed
200
201

    bool hasPtrSuffix;
202
    bool isFloatValue;
hjk's avatar
hjk committed
203
204
205
206
207
    int version;
};

struct Value : public ValueBase
{
208
209
210
211
    Value() : value(QString::fromLatin1(noValue)) {}
    Value(const char *str) : value(QLatin1String(str)) {}
    Value(const QByteArray &ba) : value(QString::fromLatin1(ba.data(), ba.size())) {}
    Value(const QString &str) : value(str) {}
212

hjk's avatar
hjk committed
213
    bool matches(const QString &actualValue0, const Context &context) const
214
    {
215
        if (value == QString::fromLatin1(noValue))
216
            return true;
hjk's avatar
hjk committed
217
218
219
220
221
222
223
224
225
226
227
228
229
230

        if (context.qtVersion) {
            if (version == 4) {
                if (context.qtVersion < 0x40000 || context.qtVersion >= 0x50000) {
                    //QWARN("Qt 4 specific case skipped");
                    return true;
                }
            } else if (version == 5) {
                if (context.qtVersion < 0x50000 || context.qtVersion >= 0x60000) {
                    //QWARN("Qt 5 specific case skipped");
                    return true;
                }
            }
        }
231
232
233
        QString actualValue = actualValue0;
        if (actualValue == QLatin1String(" "))
            actualValue.clear(); // FIXME: Remove later.
234
235
        QString expectedValue = value;
        expectedValue.replace(QLatin1Char('@'), QString::fromLatin1(context.nameSpace));
hjk's avatar
hjk committed
236

237
        if (hasPtrSuffix)
238
239
            return actualValue.startsWith(expectedValue + QLatin1String(" @0x"))
                || actualValue.startsWith(expectedValue + QLatin1String("@0x"));
240
241
242
243
244
245
246
247
248
249
250
251

        if (isFloatValue) {
            double f1 = fabs(expectedValue.toDouble());
            double f2 = fabs(actualValue.toDouble());
            //qDebug() << "expected float: " << qPrintable(expectedValue) << f1;
            //qDebug() << "actual   float: " << qPrintable(actualValue) << f2;
            if (f1 < f2)
                std::swap(f1, f2);
            return f1 - f2 <= 0.01 * f2;
        }


252
        return actualValue == expectedValue;
253
254
    }

255
    QString value;
256
257
258
259
260
261
262
263
};

struct Pointer : Value
{
    Pointer() { hasPtrSuffix = true; }
    Pointer(const QByteArray &value) : Value(value) { hasPtrSuffix = true; }
};

264
265
266
267
268
269
struct FloatValue : Value
{
    FloatValue() { isFloatValue = true; }
    FloatValue(const QByteArray &value) : Value(value) { isFloatValue = true; }
};

hjk's avatar
hjk committed
270
271
272
273
274
275
276
277
278
279
struct Value4 : Value
{
    Value4(const QByteArray &value) : Value(value) { version = 4; }
};

struct Value5 : Value
{
    Value5(const QByteArray &value) : Value(value) { version = 5; }
};

280
281
struct Type
{
hjk's avatar
hjk committed
282
283
284
285
286
    Type() {}
    Type(const char *str) : type(str) {}
    Type(const QByteArray &ba) : type(ba) {}

    bool matches(const QByteArray &actualType0, const Context &context) const
287
    {
288
289
        QByteArray actualType =
            CPlusPlus::simplifySTLType(QString::fromLatin1(actualType0)).toLatin1();
290
        actualType.replace(' ', "");
hjk's avatar
hjk committed
291
        actualType.replace("const", "");
292
293
        QByteArray expectedType = type;
        expectedType.replace(' ', "");
294
        expectedType.replace("const", "");
hjk's avatar
hjk committed
295
        expectedType.replace('@', context.nameSpace);
296
297
298
299
300
        return actualType == expectedType;
    }
    QByteArray type;
};

hjk's avatar
hjk committed
301
struct Check
302
303
304
{
    Check() {}

hjk's avatar
hjk committed
305
    Check(const QByteArray &iname, const Value &value, const Type &type)
hjk's avatar
hjk committed
306
307
308
309
        : iname(iname), expectedName(nameFromIName(iname)),
          expectedValue(value), expectedType(type)
    {}

hjk's avatar
hjk committed
310
    Check(const QByteArray &iname, const Name &name,
311
         const Value &value, const Type &type)
312
313
314
315
316
        : iname(iname), expectedName(name),
          expectedValue(value), expectedType(type)
    {}

    QByteArray iname;
hjk's avatar
hjk committed
317
    Name expectedName;
318
    Value expectedValue;
319
    Type expectedType;
320
321
};

hjk's avatar
hjk committed
322
struct CheckType : public Check
323
{
hjk's avatar
hjk committed
324
    CheckType(const QByteArray &iname, const Name &name,
325
         const Type &type)
326
327
        : Check(iname, name, noValue, type)
    {}
328

329
    CheckType(const QByteArray &iname, const Type &type)
hjk's avatar
hjk committed
330
331
332
333
        : Check(iname, noValue, type)
    {}
};

hjk's avatar
hjk committed
334
struct Profile
hjk's avatar
hjk committed
335
336
337
338
{
    Profile(const QByteArray &contents) : contents(contents) {}

    QByteArray contents;
339
340
};

hjk's avatar
hjk committed
341

hjk's avatar
hjk committed
342
343
struct Cxx11Profile : public Profile
{
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
    Cxx11Profile()
      : Profile("greaterThan(QT_MAJOR_VERSION,4): CONFIG += c++11\n"
                "else: QMAKE_CXXFLAGS += -std=c++0x\n")
    {}
};

struct MacLibStdCppProfile : public Profile
{
    MacLibStdCppProfile()
      : Profile("macx {\n"
                "QMAKE_CXXFLAGS += -stdlib=libc++\n"
                "LIBS += -stdlib=libc++\n"
                "QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.7\n"
                "QMAKE_IOS_DEPLOYMENT_TARGET = 10.7\n"
                "QMAKE_CFLAGS -= -mmacosx-version-min=10.6\n"
                "QMAKE_CFLAGS += -mmacosx-version-min=10.7\n"
                "QMAKE_CXXFLAGS -= -mmacosx-version-min=10.6\n"
                "QMAKE_CXXFLAGS += -mmacosx-version-min=10.7\n"
                "QMAKE_OBJECTIVE_CFLAGS -= -mmacosx-version-min=10.6\n"
                "QMAKE_OBJECTIVE_CFLAGS += -mmacosx-version-min=10.7\n"
                "QMAKE_LFLAGS -= -mmacosx-version-min=10.6\n"
                "QMAKE_LFLAGS += -mmacosx-version-min=10.7\n"
                "}")
    {}
hjk's avatar
hjk committed
368
369
};

hjk's avatar
hjk committed
370
371
372
struct GdbOnly {};
struct LldbOnly {};

373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
struct GdbVersion
{
    // Minimum and maximum are inclusive.
    GdbVersion(int minimum = 0, int maximum = 0)
    {
        if (minimum && !maximum)
            maximum = minimum;
        if (maximum == 0)
            maximum = INT_MAX;

        max = maximum;
        min = minimum;
    }
    int min;
    int max;
};

hjk's avatar
hjk committed
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
struct LldbVersion
{
    // Minimum and maximum are inclusive.
    LldbVersion(int minimum = 0, int maximum = 0)
    {
        if (minimum && !maximum)
            maximum = minimum;
        if (maximum == 0)
            maximum = INT_MAX;

        max = maximum;
        min = minimum;
    }
    int min;
    int max;
};

407
408
struct ForceC {};

hjk's avatar
hjk committed
409
410
411
412
413
414
struct CoreProfile {};
struct CorePrivateProfile {};
struct GuiProfile {};

struct DataBase
{
415
416
417
418
    DataBase()
      : useQt(false), useQHash(false),
        forceC(false), gdbOnly(false), lldbOnly(false)
    {}
hjk's avatar
hjk committed
419
420

    mutable bool useQt;
421
    mutable bool useQHash;
hjk's avatar
hjk committed
422
    mutable bool forceC;
hjk's avatar
hjk committed
423
424
    mutable bool gdbOnly;
    mutable bool lldbOnly;
425
    mutable GdbVersion neededGdbVersion;
hjk's avatar
hjk committed
426
    mutable LldbVersion neededLldbVersion;
hjk's avatar
hjk committed
427
428
429
};

class Data : public DataBase
430
{
hjk's avatar
hjk committed
431
432
public:
    Data() {}
hjk's avatar
hjk committed
433
    Data(const QByteArray &code) : code(code) {}
434

435
    Data(const QByteArray &includes, const QByteArray &code)
hjk's avatar
hjk committed
436
        : includes(includes), code(code)
hjk's avatar
hjk committed
437
    {}
438

439
440
441
442
443
444
    const Data &operator%(const Check &check) const
    {
        checks.insert("local." + check.iname, check);
        return *this;
    }

hjk's avatar
hjk committed
445
446
447
448
449
450
    const Data &operator%(const Profile &profile) const
    {
        profileExtra += profile.contents;
        return *this;
    }

451
452
453
454
455
456
    const Data &operator%(const GdbVersion &gdbVersion) const
    {
        neededGdbVersion = gdbVersion;
        return *this;
    }

hjk's avatar
hjk committed
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
    const Data &operator%(const LldbVersion &lldbVersion) const
    {
        neededLldbVersion = lldbVersion;
        return *this;
    }

    const Data &operator%(const LldbOnly &) const
    {
        lldbOnly = true;
        return *this;
    }

    const Data &operator%(const GdbOnly &) const
    {
        gdbOnly = true;
        return *this;
    }

hjk's avatar
hjk committed
475
476
477
478
    const Data &operator%(const CoreProfile &) const
    {
        profileExtra +=
            "CONFIG += QT\n"
479
            "QT += core\n";
hjk's avatar
hjk committed
480
481

        useQt = true;
482
483
        useQHash = true;

hjk's avatar
hjk committed
484
485
486
487
488
        return *this;
    }

    const Data &operator%(const GuiProfile &) const
    {
489
        this->operator%(CoreProfile());
hjk's avatar
hjk committed
490
491
492
493
494
495
496
497
498
        profileExtra +=
            "QT += gui\n"
            "greaterThan(QT_MAJOR_VERSION, 4):QT *= widgets\n";

        return *this;
    }

    const Data &operator%(const CorePrivateProfile &) const
    {
499
        this->operator%(CoreProfile());
hjk's avatar
hjk committed
500
501
        profileExtra +=
            "greaterThan(QT_MAJOR_VERSION, 4) {\n"
502
            "  QT += core-private\n"
hjk's avatar
hjk committed
503
504
505
506
507
508
            "  CONFIG += no_private_qt_headers_warning\n"
            "}";

        return *this;
    }

509
510
511
512
513
514
    const Data &operator%(const ForceC &) const
    {
        forceC = true;
        return *this;
    }

hjk's avatar
hjk committed
515
public:
hjk's avatar
hjk committed
516
    mutable QByteArray profileExtra;
517
518
    mutable QByteArray includes;
    mutable QByteArray code;
hjk's avatar
hjk committed
519
    mutable QMap<QByteArray, Check> checks; // IName -> Action
hjk's avatar
hjk committed
520
521
};

522
523
524
525
struct TempStuff
{
    TempStuff() : buildTemp(QLatin1String("qt_tst_dumpers_"))
    {
526
        buildPath = QDir::currentPath() + QLatin1Char('/')  + buildTemp.path();
527
528
529
530
531
532
533
534
535
        buildTemp.setAutoRemove(false);
        QVERIFY(!buildPath.isEmpty());
    }

    QByteArray input;
    QTemporaryDir buildTemp;
    QString buildPath;
};

536
537
enum DebuggerEngine
{
538
539
540
    DumpTestGdbEngine,
    DumpTestCdbEngine,
    DumpTestLldbEngine
541
542
};

hjk's avatar
hjk committed
543
Q_DECLARE_METATYPE(Data)
hjk's avatar
hjk committed
544

545
class tst_Dumpers : public QObject
546
547
548
549
{
    Q_OBJECT

public:
550
551
552
    tst_Dumpers()
    {
        t = 0;
553
        m_keepTemp = true;
554
555
556
557
558
559
        m_gdbVersion = 0;
        m_gdbBuildVersion = 0;
        m_lldbVersion = 0;
        m_isMacGdb = false;
        m_isQnxGdb = false;
    }
560
561

private slots:
hjk's avatar
hjk committed
562
    void initTestCase();
hjk's avatar
hjk committed
563
564
    void dumper();
    void dumper_data();
565
566
    void init();
    void cleanup();
hjk's avatar
hjk committed
567
568

private:
569
    TempStuff *t;
hjk's avatar
hjk committed
570
571
    QByteArray m_debuggerBinary;
    QByteArray m_qmakeBinary;
572
    QProcessEnvironment m_env;
hjk's avatar
hjk committed
573
    bool m_usePython;
574
    DebuggerEngine m_debuggerEngine;
hjk's avatar
hjk committed
575
    bool m_keepTemp;
576
577
    int m_gdbVersion; // 7.5.1 -> 70501
    int m_gdbBuildVersion;
578
    int m_lldbVersion;
579
580
    bool m_isMacGdb;
    bool m_isQnxGdb;
hjk's avatar
hjk committed
581
582
583
584
};

void tst_Dumpers::initTestCase()
{
585
    m_debuggerBinary = qgetenv("QTC_DEBUGGER_PATH_FOR_TEST");
hjk's avatar
hjk committed
586
587
    if (m_debuggerBinary.isEmpty())
        m_debuggerBinary = "gdb";
588
    qDebug() << "Debugger    : " << m_debuggerBinary.constData();
589

590
    m_debuggerEngine = DumpTestGdbEngine;
591
    if (m_debuggerBinary.endsWith("cdb.exe"))
592
        m_debuggerEngine = DumpTestCdbEngine;
593

594
    if (m_debuggerBinary.endsWith("lldb"))
595
        m_debuggerEngine = DumpTestLldbEngine;
hjk's avatar
hjk committed
596

hjk's avatar
hjk committed
597
    m_qmakeBinary = qgetenv("QTC_QMAKE_PATH_FOR_TEST");
hjk's avatar
hjk committed
598
599
    if (m_qmakeBinary.isEmpty())
        m_qmakeBinary = "qmake";
600
    qDebug() << "QMake       : " << m_qmakeBinary.constData();
hjk's avatar
hjk committed
601

602
    Environment utilsEnv = Environment::systemEnvironment();
603

604
    if (m_debuggerEngine == DumpTestGdbEngine) {
605
606
607
608
609
610
611
612
613
614
        QProcess debugger;
        debugger.start(QString::fromLatin1(m_debuggerBinary)
                       + QLatin1String(" -i mi -quiet -nx"));
        bool ok = debugger.waitForStarted();
        debugger.write("set confirm off\npython print 43\nshow version\nquit\n");
        ok = debugger.waitForFinished();
        QVERIFY(ok);
        QByteArray output = debugger.readAllStandardOutput();
        //qDebug() << "stdout: " << output;
        m_usePython = !output.contains("Python scripting is not supported in this copy of GDB");
615
616
        qDebug() << "Python      : " << (m_usePython ? "ok" : "*** not ok ***");
        qDebug() << "Dumper dir  : " << DUMPERDIR;
617
618
619
620
621
622
623
624
625

        QString version = QString::fromLocal8Bit(output);
        int pos1 = version.indexOf(QLatin1String("&\"show version\\n"));
        QVERIFY(pos1 != -1);
        pos1 += 20;
        int pos2 = version.indexOf(QLatin1String("~\"Copyright (C) "), pos1);
        QVERIFY(pos2 != -1);
        pos2 -= 4;
        version = version.mid(pos1, pos2 - pos1);
626
        extractGdbVersion(version, &m_gdbVersion,
627
            &m_gdbBuildVersion, &m_isMacGdb, &m_isQnxGdb);
628
        qDebug() << "Gdb version : " << m_gdbVersion;
629
    } else if (m_debuggerEngine == DumpTestCdbEngine) {
630
631
        QByteArray envBat = qgetenv("QTC_MSVC_ENV_BAT");
        QMap <QString, QString> envPairs;
632
        QVERIFY(generateEnvironmentSettings(utilsEnv, QString::fromLatin1(envBat), QString(), envPairs));
633
634
635
636
637
638
639
640
        for (QMap<QString,QString>::const_iterator envIt = envPairs.begin(); envIt!=envPairs.end(); ++envIt)
                utilsEnv.set(envIt.key(), envIt.value());

        const QByteArray cdbextPath = QByteArray(CDBEXT_PATH) + QByteArray("\\qtcreatorcdbext64");
        QVERIFY(QFile::exists(QString::fromLatin1(cdbextPath + QByteArray("\\qtcreatorcdbext.dll"))));
        utilsEnv.appendOrSet(QLatin1String("_NT_DEBUGGER_EXTENSION_PATH"),
                             QString::fromLatin1(cdbextPath),
                             QLatin1String(";"));
641
    } else if (m_debuggerEngine == DumpTestLldbEngine) {
642
643
        m_usePython = true;
        QProcess debugger;
644
645
        QString cmd = QString::fromUtf8(m_debuggerBinary + " -v");
        debugger.start(cmd);
646
647
648
649
650
651
        bool ok = debugger.waitForFinished(2000);
        QVERIFY(ok);
        QByteArray output = debugger.readAllStandardOutput();
        output += debugger.readAllStandardError();
        output = output.trimmed();
        // Should be something like LLDB-178
652
653
654
655
656
657
        QByteArray ba = output.mid(output.indexOf('-') + 1);
        int pos = ba.indexOf('.');
        if (pos >= 0)
            ba = ba.left(pos);
        m_lldbVersion = ba.toInt();
        qDebug() << "Lldb version " << output << ba << m_lldbVersion;
658
        QVERIFY(m_lldbVersion);
659
660
    }
    m_env = utilsEnv.toProcessEnvironment();
hjk's avatar
hjk committed
661
662
}

663
void tst_Dumpers::init()
hjk's avatar
hjk committed
664
{
665
    t = new TempStuff();
hjk's avatar
hjk committed
666
667
}

668
void tst_Dumpers::cleanup()
hjk's avatar
hjk committed
669
{
670
671
    if (!t->buildTemp.autoRemove()) {
        QFile logger(t->buildPath + QLatin1String("/input.txt"));
hjk's avatar
hjk committed
672
        logger.open(QIODevice::ReadWrite);
673
        logger.write(t->input);
hjk's avatar
hjk committed
674
    }
675
676
    delete t;
}
677

hjk's avatar
hjk committed
678
679
680
681
void tst_Dumpers::dumper()
{
    QFETCH(Data, data);

682
    if (m_debuggerEngine == DumpTestGdbEngine) {
683
        if (data.neededGdbVersion.min > m_gdbVersion)
hjk's avatar
hjk committed
684
685
            MSKIP_SINGLE("Need minimum GDB version "
                + QByteArray::number(data.neededGdbVersion.min));
686
        if (data.neededGdbVersion.max < m_gdbVersion)
hjk's avatar
hjk committed
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
            MSKIP_SINGLE("Need maximum GDB version "
                + QByteArray::number(data.neededGdbVersion.max));
    } else {
        if (data.gdbOnly)
            MSKIP_SINGLE("Test is GDB specific");
    }

    if (m_debuggerEngine == DumpTestLldbEngine) {
        if (data.neededLldbVersion.min > m_gdbVersion)
            MSKIP_SINGLE("Need minimum LLDB version "
                + QByteArray::number(data.neededLldbVersion.min));
        if (data.neededLldbVersion.max < m_gdbVersion)
            MSKIP_SINGLE("Need maximum LLDB version "
                + QByteArray::number(data.neededLldbVersion.max));
    } else {
        if (data.lldbOnly)
            MSKIP_SINGLE("Test is LLDB specific");
704
    }
705

hjk's avatar
hjk committed
706
707
708
709
    QString cmd;
    QByteArray output;
    QByteArray error;

710
711
    const char *mainFile = data.forceC ? "main.c" : "main.cpp";

712
713
    QFile proFile(t->buildPath + QLatin1String("/doit.pro"));
    QVERIFY(proFile.open(QIODevice::ReadWrite));
714
715
716
    proFile.write("SOURCES = ");
    proFile.write(mainFile);
    proFile.write("\nTARGET = doit\n");
717
    proFile.write("\nCONFIG -= app_bundle\n");
718
719
    proFile.write("\nCONFIG -= release\n");
    proFile.write("\nCONFIG += debug\n");
hjk's avatar
hjk committed
720
721
722
723
    if (data.useQt)
        proFile.write("QT -= widgets gui\n");
    else
        proFile.write("CONFIG -= QT\n");
hjk's avatar
hjk committed
724
    proFile.write(data.profileExtra);
hjk's avatar
hjk committed
725
726
    proFile.close();

727
728
    QFile source(t->buildPath + QLatin1Char('/') + QLatin1String(mainFile));
    QVERIFY(source.open(QIODevice::ReadWrite));
hjk's avatar
hjk committed
729
    QByteArray fullCode =
hjk's avatar
hjk committed
730
            "\n\nvoid unused(const void *first,...) { (void) first; }"
hjk's avatar
hjk committed
731
            "\n\nvoid breakHere() {}"
hjk's avatar
hjk committed
732
            "\n\n" + data.includes +
733
734
735
736
737
738
739
            "\n\n" + (data.useQHash ?
                "\n#include <QByteArray>"
                "\n#if QT_VERSION >= 0x050000"
                "\nQT_BEGIN_NAMESPACE"
                "\nQ_CORE_EXPORT extern QBasicAtomicInt qt_qhash_seed; // from qhash.cpp"
                "\nQT_END_NAMESPACE"
                "\n#endif" : "") +
hjk's avatar
hjk committed
740
            "\n\nint main(int argc, char *argv[])"
hjk's avatar
hjk committed
741
            "\n{"
742
743
744
745
746
            "\n    int qtversion = " + (data.useQt ? "QT_VERSION" : "0") + ";"
            "\n" + (data.useQHash ?
                "\n#if QT_VERSION >= 0x050000"
                "\nqt_qhash_seed.testAndSetRelaxed(-1, 0);"
                "\n#endif\n" : "") +
hjk's avatar
hjk committed
747
            "\n    unused(&argc, &argv, &qtversion);\n"
hjk's avatar
hjk committed
748
749
            "\n" + data.code +
            "\n    breakHere();"
750
            "\n    return 0;"
hjk's avatar
hjk committed
751
752
            "\n}\n";
    source.write(fullCode);
hjk's avatar
hjk committed
753
754
755
    source.close();

    QProcess qmake;
756
    qmake.setWorkingDirectory(t->buildPath);
hjk's avatar
hjk committed
757
    cmd = QString::fromLatin1(m_qmakeBinary);
hjk's avatar
hjk committed
758
759
    //qDebug() << "Starting qmake: " << cmd;
    qmake.start(cmd);
760
    QVERIFY(qmake.waitForFinished());
hjk's avatar
hjk committed
761
762
763
    output = qmake.readAllStandardOutput();
    error = qmake.readAllStandardError();
    //qDebug() << "stdout: " << output;
764
    if (!error.isEmpty()) { qDebug() << error; QVERIFY(false); }
hjk's avatar
hjk committed
765
766

    QProcess make;
767
    make.setWorkingDirectory(t->buildPath);
768
769
    cmd = m_debuggerEngine == DumpTestCdbEngine ? QString::fromLatin1("nmake")
                                                : QString::fromLatin1("make");
hjk's avatar
hjk committed
770
771
    //qDebug() << "Starting make: " << cmd;
    make.start(cmd);
772
    QVERIFY(make.waitForFinished());
hjk's avatar
hjk committed
773
774
775
    output = make.readAllStandardOutput();
    error = make.readAllStandardError();
    //qDebug() << "stdout: " << output;
776
    if (make.exitCode()) {
hjk's avatar
hjk committed
777
778
779
780
781
782
        qDebug() << error;
        qDebug() << "\n------------------ CODE --------------------";
        qDebug() << fullCode;
        qDebug() << "\n------------------ CODE --------------------";
        qDebug() << ".pro: " << qPrintable(proFile.fileName());
    }
hjk's avatar
hjk committed
783
784
785

    QByteArray dumperDir = DUMPERDIR;

786
787
788
    QSet<QByteArray> expandedINames;
    expandedINames.insert("local");
    foreach (const Check &check, data.checks) {
hjk's avatar
hjk committed
789
790
791
792
793
794
795
        QByteArray parent = check.iname;
        while (true) {
            parent = parentIName(parent);
            if (parent.isEmpty())
                break;
            expandedINames.insert("local." + parent);
        }
796
797
798
799
800
801
802
803
804
    }

    QByteArray expanded;
    foreach (const QByteArray &iname, expandedINames) {
        if (!expanded.isEmpty())
            expanded.append(',');
        expanded += iname;
    }

805
    QByteArray exe;
806
    QStringList args;
807
    QByteArray cmds;
808

809
    if (m_debuggerEngine == DumpTestGdbEngine) {
810
        exe = m_debuggerBinary;
811
812
813
814
815
816
817
818
819
820
821
822
823
        args << QLatin1String("-i")
             << QLatin1String("mi")
             << QLatin1String("-quiet")
             << QLatin1String("-nx");
        QByteArray nograb = "-nograb";

        cmds = "set confirm off\n"
                "file doit\n"
                "break breakHere\n"
                "set print object on\n"
                "set auto-load python-scripts no\n";

        if (m_usePython) {
824
            cmds += "python execfile('" + dumperDir + "/gbridge.py')\n"
825
826
827
                    "run " + nograb + "\n"
                    "up\n"
                    "python print('@%sS@%s@' % ('N', qtNamespace()))\n"
828
                    "bb options:fancy,autoderef,dyntype,pe vars: expanded:" + expanded + " typeformats:\n";
829
830
831
832
833
834
835
836
        } else {
            cmds += "run\n";
            foreach (const Check &check, data.checks) {
                QByteArray iname = check.iname;
                //qDebug() << "INAME: " << iname;
                cmds += "-var-create " + iname + " * "
                        + check.expectedName.name + "\n";
            }
hjk's avatar
hjk committed
837
        }
838
        cmds += "quit\n";
ck's avatar
ck committed
839

840
    } else if (m_debuggerEngine == DumpTestCdbEngine) {
841
        exe = m_debuggerBinary;
842
843
844
845
846
847
848
849
850
851
852
853
        args << QLatin1String("-aqtcreatorcdbext.dll")
             << QLatin1String("-lines")
             << QLatin1String("-G")
             << QLatin1String("-c.idle_cmd")
             << QLatin1String("!qtcreatorcdbext.idle")
             << QLatin1String("debug\\doit.exe");
        cmds = "l+t\n"
               "l+s\n"
               "bu `doit!" + source.fileName().toLatin1() + ":5`\n"
               "sxi 0x4000001f\n"
               "g\n"
               "gu\n"
854
855
856
857
858
859
860
               "!qtcreatorcdbext.expandlocals -t 0 -c 0 " + expanded + "\n";
        int token = 0;
        QStringList sortediNames;
        foreach (QByteArray iName, expandedINames)
            sortediNames << QString::fromLatin1(iName);
        sortediNames.sort();
        foreach (QString iName, sortediNames)
861
862
            cmds += "!qtcreatorcdbext.locals -t " + QByteArray::number(++token)
                    + " -c 0 " + iName.toLatin1() + "\n";
863
        cmds += "q\n";
864
    } else if (m_debuggerEngine == DumpTestLldbEngine) {
865
        exe = "python";
866
867
        args << QLatin1String(dumperDir + "/lbridge.py")
             << QString::fromUtf8(m_debuggerBinary)
868
869
870
             << t->buildPath + QLatin1String("/doit")
             << QString::fromUtf8(expanded);
        //qDebug() << exe.constData() << ' ' << qPrintable(args.join(QLatin1Char(' ')));
871
    }
hjk's avatar
hjk committed
872

873
    t->input = cmds;
hjk's avatar
hjk committed
874

hjk's avatar
hjk committed
875
    QProcess debugger;
876
    debugger.setProcessEnvironment(m_env);
877
    debugger.setWorkingDirectory(t->buildPath);
878
    debugger.start(QString::fromLatin1(exe), args);
879
    QVERIFY(debugger.waitForStarted());
hjk's avatar
hjk committed
880
    debugger.write(cmds);
881
    QVERIFY(debugger.waitForFinished());
hjk's avatar
hjk committed
882
883
884
885
886
    output = debugger.readAllStandardOutput();
    //qDebug() << "stdout: " << output;
    error = debugger.readAllStandardError();
    if (!error.isEmpty()) { qDebug() << error; }

hjk's avatar
hjk committed
887
    if (m_keepTemp) {
888
889
        QFile logger(t->buildPath + QLatin1String("/output.txt"));
        logger.open(QIODevice::ReadWrite);
hjk's avatar
hjk committed
890
891
892
893
        logger.write("=== STDOUT ===\n");
        logger.write(output);
        logger.write("\n=== STDERR ===\n");
        logger.write(error);
ck's avatar
ck committed
894
895
    }

hjk's avatar
hjk committed
896
    Context context;
897
    QByteArray contents;
898
    if (m_debuggerEngine == DumpTestGdbEngine) {
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
        int posDataStart = output.indexOf("data=");
        QVERIFY(posDataStart != -1);
        int posDataEnd = output.indexOf(",typeinfo", posDataStart);
        QVERIFY(posDataEnd != -1);
        contents = output.mid(posDataStart, posDataEnd - posDataStart);

        int posNameSpaceStart = output.indexOf("@NS@");
        QVERIFY(posNameSpaceStart != -1);
        posNameSpaceStart += sizeof("@NS@") - 1;
        int posNameSpaceEnd = output.indexOf("@", posNameSpaceStart);
        QVERIFY(posNameSpaceEnd != -1);
        context.nameSpace = output.mid(posNameSpaceStart, posNameSpaceEnd - posNameSpaceStart);
        //qDebug() << "FOUND NS: " << context.nameSpace;
        if (context.nameSpace == "::")
            context.nameSpace.clear();
        contents.replace("\\\"", "\"");
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
    } else if (m_debuggerEngine == DumpTestLldbEngine) {
        //qDebug() << "GOT OUTPUT: " << output;
        QVERIFY(output.startsWith("data="));
        contents = output;

        //int posNameSpaceStart = output.indexOf("@NS@");
        //QVERIFY(posNameSpaceStart != -1);
        //posNameSpaceStart += sizeof("@NS@") - 1;
        //int posNameSpaceEnd = output.indexOf("@", posNameSpaceStart);
        //QVERIFY(posNameSpaceEnd != -1);
        //context.nameSpace = output.mid(posNameSpaceStart, posNameSpaceEnd - posNameSpaceStart);
        //qDebug() << "FOUND NS: " << context.nameSpace;
        if (context.nameSpace == "::")
            context.nameSpace.clear();
        contents.replace("\\\"", "\"");
930
931
932
933
934
    } else {
        const QByteArray locals = "|locals|";
        int pos1 = output.indexOf(locals);
        QVERIFY(pos1 != -1);
        do {
935
936
937
            pos1 += locals.length();
            if (output.at(pos1) == '[')
                ++pos1;
938
939
            int pos2 = output.indexOf("\n", pos1);
            QVERIFY(pos2 != -1);
940
941
            if (output.at(pos2 - 1) == ']')
                --pos2;
942
943
944
945
            contents += output.mid(pos1, pos2 - pos1);
            pos1 = output.indexOf(locals, pos2);
        } while (pos1 != -1);
    }
hjk's avatar
hjk committed
946
947

    GdbMi actual;
hjk's avatar
hjk committed
948
    actual.fromString(contents);
949
950
951
952
    WatchData local;
    local.iname = "local";

    QList<WatchData> list;
hjk's avatar
hjk committed
953
    foreach (const GdbMi &child, actual.children()) {
954
        WatchData dummy;
hjk's avatar
hjk committed
955
956
        dummy.iname = child["iname"].data();
        dummy.name = QLatin1String(child["name"].data());
hjk's avatar
hjk committed
957
        if (dummy.iname == "local.qtversion")
hjk's avatar
hjk committed
958
            context.qtVersion = child["value"].toInt();
hjk's avatar
hjk committed
959
960
        else
            parseWatchData(expandedINames, dummy, child, &list);
961
962
    }

hjk's avatar
hjk committed
963
    //qDebug() << "QT VERSION " << QByteArray::number(context.qtVersion, 16);
hjk's avatar
hjk committed
964
    QSet<QByteArray> seenINames;
965
    bool ok = true;
966
    foreach (const WatchData &item, list) {
hjk's avatar
hjk committed
967
        seenINames.insert(item.iname);
968
969
        if (data.checks.contains(item.iname)) {
            Check check = data.checks.take(item.iname);
hjk's avatar
hjk committed
970
            if (!check.expectedName.matches(item.name.toLatin1(), context)) {
hjk's avatar
hjk committed
971
                qDebug() << "INAME        : " << item.iname;
hjk's avatar
hjk committed
972
973
                qDebug() << "NAME ACTUAL  : " << item.name.toLatin1();
                qDebug() << "NAME EXPECTED: " << check.expectedName.name;
974
                ok = false;
ck's avatar
ck committed
975
            }
hjk's avatar
hjk committed
976
            if (!check.expectedValue.matches(item.value, context)) {
hjk's avatar
hjk committed
977
                qDebug() << "INAME         : " << item.iname;
978
                qDebug() << "VALUE ACTUAL  : " << item.value << toHex(item.value);
979
980
                qDebug() << "VALUE EXPECTED: "
                    << check.expectedValue.value << toHex(check.expectedValue.value);
981
                ok = false;
ck's avatar
ck committed
982
            }
hjk's avatar
hjk committed
983
            if (!check.expectedType.matches(item.type, context)) {
hjk's avatar
hjk committed
984
                qDebug() << "INAME        : " << item.iname;
985
                qDebug() << "TYPE ACTUAL  : " << item.type;
986
                qDebug() << "TYPE EXPECTED: " << check.expectedType.type;
987
                ok = false;
988
            }
ck's avatar
ck committed
989
990
        }
    }
991
992

    if (!data.checks.isEmpty()) {
hjk's avatar
hjk committed
993
        bool fail = false;
994
        qDebug() << "SOME TESTS NOT EXECUTED: ";
hjk's avatar
hjk committed
995
        foreach (const Check &check, data.checks) {
996
            qDebug() << "  TEST NOT FOUND FOR INAME: " << check.iname;
hjk's avatar
hjk committed
997
998
999
            if (!fail && check.expectedValue.version != 0)
                fail = true;
        }
hjk's avatar
hjk committed
1000
        qDebug() << "SEEN INAMES " << seenINames;
hjk's avatar
hjk committed
1001
        qDebug() << "EXPANDED     : " << expanded;
1002
        ok = false;
1003
    }
1004
    if (!ok) {
1005
        qDebug() << "CONTENTS     : " << contents;
1006
1007
        qDebug() << "Qt VERSION   : "
            << qPrintable(QString::number(context.qtVersion, 16));
1008
        qDebug() << "BUILD DIR    : " << qPrintable(t->buildPath);
1009
    }
1010
1011
    QVERIFY(ok);
    t->buildTemp.setAutoRemove(m_keepTemp);
ck's avatar
ck committed
1012
1013
}

hjk's avatar
hjk committed
1014
void tst_Dumpers::dumper_data()
ck's avatar
ck committed
1015
{
hjk's avatar
hjk committed
1016
    QTest::addColumn<Data>("data");
ck's avatar
ck committed
1017

hjk's avatar
hjk committed
1018
    QByteArray fooData =
hjk's avatar
hjk committed
1019
            "#include <QHash>\n"
hjk's avatar
hjk committed
1020
            "#include <QMap>\n"
hjk's avatar
hjk committed
1021
1022
            "#include <QObject>\n"
            "#include <QString>\n"
hjk's avatar
hjk committed
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
            "class Foo\n"
            "{\n"
            "public:\n"
            "    Foo(int i = 0)\n"
            "        : a(i), b(2)\n"
            "    {}\n"
            "    virtual ~Foo()\n"
            "    {\n"
            "        a = 5;\n"
            "    }\n"
            "    void doit()\n"
            "    {\n"
            "        static QObject ob;\n"
            "        m[\"1\"] = \"2\";\n"
            "        h[&ob] = m.begin();\n"
            "        --b;\n"
            "    }\n"
            "public:\n"
            "    int a, b;\n"
            "    char x[6];\n"
            "private:\n"
            "    typedef QMap<QString, QString> Map;\n"
            "    Map m;\n"
            "    QHash<QObject *, Map::iterator> h;\n"
hjk's avatar
hjk committed
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
            "};\n";

    QByteArray nsData =
            "namespace nsA {\n"
            "namespace nsB {\n"
           " struct SomeType\n"
           " {\n"
           "     SomeType(int a) : a(a) {}\n"
           "     int a;\n"
           " };\n"
           " } // namespace nsB\n"
           " } // namespace nsA\n";
1059

1060
    QTest::newRow("AnonymousStructGdb")
1061
1062
1063
1064
1065
            << Data("union {\n"
                    "     struct { int i; int b; };\n"
                    "     struct { float f; };\n"
                    "     double d;\n"
                    " } a = { { 42, 43 } };\n (void)a;")
1066
               % GdbOnly()
1067
               % CheckType("a", "a", "union {...}")
hjk's avatar
hjk committed
1068
               % Check("a.b", "43", "int")
1069
1070
               % Check("a.d", FloatValue("9.1245819032257467e-313"), "double")
               % Check("a.f", FloatValue("5.88545355e-44"), "float")
hjk's avatar
hjk committed
1071
               % Check("a.i", "42", "int");
1072

1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
    // FIXME: Merge with GDB case
    QTest::newRow("AnonymousStructLldb")
            << Data("union {\n"
                    "     struct { int i; int b; };\n"
                    "     struct { float f; };\n"
                    "     double d;\n"
                    " } a = { { 42, 43 } };\n (void)a;")
               //% CheckType("a", "a", "union {...}")
               % LldbOnly()
               % Check("a.#1.b", "43", "int")
               % Check("a.d", FloatValue("9.1245819032257467e-313"), "double")
               % Check("a.#2.f", FloatValue("5.88545355e-44"), "float")
               % Check("a.#1.i", "42", "int");

1087
    QTest::newRow("QByteArray0")
hjk's avatar
hjk committed
1088
1089
            << Data("#include <QByteArray>\n",
                    "QByteArray ba;")
hjk's avatar
hjk committed
1090
               % CoreProfile()
hjk's avatar
hjk committed
1091
               % Check("ba", "ba", "\"\"", "@QByteArray");
1092
1093

    QTest::newRow("QByteArray1")
hjk's avatar
hjk committed
1094
1095
            << Data("#include <QByteArray>\n",
                    "QByteArray ba = \"Hello\\\"World\";\n"
1096