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"