综合平台编程器项目的远程存储
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

253 linhas
10 KiB

  1. #include "infrastructure/application_settings_loader.h"
  2. #include "support/test_support.h"
  3. #include <QCoreApplication>
  4. #include <QFile>
  5. #include <QTemporaryDir>
  6. #include <algorithm>
  7. #include <exception>
  8. #include <iostream>
  9. #include <stdexcept>
  10. #include <string>
  11. namespace {
  12. using TestSupport::require;
  13. void writeUtf8(const QString &path, const QByteArray &contents)
  14. {
  15. QFile file(path);
  16. require(file.open(QIODevice::WriteOnly), "test configuration file must open");
  17. require(file.write(contents) == contents.size(),
  18. "test configuration file must be written completely");
  19. }
  20. bool containsMessage(
  21. const ApplicationSettingsLoadResult &result,
  22. const std::string &fragment)
  23. {
  24. return std::any_of(
  25. result.messages.cbegin(), result.messages.cend(),
  26. [&fragment](const std::string &message)
  27. {
  28. return message.find(fragment) != std::string::npos;
  29. });
  30. }
  31. QByteArray readAll(const QString &path)
  32. {
  33. QFile file(path);
  34. require(file.open(QIODevice::ReadOnly), "test configuration file must open for reading");
  35. return file.readAll();
  36. }
  37. QByteArray completeConfiguration()
  38. {
  39. return QByteArrayLiteral(
  40. "[Config]\n"
  41. "Version=1\n"
  42. "[ProjectLimits]\n"
  43. "MaxHmiPages=8\n"
  44. "MaxHmiControlsPerPage=24\n"
  45. "MaxAlarmDefinitions=40\n"
  46. "MaxControlLogics=6\n"
  47. "MaxRungsPerLogic=30\n"
  48. "MaxOutputMessages=120\n"
  49. "[HmiDefaults]\n"
  50. "DefaultPageWidth=1024\n"
  51. "DefaultPageHeight=600\n"
  52. "[PlcDefaults]\n"
  53. "PortName=COM7\n"
  54. "ServerAddress=2\n"
  55. "BaudRate=19200\n"
  56. "DataBits=7\n"
  57. "Parity=3\n"
  58. "StopBits=2\n");
  59. }
  60. void testMissingFileCreatesDefaults()
  61. {
  62. QTemporaryDir directory;
  63. require(directory.isValid(), "temporary directory must be valid");
  64. const QString path = directory.filePath("config/application.ini");
  65. const ApplicationSettingsLoadResult result =
  66. ApplicationSettingsLoader::load(path);
  67. require(QFile::exists(path), "a missing application.ini must be created");
  68. require(!result.warningRequired,
  69. "successful first-run configuration creation must not warn");
  70. require(result.settings.projectLimits.maximumHmiPages == 32U
  71. && result.settings.hmiDefaults.pageWidth == 800
  72. && result.settings.plcDefaults.portName == "COM3",
  73. "first run must use code defaults");
  74. const QByteArray created = readAll(path);
  75. require(created.contains("[HmiDefaults]\n")
  76. && created.contains("DefaultPageWidth=800\n")
  77. && created.contains("DefaultPageHeight=400\n")
  78. && created.contains("[PlcDefaults]\n")
  79. && created.contains("PortName=COM3\n"),
  80. "the generated file must expose HMI dimensions and PLC defaults");
  81. require(containsMessage(result, "已创建默认配置文件"),
  82. "first run must report the created file");
  83. }
  84. void testCompleteConfigurationLoadsEveryPublicField()
  85. {
  86. QTemporaryDir directory;
  87. require(directory.isValid(), "temporary directory must be valid");
  88. const QString path = directory.filePath("application.ini");
  89. writeUtf8(path, completeConfiguration());
  90. const ApplicationSettingsLoadResult result =
  91. ApplicationSettingsLoader::load(path);
  92. const ApplicationSettings &settings = result.settings;
  93. require(!result.warningRequired, "a valid configuration must be accepted");
  94. require(settings.projectLimits.maximumHmiPages == 8U
  95. && settings.projectLimits.maximumHmiControlsPerPage == 24U
  96. && settings.projectLimits.maximumAlarmDefinitions == 40U
  97. && settings.projectLimits.maximumControlLogics == 6U
  98. && settings.projectLimits.maximumRungsPerLogic == 30U
  99. && settings.projectLimits.maximumOutputMessages == 120,
  100. "all configurable project limits must be loaded");
  101. require(settings.hmiDefaults.pageWidth == 1024
  102. && settings.hmiDefaults.pageHeight == 600,
  103. "HMI default dimensions must be loaded");
  104. require(settings.plcDefaults.portName == "COM7"
  105. && settings.plcDefaults.serverAddress == 2
  106. && settings.plcDefaults.baudRate == 19200
  107. && settings.plcDefaults.dataBits == 7
  108. && settings.plcDefaults.parity == 3
  109. && settings.plcDefaults.stopBits == 2,
  110. "PLC defaults must be loaded");
  111. }
  112. void testMissingAndUnknownFieldsDoNotInvalidateConfiguration()
  113. {
  114. QTemporaryDir directory;
  115. require(directory.isValid(), "temporary directory must be valid");
  116. const QString path = directory.filePath("application.ini");
  117. writeUtf8(
  118. path,
  119. QByteArrayLiteral(
  120. "[Config]\nVersion=1\n"
  121. "[ProjectLimits]\nMaxHmiPages=5\nFutureLimit=99\n"));
  122. const ApplicationSettingsLoadResult result =
  123. ApplicationSettingsLoader::load(path);
  124. require(!result.warningRequired
  125. && result.settings.projectLimits.maximumHmiPages == 5U
  126. && result.settings.projectLimits.maximumAlarmDefinitions == 256U,
  127. "missing fields must default without discarding valid values");
  128. require(containsMessage(result, "未知字段")
  129. && containsMessage(result, "缺失"),
  130. "missing and unknown fields must be reported");
  131. }
  132. void testDuplicateOrInvalidFieldsRejectTheWholeFile()
  133. {
  134. QTemporaryDir directory;
  135. require(directory.isValid(), "temporary directory must be valid");
  136. const QString duplicate_path = directory.filePath("duplicate.ini");
  137. QByteArray duplicate = completeConfiguration();
  138. duplicate.append("[ProjectLimits]\nMaxHmiPages=4\n");
  139. writeUtf8(duplicate_path, duplicate);
  140. const ApplicationSettingsLoadResult duplicate_result =
  141. ApplicationSettingsLoader::load(duplicate_path);
  142. require(duplicate_result.warningRequired
  143. && duplicate_result.settings.projectLimits.maximumHmiPages == 32U
  144. && containsMessage(duplicate_result, "重复"),
  145. "a duplicate field must reject the entire configuration");
  146. const QString invalid_path = directory.filePath("invalid.ini");
  147. QByteArray invalid = completeConfiguration();
  148. invalid.replace("DefaultPageWidth=1024", "DefaultPageWidth=2000");
  149. writeUtf8(invalid_path, invalid);
  150. const ApplicationSettingsLoadResult invalid_result =
  151. ApplicationSettingsLoader::load(invalid_path);
  152. require(invalid_result.warningRequired
  153. && invalid_result.settings.hmiDefaults.pageWidth == 800
  154. && invalid_result.settings.plcDefaults.portName == "COM3",
  155. "one invalid value must roll every section back to code defaults");
  156. const QString non_integer_path = directory.filePath("non-integer.ini");
  157. QByteArray non_integer = completeConfiguration();
  158. non_integer.replace("MaxHmiPages=8", "MaxHmiPages=8pages");
  159. writeUtf8(non_integer_path, non_integer);
  160. const ApplicationSettingsLoadResult non_integer_result =
  161. ApplicationSettingsLoader::load(non_integer_path);
  162. require(non_integer_result.warningRequired
  163. && non_integer_result.settings.projectLimits.maximumHmiPages == 32U
  164. && containsMessage(non_integer_result, "8pages"),
  165. "quantity settings must reject values that are not pure integers");
  166. }
  167. void testVersionAndEncodingErrorsRejectTheWholeFile()
  168. {
  169. QTemporaryDir directory;
  170. require(directory.isValid(), "temporary directory must be valid");
  171. const QString version_path = directory.filePath("future-version.ini");
  172. QByteArray future_version = completeConfiguration();
  173. future_version.replace("Version=1", "Version=2");
  174. writeUtf8(version_path, future_version);
  175. const ApplicationSettingsLoadResult version_result =
  176. ApplicationSettingsLoader::load(version_path);
  177. require(version_result.warningRequired
  178. && version_result.settings.projectLimits.maximumHmiPages == 32U
  179. && containsMessage(version_result, "Config.Version")
  180. && containsMessage(version_result, "允许范围为 1~1"),
  181. "an incompatible configuration version must reject the whole file");
  182. const QString encoding_path = directory.filePath("invalid-utf8.ini");
  183. QByteArray invalid_utf8("[Config]\nVersion=1\n# ");
  184. invalid_utf8.append(static_cast<char>(0xFF));
  185. writeUtf8(encoding_path, invalid_utf8);
  186. const ApplicationSettingsLoadResult encoding_result =
  187. ApplicationSettingsLoader::load(encoding_path);
  188. require(encoding_result.warningRequired
  189. && containsMessage(encoding_result, "UTF-8")
  190. && encoding_result.settings.plcDefaults.portName == "COM3",
  191. "invalid UTF-8 must fall back to all code defaults");
  192. }
  193. void testCreationFailureKeepsTheApplicationStartable()
  194. {
  195. QTemporaryDir directory;
  196. require(directory.isValid(), "temporary directory must be valid");
  197. const QString blocker = directory.filePath("not-a-directory");
  198. writeUtf8(blocker, QByteArrayLiteral("block"));
  199. const ApplicationSettingsLoadResult result =
  200. ApplicationSettingsLoader::load(blocker + "/application.ini");
  201. require(result.warningRequired
  202. && result.settings.projectLimits.maximumOutputMessages == 1000
  203. && containsMessage(result, "使用代码默认值"),
  204. "configuration creation failure must fall back without aborting startup");
  205. }
  206. } // namespace
  207. int main(int argc, char *argv[])
  208. {
  209. QCoreApplication application(argc, argv);
  210. try
  211. {
  212. testMissingFileCreatesDefaults();
  213. testCompleteConfigurationLoadsEveryPublicField();
  214. testMissingAndUnknownFieldsDoNotInvalidateConfiguration();
  215. testDuplicateOrInvalidFieldsRejectTheWholeFile();
  216. testVersionAndEncodingErrorsRejectTheWholeFile();
  217. testCreationFailureKeepsTheApplicationStartable();
  218. }
  219. catch (const std::exception &error)
  220. {
  221. std::cerr << "application settings tests failed: "
  222. << error.what() << '\n';
  223. return 1;
  224. }
  225. std::cout << "application settings tests passed\n";
  226. return 0;
  227. }