summaryrefslogtreecommitdiffstats
path: root/src/qdoc/qdoc/tests/validateqdocoutputfiles/tst_validateqdocoutputfiles.cpp
blob: bcabb4178b2a83264aafbdcf0844089e543cdea4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include <QtTest/QtTest>

#include <utility>

class tst_validateQdocOutputFiles : public QObject
{
    Q_OBJECT
private:
    void runQDocProcess(const QStringList &arguments);
    std::optional<QByteArray> gitDiffDirectories(const QString &actualPath,
                                                 const QString &expectedPath);

private slots:
    void initTestCase();
    void init();
    void qdocProjects_data();
    void qdocProjects();

private:
    const QString m_testDataDirectory = QFINDTESTDATA("testdata");
    QString m_qdocBinary{};
    QString m_extraParams{};
    QScopedPointer<QTemporaryDir> m_outputDir{};
};

static inline bool regenerate{false};

//! Update `README.md` if you change the name of this environment variable!
static constexpr QLatin1StringView REGENERATE_ENVVAR{"QDOC_REGENERATE_TESTDATA"};

void tst_validateQdocOutputFiles::initTestCase()
{
    QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
    if (environment.contains(REGENERATE_ENVVAR)) {
        qInfo() << "Regenerating expected output for all tests.";
        regenerate = true;
        qInfo("Removing %s environment variable.", REGENERATE_ENVVAR.constData());
        environment.remove(REGENERATE_ENVVAR);
    }

    // Build the path to the QDoc binary the same way moc tests do for moc.
    const auto binpath = QLibraryInfo::path(QLibraryInfo::BinariesPath);
    const auto extension = QSysInfo::productType() == "windows" ? ".exe" : "";
    m_qdocBinary = binpath + QLatin1String("/qdoc") + extension;
    QVERIFY(QFile::exists(m_qdocBinary));

    // Resolve the path to the file containing extra parameters
    m_extraParams = QFileInfo(QTest::currentAppName()).dir().filePath(DOCINCPATH);
    if (!QFileInfo::exists(m_extraParams)) {
        qWarning("Cannot locate %s", m_extraParams.toLocal8Bit().constData());
        m_extraParams.clear();
    } else {
        m_extraParams.insert(0, '@');
    }
}

void tst_validateQdocOutputFiles::init()
{
    m_outputDir.reset(new QTemporaryDir());
    if (!m_outputDir->isValid()) {
        const QString errorMessage =
                "Couldn't create temporary directory: " + m_outputDir->errorString();
        QFAIL(qPrintable(errorMessage));
    }
}

void tst_validateQdocOutputFiles::runQDocProcess(const QStringList &arguments)
{
    QProcess qdocProcess;
    qdocProcess.setProgram(m_qdocBinary);

    qdocProcess.setArguments(arguments);

    auto failQDoc = [&](QProcess::ProcessError) {
        qFatal("Running qdoc failed with exit code %i: %s",
               qdocProcess.exitCode(), qUtf8Printable(qdocProcess.errorString()));
    };
    QObject::connect(&qdocProcess, &QProcess::errorOccurred, this, failQDoc);

    qdocProcess.start();
    qdocProcess.waitForFinished();
    if (qdocProcess.exitCode() == 0)
        return;

    QString errors = qdocProcess.readAllStandardError();
    if (!errors.isEmpty())
        qInfo().nospace() << "Received errors:\n" << qUtf8Printable(errors);
    if (!QTest::currentTestFailed())
        failQDoc(QProcess::UnknownError);
}

std::optional<QByteArray>
tst_validateQdocOutputFiles::gitDiffDirectories(const QString &actualPath, const QString &expectedPath)
{
    QProcess gitProcess;
    gitProcess.setProgram("git");

    const QStringList arguments{"diff", "--", actualPath, expectedPath};
    gitProcess.setArguments(arguments);

    auto failGit = [&](QProcess::ProcessError) {
        qFatal("Running git failed with exit code %i: %s",
               gitProcess.exitCode(), gitProcess.errorString().toLocal8Bit().constData());
    };
    QObject::connect(&gitProcess, &QProcess::errorOccurred, this, failGit);

    gitProcess.start();
    gitProcess.waitForFinished();

    if (gitProcess.exitCode() == 0)
        return {};

    return gitProcess.readAllStandardOutput();
}

void tst_validateQdocOutputFiles::qdocProjects_data()
{
    using namespace Qt::StringLiterals;
    QTest::addColumn<QString>("qdocconf");
    QTest::addColumn<QString>("expectedPath");

    QDirIterator qdocconfit(m_testDataDirectory, QStringList { u"*.qdocconf"_s },
                            QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
    while (qdocconfit.hasNext()) {
        const QFileInfo configFile = qdocconfit.nextFileInfo();
        if (configFile.baseName() != configFile.dir().dirName())
            continue;

        const QString testName =
                configFile.dir().dirName() + u'/' + configFile.fileName();

        QTest::newRow(testName.toUtf8().constData())
                << configFile.absoluteFilePath() << configFile.dir().absolutePath() + "/expected/";
    }
}

void tst_validateQdocOutputFiles::qdocProjects()
{
    QFETCH(const QString, qdocconf);
    QFETCH(const QString, expectedPath);

    QString actualPath{m_outputDir->path()};
    if (regenerate) {
        actualPath = expectedPath;
        QDir pathToRemove{expectedPath};
        if (!pathToRemove.removeRecursively())
            qCritical("Cannot remove expected output directory, aborting!");
    }

    const QStringList arguments{ "-outputdir", actualPath, m_extraParams, qdocconf };

    runQDocProcess(arguments);

    if (regenerate) {
        const QString message = "Regenerated expected output files for" + qdocconf;
        QSKIP(message.toLocal8Bit().constData());
    }

    std::optional<QByteArray> gitDiff = gitDiffDirectories(actualPath, expectedPath);
    if (gitDiff.has_value()) {
        qInfo() << qUtf8Printable(gitDiff.value());
        QFAIL("Inspect the output for details.");
    }
    QVERIFY(true);
}

QTEST_MAIN(tst_validateQdocOutputFiles)
#include "tst_validateqdocoutputfiles.moc"