File indexing completed on 2024-05-12 05:55:44

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