1 /*
2 * This file is part of the Okteta Kasten module, made within the KDE community.
3 *
4 * SPDX-FileCopyrightText: 2011, 2012 Alex Richardson <alex.richardson@gmx.de>
5 *
6 * SPDX-License-Identifier: LGPL-2.1-or-later
7 */
8
9 #include "view/structures/script/scriptengineinitializer.hpp"
10 #include "view/structures/allprimitivetypes.hpp"
11
12 #include <QTest>
13 #include <QString>
14 #include <QDebug>
15 #include <QScriptEngine>
16 #include "view/structures/parsers/scriptvalueconverter.hpp"
17 #include "view/structures/datatypes/datainformation.hpp"
18 #include "view/structures/datatypes/primitive/primitivedatainformation.hpp"
19 #include "view/structures/datatypes/primitive/enumdatainformation.hpp"
20 #include "view/structures/script/scriptlogger.hpp"
21 #include "view/structures/parsers/parserutils.hpp"
22
23 class ScriptValueConverterTest : public QObject
24 {
25 Q_OBJECT
26
27 private Q_SLOTS:
28 void initTestCase();
29 void testPrimitives();
30 void testPrimitives_data();
31 void testParseEnum();
32 void basicConverterTest();
33 void testParseEnum_data();
34
35 private:
36 DataInformation* convert(const QString& code);
37 DataInformation* convert(const QScriptValue& value);
38 QScriptValue evaluate(const char* code);
39 void dumpLoggerOutput();
40 QScopedPointer<QScriptEngine> engine;
41 QScopedPointer<ScriptLogger> logger;
42 };
43
convert(const QString & code)44 DataInformation* ScriptValueConverterTest::convert(const QString& code)
45 {
46 QScriptValue value = engine->evaluate(code);
47 return ScriptValueConverter::convert(value, QStringLiteral("value"), logger.data());
48 }
49
convert(const QScriptValue & value)50 DataInformation* ScriptValueConverterTest::convert(const QScriptValue& value)
51 {
52 return ScriptValueConverter::convert(value, QStringLiteral("value"), logger.data());
53 }
54
evaluate(const char * code)55 QScriptValue ScriptValueConverterTest::evaluate(const char* code)
56 {
57 return engine->evaluate(QString::fromUtf8(code));
58 }
59
initTestCase()60 void ScriptValueConverterTest::initTestCase()
61 {
62 engine.reset(ScriptEngineInitializer::newEngine());
63 logger.reset(new ScriptLogger());
64 }
65
basicConverterTest()66 void ScriptValueConverterTest::basicConverterTest()
67 {
68 logger->clear();
69 // check that passing functions works
70 QScriptValue sVal = evaluate("var foo = { value : uint8(),\n"
71 " str : struct({first : uint8(), second : uint16()}),\n"
72 " obj : array(uint32(), 10) \n}\n foo");
73 QVector<DataInformation*> converted = ScriptValueConverter::convertValues(sVal, logger.data());
74 QCOMPARE(converted.size(), 3);
75 QVERIFY(converted[0]->isPrimitive());
76 QCOMPARE(converted[0]->name(), QStringLiteral("value"));
77 QVERIFY(converted[1]->isStruct());
78 QCOMPARE(converted[1]->name(), QStringLiteral("str"));
79 QCOMPARE(converted[1]->childCount(), 2u);
80 QVERIFY(converted[2]->isArray());
81 QCOMPARE(converted[2]->name(), QStringLiteral("obj"));
82 QCOMPARE(converted[2]->childCount(), 10u);
83
84 // test with an array now
85 sVal = evaluate("var foo = [uint8(), uint16(), uint32()]; foo");
86 qDeleteAll(converted);
87 converted = ScriptValueConverter::convertValues(sVal, logger.data());
88 QCOMPARE(converted.size(), 3);
89 QVERIFY(converted[0]->isPrimitive());
90 QVERIFY(converted[1]->isPrimitive());
91 QVERIFY(converted[2]->isPrimitive());
92 QVERIFY(converted[0]->asPrimitive()->type() == PrimitiveDataType::UInt8);
93 QVERIFY(converted[1]->asPrimitive()->type() == PrimitiveDataType::UInt16);
94 QVERIFY(converted[2]->asPrimitive()->type() == PrimitiveDataType::UInt32);
95 QCOMPARE(converted[0]->name(), QStringLiteral("0"));
96 QCOMPARE(converted[1]->name(), QStringLiteral("1"));
97 QCOMPARE(converted[2]->name(), QStringLiteral("2"));
98
99 // check number is not a valid object
100 sVal = evaluate("1 + 2");
101 QVERIFY(sVal.isNumber());
102 QVERIFY2(!convert(sVal), " numbers should not be valid!");
103 QCOMPARE(logger->rowCount(QModelIndex()), 1);
104
105 // should be exactly 1 error message
106 sVal = evaluate("function foo() { return uint8(); }; foo");
107 QVERIFY(sVal.isFunction());
108 QVERIFY2(!convert(sVal), "functions should not be valid!");
109 QCOMPARE(logger->rowCount(QModelIndex()), 2);
110
111 // should be exactly 2 error messages
112 sVal = evaluate("var x = /.*/; x");
113 QVERIFY(sVal.isRegExp());
114 QVERIFY2(!convert(sVal), " regexp should not be valid!");
115 QCOMPARE(logger->rowCount(QModelIndex()), 3);
116
117 sVal = evaluate("var obj = { x : 1 }; obj.x();");
118 QVERIFY(sVal.isError());
119 QVERIFY2(!convert(sVal), " error objects should not be valid!");
120 QCOMPARE(logger->rowCount(QModelIndex()), 4);
121
122 sVal = evaluate("var x = [1, 2, 3]; x");
123 QVERIFY(sVal.isArray());
124 QVERIFY2(!convert(sVal), " array objects should not be valid!");
125 QCOMPARE(logger->rowCount(QModelIndex()), 5);
126
127 sVal = evaluate("var x = new Date(); x");
128 QVERIFY(sVal.isDate());
129 QVERIFY2(!convert(sVal), " date objects should not be valid!");
130 QCOMPARE(logger->rowCount(QModelIndex()), 6);
131
132 sVal = evaluate("var x = true; x");
133 QVERIFY(sVal.isBool());
134 QVERIFY2(!convert(sVal), " bool objects should not be valid!");
135 QCOMPARE(logger->rowCount(QModelIndex()), 7);
136
137 sVal = evaluate("var x = null; x");
138 QVERIFY(sVal.isNull());
139 QVERIFY2(!convert(sVal), " null should not be valid!");
140 QCOMPARE(logger->rowCount(QModelIndex()), 8);
141
142 sVal = evaluate("var x = undefined; x");
143 QVERIFY(sVal.isUndefined());
144 QVERIFY2(!convert(sVal), " undefined should not be valid!");
145 QCOMPARE(logger->rowCount(QModelIndex()), 9);
146
147 // object with invalid entry
148 sVal = evaluate("var foo = { value : function() { return 1; },\n"
149 " str : struct({first : uint8(), second : uint16()}),\n"
150 " obj : array(uint32(), 10) \n}\n foo");
151 qDeleteAll(converted);
152 converted = ScriptValueConverter::convertValues(sVal, logger.data());
153 QCOMPARE(converted.size(), 2);
154 // first entry is invalid
155 QCOMPARE(logger->rowCount(QModelIndex()), 11);
156 // this should cause 2 error messages -> 11 now
157 // qDebug() << logger->messages();
158 qDeleteAll(converted);
159 }
160
testPrimitives_data()161 void ScriptValueConverterTest::testPrimitives_data()
162 {
163 QTest::addColumn<QString>("code");
164 QTest::addColumn<QString>("code2");
165 QTest::addColumn<QString>("typeString");
166 QTest::addColumn<int>("expectedType");
167
168 QTest::newRow("uint8") << "uint8()" << "new uint8()" << "UInt8" << (int) PrimitiveDataType::UInt8;
169 QTest::newRow("uint16") << "uint16()" << "new uint16()" << "UInt16" << (int) PrimitiveDataType::UInt16;
170 QTest::newRow("uint32") << "uint32()" << "new uint32()" << "UInt32" << (int) PrimitiveDataType::UInt32;
171 QTest::newRow("uint64") << "uint64()" << "new uint64()" << "UInt64" << (int) PrimitiveDataType::UInt64;
172 QTest::newRow("int8") << "int8()" << "new int8()" << "Int8" << (int) PrimitiveDataType::Int8;
173 QTest::newRow("int16") << "int16()" << "new int16()" << "Int16" << (int) PrimitiveDataType::Int16;
174 QTest::newRow("int32") << "int32()" << "new int32()" << "Int32" << (int) PrimitiveDataType::Int32;
175 QTest::newRow("int64") << "int64()" << "new int64()" << "Int64" << (int) PrimitiveDataType::Int64;
176 QTest::newRow("bool8") << "bool8()" << "new bool8()" << "Bool8" << (int) PrimitiveDataType::Bool8;
177 QTest::newRow("bool16") << "bool16()" << "new bool16()" << "Bool16" << (int) PrimitiveDataType::Bool16;
178 QTest::newRow("bool32") << "bool32()" << "new bool32()" << "Bool32" << (int) PrimitiveDataType::Bool32;
179 QTest::newRow("bool64") << "bool64()" << "new bool64()" << "Bool64" << (int) PrimitiveDataType::Bool64;
180 QTest::newRow("char") << "char()" << "new char()" << "Char" << (int) PrimitiveDataType::Char;
181 QTest::newRow("float") << "float()" << "new float()" << "Float" << (int) PrimitiveDataType::Float;
182 QTest::newRow("double") << "double()" << "new double()" << "Double" << (int) PrimitiveDataType::Double;
183 }
184
testPrimitives()185 void ScriptValueConverterTest::testPrimitives()
186 {
187 QFETCH(QString, code);
188 QFETCH(QString, code2);
189 QFETCH(QString, typeString);
190 QFETCH(int, expectedType);
191 logger->clear();
192 auto type = static_cast<PrimitiveDataType>(expectedType);
193
194 QScriptValue val1 = engine->evaluate(code);
195 QScriptValue val2 = engine->evaluate(code2);
196 QCOMPARE(val1.property(ParserStrings::PROPERTY_TYPE()).toString(), typeString);
197 QCOMPARE(val2.property(ParserStrings::PROPERTY_TYPE()).toString(), typeString);
198 QCOMPARE(val1.property(ParserStrings::PROPERTY_INTERNAL_TYPE()).toString(), ParserStrings::TYPE_PRIMITIVE());
199 QCOMPARE(val2.property(ParserStrings::PROPERTY_INTERNAL_TYPE()).toString(), ParserStrings::TYPE_PRIMITIVE());
200
201 if (type == PrimitiveDataType::Invalid) {
202 return; // the cast will fail
203 }
204 QScopedPointer<DataInformation> data1(ScriptValueConverter::convert(val1, QStringLiteral("val1"),
205 logger.data()));
206 QScopedPointer<DataInformation> data2(ScriptValueConverter::convert(val2, QStringLiteral("val2"),
207 logger.data()));
208 QVERIFY(data1);
209 QVERIFY(data2);
210 PrimitiveDataInformation* p1 = data1->asPrimitive();
211 PrimitiveDataInformation* p2 = data2->asPrimitive();
212 QVERIFY(p1);
213 QVERIFY(p2);
214 QCOMPARE(p1->type(), type);
215 QCOMPARE(p2->type(), type);
216 if (type == PrimitiveDataType::Bitfield) {
217 return; // the following tests don't work with bitfields
218 }
219 QScopedPointer<DataInformation> data3(convert(QStringLiteral("\"%1\"").arg(typeString)));
220 QVERIFY(data3);
221 PrimitiveDataInformation* p3 = data3->asPrimitive();
222 QVERIFY(p3);
223 QCOMPARE(p3->type(), type);
224 }
225
testParseEnum()226 void ScriptValueConverterTest::testParseEnum()
227 {
228 // QFETCH(QString, name);
229 QFETCH(QString, code);
230 QFETCH(int, expectedCount);
231
232 QScriptValue val = engine->evaluate(code);
233
234 QVERIFY(val.isValid());
235 QVERIFY(!val.isNull());
236 QVERIFY(!val.isUndefined());
237 QVERIFY(val.isObject());
238 QCOMPARE(val.property(ParserStrings::PROPERTY_INTERNAL_TYPE()).toString(), QStringLiteral("enum"));
239
240 QScopedPointer<DataInformation> data(ScriptValueConverter::convert(val, QStringLiteral("val"), logger.data()));
241 if (expectedCount > 0) {
242 QVERIFY(data);
243 } else {
244 QVERIFY(!data);
245 return;
246 }
247 EnumDataInformation* e = data->asEnum();
248 QVERIFY(e);
249
250 QMap<AllPrimitiveTypes, QString> enumVals = e->enumValues()->values();
251 QCOMPARE(enumVals.size(), expectedCount);
252
253 if (expectedCount != 0) {
254 QFETCH(quint64, expectedValue);
255 // to ensure it does not match when value is not found add 1 to the default
256 AllPrimitiveTypes result = enumVals.key(QStringLiteral("value"), expectedValue + 1);
257 QCOMPARE(result.value<quint64>(), expectedValue);
258 }
259 }
260
261 namespace {
arg2(const QString & str,const char * arg_1,const char * arg_2)262 inline QString arg2(const QString& str, const char* arg_1, const char* arg_2)
263 {
264 return str.arg(QString::fromUtf8(arg_1), QString::fromUtf8(arg_2));
265 }
266 }
267
testParseEnum_data()268 void ScriptValueConverterTest::testParseEnum_data()
269 {
270 QString baseStr = QStringLiteral("enumeration(\"someValues\", %1, { value : %2})");
271 QTest::addColumn<QString>("code");
272 QTest::addColumn<int>("expectedCount");
273 QTest::addColumn<quint64>("expectedValue");
274
275 QTest::newRow("invalid_type_struct") << arg2(baseStr, "struct({ val : uint8() })", "1234.234")
276 << 0 << quint64(0);
277 QTest::newRow("invalid_type_array") << arg2(baseStr, "array(uint8(), 1)", "1234.234") << 0
278 << quint64(0);
279 QTest::newRow("invalid_type_union") << arg2(baseStr, "union({ val : uint8() })", "1234.234")
280 << 0 << quint64(0);
281 QTest::newRow("invalid_type_double") << arg2(baseStr, "double()", "1234.234") << 0 << quint64(0);
282 QTest::newRow("invalid_type_float") << arg2(baseStr, "float()", "1234.234") << 0 << quint64(0);
283 QTest::newRow("invalid_type_string") << arg2(baseStr, "string()", "1234.234") << 0 << quint64(0);
284
285 QTest::newRow("float2int8") << arg2(baseStr, "uint8()", "1.234") << 1 << quint64(1);
286 QTest::newRow("float2int8_range") << arg2(baseStr, "uint8()", "1234.234") << 0 << quint64(0);
287 QTest::newRow("float2int32") << arg2(baseStr, "uint32()", "1234.1234") << 1 << quint64(1234);
288 QTest::newRow("float2int32_range") << arg2(baseStr, "uint32()", "5294967296.234") << 0 << quint64(0);
289 QTest::newRow("float2int64") << arg2(baseStr, "uint64()", "5294967296.234") << 1
290 << quint64(5294967296UL);
291 QTest::newRow("double_overflow") << arg2(baseStr, "uint64()", "9007199254740993.0") << 0
292 << quint64(9007199254740993UL); // only 992 and 994 can be represented as a double
293 QTest::newRow("uint64_max_hex") << arg2(baseStr, "uint64()", "new String(\"0xFFFFFFFFFFFFFFFF\")") << 1
294 << quint64(0xFFFFFFFFFFFFFFFFL);
295 QTest::newRow("uint64_max") << arg2(baseStr, "uint64()", "new String(\"18446744073709551615\")") << 1
296 << quint64(18446744073709551615UL);
297 }
298
299 QTEST_GUILESS_MAIN(ScriptValueConverterTest)
300
301 #include "scriptvalueconvertertest.moc"
302