|
- #include "infrastructure/application_settings_loader.h"
- #include "support/test_support.h"
-
- #include <QCoreApplication>
- #include <QFile>
- #include <QTemporaryDir>
-
- #include <algorithm>
- #include <exception>
- #include <iostream>
- #include <stdexcept>
- #include <string>
-
- namespace {
-
- using TestSupport::require;
-
- void writeUtf8(const QString &path, const QByteArray &contents)
- {
- QFile file(path);
- require(file.open(QIODevice::WriteOnly), "test configuration file must open");
- require(file.write(contents) == contents.size(),
- "test configuration file must be written completely");
- }
-
- bool containsMessage(
- const ApplicationSettingsLoadResult &result,
- const std::string &fragment)
- {
- return std::any_of(
- result.messages.cbegin(), result.messages.cend(),
- [&fragment](const std::string &message)
- {
- return message.find(fragment) != std::string::npos;
- });
- }
-
- QByteArray readAll(const QString &path)
- {
- QFile file(path);
- require(file.open(QIODevice::ReadOnly), "test configuration file must open for reading");
- return file.readAll();
- }
-
- QByteArray completeConfiguration()
- {
- return QByteArrayLiteral(
- "[Config]\n"
- "Version=1\n"
- "[ProjectLimits]\n"
- "MaxHmiPages=8\n"
- "MaxHmiControlsPerPage=24\n"
- "MaxAlarmDefinitions=40\n"
- "MaxControlLogics=6\n"
- "MaxRungsPerLogic=30\n"
- "MaxOutputMessages=120\n"
- "[HmiDefaults]\n"
- "DefaultPageWidth=1024\n"
- "DefaultPageHeight=600\n"
- "[PlcDefaults]\n"
- "PortName=COM7\n"
- "ServerAddress=2\n"
- "BaudRate=19200\n"
- "DataBits=7\n"
- "Parity=3\n"
- "StopBits=2\n");
- }
-
- void testMissingFileCreatesDefaults()
- {
- QTemporaryDir directory;
- require(directory.isValid(), "temporary directory must be valid");
- const QString path = directory.filePath("config/application.ini");
-
- const ApplicationSettingsLoadResult result =
- ApplicationSettingsLoader::load(path);
- require(QFile::exists(path), "a missing application.ini must be created");
- require(!result.warningRequired,
- "successful first-run configuration creation must not warn");
- require(result.settings.projectLimits.maximumHmiPages == 32U
- && result.settings.hmiDefaults.pageWidth == 800
- && result.settings.plcDefaults.portName == "COM3",
- "first run must use code defaults");
- const QByteArray created = readAll(path);
- require(created.contains("[HmiDefaults]\n")
- && created.contains("DefaultPageWidth=800\n")
- && created.contains("DefaultPageHeight=400\n")
- && created.contains("[PlcDefaults]\n")
- && created.contains("PortName=COM3\n"),
- "the generated file must expose HMI dimensions and PLC defaults");
- require(containsMessage(result, "已创建默认配置文件"),
- "first run must report the created file");
- }
-
- void testCompleteConfigurationLoadsEveryPublicField()
- {
- QTemporaryDir directory;
- require(directory.isValid(), "temporary directory must be valid");
- const QString path = directory.filePath("application.ini");
- writeUtf8(path, completeConfiguration());
-
- const ApplicationSettingsLoadResult result =
- ApplicationSettingsLoader::load(path);
- const ApplicationSettings &settings = result.settings;
- require(!result.warningRequired, "a valid configuration must be accepted");
- require(settings.projectLimits.maximumHmiPages == 8U
- && settings.projectLimits.maximumHmiControlsPerPage == 24U
- && settings.projectLimits.maximumAlarmDefinitions == 40U
- && settings.projectLimits.maximumControlLogics == 6U
- && settings.projectLimits.maximumRungsPerLogic == 30U
- && settings.projectLimits.maximumOutputMessages == 120,
- "all configurable project limits must be loaded");
- require(settings.hmiDefaults.pageWidth == 1024
- && settings.hmiDefaults.pageHeight == 600,
- "HMI default dimensions must be loaded");
- require(settings.plcDefaults.portName == "COM7"
- && settings.plcDefaults.serverAddress == 2
- && settings.plcDefaults.baudRate == 19200
- && settings.plcDefaults.dataBits == 7
- && settings.plcDefaults.parity == 3
- && settings.plcDefaults.stopBits == 2,
- "PLC defaults must be loaded");
- }
-
- void testMissingAndUnknownFieldsDoNotInvalidateConfiguration()
- {
- QTemporaryDir directory;
- require(directory.isValid(), "temporary directory must be valid");
- const QString path = directory.filePath("application.ini");
- writeUtf8(
- path,
- QByteArrayLiteral(
- "[Config]\nVersion=1\n"
- "[ProjectLimits]\nMaxHmiPages=5\nFutureLimit=99\n"));
-
- const ApplicationSettingsLoadResult result =
- ApplicationSettingsLoader::load(path);
- require(!result.warningRequired
- && result.settings.projectLimits.maximumHmiPages == 5U
- && result.settings.projectLimits.maximumAlarmDefinitions == 256U,
- "missing fields must default without discarding valid values");
- require(containsMessage(result, "未知字段")
- && containsMessage(result, "缺失"),
- "missing and unknown fields must be reported");
- }
-
- void testDuplicateOrInvalidFieldsRejectTheWholeFile()
- {
- QTemporaryDir directory;
- require(directory.isValid(), "temporary directory must be valid");
- const QString duplicate_path = directory.filePath("duplicate.ini");
- QByteArray duplicate = completeConfiguration();
- duplicate.append("[ProjectLimits]\nMaxHmiPages=4\n");
- writeUtf8(duplicate_path, duplicate);
-
- const ApplicationSettingsLoadResult duplicate_result =
- ApplicationSettingsLoader::load(duplicate_path);
- require(duplicate_result.warningRequired
- && duplicate_result.settings.projectLimits.maximumHmiPages == 32U
- && containsMessage(duplicate_result, "重复"),
- "a duplicate field must reject the entire configuration");
-
- const QString invalid_path = directory.filePath("invalid.ini");
- QByteArray invalid = completeConfiguration();
- invalid.replace("DefaultPageWidth=1024", "DefaultPageWidth=2000");
- writeUtf8(invalid_path, invalid);
- const ApplicationSettingsLoadResult invalid_result =
- ApplicationSettingsLoader::load(invalid_path);
- require(invalid_result.warningRequired
- && invalid_result.settings.hmiDefaults.pageWidth == 800
- && invalid_result.settings.plcDefaults.portName == "COM3",
- "one invalid value must roll every section back to code defaults");
-
- const QString non_integer_path = directory.filePath("non-integer.ini");
- QByteArray non_integer = completeConfiguration();
- non_integer.replace("MaxHmiPages=8", "MaxHmiPages=8pages");
- writeUtf8(non_integer_path, non_integer);
- const ApplicationSettingsLoadResult non_integer_result =
- ApplicationSettingsLoader::load(non_integer_path);
- require(non_integer_result.warningRequired
- && non_integer_result.settings.projectLimits.maximumHmiPages == 32U
- && containsMessage(non_integer_result, "8pages"),
- "quantity settings must reject values that are not pure integers");
- }
-
- void testVersionAndEncodingErrorsRejectTheWholeFile()
- {
- QTemporaryDir directory;
- require(directory.isValid(), "temporary directory must be valid");
-
- const QString version_path = directory.filePath("future-version.ini");
- QByteArray future_version = completeConfiguration();
- future_version.replace("Version=1", "Version=2");
- writeUtf8(version_path, future_version);
- const ApplicationSettingsLoadResult version_result =
- ApplicationSettingsLoader::load(version_path);
- require(version_result.warningRequired
- && version_result.settings.projectLimits.maximumHmiPages == 32U
- && containsMessage(version_result, "Config.Version")
- && containsMessage(version_result, "允许范围为 1~1"),
- "an incompatible configuration version must reject the whole file");
-
- const QString encoding_path = directory.filePath("invalid-utf8.ini");
- QByteArray invalid_utf8("[Config]\nVersion=1\n# ");
- invalid_utf8.append(static_cast<char>(0xFF));
- writeUtf8(encoding_path, invalid_utf8);
- const ApplicationSettingsLoadResult encoding_result =
- ApplicationSettingsLoader::load(encoding_path);
- require(encoding_result.warningRequired
- && containsMessage(encoding_result, "UTF-8")
- && encoding_result.settings.plcDefaults.portName == "COM3",
- "invalid UTF-8 must fall back to all code defaults");
- }
-
- void testCreationFailureKeepsTheApplicationStartable()
- {
- QTemporaryDir directory;
- require(directory.isValid(), "temporary directory must be valid");
- const QString blocker = directory.filePath("not-a-directory");
- writeUtf8(blocker, QByteArrayLiteral("block"));
-
- const ApplicationSettingsLoadResult result =
- ApplicationSettingsLoader::load(blocker + "/application.ini");
- require(result.warningRequired
- && result.settings.projectLimits.maximumOutputMessages == 1000
- && containsMessage(result, "使用代码默认值"),
- "configuration creation failure must fall back without aborting startup");
- }
-
- } // namespace
-
- int main(int argc, char *argv[])
- {
- QCoreApplication application(argc, argv);
- try
- {
- testMissingFileCreatesDefaults();
- testCompleteConfigurationLoadsEveryPublicField();
- testMissingAndUnknownFieldsDoNotInvalidateConfiguration();
- testDuplicateOrInvalidFieldsRejectTheWholeFile();
- testVersionAndEncodingErrorsRejectTheWholeFile();
- testCreationFailureKeepsTheApplicationStartable();
- }
- catch (const std::exception &error)
- {
- std::cerr << "application settings tests failed: "
- << error.what() << '\n';
- return 1;
- }
- std::cout << "application settings tests passed\n";
- return 0;
- }
|