ChimeraTK-DeviceAccess 03.31.00
Loading...
Searching...
No Matches
testDeviceToJmap.cc
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4#define BOOST_TEST_DYN_LINK
5
6#define BOOST_TEST_MODULE testDeviceToJmap
7
8#include "AccessMode.h"
9#include "BackendFactory.h"
10#include "DataDescriptor.h"
11#include "Device.h"
12#include "RegisterCatalogue.h"
13#include "RegisterInfo.h"
14
15#include <nlohmann/json.hpp>
16#include <sys/wait.h>
17
18#include <boost/test/unit_test.hpp>
19
20#include <cstdlib>
21#include <format>
22#include <fstream>
23#include <set>
24#include <sstream>
25#include <string>
26#include <vector>
27
28using namespace boost::unit_test_framework;
29using namespace ChimeraTK;
30
31// Path to the chimeratk-device-to-jmap tool, injected by CMake (top-level CMakeLists.txt).
32#ifndef CHIMERATK_DEVICE_TO_JMAP
33# define CHIMERATK_DEVICE_TO_JMAP "chimeratk-device-to-jmap"
34#endif
35
36BOOST_AUTO_TEST_SUITE(testDeviceToJmap)
37
38/**********************************************************************************************************************/
39
40// Fixed paths used throughout this file (relative to the test working directory, which is the tests build directory).
41static const std::string dummySourceCdd = "(dummy?map=simpleJsonFile.jmap)";
42static const std::string lmapDmap = "logicalnamemap.dmap";
43
44/**********************************************************************************************************************/
45/* Helpers */
46/**********************************************************************************************************************/
47
54static bool runTool(
55 const std::string& extraArgs, const std::string& stdoutFile, const std::string& stderrFile, int& exitCode) {
56 std::string command =
57 std::format(R"("{}" {} >"{}" 2>"{}")", CHIMERATK_DEVICE_TO_JMAP, extraArgs, stdoutFile, stderrFile);
58 int status = std::system(command.c_str());
59 if(!WIFEXITED(status)) {
60 return false;
61 }
62 exitCode = WEXITSTATUS(status);
63 return true;
64}
65
66/**********************************************************************************************************************/
67
69static std::string readFile(const std::string& path) {
70 std::ifstream stream(path, std::ios::binary);
71 std::ostringstream buffer;
72 buffer << stream.rdbuf();
73 return buffer.str();
74}
75
76/**********************************************************************************************************************/
77
85static bool compareRegisters(const RegisterInfo& source, const RegisterInfo& destination) {
86 if(source.getNumberOfElements() != destination.getNumberOfElements()) {
87 return false;
88 }
89 const DataDescriptor& sourceDescriptor = source.getDataDescriptor();
90 const DataDescriptor& destinationDescriptor = destination.getDataDescriptor();
91 if(sourceDescriptor.fundamentalType() != destinationDescriptor.fundamentalType()) {
92 return false;
93 }
94 // The read capability must match.
95 if(source.isReadable() != destination.isReadable()) {
96 return false;
97 }
98 // The only accepted difference in write capability is a writable source register becoming read-only (the documented
99 // lossy transformation of writable wait_for_new_data registers into read-only interrupts).
100 if(source.isWriteable() != destination.isWriteable() && (!source.isWriteable() || destination.isWriteable())) {
101 return false;
102 }
103 // Signedness, integrality and element size come from DataDescriptor::minimumDataType() and are only defined for
104 // numeric registers.
105 if(sourceDescriptor.fundamentalType() == DataDescriptor::FundamentalType::numeric) {
106 return sourceDescriptor.isSigned() == destinationDescriptor.isSigned() &&
107 sourceDescriptor.isIntegral() == destinationDescriptor.isIntegral() &&
108 sourceDescriptor.minimumDataType().getNumberOfBytes() ==
109 destinationDescriptor.minimumDataType().getNumberOfBytes();
110 }
111 return true;
112}
113
114/**********************************************************************************************************************/
115
122static size_t compareCatalogues(const RegisterCatalogue& source, const RegisterCatalogue& destination) {
123 size_t mismatches = 0;
124 for(const auto& registerInfo : source) {
125 if(registerInfo.getNumberOfDimensions() > 1) {
126 continue;
127 }
128 const std::string path = registerInfo.getRegisterName();
129 if(!destination.hasRegister(path)) {
130 ++mismatches;
131 continue;
132 }
133 if(!compareRegisters(source.getRegister(path), destination.getRegister(path))) {
134 ++mismatches;
135 }
136 }
137 // Every one-dimensional destination register must also be present in the source.
138 for(const auto& registerInfo : destination) {
139 if(registerInfo.getNumberOfDimensions() > 1) {
140 continue;
141 }
142 const std::string path = registerInfo.getRegisterName();
143 if(!source.hasRegister(path)) {
144 ++mismatches;
145 }
146 }
147 return mismatches;
148}
149
150/**********************************************************************************************************************/
151
153static size_t roundTrip(const std::string& sourceCdd, const std::string& toolArguments, const std::string& outputFile,
154 const std::string& stdoutFile, const std::string& stderrFile) {
155 Device source;
156 source.open(sourceCdd);
157 auto sourceCatalogue = source.getRegisterCatalogue();
158
159 int exitCode = -1;
160 BOOST_REQUIRE(runTool(toolArguments, stdoutFile, stderrFile, exitCode));
161 BOOST_REQUIRE(exitCode == 0);
162
163 Device destination;
164 destination.open("(dummy?map=" + outputFile + ")");
165 auto destinationCatalogue = destination.getRegisterCatalogue();
166
167 return compareCatalogues(sourceCatalogue, destinationCatalogue);
168}
169
170/**********************************************************************************************************************/
171
173static std::string generateFromDummy(const std::string& outputFile) {
174 int exitCode = -1;
175 BOOST_REQUIRE(runTool(std::format(R"(--device "{}" --output {})", dummySourceCdd, outputFile), outputFile + ".stdout",
176 outputFile + ".stderr", exitCode));
177 BOOST_REQUIRE(exitCode == 0);
178 return outputFile;
179}
180
181/**********************************************************************************************************************/
182
184static std::string generateFromLMap() {
185 std::string outputFile = "devToJmap_lmap.jmap";
186 int exitCode = -1;
187 BOOST_REQUIRE(runTool(std::format(R"(--device LMAP0 --dmap {} --output {})", lmapDmap, outputFile),
188 outputFile + ".stdout", outputFile + ".stderr", exitCode));
189 BOOST_REQUIRE(exitCode == 0);
190 return outputFile;
191}
192
193/**********************************************************************************************************************/
194
196static RegisterCatalogue openGenerated(const std::string& jmapFile) {
198 device.open("(dummy?map=" + jmapFile + ")");
200}
201
202/**********************************************************************************************************************/
203
205static void cleanup(const std::vector<std::string>& files) {
206 for(const auto& file : files) {
207 std::remove(file.c_str());
208 }
209}
210
211/**********************************************************************************************************************/
212
214static std::set<std::string> collectWritableInterrupts(const RegisterCatalogue& catalogue) {
215 std::set<std::string> result;
216 for(const auto& registerInfo : catalogue) {
217 if(registerInfo.isWriteable() &&
218 registerInfo.getSupportedAccessModes().has(ChimeraTK::AccessMode::wait_for_new_data) &&
219 registerInfo.getDataDescriptor().fundamentalType() != DataDescriptor::FundamentalType::nodata) {
220 result.insert(registerInfo.getRegisterName());
221 }
222 }
223 return result;
224}
225
226/**********************************************************************************************************************/
227
229static std::set<std::string> collectTwoDimensional(const RegisterCatalogue& catalogue) {
230 std::set<std::string> result;
231 for(const auto& registerInfo : catalogue) {
232 if(registerInfo.getNumberOfDimensions() > 1) {
233 result.insert(registerInfo.getRegisterName());
234 }
235 }
236 return result;
237}
238
239/**********************************************************************************************************************/
240/**********************************************************************************************************************/
241/* Test cases */
242/**********************************************************************************************************************/
243/**********************************************************************************************************************/
244
245BOOST_AUTO_TEST_CASE(TestRoundTripViaNumericAddressedDevice) {
246 // Round trip via the numeric-addressed DummyBackend on simpleJsonFile.jmap. The generated jmap re-opened on a
247 // DummyBackend must expose a matching one-dimensional register catalogue.
248 size_t mismatches =
249 roundTrip(dummySourceCdd, std::format(R"(--device "{}" --output {})", dummySourceCdd, "devToJmap_dummy.jmap"),
250 "devToJmap_dummy.jmap", "devToJmap_dummy.jmap.stdout", "devToJmap_dummy.jmap.stderr");
251 BOOST_TEST(mismatches == 0U);
252 cleanup({"devToJmap_dummy.jmap", "devToJmap_dummy.jmap.stdout", "devToJmap_dummy.jmap.stderr"});
253}
254
255/**********************************************************************************************************************/
256
257BOOST_AUTO_TEST_CASE(TestRoundTripViaNonNumericDevice) {
258 // Round trip via the non-numeric LogicalNameMapping backend on valid.xlmap. Demonstrates that the tool is
259 // independent of numeric addressing. Writable wait_for_new_data variables become read-only interrupts.
261 size_t mismatches =
262 roundTrip("LMAP0", std::format(R"(--device LMAP0 --dmap {} --output {})", lmapDmap, "devToJmap_lmaprt.jmap"),
263 "devToJmap_lmaprt.jmap", "devToJmap_lmaprt.jmap.stdout", "devToJmap_lmaprt.jmap.stderr");
264 BOOST_TEST(mismatches == 0U);
265 cleanup({"devToJmap_lmaprt.jmap", "devToJmap_lmaprt.jmap.stdout", "devToJmap_lmaprt.jmap.stderr"});
266}
267
268/**********************************************************************************************************************/
269
270BOOST_AUTO_TEST_CASE(TestNumericIntegralTypes) {
271 // The plain xlmap variables cover each integral numeric type; they must be preserved faithfully (signedness,
272 // integrality, element size) in the generated jmap.
274 auto outputFile = generateFromLMap();
275 auto catalogue = openGenerated(outputFile);
276
277 BOOST_TEST(catalogue.hasRegister("/var_int8"));
278 BOOST_TEST(catalogue.getRegister("/var_int8").getDataDescriptor().minimumDataType().getNumberOfBytes() == 1U);
279 BOOST_TEST(catalogue.getRegister("/var_int8").getDataDescriptor().isSigned());
280 BOOST_TEST(catalogue.getRegister("/var_int8").getDataDescriptor().isIntegral());
281 BOOST_TEST(catalogue.getRegister("/var_uint8").getDataDescriptor().minimumDataType().getNumberOfBytes() == 1U);
282 BOOST_TEST(!catalogue.getRegister("/var_uint8").getDataDescriptor().isSigned());
283 BOOST_TEST(catalogue.getRegister("/var_int16").getDataDescriptor().minimumDataType().getNumberOfBytes() == 2U);
284 BOOST_TEST(catalogue.getRegister("/var_int16").getDataDescriptor().isSigned());
285 BOOST_TEST(catalogue.getRegister("/var_uint16").getDataDescriptor().minimumDataType().getNumberOfBytes() == 2U);
286 BOOST_TEST(!catalogue.getRegister("/var_uint16").getDataDescriptor().isSigned());
287 BOOST_TEST(catalogue.getRegister("/var_int32").getDataDescriptor().minimumDataType().getNumberOfBytes() == 4U);
288 BOOST_TEST(catalogue.getRegister("/var_int32").getDataDescriptor().isSigned());
289 BOOST_TEST(catalogue.getRegister("/var_uint32").getDataDescriptor().minimumDataType().getNumberOfBytes() == 4U);
290 BOOST_TEST(!catalogue.getRegister("/var_uint32").getDataDescriptor().isSigned());
291 BOOST_TEST(catalogue.getRegister("/var_int64").getDataDescriptor().minimumDataType().getNumberOfBytes() == 8U);
292 BOOST_TEST(catalogue.getRegister("/var_int64").getDataDescriptor().isSigned());
293 BOOST_TEST(catalogue.getRegister("/var_uint64").getDataDescriptor().minimumDataType().getNumberOfBytes() == 8U);
294 BOOST_TEST(!catalogue.getRegister("/var_uint64").getDataDescriptor().isSigned());
295
296 cleanup({outputFile, outputFile + ".stdout", outputFile + ".stderr"});
297}
298
299/**********************************************************************************************************************/
300
301BOOST_AUTO_TEST_CASE(TestFloatingPointTypes) {
302 // IEEE754 payloads (var_float32, var_float64) must be preserved as non-integral numeric types.
304 auto outputFile = generateFromLMap();
305 auto catalogue = openGenerated(outputFile);
306
307 BOOST_TEST(catalogue.hasRegister("/var_float32"));
308 BOOST_TEST(catalogue.getRegister("/var_float32").getDataDescriptor().minimumDataType().getNumberOfBytes() == 4U);
309 BOOST_TEST(!catalogue.getRegister("/var_float32").getDataDescriptor().isIntegral());
310 BOOST_TEST(catalogue.getRegister("/var_float64").getDataDescriptor().minimumDataType().getNumberOfBytes() == 8U);
311 BOOST_TEST(!catalogue.getRegister("/var_float64").getDataDescriptor().isIntegral());
312
313 cleanup({outputFile, outputFile + ".stdout", outputFile + ".stderr"});
314}
315
316/**********************************************************************************************************************/
317
318BOOST_AUTO_TEST_CASE(TestStringRegisters) {
319 // Strings are written with the ASCII string representation; var_string covers the fixed-bytesPerElement path and
320 // BSP/SOME_INFO the numeric-addressed path.
322 auto lmapFile = generateFromLMap();
323 auto lmapCatalogue = openGenerated(lmapFile);
324 BOOST_TEST(lmapCatalogue.hasRegister("/var_string"));
325 BOOST_TEST(lmapCatalogue.getRegister("/var_string").getDataDescriptor().fundamentalType() ==
326 DataDescriptor::FundamentalType::string);
327 cleanup({lmapFile, lmapFile + ".stdout", lmapFile + ".stderr"});
328
329 auto dummyFile = generateFromDummy("devToJmap_dummy_string.jmap");
330 auto dummyCatalogue = openGenerated(dummyFile);
331 BOOST_TEST(dummyCatalogue.hasRegister("/BSP/SOME_INFO"));
332 BOOST_TEST(dummyCatalogue.getRegister("/BSP/SOME_INFO").getDataDescriptor().fundamentalType() ==
333 DataDescriptor::FundamentalType::string);
334 cleanup({dummyFile, dummyFile + ".stdout", dummyFile + ".stderr"});
335}
336
337/**********************************************************************************************************************/
338
339BOOST_AUTO_TEST_CASE(TestStringLengthOption) {
340 // --string-length sets the fixed byte size per element assumed for ASCII string registers (default 80). A custom
341 // value writes the string registers with that bytesPerElement, while the default of 80 is implied when the option
342 // is omitted.
344 std::string customFile = "devToJmap_strlen16.jmap";
345 int exitCode = -1;
346 BOOST_REQUIRE(runTool(std::format(R"(--device LMAP0 --dmap {} --string-length 16 --output {})", lmapDmap, customFile),
347 customFile + ".stdout", customFile + ".stderr", exitCode));
348 BOOST_REQUIRE(exitCode == 0);
349 nlohmann::json customJmap = nlohmann::json::parse(readFile(customFile));
350 BOOST_TEST(customJmap["addressSpace"]["var_string"]["bytesPerElement"] == 16U);
351 cleanup({customFile, customFile + ".stdout", customFile + ".stderr"});
352
353 auto defaultFile = generateFromLMap();
354 nlohmann::json defaultJmap = nlohmann::json::parse(readFile(defaultFile));
355 BOOST_TEST(defaultJmap["addressSpace"]["var_string"]["bytesPerElement"] == 80U);
356 cleanup({defaultFile, defaultFile + ".stdout", defaultFile + ".stderr"});
357}
358
359/**********************************************************************************************************************/
360
361BOOST_AUTO_TEST_CASE(TestVoidInterruptRegisters) {
362 // Pure interrupt sources are written as void registers (BSP/VOID_INTERRUPT_0 and BSP/VOID_INTERRUPT_3_0_1).
363 auto dummyFile = generateFromDummy("devToJmap_dummy_void.jmap");
364 auto catalogue = openGenerated(dummyFile);
365 BOOST_TEST(catalogue.hasRegister("/BSP/VOID_INTERRUPT_0"));
366 BOOST_TEST(catalogue.getRegister("/BSP/VOID_INTERRUPT_0").getDataDescriptor().fundamentalType() ==
367 DataDescriptor::FundamentalType::nodata);
368 BOOST_TEST(catalogue.hasRegister("/BSP/VOID_INTERRUPT_3_0_1"));
369 BOOST_TEST(catalogue.getRegister("/BSP/VOID_INTERRUPT_3_0_1").getDataDescriptor().fundamentalType() ==
370 DataDescriptor::FundamentalType::nodata);
371 cleanup({dummyFile, dummyFile + ".stdout", dummyFile + ".stderr"});
372}
373
374/**********************************************************************************************************************/
375
376BOOST_AUTO_TEST_CASE(TestBooleanRegisters) {
377 // Boolean registers (IEEE-bit width 1, e.g. ENA and INACTIVE_BUF_ID in DOUBLE_BUF) are represented as values, not
378 // as strings or void.
379 auto dummyFile = generateFromDummy("devToJmap_dummy_bool.jmap");
380 auto catalogue = openGenerated(dummyFile);
381 for(const std::string& path : {std::string("/DAQ/DOUBLE_BUF/ENA"), std::string("/DAQ/DOUBLE_BUF/INACTIVE_BUF_ID")}) {
382 BOOST_TEST(catalogue.hasRegister(path));
383 const auto fundamentalType = catalogue.getRegister(path).getDataDescriptor().fundamentalType();
384 const bool isNumericOrBool = fundamentalType == DataDescriptor::FundamentalType::numeric ||
385 fundamentalType == DataDescriptor::FundamentalType::boolean;
386 BOOST_TEST(isNumericOrBool);
387 BOOST_TEST(catalogue.getRegister(path).isWriteable());
388 }
389 cleanup({dummyFile, dummyFile + ".stdout", dummyFile + ".stderr"});
390}
391
392/**********************************************************************************************************************/
393
394BOOST_AUTO_TEST_CASE(TestConstants) {
395 // Constants are read-only registers (Constant, Constant2, ArrayConstant); ArrayConstant has five elements.
397 auto outputFile = generateFromLMap();
398 auto catalogue = openGenerated(outputFile);
399
400 BOOST_TEST(catalogue.hasRegister("/Constant"));
401 BOOST_TEST(!catalogue.getRegister("/Constant").isWriteable());
402 BOOST_TEST(catalogue.getRegister("/Constant").isReadable());
403 BOOST_TEST(catalogue.getRegister("/Constant").getNumberOfElements() == 1U);
404 BOOST_TEST(catalogue.hasRegister("/Constant2"));
405 BOOST_TEST(!catalogue.getRegister("/Constant2").isWriteable());
406 BOOST_TEST(catalogue.hasRegister("/ArrayConstant"));
407 BOOST_TEST(!catalogue.getRegister("/ArrayConstant").isWriteable());
408 BOOST_TEST(catalogue.getRegister("/ArrayConstant").getNumberOfElements() == 5U);
409
410 cleanup({outputFile, outputFile + ".stdout", outputFile + ".stderr"});
411}
412
413/**********************************************************************************************************************/
414
415BOOST_AUTO_TEST_CASE(TestArrayRegisters) {
416 // Array registers preserve their number of elements (ArrayVariable has six, ArrayConstant five).
418 auto outputFile = generateFromLMap();
419 auto catalogue = openGenerated(outputFile);
420 BOOST_TEST(catalogue.hasRegister("/ArrayVariable"));
421 BOOST_TEST(catalogue.getRegister("/ArrayVariable").getNumberOfElements() == 6U);
422 BOOST_TEST(catalogue.getRegister("/ArrayConstant").getNumberOfElements() == 5U);
423 cleanup({outputFile, outputFile + ".stdout", outputFile + ".stderr"});
424}
425
426/**********************************************************************************************************************/
427
428BOOST_AUTO_TEST_CASE(TestModuleHierarchy) {
429 // Module hierarchy is reflected in the register paths (MyModule.SomeSubmodule.Variable).
431 auto outputFile = generateFromLMap();
432 auto catalogue = openGenerated(outputFile);
433 BOOST_TEST(catalogue.hasRegister("/MyModule/SomeSubmodule/Variable"));
434 BOOST_TEST(catalogue.getRegister("/MyModule/SomeSubmodule/Variable").getNumberOfElements() == 1U);
435 cleanup({outputFile, outputFile + ".stdout", outputFile + ".stderr"});
436}
437
438/**********************************************************************************************************************/
439
440BOOST_AUTO_TEST_CASE(TestAccessRights) {
441 // Access rights RW/RO/WO survive the round trip: SomeTopLevelRegister is RW, BSP/VERSION is RO, APP/SomeTable is WO.
442 auto dummyFile = generateFromDummy("devToJmap_dummy_access.jmap");
443 auto catalogue = openGenerated(dummyFile);
444
445 BOOST_TEST(catalogue.hasRegister("/SomeTopLevelRegister"));
446 BOOST_TEST(catalogue.getRegister("/SomeTopLevelRegister").isWriteable());
447 BOOST_TEST(catalogue.getRegister("/SomeTopLevelRegister").isReadable());
448 BOOST_TEST(catalogue.hasRegister("/BSP/VERSION"));
449 BOOST_TEST(!catalogue.getRegister("/BSP/VERSION").isWriteable());
450 BOOST_TEST(catalogue.getRegister("/BSP/VERSION").isReadable());
451 BOOST_TEST(catalogue.hasRegister("/APP/SomeTable"));
452 BOOST_TEST(catalogue.getRegister("/APP/SomeTable").isWriteable());
453 BOOST_TEST(!catalogue.getRegister("/APP/SomeTable").isReadable());
454
455 cleanup({dummyFile, dummyFile + ".stdout", dummyFile + ".stderr"});
456}
457
458/**********************************************************************************************************************/
459
460BOOST_AUTO_TEST_CASE(TestWritableInterruptsBecomeReadOnly) {
461 // Writable registers with AccessMode::wait_for_new_data (the xlmap variables) are written as read-only interrupts,
462 // so they must no longer be writable in the generated catalogue.
464 auto outputFile = generateFromLMap();
466 device.open("(dummy?map=" + outputFile + ")");
467 auto fullCatalogue = device.getRegisterCatalogue();
468 for(const std::string& path :
469 {std::string("/var_int32"), std::string("/var_float64"), std::string("/ArrayVariable")}) {
470 auto registerInfo = fullCatalogue.getRegister(path);
471 BOOST_TEST(!registerInfo.isWriteable());
472 BOOST_TEST(registerInfo.isReadable());
473 BOOST_TEST(registerInfo.getSupportedAccessModes().has(ChimeraTK::AccessMode::wait_for_new_data));
474 }
475
476 cleanup({outputFile, outputFile + ".stdout", outputFile + ".stderr"});
477}
478
479/**********************************************************************************************************************/
480
481BOOST_AUTO_TEST_CASE(TestEmptyCatalogue) {
482 // An empty device catalogue yields a valid, empty jmap file and no warning.
483 std::string emptySource = "devToJmap_empty_source.jmap";
484 {
485 std::ofstream stream(emptySource);
486 stream << R"({
487 "mapFormatVersion": "1.0",
488 "metadata": {},
489 "interruptHandler": {},
490 "addressSpace": {}
491})";
492 }
493 std::string outputFile = "devToJmap_empty_out.jmap";
494 int exitCode = -1;
495 BOOST_REQUIRE(runTool(std::format(R"(--device "{}" --output {})", "(dummy?map=" + emptySource + ")", outputFile),
496 outputFile + ".stdout", outputFile + ".stderr", exitCode));
497 BOOST_TEST(exitCode == 0);
498 // The ASAN runtime writes its own startup diagnostics to stderr (unavoidable on sanitizer builds), so the file
499 // cannot be expected to be literally empty. Assert instead that DeviceAccess itself emitted no warning or error.
500 auto stderrContent = readFile(outputFile + ".stderr");
501 BOOST_TEST(stderrContent.find("Warning:") == std::string::npos);
502 BOOST_TEST(stderrContent.find("Error:") == std::string::npos);
503
504 auto catalogue = openGenerated(outputFile);
505 BOOST_TEST(catalogue.getNumberOfRegisters() == 0U);
506
507 cleanup({emptySource, outputFile, outputFile + ".stdout", outputFile + ".stderr"});
508}
509
510/**********************************************************************************************************************/
511
512BOOST_AUTO_TEST_CASE(TestTwoDimensionalSkippedWithWarning) {
513 // Two-dimensional registers are unsupported and skipped with a single warning listing every affected register,
514 // but a valid partial jmap is produced and the remaining (one-dimensional) registers are kept.
515 Device source;
516 source.open(dummySourceCdd);
517 auto affected = collectTwoDimensional(source.getRegisterCatalogue());
518
519 auto dummyFile = generateFromDummy("devToJmap_dummy_2d.jmap");
520 BOOST_TEST(!affected.empty());
521 BOOST_TEST(readFile(dummyFile + ".stderr").find("two-dimensional registers") != std::string::npos);
522
523 auto catalogue = openGenerated(dummyFile);
524 // The 2D parents must not be present, while a one-dimensional register remains.
525 for(const auto& path : affected) {
526 BOOST_TEST(!catalogue.hasRegister(path));
527 }
528 BOOST_TEST(catalogue.hasRegister("/DAQ/DOUBLE_BUF/ENA"));
529
530 cleanup({dummyFile, dummyFile + ".stdout", dummyFile + ".stderr"});
531}
532
533/**********************************************************************************************************************/
534
535BOOST_AUTO_TEST_CASE(TestAllTwoDimensionalSkipped) {
536 // Edge case: a device whose catalogue consists solely of two-dimensional registers. The 2D parent is skipped with
537 // a warning, while its one-dimensional channel children are still written; the tool exits successfully.
538 std::string sourceFile = "devToJmap_2donly_source.jmap";
539 {
540 // Build the 2D-only source fixture by loading the existing test fixture tests/muxedDataAccessor.jmap and keeping
541 // only the single two-dimensional register TEST/DMA.
542 using nlohmann::json;
543 json source = json::parse(readFile("muxedDataAccessor.jmap"));
544 std::ofstream stream(sourceFile);
545 stream << json{{"mapFormatVersion", source["mapFormatVersion"]}, {"metadata", json::object()},
546 {"interruptHandler", json::object()},
547 {"addressSpace",
548 json{{"TEST", json{{"children", json{{"DMA", source["addressSpace"]["TEST"]["children"]["DMA"]}}}}}}}};
549 }
550 std::string outputFile = "devToJmap_2donly_out.jmap";
551 int exitCode = -1;
552 BOOST_REQUIRE(runTool(std::format(R"(--device '(dummy?map={})' --output {})", sourceFile, outputFile),
553 outputFile + ".stdout", outputFile + ".stderr", exitCode));
554 BOOST_TEST(exitCode == 0);
555 BOOST_TEST(readFile(outputFile + ".stderr").find("two-dimensional registers") != std::string::npos);
556
557 auto catalogue = openGenerated(outputFile);
558 // The two-dimensional register itself is skipped, yet its one-dimensional channel children are kept.
559 BOOST_TEST(!catalogue.hasRegister("/TEST/DMA"));
560 BOOST_TEST(catalogue.hasRegister("/TEST/DMA/0"));
561 BOOST_TEST(catalogue.hasRegister("/TEST/DMA/15"));
562
563 cleanup({sourceFile, outputFile, outputFile + ".stdout", outputFile + ".stderr"});
564}
565
566/**********************************************************************************************************************/
567
568BOOST_AUTO_TEST_CASE(TestWritableInterruptWarning) {
569 // Writable wait_for_new_data registers produce a single stderr warning listing every affected register.
571 auto outputFile = generateFromLMap();
572
573 Device source;
574 source.open("LMAP0");
575 auto affected = collectWritableInterrupts(source.getRegisterCatalogue());
576 BOOST_TEST(!affected.empty());
577 BOOST_TEST(readFile(outputFile + ".stderr").find("writable registers") != std::string::npos);
578
579 cleanup({outputFile, outputFile + ".stdout", outputFile + ".stderr"});
580}
581
582/**********************************************************************************************************************/
583
584BOOST_AUTO_TEST_CASE(TestMissingOutputOption) {
585 // Running without --output must exit with an error and write nothing to stdout.
586 std::string stdoutFile = "devToJmap_noout.stdout";
587 std::string stderrFile = "devToJmap_noout.stderr";
588 int exitCode = 0;
589 BOOST_REQUIRE(runTool(std::format(R"(--device "{}")", dummySourceCdd), stdoutFile, stderrFile, exitCode));
590 BOOST_TEST(exitCode != 0);
591 BOOST_TEST(readFile(stdoutFile).empty());
592 cleanup({stdoutFile, stderrFile});
593}
594
595/**********************************************************************************************************************/
596
597BOOST_AUTO_TEST_CASE(TestHelpOption) {
598 // --help prints the usage text to stdout and exits successfully without requiring a device.
599 std::string stdoutFile = "devToJmap_help.stdout";
600 std::string stderrFile = "devToJmap_help.stderr";
601 int exitCode = -1;
602 BOOST_REQUIRE(runTool("--help", stdoutFile, stderrFile, exitCode));
603 BOOST_TEST(exitCode == 0);
604 BOOST_TEST(readFile(stdoutFile).find("Usage:") != std::string::npos);
605 cleanup({stdoutFile, stderrFile});
606}
607
608/**********************************************************************************************************************/
609
610BOOST_AUTO_TEST_CASE(TestDeterminism) {
611 // Identical input must yield byte-identical jmap files on consecutive runs.
612 auto firstFile = generateFromDummy("devToJmap_det_a.jmap");
613 std::string first = readFile(firstFile);
614 int exitCode = -1;
615 std::string secondFile = "devToJmap_det_b.jmap";
616 BOOST_REQUIRE(runTool(std::format(R"(--device "{}" --output {})", dummySourceCdd, secondFile), secondFile + ".stdout",
617 secondFile + ".stderr", exitCode));
618 BOOST_TEST(exitCode == 0);
619 BOOST_TEST(first == readFile(secondFile));
620 cleanup({firstFile, firstFile + ".stdout", firstFile + ".stderr", secondFile, secondFile + ".stdout",
621 secondFile + ".stderr"});
622}
623
624/**********************************************************************************************************************/
625
626BOOST_AUTO_TEST_CASE(TestIdentificationModes) {
627 // A CDD and an alias (resolved via --dmap) addressing equivalent devices must produce the same jmap, and --dmap
628 // must never change how --device is interpreted.
629 std::string aliasDmap = "devToJmap_id.dmap";
630 {
631 std::ofstream stream(aliasDmap);
632 stream << "DUMMYJ (dummy?map=simpleJsonFile.jmap)\n";
633 }
634
635 std::string cddFile = "devToJmap_id_cdd.jmap";
636 std::string aliasFile = "devToJmap_id_alias.jmap";
637 int exitCode = -1;
638 BOOST_REQUIRE(runTool(std::format(R"(--device "{}" --output {})", dummySourceCdd, cddFile), cddFile + ".stdout",
639 cddFile + ".stderr", exitCode));
640 BOOST_TEST(exitCode == 0);
641 BOOST_REQUIRE(runTool(std::format(R"(--device DUMMYJ --dmap {} --output {})", aliasDmap, aliasFile),
642 aliasFile + ".stdout", aliasFile + ".stderr", exitCode));
643 BOOST_TEST(exitCode == 0);
644
645 BOOST_TEST(readFile(cddFile) == readFile(aliasFile));
646
647 cleanup({aliasDmap, cddFile, cddFile + ".stdout", cddFile + ".stderr", aliasFile, aliasFile + ".stdout",
648 aliasFile + ".stderr"});
649}
650
651/**********************************************************************************************************************/
652
653BOOST_AUTO_TEST_CASE(TestDeviceOpenFailure) {
654 // A device that cannot be opened (here an unknown backend type) makes the tool report the ChimeraTK logic_error on
655 // stderr and exit with status 1 instead of terminating via an unhandled exception.
656 std::string outputFile = "devToJmap_openfail.jmap";
657 std::string stdoutFile = "devToJmap_openfail.stdout";
658 std::string stderrFile = "devToJmap_openfail.stderr";
659 int exitCode = -1;
660 BOOST_REQUIRE(runTool(std::format(R"(--device "{}" --output {})", "(noSuchBackend?x=1)", outputFile), stdoutFile,
661 stderrFile, exitCode));
662 BOOST_TEST(exitCode == 1);
663 BOOST_TEST(!readFile(stderrFile).empty());
664 cleanup({outputFile, stdoutFile, stderrFile});
665}
666
667/**********************************************************************************************************************/
668
669BOOST_AUTO_TEST_SUITE_END()
nlohmann::json json
static BackendFactory & getInstance()
Static function to get an instance of factory.
void setDMapFilePath(std::string dMapFilePath)
This function sets the _DMapFilePath.
Class describing the actual payload data format of a register in an abstract manner.
FundamentalType fundamentalType() const
Get the fundamental data type.
bool isIntegral() const
Return whether the data is integral or not (e.g.
DataType minimumDataType() const
Get the minimum data type required to represent the described data type in the host CPU.
bool isSigned() const
Return whether the data is signed or not.
size_t getNumberOfBytes() const
Return the number of bytes per element for the data type.
Class allows to read/write registers from device.
Definition Device.h:39
RegisterCatalogue getRegisterCatalogue() const
Return the register catalogue with detailed information on all registers.
Definition Device.cc:22
void open(std::string const &aliasName)
Open a device by the given alias name from the DMAP file.
Definition Device.cc:58
Catalogue of register information.
RegisterInfo getRegister(const RegisterPath &registerPathName) const
Get register information for a given full path name.
size_t getNumberOfRegisters() const
Get number of registers in the catalogue.
bool hasRegister(const RegisterPath &registerPathName) const
Check if register with the given path name exists.
bool isWriteable() const
Return whether the register is writeable.
const DataDescriptor & getDataDescriptor() const
Return desciption of the actual payload data for this register.
unsigned int getNumberOfElements() const
Return number of elements per channel.
bool isReadable() const
Return whether the register is readable.
This header collects some conversion routines which should be useful both for ApplicationCore Python ...
@ wait_for_new_data
Make any read blocking until new data has arrived since the last read.
BOOST_AUTO_TEST_CASE(TestRoundTripViaNumericAddressedDevice)
#define CHIMERATK_DEVICE_TO_JMAP
ctk::Device device