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