aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp
blob: 7f12f2934299fded901b7175dc6d14b3d32b2ded (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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include <QtTest/qtest.h>

#include <QtQuickTestUtils/private/qmlutils_p.h>

#include <QtCore/qstring.h>
#include <QtCore/qprocess.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qurl.h>
#include <QtCore/qscopeguard.h>
#include <QtCore/qfile.h>
#include <QtCore/qlibraryinfo.h>
#include <QtCore/qdir.h>
#include <QtCore/qhash.h>

#include <functional>

using namespace Qt::StringLiterals;

class tst_qmltc_qprocess : public QQmlDataTest
{
    Q_OBJECT

    QString m_qmltcPath;
    QString m_tmpPath;
    QStringList m_resources;

    QString runQmltc(const QString &inputFile, std::function<void(QProcess &)> handleResult,
                     const QStringList &extraArgs = {});
    QString runQmltc(const QString &inputFile, bool shouldSucceed,
                     const QStringList &extraArgs = {});

    QString modifiedPath(const QString &path) { return path + u".orig"_s; }

public:
    tst_qmltc_qprocess() : QQmlDataTest(QT_QMLTEST_DATADIR) { }

private slots:
    void initTestCase() override;
    void cleanupTestCase();

    void sanity();
    void noBuiltins();
    void noQtQml();
    void inlineComponent();
    void singleton();
    void warningsAsErrors();
    void invalidAliasRevision();
    void topLevelComponent();
    void dashesInFilename();
    void invalidSignalHandlers();
};

#ifndef TST_QMLTC_QPROCESS_RESOURCES
#  error "This test expects TST_QMLTC_QPROCESS_RESOURCES to be defined through CMake."
#endif

void tst_qmltc_qprocess::initTestCase()
{
    QQmlDataTest::initTestCase();
    m_qmltcPath = QLibraryInfo::path(QLibraryInfo::BinariesPath) + u"/qmltc"_s;
#ifdef Q_OS_WIN
    m_qmltcPath += u".exe"_s;
#endif
    if (!QFileInfo(m_qmltcPath).exists()) {
        const QString message = u"qmltc executable not found (looked for %0)"_s.arg(m_qmltcPath);
        QFAIL(qPrintable(message));
    }

    m_tmpPath = QDir::tempPath() + u"/tst_qmltc_qprocess_artifacts"_s;
    QVERIFY(QDir(m_tmpPath).removeRecursively()); // in case it's already there
    QVERIFY(QDir().mkpath(m_tmpPath));

    m_resources = QStringLiteral(TST_QMLTC_QPROCESS_RESOURCES).split(u"_::_"_s);
}

void tst_qmltc_qprocess::cleanupTestCase()
{
    QVERIFY(QDir(m_tmpPath).removeRecursively());
}

QString tst_qmltc_qprocess::runQmltc(const QString &inputFile,
                                     std::function<void(QProcess &)> handleResult,
                                     const QStringList &extraArgs)
{
    QStringList args;

    args << (QFileInfo(inputFile).isAbsolute() ? inputFile : testFile(inputFile));
    for (const QString &resource : m_resources)
        args << u"--resource"_s << resource;
    args << u"--header"_s << (m_tmpPath + u"/"_s + QFileInfo(inputFile).baseName() + u".h"_s);
    args << u"--impl"_s << (m_tmpPath + u"/"_s + QFileInfo(inputFile).baseName() + u".cpp"_s);

    args << extraArgs;
    QString errors;

    QProcess process;
    process.start(m_qmltcPath, args);
    handleResult(process); // may fail the test
    errors = process.readAllStandardError();

    if (QTest::currentTestFailed()) {
        qDebug() << "Command:" << process.program() << args.join(u' ');
        qDebug() << "Exit status:" << process.exitStatus();
        qDebug() << "Exit code:" << process.exitCode();
        qDebug() << "stderr:" << errors;
        qDebug() << "stdout:" << process.readAllStandardOutput();
    }

    return errors;
}

QString tst_qmltc_qprocess::runQmltc(const QString &inputFile, bool shouldSucceed,
                                     const QStringList &extraArgs)
{
    return runQmltc(
            inputFile,
            [&](QProcess &process) {
                QVERIFY(process.waitForFinished());
                QCOMPARE(process.exitStatus(), QProcess::NormalExit);

                if (shouldSucceed)
                    QCOMPARE(process.exitCode(), 0);
                else
                    QVERIFY(process.exitCode() != 0);
            },
            extraArgs);
}

void tst_qmltc_qprocess::sanity()
{
    const auto output = runQmltc(u"dummy.qml"_s, true);
    QVERIFY2(output.isEmpty(), qPrintable(output));
}

void tst_qmltc_qprocess::noBuiltins()
{
    const auto renameBack = [&](const QString &original) {
        const auto current = modifiedPath(original);
        QFile file(current);
        QVERIFY(file.exists());
        QVERIFY(file.rename(original));
    };

    for (QString builtin : { u"builtins.qmltypes"_s, u"jsroot.qmltypes"_s }) {
        const auto path = QLibraryInfo::path(QLibraryInfo::QmlImportsPath) + u"/"_s + builtin;

        QScopeGuard scope(std::bind(renameBack, path));
        QFile file(path);
        QVERIFY(file.exists());
        QVERIFY(file.rename(modifiedPath(path)));

        // test that qmltc exits gracefully
        const auto errors = runQmltc(u"dummy.qml"_s, false);
        QVERIFY(errors.contains(u"Failed to find the following builtins: %1"_s.arg(builtin)));
    }
}

void tst_qmltc_qprocess::noQtQml()
{
    const auto renameBack = [&](const QString &original) {
        const auto current = modifiedPath(original);
        QVERIFY(QDir(current).exists());
        QVERIFY(QDir().rename(current, original));
    };

    const auto modulePath = QLibraryInfo::path(QLibraryInfo::QmlImportsPath) + u"/QtQml"_s;
    QScopeGuard scope(std::bind(renameBack, modulePath));
    QVERIFY(QDir(modulePath).exists());
    QVERIFY(QDir().rename(modulePath, modifiedPath(modulePath)));

    // test that qmltc exits gracefully
    const auto errors = runQmltc(u"dummy.qml"_s, false);
    QVERIFY(errors.contains(u"Failed to import QtQml. Are your import paths set up properly?"_s));
}

void tst_qmltc_qprocess::inlineComponent()
{
    {
        const auto errors = runQmltc(u"inlineComponentInvalidAlias.qml"_s, false);
        QVERIFY(errors.contains(u"Cannot resolve alias \"myHello\" [unresolved-alias]"_s));
    }
    {
        const auto errors = runQmltc(u"inlineComponentWithEnum.qml"_s, false);
        QVERIFY(errors.contains(
                u"inlineComponentWithEnum.qml:5:9: Enums declared inside of inline component are ignored. [syntax]"_s));
    }
}

void tst_qmltc_qprocess::singleton()
{
    {
        const auto errors = runQmltc(u"singletonUncreatable.qml"_s, false);
        QVERIFY(errors.contains("singletonUncreatable.qml:4:1: Type UncreatableType is not "
                                "creatable. [uncreatable-type]"));
    }
    {
        const auto errors = runQmltc(u"uncreatable.qml"_s, false);
        QVERIFY(errors.contains(
                "uncreatable.qml:6:5: Type UncreatableType is not creatable. [uncreatable-type]"));
        QVERIFY(errors.contains("uncreatable.qml:7:5: Singleton Type SingletonThing is not "
                                "creatable. [uncreatable-type]"));
        QVERIFY(errors.contains("uncreatable.qml:8:5: Singleton Type SingletonType is not "
                                "creatable. [uncreatable-type]"));
        QVERIFY(errors.contains("uncreatable.qml:10:18: Singleton Type SingletonThing is not "
                                "creatable. [uncreatable-type]"));
        QVERIFY(errors.contains("uncreatable.qml:15:18: Singleton Type SingletonType is not "
                                "creatable. [uncreatable-type]"));
        QVERIFY(!errors.contains("NotSingletonType"));
    }
}

void tst_qmltc_qprocess::warningsAsErrors()
{
    const auto errors = runQmltc(u"erroneousFile.qml"_s, false);
    QVERIFY2(errors.contains(u"Error:"_s), qPrintable(errors)); // Note: not a warning!
}

void tst_qmltc_qprocess::invalidAliasRevision()
{
    const auto errors = runQmltc(u"invalidAliasRevision.qml"_s, false);
    QVERIFY(errors.contains(u"Cannot resolve alias \"unexistingProperty\" [unresolved-alias]"_s));
}

void tst_qmltc_qprocess::topLevelComponent()
{
    {
        const auto errors = runQmltc(u"ComponentType.qml"_s, false);
        QVERIFY(errors.contains(
                u"ComponentType.qml:2:1: Qml top level type cannot be 'Component'. [top-level-component]"_s));
    }
}

void tst_qmltc_qprocess::dashesInFilename()
{
    {
        const auto errors = runQmltc(u"kebab-case.qml"_s, false);
        QVERIFY(errors.contains(
                u"The given QML filename is unsuited for type compilation: the name must consist of letters, digits and underscores, starting with a letter or an underscore and ending in '.qml'!"_s));
    }
}

void tst_qmltc_qprocess::invalidSignalHandlers()
{
    {
        const auto errors = runQmltc(u"invalidSignalHandlers.qml"_s, false);
        QVERIFY(errors.contains(
                u"invalidSignalHandlers.qml:5:5: Type QFont of parameter in signal called signalWithConstPointerToGadget should be passed by value or const reference to be able to compile onSignalWithConstPointerToGadget.  [signal-handler-parameters]"_s));
        QVERIFY(errors.contains(
                u"invalidSignalHandlers.qml:6:5: Type QFont of parameter in signal called signalWithConstPointerToGadgetConst should be passed by value or const reference to be able to compile onSignalWithConstPointerToGadgetConst.  [signal-handler-parameters]"_s));
        QVERIFY(errors.contains(
                u"invalidSignalHandlers.qml:7:5: Type QFont of parameter in signal called signalWithPointerToGadgetConst should be passed by value or const reference to be able to compile onSignalWithPointerToGadgetConst.  [signal-handler-parameters]"_s));
        QVERIFY(errors.contains(
                u"invalidSignalHandlers.qml:8:5: Type QFont of parameter in signal called signalWithPointerToGadget should be passed by value or const reference to be able to compile onSignalWithPointerToGadget.  [signal-handler-parameters]"_s));
        QVERIFY(errors.contains(
                u"invalidSignalHandlers.qml:9:5: Type int of parameter in signal called signalWithPrimitivePointer should be passed by value or const reference to be able to compile onSignalWithPrimitivePointer.  [signal-handler-parameters]"_s));
        QVERIFY(errors.contains(
                u"invalidSignalHandlers.qml:10:5: Type int of parameter in signal called signalWithConstPrimitivePointer should be passed by value or const reference to be able to compile onSignalWithConstPrimitivePointer.  [signal-handler-parameters]"_s));
    }
}

QTEST_MAIN(tst_qmltc_qprocess)
#include "tst_qmltc_qprocess.moc"