Duplicate tests that use examples from qtdeclarative

Moving examples from qtdeclarative to qtdoc broke tests that operate
on the example sources. This patch duplicates those tests and the
documentation snippets that serve as test data to reestablish test
coverage.

Task-number: QTBUG-69383
Change-Id: I31fd1729f794081a11844be9c27ed4f9a8a5ce73
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
This commit is contained in:
Paul Wicking 2018-07-13 13:24:23 +02:00
parent fbc2810ed5
commit c42a5dfb51
11 changed files with 786 additions and 0 deletions

8
tests/auto/auto.pro Normal file
View File

@ -0,0 +1,8 @@
TEMPLATE=subdirs
qtHaveModule(quick) {
SUBDIRS += quick
}
qtHaveModule(qml) {
SUBDIRS += qml
}

9
tests/auto/qml/qml.pro Normal file
View File

@ -0,0 +1,9 @@
TEMPLATE = subdirs
QT_FOR_CONFIG += qml-private
qtConfig(qml-devtools):
SUBDIRS += qmlmin
qtConfig(private_tests): \
SUBDIRS += qqmlparser

View File

@ -0,0 +1,12 @@
CONFIG += testcase
TARGET = tst_qmlmin
QT += qml testlib gui-private
macos:CONFIG -= app_bundle
SOURCES += tst_qmlmin.cpp
DEFINES += SRCDIR=\\\"$$PWD\\\"
# Boot2qt is cross compiled but it has sources available
!boot2qt {
cross_compile: DEFINES += QTEST_CROSS_COMPILED
}

View File

@ -0,0 +1,168 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qtest.h>
#include <QLibraryInfo>
#include <QDir>
#if QT_CONFIG(process)
#include <QProcess>
#endif
#include <QDebug>
#include <QQmlError>
#include <cstdlib>
class tst_qmlmin : public QObject
{
Q_OBJECT
public:
tst_qmlmin();
private slots:
void initTestCase();
#if QT_CONFIG(process) && !defined(QTEST_CROSS_COMPILED) // sources not available when cross compiled
void qmlMinify_data();
void qmlMinify();
#endif
private:
QString qmlminPath;
QStringList excludedDirs;
QStringList invalidFiles;
QStringList findFiles(const QDir &);
bool isInvalidFile(const QFileInfo &fileName) const;
};
tst_qmlmin::tst_qmlmin()
{
}
void tst_qmlmin::initTestCase()
{
qmlminPath = QLibraryInfo::location(QLibraryInfo::BinariesPath) + QLatin1String("/qmlmin");
#ifdef Q_OS_WIN
qmlminPath += QLatin1String(".exe");
#endif
if (!QFileInfo(qmlminPath).exists()) {
QString message = QString::fromLatin1("qmlmin executable not found (looked for %0)")
.arg(qmlminPath);
QFAIL(qPrintable(message));
}
// Add directories you want excluded here
// excludedDirs << "exclude/this/dir";
// Add invalid files (i.e. files with syntax errors)
// invalidFiles << "exclude/this/file.txt";
}
QStringList tst_qmlmin::findFiles(const QDir &d)
{
for (int ii = 0; ii < excludedDirs.count(); ++ii) {
QString s = excludedDirs.at(ii);
if (d.absolutePath().endsWith(s))
return QStringList();
}
QStringList rv;
QStringList files = d.entryList(QStringList() << QLatin1String("*.qml") << QLatin1String("*.js"),
QDir::Files);
foreach (const QString &file, files) {
rv << d.absoluteFilePath(file);
}
QStringList dirs = d.entryList(QDir::Dirs | QDir::NoDotAndDotDot |
QDir::NoSymLinks);
foreach (const QString &dir, dirs) {
QDir sub = d;
sub.cd(dir);
rv << findFiles(sub);
}
return rv;
}
bool tst_qmlmin::isInvalidFile(const QFileInfo &fileName) const
{
foreach (const QString &invalidFile, invalidFiles) {
if (fileName.absoluteFilePath().endsWith(invalidFile))
return true;
}
return false;
}
/*
This test runs all the examples in the Qt QML UI source tree and ensures
that they start and exit cleanly.
Examples are any .qml files under the examples/ directory that start
with a lower case letter.
*/
#if QT_CONFIG(process) && !defined(QTEST_CROSS_COMPILED) // sources not available when cross compiled
void tst_qmlmin::qmlMinify_data()
{
QTest::addColumn<QString>("file");
QString examples = QLatin1String(SRCDIR) + "/../../../../examples/";
QString tests = QLatin1String(SRCDIR) + "/../../../../tests/";
QStringList files;
files << findFiles(QDir(examples));
files << findFiles(QDir(tests));
foreach (const QString &file, files)
QTest::newRow(qPrintable(file)) << file;
}
#endif
#if QT_CONFIG(process) && !defined(QTEST_CROSS_COMPILED) // sources not available when cross compiled
void tst_qmlmin::qmlMinify()
{
QFETCH(QString, file);
QProcess qmlminify;
// Restrict line width to 100 characters
qmlminify.start(qmlminPath, QStringList() << QLatin1String("--verify-only") << QLatin1String("-w100") << file);
qmlminify.waitForFinished();
QCOMPARE(qmlminify.error(), QProcess::UnknownError);
QCOMPARE(qmlminify.exitStatus(), QProcess::NormalExit);
if (isInvalidFile(file))
QCOMPARE(qmlminify.exitCode(), EXIT_FAILURE); // cannot minify files with syntax errors
else
QCOMPARE(qmlminify.exitCode(), 0);
}
#endif
QTEST_MAIN(tst_qmlmin)
#include "tst_qmlmin.moc"

