diff options
Diffstat (limited to 'tests/auto/maketestselftest/tst_maketestselftest.cpp')
-rw-r--r-- | tests/auto/maketestselftest/tst_maketestselftest.cpp | 587 |
1 files changed, 587 insertions, 0 deletions
diff --git a/tests/auto/maketestselftest/tst_maketestselftest.cpp b/tests/auto/maketestselftest/tst_maketestselftest.cpp new file mode 100644 index 0000000000..8ed8cb88d0 --- /dev/null +++ b/tests/auto/maketestselftest/tst_maketestselftest.cpp @@ -0,0 +1,587 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QDir> +#include <QFile> +#include <QRegExp> +#include <QStringList> +#include <QTest> +#include <QSet> +#include <QProcess> +#include <QDebug> + +enum FindSubdirsMode { + Flat = 0, + Recursive +}; + +class tst_MakeTestSelfTest: public QObject +{ + Q_OBJECT + +private slots: + void tests_auto_pro(); + + void tests_pro_files(); + void tests_pro_files_data(); + + void naming_convention(); + void naming_convention_data(); + + void make_check(); + +private: + QStringList find_subdirs(QString const&, FindSubdirsMode, QString const& = QString()); + + QSet<QString> all_test_classes; +}; + +bool looks_like_testcase(QString const&,QString*); +bool looks_like_subdirs(QString const&); +QStringList find_test_class(QString const&); + +/* + Verify that auto.pro only contains other .pro files (and not directories). + We enforce this so that we can process every .pro file other than auto.pro + independently and get all the tests. + If tests were allowed to appear directly in auto.pro, we'd have the problem + that we need to somehow run these tests from auto.pro while preventing + recursion into the other .pro files. +*/ +void tst_MakeTestSelfTest::tests_auto_pro() +{ + QStringList subdirsList = find_subdirs(SRCDIR "/../auto.pro", Flat); + if (QTest::currentTestFailed()) { + return; + } + + foreach (QString const& subdir, subdirsList) { + QVERIFY2(subdir.endsWith(".pro"), qPrintable(QString( + "auto.pro contains a subdir `%1'.\n" + "auto.pro must _only_ contain other .pro files, not actual subdirs.\n" + "Please move `%1' into some other .pro file referenced by auto.pro." + ).arg(subdir))); + } +} + +/* Verify that all tests are listed somewhere in one of the autotest .pro files */ +void tst_MakeTestSelfTest::tests_pro_files() +{ + static QStringList lines; + + if (lines.isEmpty()) { + QDir dir(SRCDIR "/.."); + QStringList proFiles = dir.entryList(QStringList() << "*.pro"); + foreach (QString const& proFile, proFiles) { + QString filename = QString("%1/../%2").arg(SRCDIR).arg(proFile); + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) { + QFAIL(qPrintable(QString("open %1: %2").arg(filename).arg(file.errorString()))); + } + while (!file.atEnd()) { + lines << file.readLine().trimmed(); + } + } + } + + QFETCH(QString, subdir); + QRegExp re(QString("( |=|^|#)%1( |\\\\|$)").arg(QRegExp::escape(subdir))); + foreach (const QString& line, lines) { + if (re.indexIn(line) != -1) { + return; + } + } + + + + QFAIL(qPrintable(QString( + "Subdir `%1' is missing from tests/auto/*.pro\n" + "This means the test won't be compiled or run on any platform.\n" + "If this is intentional, please put the test name in a comment in one of the .pro files.").arg(subdir)) + ); + +} + +void tst_MakeTestSelfTest::tests_pro_files_data() +{ + QTest::addColumn<QString>("subdir"); + QDir dir(SRCDIR "/.."); + QStringList subdirs = dir.entryList(QDir::AllDirs|QDir::NoDotAndDotDot); + + foreach (const QString& subdir, subdirs) { + if (subdir == QString::fromLatin1("tmp") + || subdir.startsWith(".")) + { + continue; + } + QTest::newRow(qPrintable(subdir)) << subdir; + } +} + +QString format_list(QStringList const& list) +{ + if (list.count() == 1) { + return list.at(0); + } + return QString("one of (%1)").arg(list.join(", ")); +} + +void tst_MakeTestSelfTest::naming_convention() +{ + QFETCH(QString, subdir); + QFETCH(QString, target); + + QDir dir(SRCDIR "/../" + subdir); + + QStringList cppfiles = dir.entryList(QStringList() << "*.h" << "*.cpp"); + if (cppfiles.isEmpty()) { + // Common convention is to have test/test.pro and source files in parent dir + if (dir.dirName() == "test") { + dir.cdUp(); + cppfiles = dir.entryList(QStringList() << "*.h" << "*.cpp"); + } + + if (cppfiles.isEmpty()) { + QSKIP("Couldn't locate source files for test", SkipSingle); + } + } + + QStringList possible_test_classes; + foreach (QString const& file, cppfiles) { + possible_test_classes << find_test_class(dir.path() + "/" + file); + } + + if (possible_test_classes.isEmpty()) { + QSKIP(qPrintable(QString("Couldn't locate test class in %1").arg(format_list(cppfiles))), SkipSingle); + } + + QVERIFY2(possible_test_classes.contains(target), qPrintable(QString( + "TARGET is %1, while test class appears to be %2.\n" + "TARGET and test class _must_ match so that all testcase names can be accurately " + "determined even if a test fails to compile or run.") + .arg(target) + .arg(format_list(possible_test_classes)) + )); + + QVERIFY2(!all_test_classes.contains(target), qPrintable(QString( + "It looks like there are multiple tests named %1.\n" + "This makes it impossible to separate results for these tests.\n" + "Please ensure all tests are uniquely named.") + .arg(target) + )); + + all_test_classes << target; +} + +void tst_MakeTestSelfTest::naming_convention_data() +{ + QTest::addColumn<QString>("subdir"); + QTest::addColumn<QString>("target"); + + foreach (const QString& subdir, find_subdirs(SRCDIR "/../auto.pro", Recursive)) { + if (QFileInfo(SRCDIR "/../" + subdir).isDir()) { + QString target; + if (looks_like_testcase(SRCDIR "/../" + subdir + "/" + QFileInfo(subdir).baseName() + ".pro", &target)) { + QTest::newRow(qPrintable(subdir)) << subdir << target.toLower(); + } + } + } +} + +/* + Returns true if a .pro file seems to be for an autotest. + Running qmake to figure this out takes too long. +*/ +bool looks_like_testcase(QString const& pro_file, QString* target) +{ + QFile file(pro_file); + if (!file.open(QIODevice::ReadOnly)) { + return false; + } + + *target = QString(); + + bool loaded_qttest = false; + + do { + QByteArray line = file.readLine(); + if (line.isEmpty()) { + break; + } + + line = line.trimmed(); + line.replace(' ', ""); + + if (line == "load(qttest_p4)") { + loaded_qttest = true; + } + + if (line.startsWith("TARGET=")) { + *target = QString::fromLatin1(line.mid(sizeof("TARGET=")-1)); + if (target->contains('/')) { + *target = target->right(target->lastIndexOf('/')+1); + } + } + + if (loaded_qttest && !target->isEmpty()) { + break; + } + } while(1); + + if (!loaded_qttest) { + return false; + } + + if (!target->isEmpty() && !target->startsWith("tst_")) { + return false; + } + + // If no target was set, default to tst_<dirname> + if (target->isEmpty()) { + *target = "tst_" + QFileInfo(pro_file).baseName(); + } + + return true; +} + +/* + Returns true if a .pro file seems to be a subdirs project. + Running qmake to figure this out takes too long. +*/ +bool looks_like_subdirs(QString const& pro_file) +{ + QFile file(pro_file); + if (!file.open(QIODevice::ReadOnly)) { + return false; + } + + do { + QByteArray line = file.readLine(); + if (line.isEmpty()) { + break; + } + + line = line.trimmed(); + line.replace(' ', ""); + + if (line == "TEMPLATE=subdirs") { + return true; + } + } while(1); + + return false; +} + +/* + Returns a list of all subdirs in a given .pro file +*/ +QStringList tst_MakeTestSelfTest::find_subdirs(QString const& pro_file, FindSubdirsMode mode, QString const& prefix) +{ + QStringList out; + + QByteArray features = qgetenv("QMAKEFEATURES"); + + if (features.isEmpty()) { + features = SRCDIR "/features"; + } + else { + features.prepend(SRCDIR "/features" +#ifdef Q_OS_WIN32 + ";" +#else + ":" +#endif + ); + } + + QStringList args; + args << pro_file << "-o" << SRCDIR "/dummy_output" << "CONFIG+=dump_subdirs"; + + /* Turn on every option there is, to ensure we process every single directory */ + args + << "QT_CONFIG+=dbus" + << "QT_CONFIG+=declarative" + << "QT_CONFIG+=egl" + << "QT_CONFIG+=multimedia" + << "QT_CONFIG+=OdfWriter" + << "QT_CONFIG+=opengl" + << "QT_CONFIG+=openvg" + << "QT_CONFIG+=phonon" + << "QT_CONFIG+=private_tests" + << "QT_CONFIG+=pulseaudio" + << "QT_CONFIG+=qt3support" + << "QT_CONFIG+=script" + << "QT_CONFIG+=svg" + << "QT_CONFIG+=webkit" + << "QT_CONFIG+=xmlpatterns" + << "CONFIG+=mac" + << "CONFIG+=embedded" + << "CONFIG+=symbian" + ; + + + + QString cmd_with_args = QString("qmake %1").arg(args.join(" ")); + + QProcess proc; + + proc.setProcessChannelMode(QProcess::MergedChannels); + + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert("QMAKEFEATURES", features); + proc.setProcessEnvironment(env); + + proc.start("qmake", args); + if (!proc.waitForStarted(10000)) { + QTest::qFail(qPrintable(QString("Failed to run qmake: %1\nCommand: %2") + .arg(proc.errorString()) + .arg(cmd_with_args)), + __FILE__, __LINE__ + ); + return out; + } + if (!proc.waitForFinished(30000)) { + QTest::qFail(qPrintable(QString("qmake did not finish within 30 seconds\nCommand: %1\nOutput: %2") + .arg(proc.errorString()) + .arg(cmd_with_args) + .arg(QString::fromLocal8Bit(proc.readAll()))), + __FILE__, __LINE__ + ); + return out; + } + + if (proc.exitStatus() != QProcess::NormalExit) { + QTest::qFail(qPrintable(QString("qmake crashed\nCommand: %1\nOutput: %2") + .arg(cmd_with_args) + .arg(QString::fromLocal8Bit(proc.readAll()))), + __FILE__, __LINE__ + ); + return out; + } + + if (proc.exitCode() != 0) { + QTest::qFail(qPrintable(QString("qmake exited with code %1\nCommand: %2\nOutput: %3") + .arg(proc.exitCode()) + .arg(cmd_with_args) + .arg(QString::fromLocal8Bit(proc.readAll()))), + __FILE__, __LINE__ + ); + return out; + } + + QList<QByteArray> lines = proc.readAll().split('\n'); + if (!lines.count()) { + QTest::qFail(qPrintable(QString("qmake seems to have not output anything\nCommand: %1\n") + .arg(cmd_with_args)), + __FILE__, __LINE__ + ); + return out; + } + + foreach (QByteArray const& line, lines) { + static const QByteArray marker = "Project MESSAGE: subdir: "; + if (line.startsWith(marker)) { + QString subdir = QString::fromLocal8Bit(line.mid(marker.size()).trimmed()); + out << prefix + subdir; + + if (mode == Flat) { + continue; + } + + // Need full path to subdir + QString subdir_filepath = subdir; + subdir_filepath.prepend(QFileInfo(pro_file).path() + "/"); + + // Add subdirs recursively + if (subdir.endsWith(".pro") && looks_like_subdirs(subdir_filepath)) { + // Need full path to .pro file + out << find_subdirs(subdir_filepath, mode, prefix); + } + + if (QFileInfo(subdir_filepath).isDir()) { + subdir_filepath += "/" + subdir + ".pro"; + if (looks_like_subdirs(subdir_filepath)) { + out << find_subdirs(subdir_filepath, mode, prefix + subdir + "/"); + } + } + } + } + + return out; +} + +void tst_MakeTestSelfTest::make_check() +{ + /* + Run `make check' over the whole tests tree with a custom TESTRUNNER, + to verify that the TESTRUNNER mechanism works right. + */ + QString testsDir(SRCDIR "/.."); + QString checktest(SRCDIR "/checktest/checktest"); + +#if defined(Q_OS_WIN32) || defined(Q_OS_MAC) + if (qgetenv("RUN_SLOW_TESTS").isEmpty()) { + QSKIP("This test is too slow to run by default on this OS. Set RUN_SLOW_TESTS=1 to run it.", SkipAll); + } +#endif + +#ifdef Q_OS_WIN32 + checktest.replace("/", "\\"); + checktest += ".exe"; +#endif + + QProcess make; + make.setWorkingDirectory(testsDir); + + QStringList arguments; + arguments << "-k"; + arguments << "check"; + arguments << QString("TESTRUNNER=%1").arg(checktest); + + // find the right make; from externaltests.cpp + static const char makes[] = + "nmake.exe\0" + "mingw32-make.exe\0" + "gmake\0" + "make\0" + ; + + bool ok = false; + for (const char *p = makes; *p; p += strlen(p) + 1) { + make.start(p, arguments); + if (make.waitForStarted()) { + ok = true; + break; + } + } + + if (!ok) { + QFAIL("Could not find the right make tool in PATH"); + } + + QVERIFY(make.waitForFinished(1000 * 60 * 10)); + QCOMPARE(make.exitStatus(), QProcess::NormalExit); + + int pass = 0; + QList<QByteArray> out = make.readAllStandardOutput().split('\n'); + QStringList fails; + foreach (QByteArray line, out) { + while (line.endsWith("\r")) { + line.chop(1); + } + if (line.startsWith("CHECKTEST FAIL")) { + fails << QString::fromLocal8Bit(line); + } + if (line.startsWith("CHECKTEST PASS")) { + ++pass; + } + } + + // We can't check that the exit code of make is 0, because some tests + // may have failed to compile, but that doesn't mean `make check' is broken. + // We do assume there are at least this many unbroken tests, though. + QVERIFY2(fails.count() == 0, + qPrintable(QString("`make check' doesn't work for %1 tests:\n%2") + .arg(fails.count()).arg(fails.join("\n"))) + ); + QVERIFY(pass > 50); +} + +QStringList find_test_class(QString const& filename) +{ + QStringList out; + + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) { + return out; + } + + static char const* klass_indicators[] = { + "QTEST_MAIN(", + "QTEST_APPLESS_MAIN(", + "class", + "staticconstcharklass[]=\"", /* hax0r tests which define their own metaobject */ + 0 + }; + + do { + QByteArray line = file.readLine(); + if (line.isEmpty()) { + break; + } + + line = line.trimmed(); + line.replace(' ', ""); + + for (int i = 0; klass_indicators[i]; ++i) { + char const* prefix = klass_indicators[i]; + if (!line.startsWith(prefix)) { + continue; + } + QByteArray klass = line.mid(strlen(prefix)); + if (!klass.startsWith("tst_")) { + continue; + } + for (int j = 0; j < klass.size(); ++j) { + char c = klass[j]; + if (c == '_' + || (c >= '0' && c <= '9') + || (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z')) { + continue; + } + else { + klass.truncate(j); + break; + } + } + QString klass_str = QString::fromLocal8Bit(klass).toLower(); + if (!out.contains(klass_str)) + out << klass_str; + break; + } + } while(1); + + return out; +} + +QTEST_MAIN(tst_MakeTestSelfTest) +#include "tst_maketestselftest.moc" |