Warning, file /utilities/okteta/kasten/controllers/test/jsparsertest.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  This file is part of the Okteta Kasten module, made within the KDE community.
0003  *
0004  *  SPDX-FileCopyrightText: 2013 Alex Richardson <alex.richardson@gmx.de>
0005  *
0006  *  SPDX-License-Identifier: LGPL-2.1-or-later
0007  */
0008 
0009 // TODO: find better way to work-around simple name creation for QTest::newRow
0010 #undef QT_USE_QSTRINGBUILDER
0011 
0012 #include <QTest>
0013 #include <QScriptEngine>
0014 #include "view/structures/script/scriptengineinitializer.hpp"
0015 #include "view/structures/parsers/scriptvalueconverter.hpp"
0016 #include "testutils.hpp"
0017 #include <functional>
0018 
0019 struct JsTestData
0020 {
0021     using CheckCallback = std::function<void(DataInformation*)>;
0022     JsTestData() = default;
0023     JsTestData(const char* tag, const char* constructor, const CheckCallback& check)
0024         : tag(tag)
0025         , constructorCall(QString::fromUtf8(constructor))
0026         , check(check)
0027     {}
0028     QByteArray tag;
0029     QString constructorCall;
0030     CheckCallback check;
0031 };
0032 Q_DECLARE_METATYPE(JsTestData)
0033 Q_DECLARE_METATYPE(JsTestData::CheckCallback)
0034 
0035 class JsParserTest : public QObject
0036 {
0037     Q_OBJECT
0038 
0039 private Q_SLOTS:
0040     void initTestCase();
0041     void testValidationFunc();
0042     void testValidationFunc_data();
0043     void testUpdateFunc();
0044     void testUpdateFunc_data();
0045     void testByteOrder();
0046     void testByteOrder_data();
0047     void testName();
0048     void testName_data();
0049     void testCustomTypeName();
0050     void testCustomTypeName_data();
0051     void testImport();
0052     void testImportPathTraversal();
0053 
0054 private:
0055     /** data gets set to the parsed result.
0056      * This is needed since functions with QVERIFY/QCOMPARE must return void */
0057     void testCommon(DataInformation** data);
0058 
0059 private:
0060     QScriptEngine engine;
0061     QVector<JsTestData> primitiveData;
0062     QVector<JsTestData> bitfieldData;
0063     QVector<JsTestData> allData;
0064 };
0065 
0066 static JsTestData::CheckCallback primitiveTypeCheck(PrimitiveDataType type)
0067 {
0068     return [type](DataInformation* data) {
0069         QVERIFY(data->isPrimitive());
0070         QCOMPARE(data->asPrimitive()->type(), type);
0071     };
0072 }
0073 
0074 static JsTestData::CheckCallback bitfieldCheck(AbstractBitfieldDataInformation::Type type)
0075 {
0076     return [type](DataInformation* data) {
0077         QVERIFY(data->isPrimitive());
0078         QCOMPARE(data->asBitfield()->bitfieldType(), type);
0079     };
0080 }
0081 
0082 void JsParserTest::initTestCase()
0083 {
0084     ScriptEngineInitializer::addFuctionsToScriptEngine(&engine);
0085 
0086     primitiveData
0087         << JsTestData("float", "float()", primitiveTypeCheck(PrimitiveDataType::Float))
0088         << JsTestData("double", "double()", primitiveTypeCheck(PrimitiveDataType::Double))
0089         << JsTestData("char", "char()", primitiveTypeCheck(PrimitiveDataType::Char))
0090 
0091         << JsTestData("uint8", "uint8()", primitiveTypeCheck(PrimitiveDataType::UInt8))
0092         << JsTestData("uint16", "uint16()", primitiveTypeCheck(PrimitiveDataType::UInt16))
0093         << JsTestData("uint32", "uint32()", primitiveTypeCheck(PrimitiveDataType::UInt32))
0094         << JsTestData("uint64", "uint64()", primitiveTypeCheck(PrimitiveDataType::UInt64))
0095 
0096         << JsTestData("int8", "int8()", primitiveTypeCheck(PrimitiveDataType::Int8))
0097         << JsTestData("int16", "int16()", primitiveTypeCheck(PrimitiveDataType::Int16))
0098         << JsTestData("int32", "int32()", primitiveTypeCheck(PrimitiveDataType::Int32))
0099         << JsTestData("int64", "int64()", primitiveTypeCheck(PrimitiveDataType::Int64))
0100 
0101         << JsTestData("bool8", "bool8()", primitiveTypeCheck(PrimitiveDataType::Bool8))
0102         << JsTestData("bool16", "bool16()", primitiveTypeCheck(PrimitiveDataType::Bool16))
0103         << JsTestData("bool32", "bool32()", primitiveTypeCheck(PrimitiveDataType::Bool32))
0104         << JsTestData("bool64", "bool64()", primitiveTypeCheck(PrimitiveDataType::Bool64));
0105 
0106     bitfieldData
0107         << JsTestData("signed bitfield", "bitfield(\"signed\", 5)",
0108                       bitfieldCheck(AbstractBitfieldDataInformation::Type::Signed))
0109         << JsTestData("unsigned bitfield", "bitfield(\"unsigned\", 5)",
0110                   bitfieldCheck(AbstractBitfieldDataInformation::Type::Unsigned))
0111         << JsTestData("bool bitfield", "bitfield(\"bool\", 5)",
0112                   bitfieldCheck(AbstractBitfieldDataInformation::Type::Boolean));
0113 
0114     allData << primitiveData << bitfieldData;
0115     // TODO struct, union, taggedUnion, pointer, flags, enum, array, string
0116 
0117     // needed so that imports can be resolved
0118     QString resources = QFINDTESTDATA("resources");
0119     QString examples = QFINDTESTDATA("../view/structures/examples");
0120     QVERIFY2(!resources.isEmpty(), "Test data must exist!");
0121     QVERIFY2(!examples.isEmpty(), "Test data must exist!");
0122     qputenv("XDG_DATA_DIRS",
0123             QFile::encodeName(QFileInfo(resources).absoluteFilePath()) + ':' +
0124             QFile::encodeName(QFileInfo(examples).absoluteFilePath()));
0125 }
0126 
0127 void JsParserTest::testByteOrder_data()
0128 {
0129     QTest::addColumn<QString>("code");
0130     QTest::addColumn<JsTestData::CheckCallback>("checkFunction");
0131     QTest::addColumn<int>("expectedByteOrder");
0132     // verify that default is inherit
0133     for (const JsTestData& data : qAsConst(allData)) {
0134         // default should be inherit
0135         QString codeStr = QStringLiteral("%1;");
0136         QTest::newRow(data.tag.constData()) << codeStr.arg(data.constructorCall)
0137                                             << data.check << (int)DataInformation::DataInformationEndianess::EndianessInherit;
0138 
0139         // use set() function to specify byteOrder
0140         codeStr = QStringLiteral("%1.set({byteOrder: \"inherit\"})");
0141         QTest::newRow((data.tag + " set() inherit").constData()) << codeStr.arg(data.constructorCall)
0142                                                                  << data.check << (int)DataInformation::DataInformationEndianess::EndianessInherit;
0143         codeStr = QStringLiteral("%1.set({byteOrder: \"littleEndian\"})");
0144         QTest::newRow((data.tag + " set() little endian").constData()) << codeStr.arg(data.constructorCall)
0145                                                                        << data.check << (int)DataInformation::DataInformationEndianess::EndianessLittle;
0146         codeStr = QStringLiteral("%1.set({byteOrder: \"bigEndian\"})");
0147         QTest::newRow((data.tag + " set() big endian").constData()) << codeStr.arg(data.constructorCall)
0148                                                                     << data.check << (int)DataInformation::DataInformationEndianess::EndianessBig;
0149         codeStr = QStringLiteral("%1.set({byteOrder: \"fromSettings\"})");
0150         QTest::newRow((data.tag + " set() from settings").constData()) << codeStr.arg(data.constructorCall)
0151                                                                        << data.check << (int)DataInformation::DataInformationEndianess::EndianessFromSettings;
0152 
0153         // direct property access to specify byteOrder
0154         codeStr = QStringLiteral("var obj = %1; obj.byteOrder = \"inherit\"; obj;");
0155         QTest::newRow((data.tag + " property assign inherit").constData()) << codeStr.arg(data.constructorCall)
0156                                                                            << data.check << (int)DataInformation::DataInformationEndianess::EndianessInherit;
0157         codeStr = QStringLiteral("var obj = %1; obj.byteOrder = \"little-endian\"; obj;");
0158         QTest::newRow((data.tag + " property assign little endian").constData()) << codeStr.arg(data.constructorCall)
0159                                                                                  << data.check << (int)DataInformation::DataInformationEndianess::EndianessLittle;
0160         codeStr = QStringLiteral("var obj = %1; obj.byteOrder = \"big-endian\"; obj;");
0161         QTest::newRow((data.tag + " property assign big endian").constData()) << codeStr.arg(data.constructorCall)
0162                                                                               << data.check << (int)DataInformation::DataInformationEndianess::EndianessBig;
0163         codeStr = QStringLiteral("var obj = %1; obj.byteOrder = \"from-settings\"; obj;");
0164         QTest::newRow((data.tag + " property assign from settings").constData()) << codeStr.arg(data.constructorCall)
0165                                                                                  << data.check << (int)DataInformation::DataInformationEndianess::EndianessFromSettings;
0166     }
0167 }
0168 
0169 void JsParserTest::testCommon(DataInformation** dataPtr)
0170 {
0171     QFETCH(QString, code);
0172     QFETCH(JsTestData::CheckCallback, checkFunction);
0173     QScriptValue value = engine.evaluate(code);
0174     QVERIFY(value.isValid());
0175     QVERIFY(!value.isError());
0176     QVERIFY(value.isObject());
0177     ScriptLogger logger;
0178     QScopedPointer<DataInformation> data
0179         (ScriptValueConverter::convert(value, QStringLiteral("converted"), &logger));
0180     QVERIFY(logger.rowCount() == 0);
0181     QVERIFY(data);
0182     checkFunction(data.data());
0183     *dataPtr = data.take();
0184 }
0185 
0186 void JsParserTest::testByteOrder()
0187 {
0188     DataInformation* data = nullptr;
0189     testCommon(&data);
0190     if (QTest::currentTestFailed()) {
0191         return; // Qt doesn't use exceptions, we must manually check after each call
0192     }
0193     QFETCH(int, expectedByteOrder);
0194     QCOMPARE((int)data->byteOrder(), expectedByteOrder);
0195 }
0196 
0197 namespace {
0198 QString updateFunction() { return QStringLiteral("function () { /* do nothing*/; }"); }
0199 }
0200 
0201 void JsParserTest::testUpdateFunc_data()
0202 {
0203     QTest::addColumn<QString>("code");
0204     QTest::addColumn<JsTestData::CheckCallback>("checkFunction");
0205 
0206     for (const JsTestData& data : qAsConst(allData)) {
0207         QString codeStr = QStringLiteral("%1.setUpdate(") + updateFunction() + QStringLiteral(");");
0208         QTest::newRow((data.tag + "-setUpdate()").constData())
0209             << codeStr.arg(data.constructorCall) << data.check;
0210 
0211         codeStr = QStringLiteral("%1.set({updateFunc: ") + updateFunction() + QStringLiteral("});");
0212         QTest::newRow((data.tag + "-set()").constData())
0213             << codeStr.arg(data.constructorCall) << data.check;
0214 
0215         codeStr = QStringLiteral("var obj = %1; obj.updateFunc = ") + updateFunction() + QStringLiteral("; obj;");
0216         QTest::newRow((data.tag + "-property assign").constData())
0217             << codeStr.arg(data.constructorCall) << data.check;
0218     }
0219 }
0220 
0221 void JsParserTest::testUpdateFunc()
0222 {
0223     DataInformation* data = nullptr;
0224     testCommon(&data);
0225     if (QTest::currentTestFailed()) {
0226         return; // Qt doesn't use exceptions, we must manually check after each call
0227     }
0228     QVERIFY(data);
0229     QScriptValue update = data->updateFunc();
0230     QVERIFY(update.isValid());
0231     QVERIFY(update.isFunction());
0232     QCOMPARE(update.toString(), updateFunction());
0233 }
0234 
0235 namespace {
0236 QString validationFunction() { return QStringLiteral("function () { return true; }"); }
0237 }
0238 
0239 void JsParserTest::testValidationFunc_data()
0240 {
0241     QTest::addColumn<QString>("code");
0242     QTest::addColumn<JsTestData::CheckCallback>("checkFunction");
0243 
0244     for (const JsTestData& data : qAsConst(allData)) {
0245         QString codeStr = QStringLiteral("%1.setValidation(") + validationFunction() + QStringLiteral(");");
0246         QTest::newRow((data.tag + "-setUpdate()").constData())
0247             << codeStr.arg(data.constructorCall) << data.check;
0248 
0249         codeStr = QStringLiteral("%1.set({validationFunc: ") + validationFunction() + QStringLiteral("});");
0250         QTest::newRow((data.tag + "-set()").constData())
0251             << codeStr.arg(data.constructorCall) << data.check;
0252 
0253         codeStr = QStringLiteral("var obj = %1; obj.validationFunc = ") + validationFunction() + QStringLiteral("; obj;");
0254         QTest::newRow((data.tag + "-property assign").constData())
0255             << codeStr.arg(data.constructorCall) << data.check;
0256     }
0257 }
0258 
0259 void JsParserTest::testValidationFunc()
0260 {
0261     DataInformation* data = nullptr;
0262     testCommon(&data);
0263     if (QTest::currentTestFailed()) {
0264         return; // Qt doesn't use exceptions, we must manually check after each call
0265 
0266     }
0267     QScriptValue validation = data->validationFunc();
0268     QVERIFY(validation.isValid());
0269     QVERIFY(validation.isFunction());
0270     QCOMPARE(validation.toString(), validationFunction());
0271 }
0272 
0273 void JsParserTest::testName_data()
0274 {
0275     QTest::addColumn<QString>("code");
0276     QTest::addColumn<JsTestData::CheckCallback>("checkFunction");
0277 
0278     for (const JsTestData& data : qAsConst(allData)) {
0279         QString codeStr = QStringLiteral("%1.set({name: \"expectedName\"});");
0280         QTest::newRow((data.tag + "-set()").constData())
0281             << codeStr.arg(data.constructorCall) << data.check;
0282 
0283         codeStr = QStringLiteral("var obj = %1;obj.name = \"expectedName\"; obj;");
0284         QTest::newRow((data.tag + "-property assignment").constData())
0285             << codeStr.arg(data.constructorCall) << data.check;
0286     }
0287 }
0288 
0289 void JsParserTest::testName()
0290 {
0291     DataInformation* data = nullptr;
0292     testCommon(&data);
0293     if (QTest::currentTestFailed()) {
0294         return; // Qt doesn't use exceptions, we must manually check after each call
0295 
0296     }
0297     QCOMPARE(data->name(), QStringLiteral("expectedName"));
0298 }
0299 
0300 void JsParserTest::testCustomTypeName_data()
0301 {
0302     QTest::addColumn<QString>("code");
0303     QTest::addColumn<JsTestData::CheckCallback>("checkFunction");
0304 
0305     for (const JsTestData& data : qAsConst(allData)) {
0306         QString codeStr = QStringLiteral("%1.set({typeName: 'myCustomType'});");
0307         QTest::newRow((data.tag + "-set()").constData())
0308             << codeStr.arg(data.constructorCall) << data.check;
0309 
0310         codeStr = QStringLiteral("var obj = %1;obj.typeName = 'myCustomType'; obj;");
0311         QTest::newRow((data.tag + "-property assignment").constData())
0312             << codeStr.arg(data.constructorCall) << data.check;
0313     }
0314 }
0315 
0316 void JsParserTest::testCustomTypeName()
0317 {
0318     DataInformation* data = nullptr;
0319     testCommon(&data);
0320     if (QTest::currentTestFailed()) {
0321         return; // Qt doesn't use exceptions, we must manually check after each call
0322 
0323     }
0324     QCOMPARE(data->typeName(), QStringLiteral("myCustomType"));
0325 }
0326 
0327 void JsParserTest::testImport()
0328 {
0329     QScopedPointer<QScriptEngine> eng(ScriptEngineInitializer::newEngine());
0330 #if defined(Q_OS_MACOS) || defined(Q_OS_WINDOWS)
0331     QEXPECT_FAIL("", "QStandardPaths::GenericDataLocation can't be modified on macOS/Windows", Continue);
0332 #endif
0333     QScriptValue val = eng->evaluate(QStringLiteral("s = importScript('simpleImport.js');s.foo()"));
0334     QCOMPARE(val.toString(), QStringLiteral("100"));
0335 }
0336 
0337 void JsParserTest::testImportPathTraversal()
0338 {
0339     QScopedPointer<QScriptEngine> eng(ScriptEngineInitializer::newEngine());
0340     QScriptValue val = eng->evaluate(QStringLiteral("s = importScript('../../pathtraversal.js');s.foo()"));
0341     QVERIFY(val.isError());
0342     QCOMPARE(val.toString(), QStringLiteral("Error: importScript(): You may only access installed structure files! Path traversal detected."));
0343 }
0344 
0345 QTEST_GUILESS_MAIN(JsParserTest)
0346 
0347 #include "jsparsertest.moc"