diff --git a/autotests/KDbTestUtils.cpp b/autotests/KDbTestUtils.cpp index 6a9f780c..abb9ffc7 100644 --- a/autotests/KDbTestUtils.cpp +++ b/autotests/KDbTestUtils.cpp @@ -1,337 +1,361 @@ /* This file is part of the KDE project Copyright (C) 2015 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbTestUtils.h" #include "KDbUtils_p.h" #include #include #include #include #include #include #include #include #include #include #include #include "../tests/features/tables_test_p.h" namespace QTest { KDBTESTUTILS_EXPORT bool qCompare(const KDbEscapedString &val1, const KDbEscapedString &val2, const char *actual, const char *expected, const char *file, int line) { return val1 == val2 ? compare_helper(true, "COMPARE()", toString(qPrintable(val1.toString())), toString(qPrintable(val2.toString())), actual, expected, file, line) : compare_helper(false, "Compared values are not the same", toString(qPrintable(val1.toString())), toString(qPrintable(val2.toString())), actual, expected, file, line); } KDBTESTUTILS_EXPORT bool qCompare(const KDbEscapedString &val1, const char *val2, const char *actual, const char *expected, const char *file, int line) { return val1 == val2 ? compare_helper(true, "COMPARE()", toString(qPrintable(val1.toString())), toString(val2), actual, expected, file, line) : compare_helper(false, "Compared values are not the same", toString(qPrintable(val1.toString())), toString(val2), actual, expected, file, line); } KDBTESTUTILS_EXPORT bool qCompare(const char *val1, const KDbEscapedString &val2, const char *actual, const char *expected, const char *file, int line) { return val1 == val2 ? compare_helper(true, "COMPARE()", toString(val1), toString(qPrintable(val2.toString())), actual, expected, file, line) : compare_helper(false, "Compared values are not the same", toString(val1), toString(qPrintable(val2.toString())), actual, expected, file, line); } KDBTESTUTILS_EXPORT bool qCompare(const KDbEscapedString &val1, const QString &val2, const char *actual, const char *expected, const char *file, int line) { return val1 == KDbEscapedString(val2) ? compare_helper(true, "COMPARE()", toString(qPrintable(val1.toString())), toString(val2), actual, expected, file, line) : compare_helper(false, "Compared values are not the same", toString(qPrintable(val1.toString())), toString(val2), actual, expected, file, line); } KDBTESTUTILS_EXPORT bool qCompare(const QString &val1, const KDbEscapedString &val2, const char *actual, const char *expected, const char *file, int line) { return KDbEscapedString(val1) == val2 ? compare_helper(true, "COMPARE()", toString(val1), toString(qPrintable(val2.toString())), actual, expected, file, line) : compare_helper(false, "Compared values are not the same", toString(val1), toString(qPrintable(val2.toString())), actual, expected, file, line); } } class KDbTestUtils::Private { public: Private() {} QScopedPointer kdbBuilder; QScopedPointer driverBuilder; }; KDbTestUtils::KDbTestUtils() : connection(nullptr) , d(new Private) { QCoreApplication::addLibraryPath(KDB_LOCAL_PLUGINS_DIR); // make plugins work without installing them } KDbTestUtils::~KDbTestUtils() { delete d; } KDbNativeStatementBuilder* KDbTestUtils::kdbBuilder() { Q_ASSERT(connection); if (connection && !d->kdbBuilder) { d->kdbBuilder.reset(new KDbNativeStatementBuilder(connection.data(), KDb::KDbEscaping)); } return d->kdbBuilder.data(); } KDbNativeStatementBuilder* KDbTestUtils::driverBuilder() { Q_ASSERT(connection); if (connection && !d->driverBuilder) { d->driverBuilder.reset(new KDbNativeStatementBuilder(connection.data(), KDb::DriverEscaping)); } return d->driverBuilder.data(); } void KDbTestUtils::testDriverManagerInternal(bool forceEmpty) { DriverManagerInternal::self()->forceEmpty = forceEmpty; QStringList ids = manager.driverIds(); //qDebug() << "DRIVERS:" << ids; QVERIFY2(forceEmpty == manager.result().isError(), "Error in driver manager"); //qDebug() << manager.result().message(); QVERIFY2(forceEmpty == ids.isEmpty(), "No db drivers found"); if (forceEmpty) { // no drivers, so try to find one and expect failure ids << "org.kde.kdb.sqlite"; } for (const QString &id : qAsConst(ids)) { const KDbDriverMetaData* driverMetaData; if (forceEmpty) { KDB_EXPECT_FAIL(manager.resultable(), driverMetaData = manager.driverMetaData(id), ERR_DRIVERMANAGER, "Driver metadata not found"); // find driver for the metadata KDB_EXPECT_FAIL(manager.resultable(), driver = manager.driver(id), ERR_DRIVERMANAGER, "Driver not found"); } else { KDB_VERIFY(manager.resultable(), driverMetaData = manager.driverMetaData(id), "Driver metadata not found"); QCOMPARE(driverMetaData->id(), id); // find driver for the metadata KDB_VERIFY(manager.resultable(), driver = manager.driver(id), "Driver not found"); } } DriverManagerInternal::self()->forceEmpty = false; // default state } void KDbTestUtils::testDriverManagerInternal() { testDriverManagerInternal(true); testDriverManagerInternal(false); } void KDbTestUtils::testDriver(const QString &driverId, bool fileBased, const QStringList &mimeTypes) { // find the metadata const KDbDriverMetaData* driverMetaData; KDB_VERIFY(manager.resultable(), driverMetaData = manager.driverMetaData(driverId), "Driver metadata not found"); QCOMPARE(driverMetaData->id(), driverId); QCOMPARE(driverMetaData->isFileBased(), fileBased); // test the mimetypes QStringList foundMimeTypes(driverMetaData->mimeTypes()); foundMimeTypes.sort(); QStringList expectedMimeTypes(mimeTypes); expectedMimeTypes.sort(); //qDebug() << "mimeTypes:" << mimeTypes; QCOMPARE(foundMimeTypes, expectedMimeTypes); QVERIFY(!KDb::defaultFileBasedDriverMimeType().isEmpty()); QMimeDatabase mimeDb; foreach(const QString &mimeName, expectedMimeTypes) { QVERIFY2(mimeDb.mimeTypeForName(mimeName).isValid(), qPrintable(QString("%1 MIME type not found in the MIME database").arg(mimeName))); } // find driver for the metadata KDB_VERIFY(manager.resultable(), driver = manager.driver(driverId), "Driver not found"); } void KDbTestUtils::testSqliteDriverInternal() { QStringList mimeTypes; mimeTypes << "application/x-kexiproject-sqlite3" << "application/x-sqlite3"; testDriver("org.kde.kdb.sqlite", true, // file-based mimeTypes); QVERIFY2(mimeTypes.contains(KDb::defaultFileBasedDriverMimeType()), "SQLite's MIME types should include the default file based one"); } -void KDbTestUtils::testConnectInternal(const KDbConnectionData &cdata) +void KDbTestUtils::testConnectInternal(const KDbConnectionData &cdata, + const KDbConnectionOptions &options) { //qDebug() << cdata; if (!driver) { //! @todo don't hardcode SQLite here KDB_VERIFY(manager.resultable(), driver = manager.driver("org.kde.kdb.sqlite"), "Driver not found"); } - KDbConnectionOptions connOptions; + KDbConnectionOptions connOptionsOverride(options); QStringList extraSqliteExtensionPaths; extraSqliteExtensionPaths << SQLITE_LOCAL_ICU_EXTENSION_PATH; - connOptions.insert("extraSqliteExtensionPaths", extraSqliteExtensionPaths); + connOptionsOverride.insert("extraSqliteExtensionPaths", extraSqliteExtensionPaths); connection.reset(); // remove previous connection if present const int connCount = driver->connections().count(); - connection.reset(driver->createConnection(cdata, connOptions)); + connection.reset(driver->createConnection(cdata, connOptionsOverride)); KDB_VERIFY(driver, !connection.isNull(), "Failed to create connection"); QVERIFY2(cdata.driverId().isEmpty(), "Connection data has filled driver ID"); QCOMPARE(connection->data().driverId(), driver->metaData()->id()); QVERIFY2(driver->connections().contains(connection.data()), "Driver does not list created connection"); QCOMPARE(driver->connections().count(), connCount + 1); // one more const KDbUtils::Property extraSqliteExtensionPathsProperty = connection->options()->property("extraSqliteExtensionPaths"); QVERIFY2(!extraSqliteExtensionPathsProperty.isNull(), "extraSqliteExtensionPaths property not found"); QCOMPARE(extraSqliteExtensionPathsProperty.value().type(), QVariant::StringList); QCOMPARE(extraSqliteExtensionPathsProperty.value().toStringList(), extraSqliteExtensionPaths); const KDbUtils::Property readOnlyProperty = connection->options()->property("readOnly"); QVERIFY2(!readOnlyProperty.isNull(), "readOnly property not found"); QCOMPARE(readOnlyProperty.value().toBool(), connection->options()->isReadOnly()); //! @todo Add extensive test for a read-only connection KDB_VERIFY(connection, connection->connect(), "Failed to connect"); KDB_VERIFY(connection, connection->isConnected(), "Database not connected after call to connect()"); } void KDbTestUtils::testUseInternal() { KDB_VERIFY(connection, connection->databaseExists(connection->data().databaseName()), "Database does not exists"); KDB_VERIFY(connection, connection->useDatabase(), "Failed to use database"); KDB_VERIFY(connection, connection->isDatabaseUsed(), "Database not used after call to useDatabase()"); } +void KDbTestUtils::testConnectAndUseInternal(const KDbConnectionData &cdata, + const KDbConnectionOptions &options) +{ + if (!testConnect(cdata, options) || !connection) { + qWarning() << driver->result(); + QFAIL("testConnect"); + } + if (!testUse() || !connection->isDatabaseUsed()) { + qWarning() << connection->result(); + bool result = testDisconnect(); + Q_UNUSED(result); + QFAIL("testUse"); + } +} + +void KDbTestUtils::testConnectAndUseInternal(const QString &path, + const KDbConnectionOptions &options) +{ + KDbConnectionData cdata; + cdata.setDatabaseName(path); + testConnectAndUseInternal(cdata, options); +} + void KDbTestUtils::testCreateDbInternal(const QString &dbName) { //open connection KDbConnectionData cdata; //! @todo don't hardcode SQLite (.kexi) extension here QString fullDbName(QDir::fromNativeSeparators(QFile::decodeName(FILES_OUTPUT_DIR "/") + dbName + ".kexi")); cdata.setDatabaseName(fullDbName); QVERIFY(testConnect(cdata)); QVERIFY(connection); //! @todo KDbDriver::metaData { QScopedPointer connGuard(connection.take()); if (connGuard->databaseExists(dbName)) { KDB_VERIFY(connGuard, connGuard->dropDatabase(fullDbName), "Failed to drop database"); } KDB_VERIFY(connGuard, !connGuard->databaseExists(fullDbName), "Database exists"); KDB_VERIFY(connGuard, connGuard->createDatabase(fullDbName), "Failed to create db"); KDB_VERIFY(connGuard, connGuard->databaseExists(fullDbName), "Database does not exists after creation"); connection.reset(connGuard.take()); } } void KDbTestUtils::testCreateDbWithTablesInternal(const QString &dbName) { QVERIFY(testCreateDb(dbName)); KDB_VERIFY(connection, connection->useDatabase(), "Failed to use database"); testCreateTablesInternal(); } void KDbTestUtils::testPropertiesInternal() { QStringList properties; properties << connection->databaseProperties().names(); QVERIFY(properties.contains("kexidb_major_ver")); bool ok; QVERIFY(connection->databaseProperties().value("kexidb_major_ver").toInt(&ok) >= 0); QVERIFY(ok); QVERIFY(properties.contains("kexidb_minor_ver")); QVERIFY(connection->databaseProperties().value("kexidb_minor_ver").toInt(&ok) >= 0); QVERIFY(ok); } void KDbTestUtils::testCreateTablesInternal() { QVERIFY2(tablesTest_createTables(connection.data()) == 0, "Failed to create test data"); } void KDbTestUtils::testDisconnectPrivate() { if (!connection) { return; } KDB_VERIFY(connection, connection->closeDatabase(), "Failed to close database"); KDB_VERIFY(connection, !connection->isDatabaseUsed(), "Database still used after closing"); KDB_VERIFY(connection, connection->closeDatabase(), "Second closeDatabase() call should not fail"); KDB_VERIFY(connection, connection->disconnect(), "Failed to disconnect database"); KDB_VERIFY(connection, !connection->isConnected(), "Database still connected after disconnecting"); KDB_VERIFY(connection, connection->disconnect(), "Second disconnect() call should not fail"); } void KDbTestUtils::testDisconnectInternal() { const int connCount = driver ? driver->connections().count() : 0; testDisconnectPrivate(); QVERIFY(!QTest::currentTestFailed()); connection.reset(); QCOMPARE(driver ? driver->connections().count() : -1, connCount - 1); // one less } void KDbTestUtils::testDropDbInternal() { QVERIFY(connection->dropDatabase(connection->data().databaseName())); } void KDbTestUtils::testDisconnectAndDropDbInternal() { QString dbName(connection.data()->data().databaseName()); testDisconnectPrivate(); QVERIFY(!QTest::currentTestFailed()); KDB_VERIFY(connection, connection->dropDatabase(dbName), "Failed to drop database"); connection.reset(); } diff --git a/autotests/KDbTestUtils.h b/autotests/KDbTestUtils.h index 165f9014..e8fdc64c 100644 --- a/autotests/KDbTestUtils.h +++ b/autotests/KDbTestUtils.h @@ -1,176 +1,199 @@ /* This file is part of the KDE project Copyright (C) 2015 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_TESTUTILS_H #define KDB_TESTUTILS_H #include "kdbtestutils_export.h" -#include -#include +#include +#include #include #include -#include + +#include +#include class KDbNativeStatementBuilder; Q_DECLARE_METATYPE(KDbField::TypeGroup) Q_DECLARE_METATYPE(KDbField::Type) Q_DECLARE_METATYPE(KDb::Signedness) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(KDb::BLOBEscapingType) //! @internal for KDB_VERIFY template const T* KDB_POINTER_WRAPPER(const T &t) { return &t; } //! @internal for KDB_VERIFY template const T* KDB_POINTER_WRAPPER(const T *t) { return t; } //! @internal for KDB_VERIFY template T* KDB_POINTER_WRAPPER(T *t) { return t; } //! @internal for KDB_VERIFY template T* KDB_POINTER_WRAPPER(const QPointer &t) { return t.data(); } //! @internal for KDB_VERIFY template T* KDB_POINTER_WRAPPER(const QScopedPointer &t) { return t.data(); } namespace QTest { KDBTESTUTILS_EXPORT bool qCompare(const KDbEscapedString &val1, const KDbEscapedString &val2, const char *actual, const char *expected, const char *file, int line); KDBTESTUTILS_EXPORT bool qCompare(const KDbEscapedString &val1, const char *val2, const char *actual, const char *expected, const char *file, int line); KDBTESTUTILS_EXPORT bool qCompare(const char *val1, const KDbEscapedString &val2, const char *actual, const char *expected, const char *file, int line); KDBTESTUTILS_EXPORT bool qCompare(const KDbEscapedString &val1, const QString &val2, const char *actual, const char *expected, const char *file, int line); KDBTESTUTILS_EXPORT bool qCompare(const QString &val1, const KDbEscapedString &val2, const char *actual, const char *expected, const char *file, int line); } //! Calls @a call and verifies status of @a resultable //! On error displays the status on debug and does the same as QVERIFY with @a errorMessage #define KDB_VERIFY(resultable, call, errorMessage) \ do { \ bool KDB_VERIFY_ok = (call); \ const KDbResultable *KDB_VERIFY_resultablePtr = KDB_POINTER_WRAPPER(resultable); \ if (KDB_VERIFY_resultablePtr->result().isError()) { \ qDebug() << KDB_VERIFY_resultablePtr->result(); \ } \ if (!QTest::qVerify(KDB_VERIFY_ok && !KDB_VERIFY_resultablePtr->result().isError(), # call, (errorMessage), __FILE__, __LINE__)) {\ return; \ } \ } \ while (false) //! Calls @a call and verifies status of @a resultable //! On error displays the status on debug and does the same as QVERIFY with @a errorMessage #define KDB_EXPECT_FAIL(resultable, call, expectedErrorCode, errorMessage) \ do { \ bool KDB_VERIFY_ok = (call); \ const KDbResultable *KDB_VERIFY_resultablePtr = KDB_POINTER_WRAPPER(resultable); \ if (KDB_VERIFY_resultablePtr->result().isError()) { \ qDebug() << KDB_VERIFY_resultablePtr->result(); \ } \ QVERIFY(KDB_VERIFY_resultablePtr->result().isError()); \ if (!QTest::qVerify(!KDB_VERIFY_ok, # call, (errorMessage), __FILE__, __LINE__)) {\ return; \ } \ if (!QTest::qCompare(KDB_VERIFY_resultablePtr->result().code(), expectedErrorCode, # call, # expectedErrorCode, __FILE__, __LINE__)) {\ return; \ } \ } \ while (false) //! Declares method @a name that returns false on test failure, it can be called as utility function. //! Also declared internal method name ## Internal which performs the actual test. //! This way users of this method can call QVERIFY(utils.()); #define KDBTEST_METHOD_DECL(name, argsDecl, args) \ public: \ bool name argsDecl Q_REQUIRED_RESULT { name ## Internal args ; return !QTest::currentTestFailed(); } \ private Q_SLOTS: \ void name ## Internal argsDecl //! Test utilities that provide basic database features class KDBTESTUTILS_EXPORT KDbTestUtils : public QObject { Q_OBJECT public: KDbTestUtils(); ~KDbTestUtils(); KDbDriverManager manager; QPointer driver; QScopedPointer connection; /** * Returns builder for generating KDb SQL statements */ KDbNativeStatementBuilder* kdbBuilder(); /** * Returns builder for generating driver-native SQL statements */ KDbNativeStatementBuilder* driverBuilder(); KDBTEST_METHOD_DECL(testDriverManager, (), ()); KDBTEST_METHOD_DECL(testSqliteDriver, (), ()); - KDBTEST_METHOD_DECL(testConnect, (const KDbConnectionData &cdata), (cdata)); + + //! Connects to a database + //! @since 3.2 + KDBTEST_METHOD_DECL(testConnect, + (const KDbConnectionData &cdata, + const KDbConnectionOptions &options = KDbConnectionOptions()), + (cdata, options)); + KDBTEST_METHOD_DECL(testUse, (), ()); + //! Convenience method that performs testConnect and testUse in one go + //! @since 3.2 + KDBTEST_METHOD_DECL(testConnectAndUse, + (const KDbConnectionData &cdata, + const KDbConnectionOptions &options = KDbConnectionOptions()), + (cdata, options)); + + //! Overload of testConnectAndUse for file-based databases + //! @since 3.2 + KDBTEST_METHOD_DECL(testConnectAndUse, + (const QString &path, + const KDbConnectionOptions &options = KDbConnectionOptions()), + (path, options)); + //! Creates database with name @a dbName //! Does not use the database. //! @todo don't hardcode SQLite here //! @note dbName should not include ".kexi" extension or path KDBTEST_METHOD_DECL(testCreateDb, (const QString &dbName), (dbName)); //! Creates database with name @a dbName, then uses it and creates test tables //! The database stays used. //! @note dbName should not include ".kexi" extension or path KDBTEST_METHOD_DECL(testCreateDbWithTables, (const QString &dbName), (dbName)); KDBTEST_METHOD_DECL(testProperties, (), ()); KDBTEST_METHOD_DECL(testCreateTables, (), ()); KDBTEST_METHOD_DECL(testDisconnect, (), ()); KDBTEST_METHOD_DECL(testDropDb, (), ()); KDBTEST_METHOD_DECL(testDisconnectAndDropDb, (), ()); protected: void testDisconnectPrivate(); void testDriver(const QString &driverId, bool fileBased, const QStringList &mimeTypes); void testDriverManagerInternal(bool forceEmpty); private: Q_DISABLE_COPY(KDbTestUtils) class Private; Private * const d; }; #endif diff --git a/autotests/parser/SqlParserTest.cpp b/autotests/parser/SqlParserTest.cpp index de887698..19ca85b6 100644 --- a/autotests/parser/SqlParserTest.cpp +++ b/autotests/parser/SqlParserTest.cpp @@ -1,372 +1,365 @@ /* This file is part of the KDE project Copyright (C) 2012-2017 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SqlParserTest.h" #include #include #include #include #include #include Q_DECLARE_METATYPE(KDbEscapedString) QTEST_GUILESS_MAIN(SqlParserTest) void SqlParserTest::initTestCase() { QString dir(QFile::decodeName(OUTPUT_DIR)); QString fname("errors.txt"); m_errorFile.setFileName(dir + QDir::separator() + fname); QVERIFY2(m_errorFile.open(QFile::WriteOnly | QFile::Text), qPrintable(QString("Cannot open %1 file").arg(m_errorFile.fileName()))); m_errorStream.setDevice(&m_errorFile); } bool SqlParserTest::openDatabase(const QString &path) { - KDbConnectionData cdata; - cdata.setDatabaseName(path); - if (!m_utils.testConnect(cdata) || !m_utils.connection) { - qWarning() << m_utils.driver->result(); + KDbConnectionOptions options; + options.setReadOnly(true); + if (!m_utils.testConnectAndUse(path, options)) { return false; } m_parser.reset(new KDbParser(m_utils.connection.data())); #if 0 if (m_conn->databaseExists(dbName)) { if (!m_conn->dropDatabase(dbName)) { m_conn->disconnect(); return false; } qDebug() << "Database" << dbName << "dropped."; } if (!m_conn->createDatabase(dbName)) { qDebug() << m_conn->result(); m_conn->disconnect(); return false; } #endif - if (!m_utils.testUse() || !m_utils.connection->isDatabaseUsed()) { - qWarning() << m_utils.connection->result(); - bool result = m_utils.testDisconnect(); - Q_UNUSED(result); - return false; - } return true; } static void eatComment(QString* string) { if (!string->startsWith("--")) { return; } int i = 0; for (; i < string->length() && string->at(i) == '-'; ++i) ; QString result = string->mid(i).trimmed(); *string = result; } static void eatEndLines(QString* string) { if (!string->endsWith("--")) { return; } int i = string->length() - 1; for (; i >= 0 && string->at(i) == '-'; --i) ; *string = string->left(i+1).trimmed(); } static void eatEndComment(QString* string) { int pos = string->indexOf("; --"); if (pos == -1) { return; } string->truncate(pos); *string = string->trimmed() + ';'; } void SqlParserTest::testParse_data() { QTest::addColumn("fname"); QTest::addColumn("lineNum"); QTest::addColumn("sql"); QTest::addColumn("expectError"); QString dir(QFile::decodeName(FILES_DATA_DIR)); QString fname("statements.txt"); QFile input(dir + QDir::separator() + fname); bool ok = input.open(QFile::ReadOnly | QFile::Text); QVERIFY2(ok, qPrintable(QString("Could not open data file %1").arg(input.fileName()))); QTextStream in(&input); QString category; QString testName; bool expectError = false; int lineNum = 1; QString dbPath; bool clearTestName = false; for (; !in.atEnd(); ++lineNum) { QString line(in.readLine()); if (line.startsWith("--")) { // comment eatComment(&line); eatEndLines(&line); if (line.startsWith("TODO:")) { continue; } else if (line.startsWith("CATEGORY: ")) { if (clearTestName) { expectError = false; clearTestName = false; testName.clear(); } category = line.mid(QString("CATEGORY: ").length()).trimmed(); //qDebug() << "CATEGORY:" << category; } else if (line == "QUIT") { break; } else if (line.startsWith("SQLITEFILE: ")) { if (clearTestName) { expectError = false; clearTestName = false; testName.clear(); } ok = dbPath.isEmpty(); QVERIFY2(ok, qPrintable(QString("Error at line %1: SQLite file was already specified (%2)") .arg(lineNum).arg(dbPath))); dbPath = line.mid(QString("SQLITEFILE: ").length()).trimmed(); dbPath = dir + QDir::separator() + dbPath; ok = openDatabase(dbPath); QVERIFY2(ok, qPrintable(QString("Error at line %1: Could not open SQLite file %2") .arg(lineNum).arg(dbPath))); } else if (line.startsWith("ERROR: ")) { if (clearTestName) { clearTestName = false; testName.clear(); } expectError = true; testName = line.mid(QString("ERROR: ").length()).trimmed(); } else { if (clearTestName) { expectError = false; clearTestName = false; testName.clear(); } if (!testName.isEmpty()) { testName.append(" "); } testName.append(line); } } else { eatEndComment(&line); KDbEscapedString sql(line.trimmed()); clearTestName = true; if (sql.isEmpty()) { expectError = false; continue; } ok = !dbPath.isEmpty(); QVERIFY2(ok, qPrintable(QString("Error at line %1: SQLite file was not specified, " "could not execute statement").arg(lineNum))); QTest::newRow(qPrintable(QString("file %1:%2, category '%3', test '%4', sql '%5'%6") .arg(fname).arg(lineNum).arg(category).arg(testName).arg(sql.toString()) .arg(expectError ? ", error expected" : ""))) << fname << lineNum << sql << expectError; } } input.close(); } void SqlParserTest::testParse() { QFETCH(QString, fname); QFETCH(int, lineNum); QFETCH(KDbEscapedString, sql); QFETCH(bool, expectError); QString message; if (!sql.endsWith(';')) { message = QString("%1:%2: Missing ';' at the end of line").arg(fname).arg(lineNum); m_errorStream << fname << ':' << lineNum << ' ' << message << endl; QVERIFY2(sql.endsWith(';'), qPrintable(message)); } sql.chop(1); //qDebug() << "SQL:" << sql.toString() << expectError; // 1. Parse KDbParser *parser = m_parser.data(); bool ok = parser->parse(sql); QScopedPointer query(parser->query()); QCOMPARE(parser->query(), nullptr); // second call should always return nullptr ok = ok && query; if (ok) { // sucess, so error cannot be expected ok = !expectError; message = "Unexpected success of parsing SQL statement"; if (!ok) { m_errorStream << fname << ':' << lineNum << ' ' << message << endl; if (query) { const KDbConnectionAndQuerySchema connQuery(parser->connection(), *query); qDebug() << connQuery; m_errorStream << KDbUtils::debugString(connQuery) << endl; } } QVERIFY2(ok, qPrintable(message)); } else { // failure, so error should be expected ok = expectError; message = QString("%1; Failed to parse SQL Statement:\n\"%2\"\n %3^\n") .arg(KDbUtils::debugString(parser->error()), sql.toString(), QString(parser->error().position() - 1, QChar(' '))); if (ok) { qDebug() << parser->error(); } else { m_errorStream << fname << ':' << lineNum << message << endl; } QVERIFY2(ok, qPrintable(message)); } //! @todo support more drivers if (query) { // 2. Build native SQL for SQLite QList params; KDbEscapedString querySql; ok = m_utils.driverBuilder()->generateSelectStatement(&querySql, query.data(), params); QVERIFY2(ok, "Failed to generate native SQLite SQL statement from query"); //! @todo compare with template } if (query) { // 3. Build KDbSQL QList params; KDbEscapedString querySql; ok = m_utils.kdbBuilder()->generateSelectStatement(&querySql, query.data(), params); QVERIFY2(ok, "Failed to generate KDbSQL statement from query"); //! @todo compare with template // 3.1. Parse the generated KDbSQL again ok = parser->parse(querySql); QScopedPointer secondQuery(parser->query()); QCOMPARE(parser->query(), nullptr); // second call should always return nullptr ok = ok && secondQuery; QVERIFY2(ok, "Failed to parse generated KDbSQL statement again"); // 3.2. Compare the original query from step #1 with this query ok = *query == *secondQuery; QVERIFY2(ok, "Original query differs from repeatedly parsed query"); } } void SqlParserTest::testTokens() { KDbToken t = KDbToken::LEFT; //qDebug() << t << t.toChar() << t.value() << t.isValid(); t = '+'; //qDebug() << t << t.toChar() << t.value() << t.isValid(); t = KDbToken(); //qDebug() << t << t.toChar() << t.value() << t.isValid(); QCOMPARE(KDbToken::SQL_TYPE.value(), 258); QCOMPARE(KDbToken::AS.value(), 259); QCOMPARE(KDbToken::AS_EMPTY.value(), 260); QCOMPARE(KDbToken::ASC.value(), 261); QCOMPARE(KDbToken::AUTO_INCREMENT.value(), 262); QCOMPARE(KDbToken::BIT.value(), 263); QCOMPARE(KDbToken::BITWISE_SHIFT_LEFT.value(), 264); QCOMPARE(KDbToken::BITWISE_SHIFT_RIGHT.value(), 265); QCOMPARE(KDbToken::BY.value(), 266); QCOMPARE(KDbToken::CHARACTER_STRING_LITERAL.value(), 267); QCOMPARE(KDbToken::CONCATENATION.value(), 268); QCOMPARE(KDbToken::CREATE.value(), 269); QCOMPARE(KDbToken::DESC.value(), 270); QCOMPARE(KDbToken::DISTINCT.value(), 271); QCOMPARE(KDbToken::DOUBLE_QUOTED_STRING.value(), 272); QCOMPARE(KDbToken::FROM.value(), 273); QCOMPARE(KDbToken::JOIN.value(), 274); QCOMPARE(KDbToken::KEY.value(), 275); QCOMPARE(KDbToken::LEFT.value(), 276); QCOMPARE(KDbToken::LESS_OR_EQUAL.value(), 277); QCOMPARE(KDbToken::GREATER_OR_EQUAL.value(), 278); QCOMPARE(KDbToken::SQL_NULL.value(), 279); QCOMPARE(KDbToken::SQL_IS.value(), 280); QCOMPARE(KDbToken::SQL_IS_NULL.value(), 281); QCOMPARE(KDbToken::SQL_IS_NOT_NULL.value(), 282); QCOMPARE(KDbToken::ORDER.value(), 283); QCOMPARE(KDbToken::PRIMARY.value(), 284); QCOMPARE(KDbToken::SELECT.value(), 285); QCOMPARE(KDbToken::INTEGER_CONST.value(), 286); QCOMPARE(KDbToken::REAL_CONST.value(), 287); QCOMPARE(KDbToken::RIGHT.value(), 288); QCOMPARE(KDbToken::SQL_ON.value(), 289); QCOMPARE(KDbToken::DATE_CONST.value(), 290); QCOMPARE(KDbToken::DATETIME_CONST.value(), 291); QCOMPARE(KDbToken::TIME_CONST.value(), 292); QCOMPARE(KDbToken::TABLE.value(), 293); QCOMPARE(KDbToken::IDENTIFIER.value(), 294); QCOMPARE(KDbToken::IDENTIFIER_DOT_ASTERISK.value(), 295); QCOMPARE(KDbToken::QUERY_PARAMETER.value(), 296); QCOMPARE(KDbToken::VARCHAR.value(), 297); QCOMPARE(KDbToken::WHERE.value(), 298); QCOMPARE(KDbToken::SQL.value(), 299); QCOMPARE(KDbToken::SQL_TRUE.value(), 300); QCOMPARE(KDbToken::SQL_FALSE.value(), 301); QCOMPARE(KDbToken::UNION.value(), 302); QCOMPARE(KDbToken::SCAN_ERROR.value(), 303); QCOMPARE(KDbToken::AND.value(), 304); QCOMPARE(KDbToken::BETWEEN.value(), 305); QCOMPARE(KDbToken::NOT_BETWEEN.value(), 306); QCOMPARE(KDbToken::EXCEPT.value(), 307); QCOMPARE(KDbToken::SQL_IN.value(), 308); QCOMPARE(KDbToken::INTERSECT.value(), 309); QCOMPARE(KDbToken::LIKE.value(), 310); QCOMPARE(KDbToken::ILIKE.value(), 311); QCOMPARE(KDbToken::NOT_LIKE.value(), 312); QCOMPARE(KDbToken::NOT.value(), 313); QCOMPARE(KDbToken::NOT_EQUAL.value(), 314); QCOMPARE(KDbToken::NOT_EQUAL2.value(), 315); QCOMPARE(KDbToken::OR.value(), 316); QCOMPARE(KDbToken::SIMILAR_TO.value(), 317); QCOMPARE(KDbToken::NOT_SIMILAR_TO.value(), 318); QCOMPARE(KDbToken::XOR.value(), 319); QCOMPARE(KDbToken::UMINUS.value(), 320); //! @todo add extra tokens: BETWEEN_AND, NOT_BETWEEN_AND } void SqlParserTest::cleanupTestCase() { QVERIFY(m_utils.testDisconnect()); m_errorFile.close(); #if 0 if (!m_conn->dropDatabase()) { qDebug() << m_conn->result(); } qDebug() << "Database" << m_conn->data().databaseName() << "dropped."; #endif }