Revision control
Copy as Markdown
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/QTest>
#include <QDir>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLibraryInfo>
#include <QProcess>
#include <QString>
#include <QTemporaryFile>
#include <QtQuickTestUtils/private/qmlutils_p.h>
using namespace Qt::StringLiterals;
class TestQmlimportscanner: public QQmlDataTest
{
Q_OBJECT
public:
TestQmlimportscanner();
private Q_SLOTS:
void initTestCase() override;
void cleanQmlCode_data();
void cleanQmlCode();
void rootPath();
void modules_data();
void modules();
void qmldirPreference();
void resolveDirectoryImportPath();
void importVersions();
private:
void runQmlimportscanner(const QString &mode, const QString &fileToScan,
const QString &resultFile);
QString m_qmlimportscannerPath;
};
TestQmlimportscanner::TestQmlimportscanner()
: QQmlDataTest(QT_QMLTEST_DATADIR)
{
}
void TestQmlimportscanner::initTestCase()
{
QQmlDataTest::initTestCase();
m_qmlimportscannerPath = QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath)
+ QLatin1String("/qmlimportscanner");
#ifdef Q_OS_WIN
m_qmlimportscannerPath += QLatin1String(".exe");
#endif
if (!QFileInfo(m_qmlimportscannerPath).exists()) {
QString message = QStringLiteral("qmlimportscanner executable not found (looked for %0)").arg(m_qmlimportscannerPath);
QFAIL(qPrintable(message));
}
}
void TestQmlimportscanner::cleanQmlCode_data()
{
QTest::addColumn<QString>("filename");
QTest::newRow("Simple_QML") << QStringLiteral("Simple.qml");
QTest::newRow("QML_importing_JS") << QStringLiteral("importing_js.qml");
QTest::newRow("JS_with_pragma_and_import") << QStringLiteral("QTBUG-45916.js");
QTest::newRow("qtQmlOnly") << QStringLiteral("qtQmlOnly.qml");
QTest::newRow("directoryImportWithPrefix") << QStringLiteral("ImportWithPrefix.qml");
QTest::newRow("localImport") << QStringLiteral("localImport.qml");
QTest::newRow("methodsInJavascript") << QStringLiteral("javascriptMethods.qml");
QTest::newRow("moduleImportWithPrefix") << QStringLiteral("Drawer.qml");
QTest::newRow("localAndModuleImport") << QStringLiteral("ListProperty.qml");
QTest::newRow("versionLessLocalImport") << QStringLiteral("qmldirImportAndDepend.qml");
QTest::newRow("versionLessModuleImport") << QStringLiteral("parentEnum.qml");
QTest::newRow("cdUpImport") << QStringLiteral("cdUpImport.qml");
}
void TestQmlimportscanner::cleanQmlCode()
{
QFETCH(QString, filename);
runQmlimportscanner("-qmlFiles", testFile(filename), testFile(filename + ".json"));
}
void TestQmlimportscanner::rootPath()
{
runQmlimportscanner("-rootPath", dataDirectory(), testFile("rootPath.json"));
}
void TestQmlimportscanner::modules_data()
{
QTest::addColumn<QString>("name");
QTest::newRow("CompositeSingleton") << QStringLiteral("CompositeSingleton");
QTest::newRow("CompositeWithEnum") << QStringLiteral("CompositeWithEnum");
QTest::newRow("CompositeWithinSingleton") << QStringLiteral("CompositeWithinSingleton");
QTest::newRow("Imports") << QStringLiteral("Imports");
QTest::newRow("Singleton") << QStringLiteral("Singleton");
QTest::newRow("Things") << QStringLiteral("Things");
}
void TestQmlimportscanner::modules()
{
QFETCH(QString, name);
QTemporaryFile qmlFile(QDir::tempPath() + "/tst_qmlimportscanner_XXXXXX.qml");
QVERIFY(qmlFile.open());
qmlFile.write("import " + name.toUtf8() + "\nQtObject {}");
qmlFile.close();
runQmlimportscanner("-qmlFiles", qmlFile.fileName(), testFile(name + ".json"));
}
void TestQmlimportscanner::qmldirPreference()
{
// ###
QStringList with {u"-importPath"_s, testFile("With")};
QStringList withOut {u"-importPath"_s, testFile("WithOut")};
QStringList genericArgs {u"-qmlFiles"_s, testFile("qmldirpref.qml"), u"-importPath"_s,
QLibraryInfo::path(QLibraryInfo::QmlImportsPath)};
// found path should not depend on order of importPath arguments
QStringList argcombis[2] { genericArgs + with + withOut, genericArgs + withOut + with };
for (const auto &allArgs: argcombis) {
QProcess process;
process.start(m_qmlimportscannerPath, allArgs);
QVERIFY(process.waitForFinished());
QCOMPARE(process.exitStatus(), QProcess::NormalExit);
QCOMPARE(process.exitCode(), 0);
const auto &stdErr = process.readAllStandardError();
QVERIFY2(stdErr.isEmpty(), stdErr.constData());
auto output = process.readAllStandardOutput();
// check that the "With" path is used, and the "WithOut" path is ignored
QVERIFY(output.contains("With/Module"));
QVERIFY(!output.contains("WithOut/Module"));
}
}
// version numbers are used by external tooling
// compare QTBUG-115170
void TestQmlimportscanner::importVersions()
{
QStringList args {"-add-version", "-qmlFiles", testFile("listVersions.qml")};
QProcess process;
process.start(m_qmlimportscannerPath, args);
QVERIFY(process.waitForFinished());
QCOMPARE(process.exitStatus(), QProcess::NormalExit);
QCOMPARE(process.exitCode(), 0);
auto output = process.readAllStandardOutput();
QVERIFY(output.contains("\"version\": \"2.5\""));
QVERIFY(output.contains("\"version\": \"2.1\""));
QVERIFY(output.contains("\"version\": \"1.2\""));
}
void TestQmlimportscanner::resolveDirectoryImportPath()
{
QStringList args { "-qmlFiles", testFile("cdUpImport.qml") };
QString errors;
QProcess process;
process.start(m_qmlimportscannerPath, args);
QVERIFY(process.waitForFinished());
QCOMPARE(process.exitStatus(), QProcess::NormalExit);
QCOMPARE(process.exitCode(), 0);
const auto &stdErr = process.readAllStandardError();
QVERIFY2(stdErr.isEmpty(), stdErr.constData());
QJsonParseError error;
const QJsonDocument generated = QJsonDocument::fromJson(process.readAllStandardOutput(),
&error);
QCOMPARE(error.error, QJsonParseError::NoError);
QVERIFY(generated.isArray());
const QJsonArray array = generated.array();
QCOMPARE(array.size(), 1);
const QJsonValue entry = array[0];
QVERIFY(entry.isObject());
const QJsonObject object = entry.toObject();
QCOMPARE(
object.value("path").toString(),
QDir::cleanPath(
QFileInfo(dataDirectory()).absolutePath() + QLatin1Char('/') + "Fake"));
}
void TestQmlimportscanner::runQmlimportscanner(const QString &mode, const QString &pathToScan,
const QString &resultFile)
{
const QString file(pathToScan);
QStringList args {
mode, file,
"-importPath", QLibraryInfo::path(QLibraryInfo::QmlImportsPath), dataDirectory()
};
QString errors;
QProcess process;
process.start(m_qmlimportscannerPath, args);
QVERIFY(process.waitForFinished());
QCOMPARE(process.exitStatus(), QProcess::NormalExit);
QCOMPARE(process.exitCode(), 0);
const auto &stdErr = process.readAllStandardError();
QVERIFY2(stdErr.isEmpty(), stdErr.constData());
QJsonParseError error;
const QJsonDocument generated = QJsonDocument::fromJson(process.readAllStandardOutput(),
&error);
QCOMPARE(error.error, QJsonParseError::NoError);
QVERIFY(generated.isArray());
QFile imports(resultFile);
QVERIFY(imports.open(QIODevice::ReadOnly));
QJsonDocument expected = QJsonDocument::fromJson(imports.readAll(), &error);
QCOMPARE(error.error, QJsonParseError::NoError);
QVERIFY(expected.isArray());
const QJsonArray generatedArray = generated.array();
QJsonArray expectedArray = expected.array();
for (const QJsonValue value : generatedArray) {
QVERIFY(value.isObject());
QJsonObject object = value.toObject();
// Path is omitted because it's an absolute path, dependent on host system.
object["path"] = QJsonValue::Undefined;
#ifdef LIBINFIX
#define XSTR(a) STR(a)
#define STR(A) #A
if (object.contains("plugin")) {
auto plugin = object["plugin"].toString();
const auto pos = plugin.lastIndexOf(XSTR(LIBINFIX) "plugin");
if (pos != -1)
object["plugin"] = plugin.left(pos) + "plugin";
}
#endif
object.remove("components");
object.remove("scripts");
bool found = false;
for (auto it = expectedArray.begin(), end = expectedArray.end(); it != end; ++it) {
if (*it == object) {
expectedArray.erase(it);
found = true;
break;
}
}
QVERIFY2(found, qPrintable(QDebug::toString(object)));
}
QVERIFY2(expectedArray.isEmpty(), QDebug::toString(expectedArray).toUtf8().constData());
}
QTEST_MAIN(TestQmlimportscanner)
#include "tst_qmlimportscanner.moc"