View File

@ -0,0 +1,9 @@
CONFIG += testcase
TARGET = tst_qqmlparser
QT += qml-private testlib
macos:CONFIG -= app_bundle
SOURCES += tst_qqmlparser.cpp
DEFINES += SRCDIR=\\\"$$PWD\\\"
cross_compile: DEFINES += QTEST_CROSS_COMPILED

View File

@ -0,0 +1,272 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <private/qqmljsengine_p.h>
#include <private/qqmljsparser_p.h>
#include <private/qqmljslexer_p.h>
#include <private/qqmljsastvisitor_p.h>
#include <private/qqmljsast_p.h>
#include <qtest.h>
#include <QDir>
#include <QDebug>
#include <cstdlib>
class tst_qqmlparser : public QObject
{
Q_OBJECT
public:
tst_qqmlparser();
private slots:
void initTestCase();
#if !defined(QTEST_CROSS_COMPILED) // sources not available when cross compiled
void qmlParser_data();
void qmlParser();
#endif
void invalidEscapeSequence();
void stringLiteral();
void noSubstitutionTemplateLiteral();
void templateLiteral();
private:
QStringList excludedDirs;
QStringList findFiles(const QDir &);
};
namespace check {
using namespace QQmlJS;
class Check: public AST::Visitor
{
QList<AST::Node *> nodeStack;
public:
void operator()(AST::Node *node)
{
AST::Node::accept(node, this);
}
void checkNode(AST::Node *node)
{
if (! nodeStack.isEmpty()) {
AST::Node *parent = nodeStack.last();
const quint32 parentBegin = parent->firstSourceLocation().begin();
const quint32 parentEnd = parent->lastSourceLocation().end();
if (node->firstSourceLocation().begin() < parentBegin)
qDebug() << "first source loc failed: node:" << node->kind << "at" << node->firstSourceLocation().startLine << "/" << node->firstSourceLocation().startColumn
<< "parent" << parent->kind << "at" << parent->firstSourceLocation().startLine << "/" << parent->firstSourceLocation().startColumn;
if (node->lastSourceLocation().end() > parentEnd)
qDebug() << "first source loc failed: node:" << node->kind << "at" << node->lastSourceLocation().startLine << "/" << node->lastSourceLocation().startColumn
<< "parent" << parent->kind << "at" << parent->lastSourceLocation().startLine << "/" << parent->lastSourceLocation().startColumn;
QVERIFY(node->firstSourceLocation().begin() >= parentBegin);
QVERIFY(node->lastSourceLocation().end() <= parentEnd);
}
}
virtual bool preVisit(AST::Node *node)
{
checkNode(node);
nodeStack.append(node);
return true;
}
virtual void postVisit(AST::Node *)
{
nodeStack.removeLast();
}
};
}
tst_qqmlparser::tst_qqmlparser()
{
}
void tst_qqmlparser::initTestCase()
{
// Add directories you want excluded here:
// excludedDirs << "exclude/this/dir";
}
QStringList tst_qqmlparser::findFiles(const QDir &d)
{
for (int ii = 0; ii < excludedDirs.count(); ++ii) {
QString s = excludedDirs.at(ii);
if (d.absolutePath().endsWith(s))
return QStringList();
}
QStringList rv;
QStringList files = d.entryList(QStringList() << QLatin1String("*.qml") << QLatin1String("*.js"),
QDir::Files);
foreach (const QString &file, files) {
rv << d.absoluteFilePath(file);
}
QStringList dirs = d.entryList(QDir::Dirs | QDir::NoDotAndDotDot |
QDir::NoSymLinks);
foreach (const QString &dir, dirs) {
QDir sub = d;
sub.cd(dir);
rv << findFiles(sub);
}
return rv;
}
/*
This test checks all the qml and js files in the QtQml UI source tree
and ensures that the subnode's source locations are inside parent node's source locations
*/
#if !defined(QTEST_CROSS_COMPILED) // sources not available when cross compiled
void tst_qqmlparser::qmlParser_data()
{
QTest::addColumn<QString>("file");
QString examples = QLatin1String(SRCDIR) + "/../../../../examples/";
QString tests = QLatin1String(SRCDIR) + "/../../../../tests/";
QStringList files;
files << findFiles(QDir(examples));
files << findFiles(QDir(tests));
foreach (const QString &file, files)
QTest::newRow(qPrintable(file)) << file;
}
#endif
#if !defined(QTEST_CROSS_COMPILED) // sources not available when cross compiled
void tst_qqmlparser::qmlParser()
{
QFETCH(QString, file);
using namespace QQmlJS;
QString code;
QFile f(file);
if (f.open(QFile::ReadOnly))
code = QString::fromUtf8(f.readAll());
const bool qmlMode = file.endsWith(QLatin1String(".qml"));
Engine engine;
Lexer lexer(&engine);
lexer.setCode(code, 1, qmlMode);
Parser parser(&engine);
bool ok = qmlMode ? parser.parse() : parser.parseProgram();
if (ok) {
check::Check chk;
chk(parser.rootNode());
}
}
#endif
void tst_qqmlparser::invalidEscapeSequence()
{
using namespace QQmlJS;
Engine engine;
Lexer lexer(&engine);
lexer.setCode(QLatin1String("\"\\"), 1);
Parser parser(&engine);
parser.parse();
}
void tst_qqmlparser::stringLiteral()
{
using namespace QQmlJS;
Engine engine;
Lexer lexer(&engine);
QLatin1String code("'hello string'");
lexer.setCode(code , 1);
Parser parser(&engine);
QVERIFY(parser.parseExpression());
AST::ExpressionNode *expression = parser.expression();
QVERIFY(expression);
auto *literal = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(expression);
QVERIFY(literal);
QCOMPARE(literal->value, "hello string");
QCOMPARE(literal->firstSourceLocation().begin(), 0);
QCOMPARE(literal->lastSourceLocation().end(), code.size());
}
void tst_qqmlparser::noSubstitutionTemplateLiteral()
{
using namespace QQmlJS;
Engine engine;
Lexer lexer(&engine);
QLatin1String code("`hello template`");
lexer.setCode(code, 1);
Parser parser(&engine);
QVERIFY(parser.parseExpression());
AST::ExpressionNode *expression = parser.expression();
QVERIFY(expression);
auto *literal = QQmlJS::AST::cast<QQmlJS::AST::TemplateLiteral *>(expression);
QVERIFY(literal);
QCOMPARE(literal->value, "hello template");
QCOMPARE(literal->firstSourceLocation().begin(), 0);
QCOMPARE(literal->lastSourceLocation().end(), code.size());
}
void tst_qqmlparser::templateLiteral()
{
using namespace QQmlJS;
Engine engine;
Lexer lexer(&engine);
QLatin1String code("`one plus one equals ${1+1}!`");
lexer.setCode(code, 1);
Parser parser(&engine);
QVERIFY(parser.parseExpression());
AST::ExpressionNode *expression = parser.expression();
QVERIFY(expression);
auto *templateLiteral = QQmlJS::AST::cast<QQmlJS::AST::TemplateLiteral *>(expression);
QVERIFY(templateLiteral);
QCOMPARE(templateLiteral->firstSourceLocation().begin(), 0);
auto *e = templateLiteral->expression;
QVERIFY(e);
}
QTEST_MAIN(tst_qqmlparser)
#include "tst_qqmlparser.moc"

