File indexing completed on 2024-05-12 05:55:45
0001 /* 0002 * This file is part of the Okteta Kasten module, made within the KDE community. 0003 * 0004 * SPDX-FileCopyrightText: 2012, 2013 Alex Richardson <alex.richardson@gmx.de> 0005 * 0006 * SPDX-License-Identifier: LGPL-2.1-or-later 0007 */ 0008 0009 #include "view/structures/datatypes/primitive/primitivetemplateinfo.hpp" 0010 #include "view/structures/datatypes/primitive/primitivedatainformation.hpp" 0011 #include "view/structures/datatypes/primitive/enumdatainformation.hpp" 0012 #include "view/structures/datatypes/primitive/flagdatainformation.hpp" 0013 #include "view/structures/datatypes/primitive/pointerdatainformation.hpp" 0014 #include "view/structures/datatypes/primitive/bitfield/boolbitfielddatainformation.hpp" 0015 #include "view/structures/datatypes/primitive/bitfield/unsignedbitfielddatainformation.hpp" 0016 #include "view/structures/datatypes/primitive/bitfield/signedbitfielddatainformation.hpp" 0017 #include "view/structures/datatypes/primitivefactory.hpp" 0018 #include "view/structures/datatypes/topleveldatainformation.hpp" 0019 #include "view/structures/datatypes/strings/stringdatainformation.hpp" 0020 #include "view/structures/datatypes/array/arraydatainformation.hpp" 0021 #include "view/structures/datatypes/structuredatainformation.hpp" 0022 #include "view/structures/datatypes/uniondatainformation.hpp" 0023 #include "view/structures/script/scripthandler.hpp" 0024 #include "view/structures/script/classes/arrayscriptclass.hpp" 0025 #include "view/structures/script/classes/primitivescriptclass.hpp" 0026 #include "view/structures/script/classes/structunionscriptclass.hpp" 0027 #include "view/structures/script/classes/stringscriptclass.hpp" 0028 #include "view/structures/script/classes/enumscriptclass.hpp" 0029 #include "view/structures/script/classes/bitfieldscriptclass.hpp" 0030 #include "view/structures/script/classes/pointerscriptclass.hpp" 0031 #include "view/structures/script/scriptengineinitializer.hpp" 0032 #include "view/structures/script/classes/defaultscriptclass.hpp" 0033 #include "view/structures/script/safereference.hpp" 0034 #include "view/structures/parsers/scriptvalueconverter.hpp" 0035 #include "testutils.hpp" 0036 // Qt 0037 #include <QTest> 0038 #include <QObject> 0039 #include <QScriptValueIterator> 0040 #include <QScriptEngine> 0041 // Std 0042 #include <memory> 0043 #include <utility> 0044 0045 class ScriptClassesTest : public QObject 0046 { 0047 Q_OBJECT 0048 0049 using PropertyPair = QPair<QString, QScriptValue::PropertyFlags>; 0050 0051 private: 0052 static void checkProperties(const QVector<PropertyPair>& expected, DataInformation* data, 0053 const char* tag); 0054 static PropertyPair pair(const char* name, 0055 QScriptValue::PropertyFlags flags = QScriptValue::Undeletable | QScriptValue::ReadOnly) 0056 { 0057 return PropertyPair(QString::fromUtf8(name), flags); 0058 } 0059 0060 private Q_SLOTS: 0061 void initTestCase(); 0062 // check that all properties are available in the iterator 0063 void checkIterators(); 0064 void testReplaceObject(); // check replacing datatype 0065 void cleanupTestCase(); 0066 void testSafeReferenceDeleteObject(); 0067 void testSafePrimitiveArrayReference(); 0068 void testScriptValueContents_data(); 0069 void testScriptValueContents(); 0070 0071 private: 0072 QVector<PropertyPair> commonProperties; 0073 QVector<PropertyPair> primitiveProperties; 0074 QVector<TopLevelDataInformation*> primitives; 0075 0076 QVector<PropertyPair> enumProperties; 0077 EnumDataInformation* enumData; 0078 std::unique_ptr<TopLevelDataInformation> enumDataTop; 0079 FlagDataInformation* flagData; 0080 std::unique_ptr<TopLevelDataInformation> flagDataTop; 0081 0082 QVector<PropertyPair> bitfieldProperties; 0083 SignedBitfieldDataInformation* signedBitfield; 0084 std::unique_ptr<TopLevelDataInformation> signedBitfieldTop; 0085 UnsignedBitfieldDataInformation* unsignedBitfield; 0086 std::unique_ptr<TopLevelDataInformation> unsignedBitfieldTop; 0087 BoolBitfieldDataInformation* boolBitfield; 0088 std::unique_ptr<TopLevelDataInformation> boolBitfieldTop; 0089 0090 QVector<PropertyPair> structUnionProperties; // without children 0091 StructureDataInformation* structData; 0092 std::unique_ptr<TopLevelDataInformation> structDataTop; 0093 UnionDataInformation* unionData; 0094 std::unique_ptr<TopLevelDataInformation> unionDataTop; 0095 0096 QVector<PropertyPair> arrayProperties; // without children 0097 ArrayDataInformation* arrayData; 0098 std::unique_ptr<TopLevelDataInformation> arrayDataTop; 0099 0100 QVector<PropertyPair> stringProperties; // without children 0101 StringDataInformation* stringData; 0102 std::unique_ptr<TopLevelDataInformation> stringDataTop; 0103 0104 }; 0105 0106 void ScriptClassesTest::initTestCase() 0107 { 0108 // we are only testing properties when updating 0109 // TODO fix this 0110 commonProperties << pair("name", QScriptValue::Undeletable) 0111 << pair("wasAbleToRead") << pair("parent") 0112 << pair("valid") 0113 << pair("validationError") 0114 << pair("byteOrder", QScriptValue::Undeletable) 0115 << pair("updateFunc", QScriptValue::Undeletable) 0116 << pair("validationFunc", QScriptValue::Undeletable) 0117 << pair("datatype", QScriptValue::Undeletable) 0118 << pair("typeName", QScriptValue::Undeletable) 0119 << pair("toStringFunc", QScriptValue::Undeletable); 0120 0121 primitiveProperties << commonProperties << pair("value") << pair("char") << pair("int") 0122 << pair("int8") << pair("int16") << pair("int32") << pair("int64") << pair("uint") 0123 << pair("uint8") << pair("uint16") << pair("uint32") << pair("uint64") << pair("bool") 0124 << pair("float") << pair("double") << pair("int64high32") << pair("int64low32") 0125 << pair("uint64high32") << pair("uint64low32") << pair("type"); 0126 std::sort(primitiveProperties.begin(), primitiveProperties.end()); 0127 LoggerWithContext lwc(nullptr, QString()); 0128 PrimitiveDataType type = PrimitiveDataType::START; 0129 while (type < PrimitiveDataType::Bitfield) { 0130 PrimitiveDataInformation* prim = PrimitiveFactory::newInstance(QStringLiteral("prim"), type, lwc); 0131 prim->setValue(10); 0132 primitives << new TopLevelDataInformation(prim); 0133 type = static_cast<PrimitiveDataType>(static_cast<int>(type) + 1); 0134 } 0135 0136 enumProperties << primitiveProperties << pair("enumValues", QScriptValue::Undeletable); 0137 // TODO valueString property (i.e. the current value as enumerator name) 0138 // XXX enumName 0139 std::sort(enumProperties.begin(), enumProperties.end()); 0140 0141 QMap<AllPrimitiveTypes, QString> enumValues; 0142 enumValues.insert(1, QStringLiteral("one")); 0143 enumValues.insert(2, QStringLiteral("tow")); 0144 enumValues.insert(4, QStringLiteral("four")); 0145 EnumDefinition::Ptr enumDef(new EnumDefinition(enumValues, 0146 QStringLiteral("theEnum"), PrimitiveDataType::Int32)); 0147 enumData = new EnumDataInformation(QStringLiteral("enumData"), 0148 PrimitiveFactory::newInstance(QStringLiteral("dummy"), PrimitiveDataType::Int32, lwc), enumDef); 0149 enumDataTop.reset( 0150 new TopLevelDataInformation(enumData, nullptr, ScriptEngineInitializer::newEngine())); 0151 flagData = new FlagDataInformation(QStringLiteral("flagData"), 0152 PrimitiveFactory::newInstance(QStringLiteral("dummy"), PrimitiveDataType::Int32, lwc), enumDef); 0153 flagDataTop.reset( 0154 new TopLevelDataInformation(flagData, nullptr, ScriptEngineInitializer::newEngine())); 0155 0156 bitfieldProperties << primitiveProperties << pair("width", QScriptValue::Undeletable); 0157 std::sort(bitfieldProperties.begin(), bitfieldProperties.end()); 0158 unsignedBitfield = new UnsignedBitfieldDataInformation(QStringLiteral("unsignedBit"), 42); 0159 unsignedBitfieldTop.reset( 0160 new TopLevelDataInformation(unsignedBitfield, nullptr, ScriptEngineInitializer::newEngine())); 0161 signedBitfield = new SignedBitfieldDataInformation(QStringLiteral("signedBit"), 42); 0162 signedBitfieldTop.reset( 0163 new TopLevelDataInformation(signedBitfield, nullptr, ScriptEngineInitializer::newEngine())); 0164 boolBitfield = new BoolBitfieldDataInformation(QStringLiteral("boolBit"), 42); 0165 boolBitfieldTop.reset( 0166 new TopLevelDataInformation(boolBitfield, nullptr, ScriptEngineInitializer::newEngine())); 0167 0168 stringProperties << commonProperties << pair("terminatedBy", QScriptValue::Undeletable) 0169 << pair("byteCount") << pair("maxCharCount", QScriptValue::Undeletable) 0170 << pair("charCount") << pair("encoding", QScriptValue::Undeletable) 0171 << pair("maxByteCount", QScriptValue::Undeletable); 0172 std::sort(stringProperties.begin(), stringProperties.end()); 0173 stringData = new StringDataInformation(QStringLiteral("string"), StringDataInformation::StringType::Latin1); 0174 stringDataTop.reset( 0175 new TopLevelDataInformation(stringData, nullptr, ScriptEngineInitializer::newEngine())); 0176 0177 arrayProperties << commonProperties << pair("length", QScriptValue::Undeletable) 0178 << pair("type", QScriptValue::Undeletable); 0179 std::sort(arrayProperties.begin(), arrayProperties.end()); 0180 arrayData = new ArrayDataInformation(QStringLiteral("array"), 20, 0181 PrimitiveFactory::newInstance(QStringLiteral("inner"), PrimitiveDataType::Int32, lwc)); 0182 arrayDataTop.reset( 0183 new TopLevelDataInformation(arrayData, nullptr, ScriptEngineInitializer::newEngine())); 0184 0185 structUnionProperties << commonProperties << pair("childCount"); 0186 // property children is only writable -> it is not in the iterator 0187 structData = new StructureDataInformation(QStringLiteral("struct")); 0188 structDataTop.reset( 0189 new TopLevelDataInformation(structData, nullptr, ScriptEngineInitializer::newEngine())); 0190 unionData = new UnionDataInformation(QStringLiteral("union")); 0191 unionDataTop.reset( 0192 new TopLevelDataInformation(unionData, nullptr, ScriptEngineInitializer::newEngine())); 0193 std::sort(structUnionProperties.begin(), structUnionProperties.end()); 0194 0195 } 0196 0197 Q_DECLARE_METATYPE(QScriptClass*) 0198 0199 static inline void scriptValueContentsAddRow(const char* tag, DataInformation* data, QScriptClass* cls) 0200 { 0201 QTest::newRow(tag) << data << cls; 0202 } 0203 0204 void ScriptClassesTest::testScriptValueContents_data() 0205 { 0206 QTest::addColumn<DataInformation*>("data"); 0207 QTest::addColumn<QScriptClass*>("scriptClass"); 0208 0209 scriptValueContentsAddRow("struct", structData, 0210 structDataTop->scriptHandler()->handlerInfo()->mStructUnionClass.get()); 0211 scriptValueContentsAddRow("union", unionData, 0212 unionDataTop->scriptHandler()->handlerInfo()->mStructUnionClass.get()); 0213 scriptValueContentsAddRow("array", arrayData, 0214 arrayDataTop->scriptHandler()->handlerInfo()->mArrayClass.get()); 0215 scriptValueContentsAddRow("string", stringData, 0216 stringDataTop->scriptHandler()->handlerInfo()->mStringClass.get()); 0217 } 0218 0219 void ScriptClassesTest::testScriptValueContents() 0220 { 0221 QFETCH(DataInformation*, data); 0222 QFETCH(QScriptClass*, scriptClass); 0223 0224 QScriptValue val = data->toScriptValue(data->topLevelDataInformation()); 0225 QVERIFY(val.isValid()); 0226 QVERIFY(val.isObject()); 0227 QCOMPARE(val.scriptClass(), scriptClass); 0228 QVERIFY(val.data().isVariant()); 0229 QVariant variant = val.data().toVariant(); 0230 QVERIFY(variant.isValid()); 0231 QVERIFY(variant.canConvert<SafeReference>()); 0232 QCOMPARE(variant.value<SafeReference>().data(), data); 0233 QCOMPARE(DefaultScriptClass::toDataInformation(val), data); 0234 } 0235 0236 void ScriptClassesTest::checkProperties(const QVector<PropertyPair>& expected, 0237 DataInformation* data, const char* tag) 0238 { 0239 // check in updating mode 0240 // TODO check also in other modes 0241 data->topLevelDataInformation()->scriptHandler()->handlerInfo()->setMode(ScriptHandlerInfo::Mode::Updating); 0242 QScriptValue value = data->toScriptValue(data->topLevelDataInformation()->scriptEngine(), 0243 data->topLevelDataInformation()->scriptHandler()->handlerInfo()); 0244 0245 QScriptValueIterator it(value); 0246 QList<PropertyPair> foundProperties; 0247 while (it.hasNext()) { 0248 it.next(); 0249 foundProperties.append(qMakePair(it.name(), it.flags())); 0250 } 0251 data->topLevelDataInformation()->scriptHandler()->handlerInfo()->setMode(ScriptHandlerInfo::Mode::None); 0252 std::sort(foundProperties.begin(), foundProperties.end()); 0253 if (foundProperties.size() != expected.size()) { 0254 for (int i = 0; i < qMin(foundProperties.size(), expected.size()); ++i) { 0255 if (foundProperties.at(i) != expected.at(i)) { 0256 qWarning() << tag << ":" << foundProperties.at(i) << ", but expected:" << expected.at(i); 0257 } 0258 QCOMPARE(foundProperties.at(i).first, expected.at(i).first); 0259 QCOMPARE(foundProperties.at(i).second, expected.at(i).second); 0260 } 0261 } 0262 for (int i = 0; i < foundProperties.size(); ++i) { 0263 if (foundProperties.at(i) != expected.at(i)) { 0264 qWarning() << tag << ":" << foundProperties.at(i) << "!=" << expected.at(i); 0265 } 0266 QCOMPARE(foundProperties.at(i).first, expected.at(i).first); 0267 QCOMPARE(foundProperties.at(i).second, expected.at(i).second); 0268 } 0269 0270 QCOMPARE(foundProperties.size(), expected.size()); 0271 } 0272 0273 void ScriptClassesTest::checkIterators() 0274 { 0275 for (auto* top : std::as_const(primitives)) { 0276 checkProperties(primitiveProperties, top->actualDataInformation(), "primitives"); 0277 } 0278 0279 checkProperties(enumProperties, enumData, "enum"); 0280 checkProperties(enumProperties, flagData, "flag"); 0281 0282 checkProperties(bitfieldProperties, boolBitfield, "bool bitfield"); 0283 checkProperties(bitfieldProperties, signedBitfield, "signed bitfield"); 0284 checkProperties(bitfieldProperties, unsignedBitfield, "unsignedBitfield"); 0285 0286 checkProperties(stringProperties, stringData, "string"); 0287 0288 checkProperties(arrayProperties, arrayData, "array"); 0289 0290 checkProperties(structUnionProperties, structData, "struct"); 0291 checkProperties(structUnionProperties, unionData, "union"); 0292 } 0293 0294 void ScriptClassesTest::testReplaceObject() 0295 { 0296 QScriptEngine* eng = ScriptEngineInitializer::newEngine(); 0297 auto* logger = new ScriptLogger(); 0298 logger->setLogToStdOut(true); 0299 QString unionDef = QStringLiteral( 0300 "union({\n" 0301 QT_UNICODE_LITERAL(" innerStruct : struct({ first : uint8(), second : uint16() }),\n") 0302 QT_UNICODE_LITERAL(" innerArray : array(uint8(), 5),\n") 0303 QT_UNICODE_LITERAL(" innerPointer : pointer(uint8(), double())\n") 0304 QT_UNICODE_LITERAL("});\n")); 0305 QScriptValue val = eng->evaluate(unionDef); 0306 QVERIFY(val.isObject()); 0307 DataInformation* main = ScriptValueConverter::convert(val, QStringLiteral("container"), logger, nullptr); 0308 QVERIFY(main); 0309 QCOMPARE(logger->rowCount(), 0); 0310 TopLevelDataInformation top(main, logger, eng); 0311 0312 // first we read the struct, which changes the type of the first child 0313 // access it again after changing to ensure it was set properly 0314 QScriptValue structUpdate = eng->evaluate(QStringLiteral( 0315 "(function() { this.first.datatype = int32(); this.first.name = \"changed\"; })")); 0316 QVERIFY(structUpdate.isFunction()); 0317 StructureDataInformation* structData = main->childAt(0)->asStruct(); 0318 QVERIFY(structData); 0319 structData->setUpdateFunc(structUpdate); 0320 QCOMPARE(structData->name(), QStringLiteral("innerStruct")); 0321 0322 // array changes its own type, this is the critical one 0323 // access it again after changing to ensure it was set properly 0324 QScriptValue arrayUpdate = eng->evaluate(QStringLiteral( 0325 "(function() { this.datatype = float(); this.name = \"changedToFloat\"; })")); 0326 ArrayDataInformation* arrayData = main->childAt(1)->asArray(); 0327 arrayData->setUpdateFunc(arrayUpdate); 0328 0329 QVERIFY(arrayData); 0330 QScriptValue pointerTargetUpdate = eng->evaluate(QStringLiteral( 0331 "(function() { this.datatype = array(int8(), 5); this.parent.name = \"changedToArrayPointer\"; })")); 0332 PointerDataInformation* ptrData = main->childAt(2)->asPointer(); 0333 QVERIFY(ptrData); 0334 ptrData->pointerTarget()->setUpdateFunc(pointerTargetUpdate); 0335 0336 QScriptValue unionUpdate = eng->evaluate(QStringLiteral( 0337 "(function() { this.datatype = ") + unionDef + QStringLiteral(" this.name = \"newContainer\"; })")); 0338 main->setUpdateFunc(unionUpdate); 0339 0340 // now just call update 0341 QCOMPARE(structData->childCount(), 2U); 0342 QCOMPARE((int)structData->childAt(0)->asPrimitive()->type(), (int)PrimitiveDataType::UInt8); 0343 QCOMPARE(structData->childAt(0)->name(), QStringLiteral("first")); 0344 QCOMPARE(structData->childAt(1)->name(), QStringLiteral("second")); 0345 top.scriptHandler()->updateDataInformation(structData); 0346 // now structdata should have different children 0347 QCOMPARE(structData->childCount(), 2U); 0348 QCOMPARE((int)structData->childAt(0)->asPrimitive()->type(), (int)PrimitiveDataType::Int32); // different now 0349 QCOMPARE(structData->childAt(0)->name(), QStringLiteral("changed")); // different now 0350 QCOMPARE(structData->childAt(1)->name(), QStringLiteral("second")); // still the same 0351 0352 QCOMPARE(arrayData->name(), QStringLiteral("innerArray")); 0353 top.scriptHandler()->updateDataInformation(arrayData); 0354 QVERIFY(main->childAt(1)->hasBeenUpdated()); 0355 QVERIFY(main->childAt(1)->isPrimitive()); 0356 QCOMPARE(main->childAt(1)->name(), QStringLiteral("changedToFloat")); 0357 0358 QCOMPARE(ptrData->name(), QStringLiteral("innerPointer")); 0359 QVERIFY(main->childAt(2)->isPointer()); 0360 QVERIFY(main->childAt(2)->asPointer()->pointerTarget()->isPrimitive()); 0361 top.scriptHandler()->updateDataInformation(ptrData->pointerTarget()); 0362 QVERIFY(main->childAt(2)->isPointer()); 0363 QVERIFY(main->childAt(2)->asPointer()->pointerTarget()->isArray()); 0364 QCOMPARE(main->childAt(2)->name(), QStringLiteral("changedToArrayPointer")); 0365 0366 // now reset to state before 0367 QCOMPARE(main->name(), QStringLiteral("container")); 0368 top.scriptHandler()->updateDataInformation(main); 0369 // main is now a dangling pointer 0370 main = top.actualDataInformation(); 0371 QString nnnname = QStringLiteral("newContainer"); 0372 QCOMPARE(main->name(), nnnname); 0373 QVERIFY(main->childAt(0)->isStruct()); 0374 QCOMPARE(main->childAt(0)->name(), QStringLiteral("innerStruct")); 0375 QCOMPARE(main->childAt(1)->name(), QStringLiteral("innerArray")); 0376 QCOMPARE(main->childAt(2)->name(), QStringLiteral("innerPointer")); 0377 } 0378 0379 namespace { 0380 QString invalidObjectError() { return QStringLiteral("ReferenceError: Attempting to access an invalid object"); } 0381 } 0382 0383 void ScriptClassesTest::testSafePrimitiveArrayReference() 0384 { 0385 QVERIFY(arrayData->arrayType()->isPrimitive()); 0386 QVERIFY(arrayData->length() > 2); 0387 arrayDataTop->logger()->setLogToStdOut(true); 0388 QScriptEngine* eng = arrayDataTop->scriptEngine(); 0389 eng->pushContext(); 0390 eng->currentContext()->activationObject().setProperty(QStringLiteral("myArray"), 0391 arrayData->toScriptValue(arrayDataTop.get())); 0392 arrayDataTop->scriptHandler()->handlerInfo()->setMode(ScriptHandlerInfo::Mode::Updating); 0393 QScriptValue v0 = eng->evaluate(QStringLiteral("myArray[0]")); 0394 QCOMPARE(Utils::property(v0, "name").toString(), QString::number(0)); 0395 QVERIFY(DefaultScriptClass::toDataInformation(v0) != nullptr); 0396 // access index 1 -> index 0 should become invalid, since there is only one object available 0397 QScriptValue v1 = eng->evaluate(QStringLiteral("myArray[1]")); 0398 QVERIFY(DefaultScriptClass::toDataInformation(v1) != nullptr); 0399 QVERIFY(DefaultScriptClass::toDataInformation(v0) == nullptr); 0400 QVERIFY(!eng->hasUncaughtException()); 0401 QCOMPARE(Utils::property(v1, "name").toString(), QString::number(1)); 0402 QVERIFY(!eng->hasUncaughtException()); 0403 QCOMPARE(Utils::property(v0, "name").toString(), invalidObjectError()); 0404 QVERIFY(!eng->hasUncaughtException()); 0405 // even after accessing a v0 property (which will fail), v1 properties should remain valid 0406 QCOMPARE(Utils::property(v1, "name").toString(), QString::number(1)); 0407 QVERIFY(!eng->hasUncaughtException()); 0408 arrayDataTop->scriptHandler()->handlerInfo()->setMode(ScriptHandlerInfo::Mode::None); 0409 eng->popContext(); 0410 } 0411 0412 void ScriptClassesTest::testSafeReferenceDeleteObject() 0413 { 0414 std::unique_ptr<TopLevelDataInformation> top(Utils::evalAndParse("struct({bar: uint8()}).set({name: 'foo'});")); 0415 QVERIFY(top->actualDataInformation()->isStruct()); 0416 top->scriptHandler()->handlerInfo()->setMode(ScriptHandlerInfo::Mode::TaggedUnionSelection); 0417 QScriptValue val = top->actualDataInformation()->toScriptValue(top.get()); 0418 QScriptValue name = Utils::property(val, "name"); 0419 QVERIFY(name.isValid()); 0420 QVERIFY(!name.isError()); 0421 QCOMPARE(name.toString(), QStringLiteral("foo")); 0422 top->setActualDataInformation(new DummyDataInformation(nullptr)); 0423 // val should now point to an invalid reference -> accessing name should throw an error 0424 name = Utils::property(val, "name"); 0425 QVERIFY(name.isValid()); 0426 QVERIFY(name.isError()); 0427 QCOMPARE(name.toString(), invalidObjectError()); 0428 } 0429 0430 void ScriptClassesTest::cleanupTestCase() 0431 { 0432 qDeleteAll(primitives); 0433 } 0434 0435 QTEST_GUILESS_MAIN(ScriptClassesTest) 0436 0437 #include "scriptclassestest.moc"