1
/****************************************************************************
2
**
3
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4
** All rights reserved.
5
** Contact: Nokia Corporation (qt-info@nokia.com)
6
**
7
** This file is part of the test suite of the Qt Toolkit.
8
**
9
** $QT_BEGIN_LICENSE:LGPL$
10
** GNU Lesser General Public License Usage
11
** This file may be used under the terms of the GNU Lesser General Public
12
** License version 2.1 as published by the Free Software Foundation and
13
** appearing in the file LICENSE.LGPL included in the packaging of this
14
** file. Please review the following information to ensure the GNU Lesser
15
** General Public License version 2.1 requirements will be met:
16
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17
**
18
** In addition, as a special exception, Nokia gives you certain additional
19
** rights. These rights are described in the Nokia Qt LGPL Exception
20
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21
**
22
** GNU General Public License Usage
23
** Alternatively, this file may be used under the terms of the GNU General
24
** Public License version 3.0 as published by the Free Software Foundation
25
** and appearing in the file LICENSE.GPL included in the packaging of this
26
** file. Please review the following information to ensure the GNU General
27
** Public License version 3.0 requirements will be met:
28
** http://www.gnu.org/copyleft/gpl.html.
29
**
30
** Other Usage
31
** Alternatively, this file may be used in accordance with the terms and
32
** conditions contained in a signed written agreement between you and Nokia.
33
**
34
**
35
**
36
**
37
**
38
** $QT_END_LICENSE$
39
**
40
****************************************************************************/
41
42
#include <QDir>
43
#include <QFile>
44
#include <QRegExp>
45
#include <QStringList>
46
#include <QTest>
47
#include <QSet>
48
#include <QProcess>
49
#include <QDebug>
50
51
enum FindSubdirsMode {
52
    Flat = 0,
53
    Recursive
54
};
55
56
class tst_MakeTestSelfTest: public QObject
57
{
58
    Q_OBJECT
59
60
private slots:
61
    void tests_auto_pro();
62
63
    void tests_pro_files();
64
    void tests_pro_files_data();
65
66
    void naming_convention();
67
    void naming_convention_data();
68
69
    void make_check();
70
71
private:
72
    QStringList find_subdirs(QString const&, FindSubdirsMode, QString const& = QString());
73
74
    QSet<QString> all_test_classes;
75
};
76
77
bool looks_like_testcase(QString const&,QString*);
78
bool looks_like_subdirs(QString const&);
79
QStringList find_test_class(QString const&);
80
81
/*
82
    Verify that auto.pro only contains other .pro files (and not directories).
83
    We enforce this so that we can process every .pro file other than auto.pro
84
    independently and get all the tests.
85
    If tests were allowed to appear directly in auto.pro, we'd have the problem
86
    that we need to somehow run these tests from auto.pro while preventing
87
    recursion into the other .pro files.
88
*/
89
void tst_MakeTestSelfTest::tests_auto_pro()
90
{
91
    QStringList subdirsList = find_subdirs(SRCDIR "/../auto.pro", Flat);
92
    if (QTest::currentTestFailed()) {
93
        return;
94
    }
95
96
    foreach (QString const& subdir, subdirsList) {
97
        QVERIFY2(subdir.endsWith(".pro"), qPrintable(QString(
98
            "auto.pro contains a subdir `%1'.\n"
99
            "auto.pro must _only_ contain other .pro files, not actual subdirs.\n"
100
            "Please move `%1' into some other .pro file referenced by auto.pro."
101
        ).arg(subdir)));
102
    }
103
}
104
105
/* Verify that all tests are listed somewhere in one of the autotest .pro files */
106
void tst_MakeTestSelfTest::tests_pro_files()
107
{
108
    static QStringList lines;
109
110
    if (lines.isEmpty()) {
111
        QDir dir(SRCDIR "/..");
112
        QStringList proFiles = dir.entryList(QStringList() << "*.pro");
113
        foreach (QString const& proFile, proFiles) {
114
            QString filename = QString("%1/../%2").arg(SRCDIR).arg(proFile);
115
            QFile file(filename);
116
            if (!file.open(QIODevice::ReadOnly)) {
117
                QFAIL(qPrintable(QString("open %1: %2").arg(filename).arg(file.errorString())));
118
            }
119
            while (!file.atEnd()) {
120
                lines << file.readLine().trimmed();
121
            }
122
        }
123
    }
124
125
    QFETCH(QString, subdir);
126
    QRegExp re(QString("( |=|^|#)%1( |\\\\|$)").arg(QRegExp::escape(subdir)));
127
    foreach (const QString& line, lines) {
128
        if (re.indexIn(line) != -1) {
129
            return;
130
        }
131
    }
132
133
134
135
    QFAIL(qPrintable(QString(
136
        "Subdir `%1' is missing from tests/auto/*.pro\n"
137
        "This means the test won't be compiled or run on any platform.\n"
138
        "If this is intentional, please put the test name in a comment in one of the .pro files.").arg(subdir))
139
    );
140
141
}
142
143
void tst_MakeTestSelfTest::tests_pro_files_data()
144
{
145
    QTest::addColumn<QString>("subdir");
146
    QDir dir(SRCDIR "/..");
147
    QStringList subdirs = dir.entryList(QDir::AllDirs|QDir::NoDotAndDotDot);
148
149
    foreach (const QString& subdir, subdirs) {
150
        if (subdir == QString::fromLatin1("tmp")
151
            || subdir.startsWith("."))
152
        {
153
            continue;
154
        }
155
        QTest::newRow(qPrintable(subdir)) << subdir;
156
    }
157
}
158
159
QString format_list(QStringList const& list)
160
{
161
    if (list.count() == 1) {
162
        return list.at(0);
163
    }
164
    return QString("one of (%1)").arg(list.join(", "));
165
}
166
167
void tst_MakeTestSelfTest::naming_convention()
168
{
169
    QFETCH(QString, subdir);
170
    QFETCH(QString, target);
171
172
    QDir dir(SRCDIR "/../" + subdir);
173
174
    QStringList cppfiles = dir.entryList(QStringList() << "*.h" << "*.cpp");
175
    if (cppfiles.isEmpty()) {
176
        // Common convention is to have test/test.pro and source files in parent dir
177
        if (dir.dirName() == "test") {
178
            dir.cdUp();
179
            cppfiles = dir.entryList(QStringList() << "*.h" << "*.cpp");
180
        }
181
182
        if (cppfiles.isEmpty()) {
183
            QSKIP("Couldn't locate source files for test", SkipSingle);
184
        }
185
    }
186
187
    QStringList possible_test_classes;
188
    foreach (QString const& file, cppfiles) {
189
        possible_test_classes << find_test_class(dir.path() + "/" + file);
190
    }
191
192
    if (possible_test_classes.isEmpty()) {
193
        QSKIP(qPrintable(QString("Couldn't locate test class in %1").arg(format_list(cppfiles))), SkipSingle);
194
    }
195
196
    QVERIFY2(possible_test_classes.contains(target), qPrintable(QString(
197
        "TARGET is %1, while test class appears to be %2.\n"
198
        "TARGET and test class _must_ match so that all testcase names can be accurately "
199
        "determined even if a test fails to compile or run.")
200
        .arg(target)
201
        .arg(format_list(possible_test_classes))
202
    ));
203
204
    QVERIFY2(!all_test_classes.contains(target), qPrintable(QString(
205
        "It looks like there are multiple tests named %1.\n"
206
        "This makes it impossible to separate results for these tests.\n"
207
        "Please ensure all tests are uniquely named.")
208
        .arg(target)
209
    ));
210
211
    all_test_classes << target;
212
}
213
214
void tst_MakeTestSelfTest::naming_convention_data()
215
{
216
    QTest::addColumn<QString>("subdir");
217
    QTest::addColumn<QString>("target");
218
219
    foreach (const QString& subdir, find_subdirs(SRCDIR "/../auto.pro", Recursive)) {
220
        if (QFileInfo(SRCDIR "/../" + subdir).isDir()) {
221
            QString target;
222
            if (looks_like_testcase(SRCDIR "/../" + subdir + "/" + QFileInfo(subdir).baseName() + ".pro", &target)) {
223
                QTest::newRow(qPrintable(subdir)) << subdir << target.toLower();
224
            }
225
        }
226
    }
227
}
228
229
/*
230
    Returns true if a .pro file seems to be for an autotest.
231
    Running qmake to figure this out takes too long.
232
*/
233
bool looks_like_testcase(QString const& pro_file, QString* target)
234
{
235
    QFile file(pro_file);
236
    if (!file.open(QIODevice::ReadOnly)) {
237
        return false;
238
    }
239
240
    *target = QString();
241
242
    bool loaded_qttest = false;
243
244
    do {
245
        QByteArray line = file.readLine();
246
        if (line.isEmpty()) {
247
            break;
248
        }
249
250
        line = line.trimmed();
251
        line.replace(' ', "");
252
253
        if (line == "load(qttest_p4)") {
254
            loaded_qttest = true;
255
        }
256
257
        if (line.startsWith("TARGET=")) {
258
            *target = QString::fromLatin1(line.mid(sizeof("TARGET=")-1));
259
            if (target->contains('/')) {
260
                *target = target->right(target->lastIndexOf('/')+1);
261
            }
262
        }
263
264
        if (loaded_qttest && !target->isEmpty()) {
265
            break;
266
        }
267
    } while(1);
268
269
    if (!loaded_qttest) {
270
        return false;
271
    }
272
273
    if (!target->isEmpty() && !target->startsWith("tst_")) {
274
        return false;
275
    }
276
277
    // If no target was set, default to tst_<dirname>
278
    if (target->isEmpty()) {
279
        *target = "tst_" + QFileInfo(pro_file).baseName();
280
    }
281
282
    return true;
283
}
284
285
/*
286
    Returns true if a .pro file seems to be a subdirs project.
287
    Running qmake to figure this out takes too long.
288
*/
289
bool looks_like_subdirs(QString const& pro_file)
290
{
291
    QFile file(pro_file);
292
    if (!file.open(QIODevice::ReadOnly)) {
293
        return false;
294
    }
295
296
    do {
297
        QByteArray line = file.readLine();
298
        if (line.isEmpty()) {
299
            break;
300
        }
301
302
        line = line.trimmed();
303
        line.replace(' ', "");
304
305
        if (line == "TEMPLATE=subdirs") {
306
            return true;
307
        }
308
    } while(1);
309
310
    return false;
311
}
312
313
/*
314
    Returns a list of all subdirs in a given .pro file
315
*/
316
QStringList tst_MakeTestSelfTest::find_subdirs(QString const& pro_file, FindSubdirsMode mode, QString const& prefix)
317
{
318
    QStringList out;
319
320
    QByteArray features = qgetenv("QMAKEFEATURES");
321
322
    if (features.isEmpty()) {
323
        features = SRCDIR "/features";
324
    }
325
    else {
326
        features.prepend(SRCDIR "/features"
327
#ifdef Q_OS_WIN32
328
                ";"
329
#else
330
                ":"
331
#endif
332
        );
333
    }
334
335
    QStringList args;
336
    args << pro_file << "-o" << SRCDIR "/dummy_output" << "CONFIG+=dump_subdirs";
337
338
    /* Turn on every option there is, to ensure we process every single directory */
339
    args
340
        << "QT_CONFIG+=dbus"
341
        << "QT_CONFIG+=declarative"
342
        << "QT_CONFIG+=egl"
343
        << "QT_CONFIG+=multimedia"
344
        << "QT_CONFIG+=OdfWriter"
345
        << "QT_CONFIG+=opengl"
346
        << "QT_CONFIG+=openvg"
347
        << "QT_CONFIG+=phonon"
348
        << "QT_CONFIG+=private_tests"
349
        << "QT_CONFIG+=pulseaudio"
350
        << "QT_CONFIG+=qt3support"
351
        << "QT_CONFIG+=script"
352
        << "QT_CONFIG+=svg"
353
        << "QT_CONFIG+=webkit"
354
        << "QT_CONFIG+=xmlpatterns"
355
        << "CONFIG+=mac"
356
        << "CONFIG+=embedded"
357
        << "CONFIG+=symbian"
358
    ;
359
360
361
362
    QString cmd_with_args = QString("qmake %1").arg(args.join(" "));
363
364
    QProcess proc;
365
366
    proc.setProcessChannelMode(QProcess::MergedChannels);
367
368
    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
369
    env.insert("QMAKEFEATURES", features);
370
    proc.setProcessEnvironment(env);
371
372
    proc.start("qmake", args);
373
    if (!proc.waitForStarted(10000)) {
374
        QTest::qFail(qPrintable(QString("Failed to run qmake: %1\nCommand: %2")
375
            .arg(proc.errorString())
376
            .arg(cmd_with_args)),
377
            __FILE__, __LINE__
378
        );
379
        return out;
380
    }
381
    if (!proc.waitForFinished(30000)) {
382
        QTest::qFail(qPrintable(QString("qmake did not finish within 30 seconds\nCommand: %1\nOutput: %2")
383
            .arg(proc.errorString())
384
            .arg(cmd_with_args)
385
            .arg(QString::fromLocal8Bit(proc.readAll()))),
386
            __FILE__, __LINE__
387
        );
388
        return out;
389
    }
390
391
    if (proc.exitStatus() != QProcess::NormalExit) {
392
        QTest::qFail(qPrintable(QString("qmake crashed\nCommand: %1\nOutput: %2")
393
            .arg(cmd_with_args)
394
            .arg(QString::fromLocal8Bit(proc.readAll()))),
395
            __FILE__, __LINE__
396
        );
397
        return out;
398
    }
399
400
    if (proc.exitCode() != 0) {
401
        QTest::qFail(qPrintable(QString("qmake exited with code %1\nCommand: %2\nOutput: %3")
402
            .arg(proc.exitCode())
403
            .arg(cmd_with_args)
404
            .arg(QString::fromLocal8Bit(proc.readAll()))),
405
            __FILE__, __LINE__
406
        );
407
        return out;
408
    }
409
410
    QList<QByteArray> lines = proc.readAll().split('\n');
411
    if (!lines.count()) {
412
        QTest::qFail(qPrintable(QString("qmake seems to have not output anything\nCommand: %1\n")
413
            .arg(cmd_with_args)),
414
            __FILE__, __LINE__
415
        );
416
        return out;
417
    }
418
419
    foreach (QByteArray const& line, lines) {
420
        static const QByteArray marker = "Project MESSAGE: subdir: ";
421
        if (line.startsWith(marker)) {
422
            QString subdir = QString::fromLocal8Bit(line.mid(marker.size()).trimmed());
423
            out << prefix + subdir;
424
425
            if (mode == Flat) {
426
                continue;
427
            }
428
429
            // Need full path to subdir
430
            QString subdir_filepath = subdir;
431
            subdir_filepath.prepend(QFileInfo(pro_file).path() + "/");
432
433
            // Add subdirs recursively
434
            if (subdir.endsWith(".pro") && looks_like_subdirs(subdir_filepath)) {
435
                // Need full path to .pro file
436
                out << find_subdirs(subdir_filepath, mode, prefix);
437
            }
438
439
            if (QFileInfo(subdir_filepath).isDir()) {
440
                subdir_filepath += "/" + subdir + ".pro";
441
                if (looks_like_subdirs(subdir_filepath)) {
442
                    out << find_subdirs(subdir_filepath, mode, prefix + subdir + "/");
443
                }
444
            }
445
        }
446
    }
447
448
    return out;
449
}
450
451
void tst_MakeTestSelfTest::make_check()
452
{
453
    /*
454
        Run `make check' over the whole tests tree with a custom TESTRUNNER,
455
        to verify that the TESTRUNNER mechanism works right.
456
    */
457
    QString testsDir(SRCDIR "/..");
458
    QString checktest(SRCDIR "/checktest/checktest");
459
460
#if defined(Q_OS_WIN32) || defined(Q_OS_MAC)
461
    if (qgetenv("RUN_SLOW_TESTS").isEmpty()) {
462
        QSKIP("This test is too slow to run by default on this OS. Set RUN_SLOW_TESTS=1 to run it.", SkipAll);
463
    }
464
#endif
465
466
#ifdef Q_OS_WIN32
467
    checktest.replace("/", "\\");
468
    checktest += ".exe";
469
#endif
470
471
    QProcess make;
472
    make.setWorkingDirectory(testsDir);
473
474
    QStringList arguments;
475
    arguments << "-k";
476
    arguments << "check";
477
    arguments << QString("TESTRUNNER=%1").arg(checktest);
478
479
    // find the right make; from externaltests.cpp
480
    static const char makes[] =
481
        "nmake.exe\0"
482
        "mingw32-make.exe\0"
483
        "gmake\0"
484
        "make\0"
485
    ;
486
487
    bool ok = false;
488
    for (const char *p = makes; *p; p += strlen(p) + 1) {
489
        make.start(p, arguments);
490
        if (make.waitForStarted()) {
491
            ok = true;
492
            break;
493
        }
494
    }
495
496
    if (!ok) {
497
        QFAIL("Could not find the right make tool in PATH");
498
    }
499
500
    QVERIFY(make.waitForFinished(1000 * 60 * 10));
501
    QCOMPARE(make.exitStatus(), QProcess::NormalExit);
502
503
    int pass = 0;
504
    QList<QByteArray> out = make.readAllStandardOutput().split('\n');
505
    QStringList fails;
506
    foreach (QByteArray line, out) {
507
        while (line.endsWith("\r")) {
508
            line.chop(1);
509
        }
510
        if (line.startsWith("CHECKTEST FAIL")) {
511
            fails << QString::fromLocal8Bit(line);
512
        }
513
        if (line.startsWith("CHECKTEST PASS")) {
514
            ++pass;
515
        }
516
    }
517
518
    // We can't check that the exit code of make is 0, because some tests
519
    // may have failed to compile, but that doesn't mean `make check' is broken.
520
    // We do assume there are at least this many unbroken tests, though.
521
    QVERIFY2(fails.count() == 0,
522
        qPrintable(QString("`make check' doesn't work for %1 tests:\n%2")
523
            .arg(fails.count()).arg(fails.join("\n")))
524
    );
525
    QVERIFY(pass > 50);
526
}
527
528
QStringList find_test_class(QString const& filename)
529
{
530
    QStringList out;
531
532
    QFile file(filename);
533
    if (!file.open(QIODevice::ReadOnly)) {
534
        return out;
535
    }
536
537
    static char const* klass_indicators[] = {
538
        "QTEST_MAIN(",
539
        "QTEST_APPLESS_MAIN(",
540
        "class",
541
        "staticconstcharklass[]=\"",  /* hax0r tests which define their own metaobject */
542
        0
543
    };
544
545
    do {
546
        QByteArray line = file.readLine();
547
        if (line.isEmpty()) {
548
            break;
549
        }
550
551
        line = line.trimmed();
552
        line.replace(' ', "");
553
554
        for (int i = 0; klass_indicators[i]; ++i) {
555
            char const* prefix = klass_indicators[i];
556
            if (!line.startsWith(prefix)) {
557
                continue;
558
            }
559
            QByteArray klass = line.mid(strlen(prefix));
560
            if (!klass.startsWith("tst_")) {
561
                continue;
562
            }
563
            for (int j = 0; j < klass.size(); ++j) {
564
                char c = klass[j];
565
                if (c == '_'
566
                        || (c >= '0' && c <= '9')
567
                        || (c >= 'A' && c <= 'Z')
568
                        || (c >= 'a' && c <= 'z')) {
569
                    continue;
570
                }
571
                else {
572
                    klass.truncate(j);
573
                    break;
574
                }
575
            }
576
            QString klass_str = QString::fromLocal8Bit(klass).toLower();
577
            if (!out.contains(klass_str))
578
                out << klass_str;
579
            break;
580
        }
581
    } while(1);
582
583
    return out;
584
}
585
586
QTEST_MAIN(tst_MakeTestSelfTest)
587
#include "tst_maketestselftest.moc"