View File

@ -0,0 +1,6 @@
import Qt.VisualTest 4.6
VisualTest {
Frame { msec: 0 }
Frame { msec: 10 }
}

View File

@ -0,0 +1,12 @@
CONFIG += testcase
testcase.timeout = 400 # this test is slow
TARGET = tst_examples
macos:CONFIG -= app_bundle
SOURCES += tst_examples.cpp
DEFINES += SRCDIR=\\\"$$PWD\\\"
#temporary
QT += core-private gui-private qml-private quick-private testlib
!qtHaveModule(xmlpatterns): DEFINES += QT_NO_XMLPATTERNS

View File

@ -0,0 +1,281 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qtest.h>
#include <QLibraryInfo>
#include <QDir>
#include <QDebug>
#include <QtQuick/QQuickItem>
#include <QtQuick/QQuickView>
#include <QQmlComponent>
#include <QQmlEngine>
#include <QQmlError>
static QtMessageHandler testlibMsgHandler = nullptr;
void msgHandlerFilter(QtMsgType type, const QMessageLogContext &ctxt, const QString &msg)
{
if (type == QtCriticalMsg || type == QtFatalMsg)
(*testlibMsgHandler)(type, ctxt, msg);
}
class tst_examples : public QObject
{
Q_OBJECT
public:
tst_examples();
~tst_examples();
private slots:
void init();
void cleanup();
void sgexamples_data();
void sgexamples();
void namingConvention();
private:
QStringList excludedDirs;
QStringList excludedFiles;
void namingConvention(const QDir &);
QStringList findQmlFiles(const QDir &);
QQmlEngine engine;
};
tst_examples::tst_examples()
{
// Add files to exclude here
excludedFiles << "snippets/qml/listmodel/listmodel.qml"; //Just a ListModel, no root QQuickItem
excludedFiles << "examples/quick/demos/photosurface/photosurface.qml"; // root item is Window rather than Item
// Add directories you want excluded here
excludedDirs << "shared"; //Not an example
excludedDirs << "snippets/qml/path"; //No root QQuickItem
excludedDirs << "examples/qml/qmlextensionplugins"; //Requires special import search path
excludedDirs << "examples/quick/tutorials/gettingStartedQml"; //C++ example, but no cpp files in root dir
// These snippets are not expected to run on their own.
excludedDirs << "snippets/qml/visualdatamodel_rootindex";
excludedDirs << "snippets/qml/qtbinding";
excludedDirs << "snippets/qml/imports";
excludedFiles << "snippets/qml/image-ext.qml";
excludedFiles << "examples/quick/shapes/content/main.qml"; // relies on resources
excludedFiles << "examples/quick/shapes/content/interactive.qml"; // relies on resources
#ifdef QT_NO_XMLPATTERNS
excludedDirs << "demos/twitter";
excludedDirs << "demos/flickr";
excludedDirs << "demos/photoviewer";
excludedFiles << "snippets/qml/xmlrole.qml";
excludedFiles << "particles/itemparticle/particleview.qml";
excludedFiles << "views/visualdatamodel/slideshow.qml";
#endif
#if !QT_CONFIG(opengl)
//No support for Particles
excludedFiles << "examples/qml/dynamicscene/dynamicscene.qml";
excludedFiles << "examples/quick/animation/basics/color-animation.qml";
excludedFiles << "examples/quick/particles/affectors/content/age.qml";
excludedFiles << "examples/quick/touchinteraction/multipointtouch/bearwhack.qml";
excludedFiles << "examples/quick/touchinteraction/multipointtouch/multiflame.qml";
excludedDirs << "examples/quick/particles";
// No Support for ShaderEffect
excludedFiles << "src/quick/doc/snippets/qml/animators.qml";
#endif
}
tst_examples::~tst_examples()
{
}
void tst_examples::init()
{
if (!qstrcmp(QTest::currentTestFunction(), "sgsnippets"))
testlibMsgHandler = qInstallMessageHandler(msgHandlerFilter);
}
void tst_examples::cleanup()
{
if (!qstrcmp(QTest::currentTestFunction(), "sgsnippets"))
qInstallMessageHandler(testlibMsgHandler);
}
/*
This tests that the examples follow the naming convention required
to have them tested by the examples() test.
*/
void tst_examples::namingConvention(const QDir &d)
{
for (int ii = 0; ii < excludedDirs.count(); ++ii) {
QString s = excludedDirs.at(ii);
if (d.absolutePath().endsWith(s))
return;
}
QStringList files = d.entryList(QStringList() << QLatin1String("*.qml"),
QDir::Files);
bool seenQml = !files.isEmpty();
bool seenLowercase = false;
foreach (const QString &file, files) {
if (file.at(0).isLower())
seenLowercase = true;
}
if (!seenQml) {
QStringList dirs = d.entryList(QDir::Dirs | QDir::NoDotAndDotDot |
QDir::NoSymLinks);
foreach (const QString &dir, dirs) {
QDir sub = d;
sub.cd(dir);
namingConvention(sub);
}
} else if (!seenLowercase) {
// QTBUG-28271 don't fail, but rather warn only
qWarning() << QString(
"Directory %1 violates naming convention; expected at least one qml file "
"starting with lower case, got: %2"
).arg(d.absolutePath()).arg(files.join(","));
// QFAIL(qPrintable(QString(
// "Directory %1 violates naming convention; expected at least one qml file "
// "starting with lower case, got: %2"
// ).arg(d.absolutePath()).arg(files.join(","))));
}
}
void tst_examples::namingConvention()
{
QStringList examplesLocations;
examplesLocations << QLibraryInfo::location(QLibraryInfo::ExamplesPath) + QLatin1String("/qml");
examplesLocations << QLibraryInfo::location(QLibraryInfo::ExamplesPath) + QLatin1String("/quick");
foreach (const QString &examples, examplesLocations) {
QDir d(examples);
if (d.exists())
namingConvention(d);
}
}
QStringList tst_examples::findQmlFiles(const QDir &d)
{
for (int ii = 0; ii < excludedDirs.count(); ++ii) {
QString s = excludedDirs.at(ii);
if (d.absolutePath().endsWith(s))
return QStringList();
}
QStringList rv;
QStringList cppfiles = d.entryList(QStringList() << QLatin1String("*.cpp"), QDir::Files);
if (cppfiles.isEmpty()) {
QStringList files = d.entryList(QStringList() << QLatin1String("*.qml"),
QDir::Files);
foreach (const QString &file, files) {
if (file.at(0).isLower()) {
bool superContinue = false;
for (int ii = 0; ii < excludedFiles.count(); ++ii) {
QString e = excludedFiles.at(ii);
if (d.absoluteFilePath(file).endsWith(e)) {
superContinue = true;
break;
}
}
if (superContinue)
continue;
rv << d.absoluteFilePath(file);
}
}
}
QStringList dirs = d.entryList(QDir::Dirs | QDir::NoDotAndDotDot |
QDir::NoSymLinks);
foreach (const QString &dir, dirs) {
QDir sub = d;
sub.cd(dir);
rv << findQmlFiles(sub);
}
return rv;
}
/*
This test runs all the examples in the QtQml UI source tree and ensures
that they start and exit cleanly.
Examples are any .qml files under the examples/ directory that start
with a lower case letter.
*/
void tst_examples::sgexamples_data()
{
QTest::addColumn<QString>("file");
QString examples = QLatin1String(SRCDIR) + "/../../../../examples/";
QStringList files;
files << findQmlFiles(QDir(examples));
foreach (const QString &file, files)
QTest::newRow(qPrintable(file)) << file;
}
void tst_examples::sgexamples()
{
QFETCH(QString, file);
QQuickWindow window;
window.setPersistentOpenGLContext(true);
window.setPersistentSceneGraph(true);
QQmlComponent component(&engine, QUrl::fromLocalFile(file));
if (component.status() == QQmlComponent::Error)
qWarning() << component.errors();
QCOMPARE(component.status(), QQmlComponent::Ready);
QScopedPointer<QObject> object(component.beginCreate(engine.rootContext()));
QQuickItem *root = qobject_cast<QQuickItem *>(object.data());
if (!root)
component.completeCreate();
QVERIFY(root);
window.resize(240, 320);
window.show();
QVERIFY(QTest::qWaitForWindowExposed(&window));
root->setParentItem(window.contentItem());
component.completeCreate();
qApp->processEvents();
}
QTEST_MAIN(tst_examples)
#include "tst_examples.moc"

View File

@ -0,0 +1,7 @@
TEMPLATE = subdirs
!cross_compile: PRIVATETESTS += examples
qtConfig(private_tests) {
SUBDIRS += $$PRIVATETESTS
}

2
tests/tests.pro Normal file
View File

@ -0,0 +1,2 @@
TEMPLATE = subdirs
SUBDIRS += auto