diff --git a/tests/data.qrc b/tests/data.qrc index b271332..8f2b8eb 100644 --- a/tests/data.qrc +++ b/tests/data.qrc @@ -11,5 +11,8 @@ data/LGPLv3.txt data/example-license.json data/dconf-example.override.noexistitem.json + data/dconfig2cpp/basic-types.meta.json + data/dconfig2cpp/numeric-types.meta.json + data/dconfig2cpp/complex-types.meta.json diff --git a/tests/data/dconfig2cpp/basic-types.meta.json b/tests/data/dconfig2cpp/basic-types.meta.json new file mode 100644 index 0000000..9c46c44 --- /dev/null +++ b/tests/data/dconfig2cpp/basic-types.meta.json @@ -0,0 +1,86 @@ +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "booleanTrue": { + "value": true, + "serial": 0, + "flags": ["global"], + "name": "Boolean true value", + "permissions": "readwrite", + "visibility": "public" + }, + "booleanFalse": { + "value": false, + "serial": 0, + "flags": ["global"], + "name": "Boolean false value", + "permissions": "readwrite", + "visibility": "public" + }, + "stringValue": { + "value": "hello world", + "serial": 0, + "flags": ["global"], + "name": "String value", + "permissions": "readwrite", + "visibility": "public" + }, + "emptyString": { + "value": "", + "serial": 0, + "flags": ["global"], + "name": "Empty string value", + "permissions": "readwrite", + "visibility": "public" + }, + "integerPositive": { + "value": 42, + "serial": 0, + "flags": ["global"], + "name": "Positive integer", + "permissions": "readwrite", + "visibility": "public" + }, + "integerNegative": { + "value": -10, + "serial": 0, + "flags": ["global"], + "name": "Negative integer", + "permissions": "readwrite", + "visibility": "public" + }, + "integerZero": { + "value": 0, + "serial": 0, + "flags": ["global"], + "name": "Zero integer", + "permissions": "readwrite", + "visibility": "public" + }, + "doubleValue": { + "value": 3.14, + "serial": 0, + "flags": ["global"], + "name": "Double value with decimal", + "permissions": "readwrite", + "visibility": "public" + }, + "doubleZero": { + "value": 0.0, + "serial": 0, + "flags": ["global"], + "name": "Double zero with decimal point", + "permissions": "readwrite", + "visibility": "public" + }, + "scientificValue": { + "value": 1.5e3, + "serial": 0, + "flags": ["global"], + "name": "Scientific notation value", + "permissions": "readwrite", + "visibility": "public" + } + } +} \ No newline at end of file diff --git a/tests/data/dconfig2cpp/complex-types.meta.json b/tests/data/dconfig2cpp/complex-types.meta.json new file mode 100644 index 0000000..85e8d38 --- /dev/null +++ b/tests/data/dconfig2cpp/complex-types.meta.json @@ -0,0 +1,46 @@ +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "emptyArray": { + "value": [], + "serial": 0, + "flags": ["global"], + "name": "Empty array", + "permissions": "readwrite", + "visibility": "public" + }, + "stringArray": { + "value": ["item1", "item2", "item3"], + "serial": 0, + "flags": ["global"], + "name": "String array", + "permissions": "readwrite", + "visibility": "public" + }, + "mixedArray": { + "value": ["string", 42, true, 3.14], + "serial": 0, + "flags": ["global"], + "name": "Mixed type array", + "permissions": "readwrite", + "visibility": "public" + }, + "emptyObject": { + "value": {}, + "serial": 0, + "flags": ["global"], + "name": "Empty object", + "permissions": "readwrite", + "visibility": "public" + }, + "simpleObject": { + "value": {"key1": "value1", "key2": "value2"}, + "serial": 0, + "flags": ["global"], + "name": "Simple object", + "permissions": "readwrite", + "visibility": "public" + } + } +} \ No newline at end of file diff --git a/tests/data/dconfig2cpp/numeric-types.meta.json b/tests/data/dconfig2cpp/numeric-types.meta.json new file mode 100644 index 0000000..2ec7ce8 --- /dev/null +++ b/tests/data/dconfig2cpp/numeric-types.meta.json @@ -0,0 +1,46 @@ +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "pureInteger": { + "value": 123, + "serial": 0, + "flags": ["global"], + "name": "Pure integer - should be int/qint64", + "permissions": "readwrite", + "visibility": "public" + }, + "doubleWithDecimal": { + "value": 123.45, + "serial": 0, + "flags": ["global"], + "name": "Double with decimal - should be double", + "permissions": "readwrite", + "visibility": "public" + }, + "doubleWithZeroDecimal": { + "value": 123.0, + "serial": 0, + "flags": ["global"], + "name": "Double with .0 - should be double", + "permissions": "readwrite", + "visibility": "public" + }, + "scientificNotation": { + "value": 1.0e0, + "serial": 0, + "flags": ["global"], + "name": "Scientific notation - should be double", + "permissions": "readwrite", + "visibility": "public" + }, + "largeInteger": { + "value": 9223372036854775807, + "serial": 0, + "flags": ["global"], + "name": "Large integer - should be qint64", + "permissions": "readwrite", + "visibility": "public" + } + } +} \ No newline at end of file diff --git a/tests/ut_dconfig2cpp.cpp b/tests/ut_dconfig2cpp.cpp new file mode 100644 index 0000000..05cbd7f --- /dev/null +++ b/tests/ut_dconfig2cpp.cpp @@ -0,0 +1,250 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class ut_dconfig2cpp : public testing::Test +{ +protected: + void SetUp() override { + tempDir = new QTemporaryDir(); + ASSERT_TRUE(tempDir->isValid()); + } + + void TearDown() override { + delete tempDir; + tempDir = nullptr; + } + + // Get dconfig2cpp tool path + QString getToolPath() const { + QStringList possiblePaths = { + "../tools/dconfig2cpp/dconfig2cpp", + "../../tools/dconfig2cpp/dconfig2cpp", + "./tools/dconfig2cpp/dconfig2cpp", + "tools/dconfig2cpp/dconfig2cpp" + }; + + for (const QString &path : possiblePaths) { + if (QFile::exists(path)) { + return path; + } + } + return "dconfig2cpp"; // Assume it's in PATH + } + + // Call dconfig2cpp to generate code + struct GenerationResult { + bool success; + QString generatedFilePath; + QString errorMessage; + int exitCode; + }; + + GenerationResult generateCode(const QString& jsonFilePath) { + GenerationResult result; + result.success = false; + result.exitCode = -1; + + // If it's a QRC path, copy to temporary directory first + QString actualJsonPath = jsonFilePath; + if (jsonFilePath.startsWith(":/")) { + QFile sourceFile(jsonFilePath); + if (!sourceFile.open(QIODevice::ReadOnly)) { + result.errorMessage = QString("Failed to open resource file: %1").arg(jsonFilePath); + return result; + } + + QFileInfo fileInfo(jsonFilePath); + QString tempJsonPath = tempDir->path() + "/" + fileInfo.fileName(); + + QFile tempFile(tempJsonPath); + if (!tempFile.open(QIODevice::WriteOnly)) { + result.errorMessage = QString("Failed to create temp file: %1").arg(tempJsonPath); + return result; + } + + tempFile.write(sourceFile.readAll()); + tempFile.close(); + sourceFile.close(); + + actualJsonPath = tempJsonPath; + } + + if (!QFile::exists(actualJsonPath)) { + result.errorMessage = QString("Input JSON file does not exist: %1").arg(actualJsonPath); + return result; + } + + QString toolPath = getToolPath(); + QProcess process; + + QFileInfo fileInfo(actualJsonPath); + QString baseName = fileInfo.completeBaseName().replace('.', '_').replace('-', '_'); + result.generatedFilePath = tempDir->path() + "/" + baseName + ".hpp"; + + QStringList arguments; + arguments << "-o" << result.generatedFilePath << actualJsonPath; + + process.start(toolPath, arguments); + + if (!process.waitForStarted(5000)) { + result.errorMessage = QString("Failed to start dconfig2cpp tool: %1").arg(process.errorString()); + return result; + } + + if (!process.waitForFinished(10000)) { + result.errorMessage = "dconfig2cpp tool execution timeout"; + process.kill(); + return result; + } + + result.exitCode = process.exitCode(); + + if (result.exitCode == 0) { + result.success = true; + if (!QFile::exists(result.generatedFilePath)) { + result.success = false; + result.errorMessage = QString("Generated file not found: %1").arg(result.generatedFilePath); + } + } else { + result.errorMessage = QString("dconfig2cpp failed with exit code %1. Error: %2") + .arg(result.exitCode) + .arg(QString::fromUtf8(process.readAllStandardError())); + } + + return result; + } + + QTemporaryDir *tempDir; +}; + +TEST_F(ut_dconfig2cpp, BasicTypesGeneration) { + QString testFile = ":/data/dconfig2cpp/basic-types.meta.json"; + + // If resource file doesn't exist, use filesystem path + if (!QFile::exists(testFile)) { + testFile = "./data/dconfig2cpp/basic-types.meta.json"; + } + + ASSERT_TRUE(QFile::exists(testFile)) << "Test data file not found: " << testFile.toStdString(); + + auto result = generateCode(testFile); + ASSERT_TRUE(result.success) << result.errorMessage.toStdString(); + ASSERT_TRUE(QFile::exists(result.generatedFilePath)); +} + +TEST_F(ut_dconfig2cpp, BasicTypesPropertyValidation) { + QString testFile = ":/data/dconfig2cpp/basic-types.meta.json"; + if (!QFile::exists(testFile)) { + testFile = "./data/dconfig2cpp/basic-types.meta.json"; + } + + ASSERT_TRUE(QFile::exists(testFile)); + + auto result = generateCode(testFile); + ASSERT_TRUE(result.success) << result.errorMessage.toStdString(); + + // Read generated header file content + QFile generatedFile(result.generatedFilePath); + ASSERT_TRUE(generatedFile.open(QIODevice::ReadOnly | QIODevice::Text)); + QString generatedContent = generatedFile.readAll(); + generatedFile.close(); + + // Expected Q_PROPERTY declarations + QStringList expectedProperties = { + "Q_PROPERTY(bool booleanTrue READ booleanTrue WRITE setBooleanTrue", + "Q_PROPERTY(bool booleanFalse READ booleanFalse WRITE setBooleanFalse", + "Q_PROPERTY(QString stringValue READ stringValue WRITE setStringValue", + "Q_PROPERTY(QString emptyString READ emptyString WRITE setEmptyString", + "Q_PROPERTY(qlonglong integerPositive READ integerPositive WRITE setIntegerPositive", + "Q_PROPERTY(qlonglong integerNegative READ integerNegative WRITE setIntegerNegative", + "Q_PROPERTY(qlonglong integerZero READ integerZero WRITE setIntegerZero", + "Q_PROPERTY(double doubleValue READ doubleValue WRITE setDoubleValue", + "Q_PROPERTY(double doubleZero READ doubleZero WRITE setDoubleZero", + "Q_PROPERTY(double scientificValue READ scientificValue WRITE setScientificValue" + }; + + // Verify each expected Q_PROPERTY exists + for (const QString &expectedProperty : expectedProperties) { + EXPECT_TRUE(generatedContent.contains(expectedProperty)) + << "Expected property not found: " << expectedProperty.toStdString(); + } +} + +TEST_F(ut_dconfig2cpp, NumericTypesDetection) { + QString testFile = ":/data/dconfig2cpp/numeric-types.meta.json"; + if (!QFile::exists(testFile)) { + testFile = "./data/dconfig2cpp/numeric-types.meta.json"; + } + + ASSERT_TRUE(QFile::exists(testFile)); + + auto result = generateCode(testFile); + ASSERT_TRUE(result.success) << result.errorMessage.toStdString(); + + // Read generated header file content + QFile generatedFile(result.generatedFilePath); + ASSERT_TRUE(generatedFile.open(QIODevice::ReadOnly | QIODevice::Text)); + QString generatedContent = generatedFile.readAll(); + generatedFile.close(); + + // Expected Q_PROPERTY declarations - based on numeric type detection rules + QStringList expectedProperties = { + "Q_PROPERTY(qlonglong pureInteger READ pureInteger WRITE setPureInteger", // Pure integer -> qlonglong + "Q_PROPERTY(double doubleWithDecimal READ doubleWithDecimal WRITE setDoubleWithDecimal", // Decimal -> double + "Q_PROPERTY(double doubleWithZeroDecimal READ doubleWithZeroDecimal WRITE setDoubleWithZeroDecimal", // .0 -> double + "Q_PROPERTY(double scientificNotation READ scientificNotation WRITE setScientificNotation", // Scientific notation -> double + "Q_PROPERTY(qlonglong largeInteger READ largeInteger WRITE setLargeInteger" // Large integer -> qlonglong + }; + + // Verify each expected Q_PROPERTY exists + for (const QString &expectedProperty : expectedProperties) { + EXPECT_TRUE(generatedContent.contains(expectedProperty)) + << "Expected property not found: " << expectedProperty.toStdString(); + } +} + +TEST_F(ut_dconfig2cpp, ComplexTypesGeneration) { + QString testFile = ":/data/dconfig2cpp/complex-types.meta.json"; + if (!QFile::exists(testFile)) { + testFile = "./data/dconfig2cpp/complex-types.meta.json"; + } + + ASSERT_TRUE(QFile::exists(testFile)); + + auto result = generateCode(testFile); + ASSERT_TRUE(result.success) << result.errorMessage.toStdString(); + + // Read generated header file content + QFile generatedFile(result.generatedFilePath); + ASSERT_TRUE(generatedFile.open(QIODevice::ReadOnly | QIODevice::Text)); + QString generatedContent = generatedFile.readAll(); + generatedFile.close(); + + // Expected Q_PROPERTY declarations - complex types + QStringList expectedProperties = { + "Q_PROPERTY(QList emptyArray READ emptyArray WRITE setEmptyArray", + "Q_PROPERTY(QList stringArray READ stringArray WRITE setStringArray", + "Q_PROPERTY(QList mixedArray READ mixedArray WRITE setMixedArray", + "Q_PROPERTY(QVariantMap emptyObject READ emptyObject WRITE setEmptyObject", + "Q_PROPERTY(QVariantMap simpleObject READ simpleObject WRITE setSimpleObject" + }; + + // Verify each expected Q_PROPERTY exists + for (const QString &expectedProperty : expectedProperties) { + EXPECT_TRUE(generatedContent.contains(expectedProperty)) + << "Expected property not found: " << expectedProperty.toStdString(); + } +} diff --git a/tools/dconfig2cpp/main.cpp b/tools/dconfig2cpp/main.cpp index a284976..85d92a6 100644 --- a/tools/dconfig2cpp/main.cpp +++ b/tools/dconfig2cpp/main.cpp @@ -20,6 +20,24 @@ static QString toUnicodeEscape(const QString& input) { return result; } +// Check if the original JSON value is in floating-point format +static bool isOriginalValueFloat(const QByteArray& jsonData, const QString& propertyName, const QJsonValue& value) { + if (!value.isDouble()) { + return false; + } + // Build search pattern to find the number in "propertyName": { "value": number } + QString searchPattern = QString("\"%1\"\\s*:\\s*\\{[^}]*\"value\"\\s*:\\s*([0-9.eE+-]+)").arg(propertyName); + QRegularExpression regex(searchPattern); + QString jsonString = QString::fromUtf8(jsonData); + QRegularExpressionMatch match = regex.match(jsonString); + if (match.hasMatch()) { + QString numberStr = match.captured(1); + // If contains decimal point or scientific notation, it's a floating-point number + return numberStr.contains('.') || numberStr.contains('e', Qt::CaseInsensitive); + } + return false; +} + // Converts a QJsonValue to a corresponding C++ code representation static QString jsonValueToCppCode(const QJsonValue &value){ if (value.isBool()) { @@ -216,8 +234,17 @@ int main(int argc, char *argv[]) { } else if (value.isObject()) { typeName = "QVariantMap"; } else if (value.isDouble()) { - const auto variantValue = value.toVariant(); - typeName = QString::fromLatin1(variantValue.typeName()); + // QJson treats all numbers in JSON as double. Although converting QJsonValue to + // QVariant can distinguish between double and int, Qt's JSON parsing attempts to + // convert floating-point numbers with decimal parts of 0, such as 1.0, to integers, + // resulting in the generated property type being qlonglong. However, dconfig expects + // floating-point numbers, so we try to identify whether it is a floating-point number + // or an integer by matching strings. + if (isOriginalValueFloat(data, propertyName, value)) { + typeName = "double"; + } else { + typeName = "qlonglong"; + } } else if (value.isString()) { typeName = "QString"; } else { @@ -535,7 +562,7 @@ int main(int argc, char *argv[]) { // Property variables for (const Property &property : std::as_const(properties)) { if (property.typeName == QLatin1String("int") || property.typeName == QLatin1String("qint64")) { - headerStream << " // Note: If you expect a double type, add 'e' to the number in the JSON value field, e.g., \"value\": 1.0e, not just 1.0\n"; + headerStream << " // Note: If you expect a double type, use XXX.0\n"; } else if (property.typeName == QLatin1String("QString")) { headerStream << " // Default value: \"" << property.defaultValue.toString().replace("\n", "\\n").replace("\r", "\\r") << "\"\n"; }