From d2e331dfa02475e773e51af6e7532951f4d1a233 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 10 Dec 2020 18:07:26 +0100 Subject: Allow JavaScript primitive type transformations inline in C++ We don't want to call into the engine just for adding two numbers. This implements the most common operators on primitive JavaScript values. More are to follow in the future. Change-Id: Id51a5af59a3af9fec78a2d8f293e59e6567e9204 Reviewed-by: Fabian Kosmale --- tests/auto/qml/.prev_CMakeLists.txt | 1 + tests/auto/qml/CMakeLists.txt | 1 + tests/auto/qml/qjsprimitivevalue/CMakeLists.txt | 15 ++ .../qml/qjsprimitivevalue/qjsprimitivevalue.pro | 5 + .../qjsprimitivevalue/tst_qjsprimitivevalue.cpp | 226 +++++++++++++++++++++ tests/auto/qml/qml.pro | 1 + 6 files changed, 249 insertions(+) create mode 100644 tests/auto/qml/qjsprimitivevalue/CMakeLists.txt create mode 100644 tests/auto/qml/qjsprimitivevalue/qjsprimitivevalue.pro create mode 100644 tests/auto/qml/qjsprimitivevalue/tst_qjsprimitivevalue.cpp (limited to 'tests/auto/qml') diff --git a/tests/auto/qml/.prev_CMakeLists.txt b/tests/auto/qml/.prev_CMakeLists.txt index 9e9d8299e2..40fd5a3795 100644 --- a/tests/auto/qml/.prev_CMakeLists.txt +++ b/tests/auto/qml/.prev_CMakeLists.txt @@ -6,6 +6,7 @@ qt_exclude_tool_directories_from_default_target( ) add_subdirectory(parserstress) +add_subdirectory(qjsprimitivevalue) add_subdirectory(qjsvalueiterator) add_subdirectory(qjsonbinding) add_subdirectory(qqmlfile) diff --git a/tests/auto/qml/CMakeLists.txt b/tests/auto/qml/CMakeLists.txt index 3021a3201e..ed3706d760 100644 --- a/tests/auto/qml/CMakeLists.txt +++ b/tests/auto/qml/CMakeLists.txt @@ -6,6 +6,7 @@ qt_exclude_tool_directories_from_default_target( ) add_subdirectory(parserstress) +add_subdirectory(qjsprimitivevalue) add_subdirectory(qjsvalueiterator) add_subdirectory(qjsonbinding) add_subdirectory(qqmlfile) diff --git a/tests/auto/qml/qjsprimitivevalue/CMakeLists.txt b/tests/auto/qml/qjsprimitivevalue/CMakeLists.txt new file mode 100644 index 0000000000..b2a1b8050c --- /dev/null +++ b/tests/auto/qml/qjsprimitivevalue/CMakeLists.txt @@ -0,0 +1,15 @@ +# Generated from qjsprimitivevalue.pro. + +##################################################################### +## tst_qjsprimitivevalue Test: +##################################################################### + +qt_internal_add_test(tst_qjsprimitivevalue + SOURCES + tst_qjsprimitivevalue.cpp + PUBLIC_LIBRARIES + Qt::Qml +) + +## Scopes: +##################################################################### diff --git a/tests/auto/qml/qjsprimitivevalue/qjsprimitivevalue.pro b/tests/auto/qml/qjsprimitivevalue/qjsprimitivevalue.pro new file mode 100644 index 0000000000..c62bbe7b07 --- /dev/null +++ b/tests/auto/qml/qjsprimitivevalue/qjsprimitivevalue.pro @@ -0,0 +1,5 @@ +CONFIG += testcase +TARGET = tst_qjsprimitivevalue +macos:CONFIG -= app_bundle +QT += qml +SOURCES += tst_qjsprimitivevalue.cpp diff --git a/tests/auto/qml/qjsprimitivevalue/tst_qjsprimitivevalue.cpp b/tests/auto/qml/qjsprimitivevalue/tst_qjsprimitivevalue.cpp new file mode 100644 index 0000000000..b957e7a18b --- /dev/null +++ b/tests/auto/qml/qjsprimitivevalue/tst_qjsprimitivevalue.cpp @@ -0,0 +1,226 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +#include + +class tst_QJSPrimitiveValue : public QObject +{ + Q_OBJECT + +private slots: + void operators_data(); + void operators(); + +private: + QJSEngine engine; + + const QList operands = { + QJSPrimitiveNull(), QJSPrimitiveUndefined(), true, false, + std::numeric_limits::min(), -10, -1, 0, 1, 10, std::numeric_limits::max(), + -std::numeric_limits::infinity(), -100.1, -1.2, -0.0, 0.0, 1.2, 100.1, + std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN() + }; +}; + +template +QString toScriptString(T value) +{ + if constexpr (std::is_same_v) { + return u'"' + value + u'"'; + } else { + if (value.type() == QJSPrimitiveValue::Double) { + // -0 is rendered as "0" in QJSValue's "toString()". + // However, we need the sign when constructing an expression to be evaluated. + const double result = value.toDouble(); + if (qIsNull(result)) { + if (std::signbit(result)) + return QStringLiteral("-0.0"); + else + return QStringLiteral("0.0"); + } + } + return value.toString(); + } +} + +void tst_QJSPrimitiveValue::operators_data() +{ + QTest::addColumn("lhs"); + QTest::addColumn("rhs"); + + for (QJSPrimitiveValue l : operands) { + for (QJSPrimitiveValue r : operands) + QTest::newRow(qPrintable(toScriptString(l) + " x " + toScriptString(r))) << l << r ; + } +} + +#define VERBOSECOMPARE(actual, expected) \ +do {\ + if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) { \ + qDebug() << "In" << Q_FUNC_INFO; \ + return;\ + } \ +} while (false) + +#define VERBOSEVERIFY(condition, expression, js) \ + QVERIFY2(condition, qPrintable(expression + " -> " + js.toString())) + +enum Operator { + Add, Sub, Mul, Div, Eq, SEq, NEq, SNEq, GT, LT, GEq, LEq +}; + +QString toString(Operator op) { + switch (op) { + case Add: return "+"; + case Sub: return "-"; + case Mul: return "*"; + case Div: return "/"; + case Eq: return "=="; + case SEq: return "==="; + case NEq: return "!="; + case SNEq: return "!=="; + case GT: return ">"; + case LT: return "<"; + case GEq: return ">="; + case LEq: return "<="; + } + + Q_UNREACHABLE(); + return QString(); +} + +template +void doTestOperator(QJSEngine *engine, LHS lhs, RHS rhs) +{ + Result result; + + if constexpr (op == Add) + result = lhs + rhs; + else if constexpr (op == Sub) + result = lhs - rhs; + else if constexpr (op == Mul) + result = lhs * rhs; + else if constexpr (op == Div) + result = lhs / rhs; + else if constexpr (op == Eq) + result = QJSPrimitiveValue(lhs).equals(rhs); + else if constexpr (op == SEq) + result = lhs == rhs; + else if constexpr (op == NEq) + result = !QJSPrimitiveValue(lhs).equals(rhs); + else if constexpr (op == SNEq) + result = lhs != rhs; + else if constexpr (op == GT) + result = lhs > rhs; + else if constexpr (op == LT) + result = lhs < rhs; + else if constexpr (op == GEq) + result = lhs >= rhs; + else if constexpr (op == LEq) + result = lhs <= rhs; + else + QFAIL("Unkonwn operator"); + + const QString expression = toScriptString(lhs) + " " + toString(op) + " " + toScriptString(rhs); + const QJSValue js = engine->evaluate(expression); + + if constexpr (std::is_same_v) { + VERBOSEVERIFY(js.isBool(), expression, js); + VERBOSECOMPARE(js.toBool(), result); + } else if constexpr (std::is_same_v) { + VERBOSEVERIFY(js.isString(), expression, js); + VERBOSECOMPARE(result, js.toString()); + } else { + switch (result.type()) { + case QJSPrimitiveValue::Undefined: + VERBOSEVERIFY(js.isUndefined(), expression, js); + break; + case QJSPrimitiveValue::Null: + VERBOSEVERIFY(js.isNull(), expression, js); + break; + case QJSPrimitiveValue::Boolean: + VERBOSEVERIFY(js.isBool(), expression, js); + break; + case QJSPrimitiveValue::Integer: + VERBOSEVERIFY(js.isNumber(), expression, js); + break; + case QJSPrimitiveValue::Double: + VERBOSEVERIFY(js.isNumber(), expression, js); + break; + case QJSPrimitiveValue::String: + VERBOSEVERIFY(js.isString(), expression, js); + break; + default: + QFAIL("unexpected type"); + } + + VERBOSECOMPARE(result.toBoolean(), js.toBool()); + VERBOSECOMPARE(result.toInteger(), js.toInt()); + VERBOSECOMPARE(result.toDouble(), js.toNumber()); + VERBOSECOMPARE(result.toString(), js.toString()); + } +} + +template +void doTestForAllOperators(QJSEngine *engine, LHS lhs, RHS rhs) +{ + doTestOperator(engine, lhs, rhs); + doTestOperator(engine, lhs, rhs); + doTestOperator(engine, lhs, rhs); + doTestOperator(engine, lhs, rhs); + doTestOperator(engine, lhs, rhs); + doTestOperator(engine, lhs, rhs); + doTestOperator(engine, lhs, rhs); + doTestOperator(engine, lhs, rhs); + doTestOperator(engine, lhs, rhs); + doTestOperator(engine, lhs, rhs); + doTestOperator(engine, lhs, rhs); + doTestOperator(engine, lhs, rhs); +} + +void tst_QJSPrimitiveValue::operators() +{ + QFETCH(QJSPrimitiveValue, lhs); + QFETCH(QJSPrimitiveValue, rhs); + + doTestForAllOperators(&engine, lhs, rhs); + doTestForAllOperators(&engine, lhs.toString(), rhs); + doTestForAllOperators(&engine, lhs, rhs.toString()); + doTestForAllOperators(&engine, lhs.toString() + " bar", rhs); + doTestForAllOperators(&engine, lhs, rhs.toString() + " bar"); + doTestForAllOperators(&engine, "foo" + lhs.toString(), rhs); + doTestForAllOperators(&engine, lhs, "foo" + rhs.toString()); +} + +QTEST_MAIN(tst_QJSPrimitiveValue) + +#include "tst_qjsprimitivevalue.moc" diff --git a/tests/auto/qml/qml.pro b/tests/auto/qml/qml.pro index 5409361142..abbc786ee9 100644 --- a/tests/auto/qml/qml.pro +++ b/tests/auto/qml/qml.pro @@ -6,6 +6,7 @@ METATYPETESTS += \ PUBLICTESTS += \ parserstress \ + qjsprimitivevalue \ qjsvalueiterator \ qjsonbinding \ qqmlfile \ -- cgit v1.2.3