tests/auto/maketestselftest/tst_maketestselftest.cpp
changeset 30 5dc02b23752f
parent 19 fcece45ef507
child 33 3e2da88830cd
equal deleted inserted replaced
29:b72c6db6890b 30:5dc02b23752f
    42 #include <QDir>
    42 #include <QDir>
    43 #include <QFile>
    43 #include <QFile>
    44 #include <QRegExp>
    44 #include <QRegExp>
    45 #include <QStringList>
    45 #include <QStringList>
    46 #include <QTest>
    46 #include <QTest>
       
    47 #include <QSet>
       
    48 #include <QProcess>
       
    49 #include <QDebug>
       
    50 
       
    51 enum FindSubdirsMode {
       
    52     Flat = 0,
       
    53     Recursive
       
    54 };
    47 
    55 
    48 class tst_MakeTestSelfTest: public QObject
    56 class tst_MakeTestSelfTest: public QObject
    49 {
    57 {
    50     Q_OBJECT
    58     Q_OBJECT
    51 
    59 
    52 private slots:
    60 private slots:
       
    61     void tests_auto_pro();
       
    62 
    53     void tests_pro_files();
    63     void tests_pro_files();
    54     void tests_pro_files_data();
    64     void tests_pro_files_data();
       
    65 
       
    66     void naming_convention();
       
    67     void naming_convention_data();
       
    68 
       
    69 private:
       
    70     QStringList find_subdirs(QString const&, FindSubdirsMode, QString const& = QString());
       
    71 
       
    72     QSet<QString> all_test_classes;
    55 };
    73 };
       
    74 
       
    75 bool looks_like_testcase(QString const&,QString*);
       
    76 bool looks_like_subdirs(QString const&);
       
    77 QStringList find_test_class(QString const&);
       
    78 
       
    79 /*
       
    80     Verify that auto.pro only contains other .pro files (and not directories).
       
    81     We enforce this so that we can process every .pro file other than auto.pro
       
    82     independently and get all the tests.
       
    83     If tests were allowed to appear directly in auto.pro, we'd have the problem
       
    84     that we need to somehow run these tests from auto.pro while preventing
       
    85     recursion into the other .pro files.
       
    86 */
       
    87 void tst_MakeTestSelfTest::tests_auto_pro()
       
    88 {
       
    89     QStringList subdirsList = find_subdirs(SRCDIR "/../auto.pro", Flat);
       
    90     if (QTest::currentTestFailed()) {
       
    91         return;
       
    92     }
       
    93 
       
    94     foreach (QString const& subdir, subdirsList) {
       
    95         QVERIFY2(subdir.endsWith(".pro"), qPrintable(QString(
       
    96             "auto.pro contains a subdir `%1'.\n"
       
    97             "auto.pro must _only_ contain other .pro files, not actual subdirs.\n"
       
    98             "Please move `%1' into some other .pro file referenced by auto.pro."
       
    99         ).arg(subdir)));
       
   100     }
       
   101 }
    56 
   102 
    57 /* Verify that all tests are listed somewhere in one of the autotest .pro files */
   103 /* Verify that all tests are listed somewhere in one of the autotest .pro files */
    58 void tst_MakeTestSelfTest::tests_pro_files()
   104 void tst_MakeTestSelfTest::tests_pro_files()
    59 {
   105 {
    60     static QStringList lines;
   106     static QStringList lines;
    80         if (re.indexIn(line) != -1) {
   126         if (re.indexIn(line) != -1) {
    81             return;
   127             return;
    82         }
   128         }
    83     }
   129     }
    84 
   130 
       
   131 
       
   132 
    85     QFAIL(qPrintable(QString(
   133     QFAIL(qPrintable(QString(
    86         "Subdir `%1' is missing from tests/auto/*.pro\n"
   134         "Subdir `%1' is missing from tests/auto/*.pro\n"
    87         "This means the test won't be compiled or run on any platform.\n"
   135         "This means the test won't be compiled or run on any platform.\n"
    88         "If this is intentional, please put the test name in a comment in one of the .pro files.").arg(subdir))
   136         "If this is intentional, please put the test name in a comment in one of the .pro files.").arg(subdir))
    89     );
   137     );
   104         }
   152         }
   105         QTest::newRow(qPrintable(subdir)) << subdir;
   153         QTest::newRow(qPrintable(subdir)) << subdir;
   106     }
   154     }
   107 }
   155 }
   108 
   156 
       
   157 QString format_list(QStringList const& list)
       
   158 {
       
   159     if (list.count() == 1) {
       
   160         return list.at(0);
       
   161     }
       
   162     return QString("one of (%1)").arg(list.join(", "));
       
   163 }
       
   164 
       
   165 void tst_MakeTestSelfTest::naming_convention()
       
   166 {
       
   167     QFETCH(QString, subdir);
       
   168     QFETCH(QString, target);
       
   169 
       
   170     QDir dir(SRCDIR "/../" + subdir);
       
   171 
       
   172     QStringList cppfiles = dir.entryList(QStringList() << "*.h" << "*.cpp");
       
   173     if (cppfiles.isEmpty()) {
       
   174         // Common convention is to have test/test.pro and source files in parent dir
       
   175         if (dir.dirName() == "test") {
       
   176             dir.cdUp();
       
   177             cppfiles = dir.entryList(QStringList() << "*.h" << "*.cpp");
       
   178         }
       
   179 
       
   180         if (cppfiles.isEmpty()) {
       
   181             QSKIP("Couldn't locate source files for test", SkipSingle);
       
   182         }
       
   183     }
       
   184 
       
   185     QStringList possible_test_classes;
       
   186     foreach (QString const& file, cppfiles) {
       
   187         possible_test_classes << find_test_class(dir.path() + "/" + file);
       
   188     }
       
   189 
       
   190     if (possible_test_classes.isEmpty()) {
       
   191         QSKIP(qPrintable(QString("Couldn't locate test class in %1").arg(format_list(cppfiles))), SkipSingle);
       
   192     }
       
   193 
       
   194     QVERIFY2(possible_test_classes.contains(target), qPrintable(QString(
       
   195         "TARGET is %1, while test class appears to be %2.\n"
       
   196         "TARGET and test class _must_ match so that all testcase names can be accurately "
       
   197         "determined even if a test fails to compile or run.")
       
   198         .arg(target)
       
   199         .arg(format_list(possible_test_classes))
       
   200     ));
       
   201 
       
   202     QVERIFY2(!all_test_classes.contains(target), qPrintable(QString(
       
   203         "It looks like there are multiple tests named %1.\n"
       
   204         "This makes it impossible to separate results for these tests.\n"
       
   205         "Please ensure all tests are uniquely named.")
       
   206         .arg(target)
       
   207     ));
       
   208 
       
   209     all_test_classes << target;
       
   210 }
       
   211 
       
   212 void tst_MakeTestSelfTest::naming_convention_data()
       
   213 {
       
   214     QTest::addColumn<QString>("subdir");
       
   215     QTest::addColumn<QString>("target");
       
   216 
       
   217     foreach (const QString& subdir, find_subdirs(SRCDIR "/../auto.pro", Recursive)) {
       
   218         if (QFileInfo(SRCDIR "/../" + subdir).isDir()) {
       
   219             QString target;
       
   220             if (looks_like_testcase(SRCDIR "/../" + subdir + "/" + QFileInfo(subdir).baseName() + ".pro", &target)) {
       
   221                 QTest::newRow(qPrintable(subdir)) << subdir << target.toLower();
       
   222             }
       
   223         }
       
   224     }
       
   225 }
       
   226 
       
   227 /*
       
   228     Returns true if a .pro file seems to be for an autotest.
       
   229     Running qmake to figure this out takes too long.
       
   230 */
       
   231 bool looks_like_testcase(QString const& pro_file, QString* target)
       
   232 {
       
   233     QFile file(pro_file);
       
   234     if (!file.open(QIODevice::ReadOnly)) {
       
   235         return false;
       
   236     }
       
   237 
       
   238     *target = QString();
       
   239 
       
   240     bool loaded_qttest = false;
       
   241 
       
   242     do {
       
   243         QByteArray line = file.readLine();
       
   244         if (line.isEmpty()) {
       
   245             break;
       
   246         }
       
   247 
       
   248         line = line.trimmed();
       
   249         line.replace(' ', "");
       
   250 
       
   251         if (line == "load(qttest_p4)") {
       
   252             loaded_qttest = true;
       
   253         }
       
   254 
       
   255         if (line.startsWith("TARGET=")) {
       
   256             *target = QString::fromLatin1(line.mid(sizeof("TARGET=")-1));
       
   257             if (target->contains('/')) {
       
   258                 *target = target->right(target->lastIndexOf('/')+1);
       
   259             }
       
   260         }
       
   261 
       
   262         if (loaded_qttest && !target->isEmpty()) {
       
   263             break;
       
   264         }
       
   265     } while(1);
       
   266 
       
   267     if (!loaded_qttest) {
       
   268         return false;
       
   269     }
       
   270 
       
   271     if (!target->isEmpty() && !target->startsWith("tst_")) {
       
   272         return false;
       
   273     }
       
   274 
       
   275     // If no target was set, default to tst_<dirname>
       
   276     if (target->isEmpty()) {
       
   277         *target = "tst_" + QFileInfo(pro_file).baseName();
       
   278     }
       
   279 
       
   280     return true;
       
   281 }
       
   282 
       
   283 /*
       
   284     Returns true if a .pro file seems to be a subdirs project.
       
   285     Running qmake to figure this out takes too long.
       
   286 */
       
   287 bool looks_like_subdirs(QString const& pro_file)
       
   288 {
       
   289     QFile file(pro_file);
       
   290     if (!file.open(QIODevice::ReadOnly)) {
       
   291         return false;
       
   292     }
       
   293 
       
   294     do {
       
   295         QByteArray line = file.readLine();
       
   296         if (line.isEmpty()) {
       
   297             break;
       
   298         }
       
   299 
       
   300         line = line.trimmed();
       
   301         line.replace(' ', "");
       
   302 
       
   303         if (line == "TEMPLATE=subdirs") {
       
   304             return true;
       
   305         }
       
   306     } while(1);
       
   307 
       
   308     return false;
       
   309 }
       
   310 
       
   311 /*
       
   312     Returns a list of all subdirs in a given .pro file
       
   313 */
       
   314 QStringList tst_MakeTestSelfTest::find_subdirs(QString const& pro_file, FindSubdirsMode mode, QString const& prefix)
       
   315 {
       
   316     QStringList out;
       
   317 
       
   318     QByteArray features = qgetenv("QMAKEFEATURES");
       
   319 
       
   320     if (features.isEmpty()) {
       
   321         features = SRCDIR "/features";
       
   322     }
       
   323     else {
       
   324         features.prepend(SRCDIR "/features"
       
   325 #ifdef Q_OS_WIN32
       
   326                 ";"
       
   327 #else
       
   328                 ":"
       
   329 #endif
       
   330         );
       
   331     }
       
   332 
       
   333     QStringList args;
       
   334     args << pro_file << "-o" << SRCDIR "/dummy_output" << "CONFIG+=dump_subdirs";
       
   335 
       
   336     /* Turn on every option there is, to ensure we process every single directory */
       
   337     args
       
   338         << "QT_CONFIG+=dbus"
       
   339         << "QT_CONFIG+=declarative"
       
   340         << "QT_CONFIG+=egl"
       
   341         << "QT_CONFIG+=multimedia"
       
   342         << "QT_CONFIG+=OdfWriter"
       
   343         << "QT_CONFIG+=opengl"
       
   344         << "QT_CONFIG+=openvg"
       
   345         << "QT_CONFIG+=phonon"
       
   346         << "QT_CONFIG+=private_tests"
       
   347         << "QT_CONFIG+=pulseaudio"
       
   348         << "QT_CONFIG+=qt3support"
       
   349         << "QT_CONFIG+=script"
       
   350         << "QT_CONFIG+=svg"
       
   351         << "QT_CONFIG+=webkit"
       
   352         << "QT_CONFIG+=xmlpatterns"
       
   353         << "CONFIG+=mac"
       
   354         << "CONFIG+=embedded"
       
   355         << "CONFIG+=symbian"
       
   356     ;
       
   357 
       
   358 
       
   359 
       
   360     QString cmd_with_args = QString("qmake %1").arg(args.join(" "));
       
   361 
       
   362     QProcess proc;
       
   363 
       
   364     proc.setProcessChannelMode(QProcess::MergedChannels);
       
   365 
       
   366     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
       
   367     env.insert("QMAKEFEATURES", features);
       
   368     proc.setProcessEnvironment(env);
       
   369 
       
   370     proc.start("qmake", args);
       
   371     if (!proc.waitForStarted(10000)) {
       
   372         QTest::qFail(qPrintable(QString("Failed to run qmake: %1\nCommand: %2")
       
   373             .arg(proc.errorString())
       
   374             .arg(cmd_with_args)),
       
   375             __FILE__, __LINE__
       
   376         );
       
   377         return out;
       
   378     }
       
   379     if (!proc.waitForFinished(30000)) {
       
   380         QTest::qFail(qPrintable(QString("qmake did not finish within 30 seconds\nCommand: %1\nOutput: %2")
       
   381             .arg(proc.errorString())
       
   382             .arg(cmd_with_args)
       
   383             .arg(QString::fromLocal8Bit(proc.readAll()))),
       
   384             __FILE__, __LINE__
       
   385         );
       
   386         return out;
       
   387     }
       
   388 
       
   389     if (proc.exitStatus() != QProcess::NormalExit) {
       
   390         QTest::qFail(qPrintable(QString("qmake crashed\nCommand: %1\nOutput: %2")
       
   391             .arg(cmd_with_args)
       
   392             .arg(QString::fromLocal8Bit(proc.readAll()))),
       
   393             __FILE__, __LINE__
       
   394         );
       
   395         return out;
       
   396     }
       
   397 
       
   398     if (proc.exitCode() != 0) {
       
   399         QTest::qFail(qPrintable(QString("qmake exited with code %1\nCommand: %2\nOutput: %3")
       
   400             .arg(proc.exitCode())
       
   401             .arg(cmd_with_args)
       
   402             .arg(QString::fromLocal8Bit(proc.readAll()))),
       
   403             __FILE__, __LINE__
       
   404         );
       
   405         return out;
       
   406     }
       
   407 
       
   408     QList<QByteArray> lines = proc.readAll().split('\n');
       
   409     if (!lines.count()) {
       
   410         QTest::qFail(qPrintable(QString("qmake seems to have not output anything\nCommand: %1\n")
       
   411             .arg(cmd_with_args)),
       
   412             __FILE__, __LINE__
       
   413         );
       
   414         return out;
       
   415     }
       
   416 
       
   417     foreach (QByteArray const& line, lines) {
       
   418         static const QByteArray marker = "Project MESSAGE: subdir: ";
       
   419         if (line.startsWith(marker)) {
       
   420             QString subdir = QString::fromLocal8Bit(line.mid(marker.size()).trimmed());
       
   421             out << prefix + subdir;
       
   422 
       
   423             if (mode == Flat) {
       
   424                 continue;
       
   425             }
       
   426 
       
   427             // Need full path to subdir
       
   428             QString subdir_filepath = subdir;
       
   429             subdir_filepath.prepend(QFileInfo(pro_file).path() + "/");
       
   430 
       
   431             // Add subdirs recursively
       
   432             if (subdir.endsWith(".pro") && looks_like_subdirs(subdir_filepath)) {
       
   433                 // Need full path to .pro file
       
   434                 out << find_subdirs(subdir_filepath, mode, prefix);
       
   435             }
       
   436 
       
   437             if (QFileInfo(subdir_filepath).isDir()) {
       
   438                 subdir_filepath += "/" + subdir + ".pro";
       
   439                 if (looks_like_subdirs(subdir_filepath)) {
       
   440                     out << find_subdirs(subdir_filepath, mode, prefix + subdir + "/");
       
   441                 }
       
   442             }
       
   443         }
       
   444     }
       
   445 
       
   446     return out;
       
   447 }
       
   448 
       
   449 QStringList find_test_class(QString const& filename)
       
   450 {
       
   451     QStringList out;
       
   452 
       
   453     QFile file(filename);
       
   454     if (!file.open(QIODevice::ReadOnly)) {
       
   455         return out;
       
   456     }
       
   457 
       
   458     static char const* klass_indicators[] = {
       
   459         "QTEST_MAIN(",
       
   460         "QTEST_APPLESS_MAIN(",
       
   461         "class",
       
   462         "staticconstcharklass[]=\"",  /* hax0r tests which define their own metaobject */
       
   463         0
       
   464     };
       
   465 
       
   466     do {
       
   467         QByteArray line = file.readLine();
       
   468         if (line.isEmpty()) {
       
   469             break;
       
   470         }
       
   471 
       
   472         line = line.trimmed();
       
   473         line.replace(' ', "");
       
   474 
       
   475         for (int i = 0; klass_indicators[i]; ++i) {
       
   476             char const* prefix = klass_indicators[i];
       
   477             if (!line.startsWith(prefix)) {
       
   478                 continue;
       
   479             }
       
   480             QByteArray klass = line.mid(strlen(prefix));
       
   481             if (!klass.startsWith("tst_")) {
       
   482                 continue;
       
   483             }
       
   484             for (int j = 0; j < klass.size(); ++j) {
       
   485                 char c = klass[j];
       
   486                 if (c == '_'
       
   487                         || (c >= '0' && c <= '9')
       
   488                         || (c >= 'A' && c <= 'Z')
       
   489                         || (c >= 'a' && c <= 'z')) {
       
   490                     continue;
       
   491                 }
       
   492                 else {
       
   493                     klass.truncate(j);
       
   494                     break;
       
   495                 }
       
   496             }
       
   497             QString klass_str = QString::fromLocal8Bit(klass).toLower();
       
   498             if (!out.contains(klass_str))
       
   499                 out << klass_str;
       
   500             break;
       
   501         }
       
   502     } while(1);
       
   503 
       
   504     return out;
       
   505 }
       
   506 
   109 QTEST_MAIN(tst_MakeTestSelfTest)
   507 QTEST_MAIN(tst_MakeTestSelfTest)
   110 #include "tst_maketestselftest.moc"
   508 #include "tst_maketestselftest.moc"