wireforge 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. wireforge/__init__.py +3 -0
  2. wireforge/_data/cmake/WireForgeConfig.cmake +29 -0
  3. wireforge/_data/cmake/WireForgeConfigVersion.cmake +15 -0
  4. wireforge/_data/cmake/WireForgeGenerate.cmake +64 -0
  5. wireforge/_data/runtime/LICENSE +21 -0
  6. wireforge/_data/runtime/include/wireforge/array_access.hpp +35 -0
  7. wireforge/_data/runtime/include/wireforge/bit_utils.hpp +226 -0
  8. wireforge/_data/runtime/include/wireforge/byte_io.hpp +387 -0
  9. wireforge/_data/runtime/include/wireforge/checksum.hpp +201 -0
  10. wireforge/_data/runtime/include/wireforge/codec_support.hpp +412 -0
  11. wireforge/_data/runtime/include/wireforge/config.hpp +41 -0
  12. wireforge/_data/runtime/include/wireforge/delegate.hpp +101 -0
  13. wireforge/_data/runtime/include/wireforge/dispatcher.hpp +339 -0
  14. wireforge/_data/runtime/include/wireforge/expr_support.hpp +38 -0
  15. wireforge/_data/runtime/include/wireforge/frame_decoder.hpp +262 -0
  16. wireforge/_data/runtime/include/wireforge/frame_layout.hpp +20 -0
  17. wireforge/_data/runtime/include/wireforge/link.hpp +223 -0
  18. wireforge/_data/runtime/include/wireforge/lock.hpp +146 -0
  19. wireforge/_data/runtime/include/wireforge/ring_buffer.hpp +226 -0
  20. wireforge/_data/runtime/include/wireforge/span.hpp +116 -0
  21. wireforge/_data/runtime/include/wireforge/static_vector.hpp +264 -0
  22. wireforge/_data/runtime/include/wireforge/status.hpp +75 -0
  23. wireforge/_data/runtime/include/wireforge/transport.hpp +95 -0
  24. wireforge/_data/runtime/include/wireforge/validation.hpp +153 -0
  25. wireforge/_data/runtime/include/wireforge/version.hpp +22 -0
  26. wireforge/_data/runtime/requirements.yaml +438 -0
  27. wireforge/_data/schema/deviations.schema.json +47 -0
  28. wireforge/_data/schema/fragment.schema.json +79 -0
  29. wireforge/_data/schema/llr_ids.schema.json +25 -0
  30. wireforge/_data/schema/project.schema.json +1240 -0
  31. wireforge/_data/schema/runtime_requirements.schema.json +34 -0
  32. wireforge/_data/ui/assets/index-6Pomu6U4.js +44 -0
  33. wireforge/_data/ui/assets/index-GVdDyUAG.css +1 -0
  34. wireforge/_data/ui/index.html +13 -0
  35. wireforge/bundle.py +177 -0
  36. wireforge/ci_check.py +155 -0
  37. wireforge/cli.py +617 -0
  38. wireforge/codec.py +897 -0
  39. wireforge/compat.py +536 -0
  40. wireforge/coverage_points.py +64 -0
  41. wireforge/cpp.py +273 -0
  42. wireforge/cpp_codec.py +440 -0
  43. wireforge/cpp_interface.py +874 -0
  44. wireforge/cpp_tests.py +1993 -0
  45. wireforge/differential.py +586 -0
  46. wireforge/docs_markdown.py +72 -0
  47. wireforge/expr.py +598 -0
  48. wireforge/findings.py +187 -0
  49. wireforge/fmt.py +351 -0
  50. wireforge/generate.py +644 -0
  51. wireforge/golden.py +253 -0
  52. wireforge/hlr_import.py +112 -0
  53. wireforge/impact.py +249 -0
  54. wireforge/importers.py +402 -0
  55. wireforge/issues.py +27 -0
  56. wireforge/layout.py +681 -0
  57. wireforge/lifecycle.py +1266 -0
  58. wireforge/lifecycle_results.py +205 -0
  59. wireforge/lifecycle_rules.py +78 -0
  60. wireforge/lifecycle_runtime.py +375 -0
  61. wireforge/llr.py +693 -0
  62. wireforge/llr_ids.py +104 -0
  63. wireforge/loader.py +422 -0
  64. wireforge/mcp_server.py +357 -0
  65. wireforge/model.py +322 -0
  66. wireforge/plain.py +15 -0
  67. wireforge/py.typed +0 -0
  68. wireforge/reqif.py +289 -0
  69. wireforge/resources.py +43 -0
  70. wireforge/results.py +377 -0
  71. wireforge/runtime_reqs.py +147 -0
  72. wireforge/schema.py +151 -0
  73. wireforge/server/__init__.py +1 -0
  74. wireforge/server/app.py +619 -0
  75. wireforge/server/oidc.py +99 -0
  76. wireforge/server/storage.py +146 -0
  77. wireforge/service.py +1354 -0
  78. wireforge/templates/command/project.yaml +38 -0
  79. wireforge/templates/raw/project.yaml +28 -0
  80. wireforge/templates/telemetry/project.yaml +51 -0
  81. wireforge/test_cases.py +475 -0
  82. wireforge/test_project.py +478 -0
  83. wireforge/trace.py +442 -0
  84. wireforge/validate.py +1241 -0
  85. wireforge/vcs.py +44 -0
  86. wireforge/vectors.py +222 -0
  87. wireforge/versioning.py +137 -0
  88. wireforge/wireshark.py +792 -0
  89. wireforge/wireshark_runtime.lua +725 -0
  90. wireforge-0.1.0.dist-info/METADATA +91 -0
  91. wireforge-0.1.0.dist-info/RECORD +96 -0
  92. wireforge-0.1.0.dist-info/WHEEL +5 -0
  93. wireforge-0.1.0.dist-info/entry_points.txt +2 -0
  94. wireforge-0.1.0.dist-info/licenses/LICENSE +98 -0
  95. wireforge-0.1.0.dist-info/licenses/runtime/LICENSE +21 -0
  96. wireforge-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,478 @@
1
+ """Generated GoogleTest project: tests, CMake build, presets, coverage target and analyzer config.
2
+
3
+ `write_test_project()` writes `<output>/<project>/generated_tests/` next to the generated code of
4
+ `wireforge.generate`. The project builds one test executable per profile (with the profile's
5
+ defines), compiles with the full warning set as errors, offers sanitizer and clang-tidy presets
6
+ and a `coverage` target that fails below a line-coverage threshold, or below 100 percent branch or
7
+ function coverage, of the generated code. The coverage build also writes `<build>/results/` (JUnit
8
+ and CTest results, coverage as JSON and Cobertura, runtime coverage on request, the toolchain) for
9
+ `wireforge docs --results`.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import os
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+ from typing import TYPE_CHECKING
19
+
20
+ from wireforge import cpp_tests, golden, layout, resources, test_cases, vectors, versioning
21
+ from wireforge.codec import CodecError
22
+ from wireforge.coverage_points import CoveragePointError
23
+ from wireforge.issues import Issue
24
+
25
+ if TYPE_CHECKING:
26
+ from wireforge.model import Project
27
+
28
+ VECTOR_NOTE = (
29
+ "- `hlr_vector_test.cpp`: the requirement vectors of the model, checked against the values "
30
+ "written there (not against the reference codec).\n"
31
+ )
32
+ DIRECTORY = "generated_tests"
33
+ DEFAULT_COVERAGE = 100
34
+ RUNTIME_INCLUDE = resources.runtime_include()
35
+ SOURCES = ("golden_test.cpp", "message_test.cpp", "interface_test.cpp")
36
+ VECTOR_SOURCE = "hlr_vector_test.cpp"
37
+
38
+ CLANG_TIDY = """\
39
+ # Generated by WireForge. Do not edit; regenerate instead.
40
+ Checks: >
41
+ -*,
42
+ bugprone-*,
43
+ cert-*,
44
+ cppcoreguidelines-*,
45
+ performance-*,
46
+ portability-*,
47
+ misc-*,
48
+ readability-*,
49
+ -bugprone-easily-swappable-parameters,
50
+ -cppcoreguidelines-avoid-magic-numbers,
51
+ -readability-magic-numbers,
52
+ -misc-include-cleaner
53
+ WarningsAsErrors: '*'
54
+ HeaderFilterRegex: '.*/{name}/.*'
55
+ """
56
+
57
+ CMAKE_HEAD = """\
58
+ # Generated by @TOOL@ from project @NAME@. Do not edit; regenerate instead.
59
+ #
60
+ # Tests of the generated C++ code of @NAME@; see README.md.
61
+ cmake_minimum_required(VERSION 3.21)
62
+ project(@NAME@_generated_tests LANGUAGES CXX)
63
+
64
+ set(CMAKE_CXX_STANDARD 17)
65
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
66
+ set(CMAKE_CXX_EXTENSIONS OFF)
67
+ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
68
+
69
+ option(WIREFORGE_WERROR "Treat every warning as an error" ON)
70
+ option(WIREFORGE_COVERAGE "Instrument for coverage and add the coverage target" OFF)
71
+ option(WIREFORGE_CLANG_TIDY "Run clang-tidy on the test sources while building" OFF)
72
+ option(WIREFORGE_COVERAGE_RUNTIME "Include the runtime headers in the coverage report" OFF)
73
+ option(WIREFORGE_FUZZ "Build the libFuzzer targets (Clang only)" OFF)
74
+ set(WIREFORGE_SANITIZE "" CACHE STRING "Sanitizers: address,undefined or thread")
75
+ set(WIREFORGE_COVERAGE_MIN_LINE "@THRESHOLD@" CACHE STRING
76
+ "Minimum line coverage of the generated code in percent")
77
+ set(WIREFORGE_COVERAGE_MIN_BRANCH "100" CACHE STRING
78
+ "Minimum branch coverage of the generated code in percent")
79
+ set(WIREFORGE_COVERAGE_MIN_FUNCTION "100" CACHE STRING
80
+ "Minimum function coverage of the generated code in percent")
81
+ set(WIREFORGE_COVERAGE_RUNTIME_MIN_LINE "100" CACHE STRING
82
+ "Minimum statement coverage of the runtime headers in percent")
83
+ set(WIREFORGE_RUNTIME_INCLUDE_DIR "@RUNTIME@" CACHE PATH
84
+ "Directory holding the WireForge runtime headers (wireforge/*.hpp)")
85
+ get_filename_component(WIREFORGE_GENERATED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE)
86
+ if(NOT EXISTS "${WIREFORGE_RUNTIME_INCLUDE_DIR}/wireforge/status.hpp")
87
+ message(FATAL_ERROR "Set WIREFORGE_RUNTIME_INCLUDE_DIR to the WireForge runtime/include dir")
88
+ endif()
89
+ """
90
+
91
+ CMAKE_MODEL_DIR = """\
92
+ set(WIREFORGE_MODEL_DIR "@MODEL@" CACHE PATH
93
+ "Directory of the WireForge project (base of user_includes.include_dirs)")
94
+ """
95
+
96
+ CMAKE_BODY = """\
97
+
98
+ # Compiler flags of the WireForge quality policy.
99
+ add_library(wireforge_test_flags INTERFACE)
100
+ if(MSVC)
101
+ target_compile_options(wireforge_test_flags INTERFACE /W4 /permissive- /Zc:__cplusplus)
102
+ if(WIREFORGE_WERROR)
103
+ target_compile_options(wireforge_test_flags INTERFACE /WX)
104
+ endif()
105
+ else()
106
+ target_compile_options(wireforge_test_flags INTERFACE
107
+ -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wshadow -Wold-style-cast
108
+ -Wcast-align -Wcast-qual -Wnon-virtual-dtor -Woverloaded-virtual -Wnull-dereference
109
+ -Wdouble-promotion -Wformat=2 -Wundef -Wunused -Wswitch-enum)
110
+ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
111
+ target_compile_options(wireforge_test_flags INTERFACE
112
+ -Wuseless-cast -Wduplicated-cond -Wduplicated-branches -Wlogical-op)
113
+ else()
114
+ target_compile_options(wireforge_test_flags INTERFACE -Wextra-semi -Wimplicit-fallthrough)
115
+ endif()
116
+ if(WIREFORGE_WERROR)
117
+ target_compile_options(wireforge_test_flags INTERFACE -Werror)
118
+ endif()
119
+ if(WIREFORGE_SANITIZE)
120
+ target_compile_options(wireforge_test_flags INTERFACE
121
+ -fsanitize=${WIREFORGE_SANITIZE} -fno-omit-frame-pointer -fno-sanitize-recover=all)
122
+ target_link_options(wireforge_test_flags INTERFACE -fsanitize=${WIREFORGE_SANITIZE})
123
+ endif()
124
+ if(WIREFORGE_COVERAGE)
125
+ target_compile_options(wireforge_test_flags INTERFACE --coverage -O0 -g -fstack-usage)
126
+ target_link_options(wireforge_test_flags INTERFACE --coverage)
127
+ endif()
128
+ endif()
129
+
130
+ # The self-test is built like embedded code: no exceptions, no RTTI.
131
+ add_library(wireforge_no_exceptions INTERFACE)
132
+ if(MSVC)
133
+ target_compile_options(wireforge_no_exceptions INTERFACE /GR-)
134
+ else()
135
+ target_compile_options(wireforge_no_exceptions INTERFACE -fno-exceptions -fno-rtti)
136
+ endif()
137
+
138
+ find_package(GTest 1.14 QUIET)
139
+ if(NOT GTest_FOUND)
140
+ include(FetchContent)
141
+ set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
142
+ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
143
+ FetchContent_Declare(googletest
144
+ GIT_REPOSITORY https://github.com/google/googletest.git
145
+ GIT_TAG v1.14.0
146
+ GIT_SHALLOW TRUE
147
+ SYSTEM)
148
+ FetchContent_MakeAvailable(googletest)
149
+ endif()
150
+ find_package(Threads REQUIRED)
151
+ include(GoogleTest)
152
+ enable_testing()
153
+
154
+ if(WIREFORGE_FUZZ AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
155
+ message(FATAL_ERROR "WIREFORGE_FUZZ needs Clang (libFuzzer)")
156
+ endif()
157
+
158
+ if(WIREFORGE_CLANG_TIDY)
159
+ find_program(WIREFORGE_CLANG_TIDY_EXE clang-tidy REQUIRED)
160
+ set(CMAKE_CXX_CLANG_TIDY ${WIREFORGE_CLANG_TIDY_EXE} --quiet
161
+ --extra-arg=-Wno-unknown-warning-option)
162
+ endif()
163
+
164
+ # wireforge_add_profile(<suffix> <definitions...>): tests, differential harness, self-test and fuzz
165
+ # target of one profile (@NAME@_tests<suffix>, _diff, _selftest, _fuzz).
166
+ function(wireforge_add_profile suffix)
167
+ set(includes ${CMAKE_CURRENT_SOURCE_DIR} ${WIREFORGE_GENERATED_DIR}
168
+ ${WIREFORGE_RUNTIME_INCLUDE_DIR}@INCLUDES@)
169
+ set(tests @NAME@_tests${suffix})
170
+ add_executable(${tests} @SOURCES@)
171
+ target_include_directories(${tests} PRIVATE ${includes})
172
+ target_compile_definitions(${tests} PRIVATE ${ARGN})
173
+ target_link_libraries(${tests} PRIVATE GTest::gtest_main Threads::Threads wireforge_test_flags)
174
+ gtest_discover_tests(${tests} DISCOVERY_MODE PRE_TEST
175
+ XML_OUTPUT_DIR ${CMAKE_BINARY_DIR}/results/junit/${tests})
176
+ set(diff @NAME@_diff${suffix})
177
+ add_executable(${diff} diff_harness.cpp)
178
+ target_include_directories(${diff} PRIVATE ${includes})
179
+ target_compile_definitions(${diff} PRIVATE ${ARGN})
180
+ target_link_libraries(${diff} PRIVATE Threads::Threads wireforge_test_flags)
181
+ set(selftest @NAME@_selftest${suffix})
182
+ add_executable(${selftest} selftest.cpp)
183
+ target_include_directories(${selftest} PRIVATE ${includes})
184
+ target_compile_definitions(${selftest} PRIVATE ${ARGN})
185
+ target_link_libraries(${selftest} PRIVATE wireforge_test_flags wireforge_no_exceptions)
186
+ add_test(NAME ${selftest} COMMAND ${selftest})
187
+ if(WIREFORGE_FUZZ)
188
+ set(fuzz @NAME@_fuzz${suffix})
189
+ add_executable(${fuzz} fuzz_target.cpp)
190
+ target_include_directories(${fuzz} PRIVATE ${includes})
191
+ target_compile_definitions(${fuzz} PRIVATE ${ARGN})
192
+ target_compile_options(${fuzz} PRIVATE -fsanitize=fuzzer,address,undefined
193
+ -fno-sanitize-recover=all)
194
+ target_link_options(${fuzz} PRIVATE -fsanitize=fuzzer,address,undefined)
195
+ target_link_libraries(${fuzz} PRIVATE Threads::Threads wireforge_test_flags)
196
+ endif()
197
+ endfunction()
198
+
199
+ @TARGETS@
200
+ if(WIREFORGE_COVERAGE)
201
+ find_program(WIREFORGE_GCOVR gcovr REQUIRED)
202
+ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
203
+ set(WIREFORGE_GCOV "llvm-cov gcov" CACHE STRING "gcov command used by gcovr")
204
+ else()
205
+ set(WIREFORGE_GCOV "gcov" CACHE STRING "gcov command used by gcovr")
206
+ endif()
207
+ # Runs the tests, then reports the line, branch and function coverage of the generated headers
208
+ # of @NAME@. Headers outside the generated directory (runtime, application) are not part of the
209
+ # report unless WIREFORGE_COVERAGE_RUNTIME is ON: then a second report measures the statements of
210
+ # the runtime headers that were compiled into the tests. The machine-readable results are written
211
+ # to <build>/results (junit/, ctest.xml, coverage.json, coverage_summary.json,
212
+ # coverage.cobertura.xml, coverage_runtime.json, toolchain.json).
213
+ set(WIREFORGE_RESULTS_DIR ${CMAKE_BINARY_DIR}/results)
214
+ set(wireforge_coverage_steps
215
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${WIREFORGE_RESULTS_DIR}
216
+ COMMAND ${CMAKE_CTEST_COMMAND} --test-dir ${CMAKE_BINARY_DIR} --output-on-failure
217
+ --output-junit ${WIREFORGE_RESULTS_DIR}/ctest.xml
218
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/coverage
219
+ COMMAND ${WIREFORGE_GCOVR} --root ${WIREFORGE_GENERATED_DIR}
220
+ --object-directory ${CMAKE_BINARY_DIR} --gcov-executable ${WIREFORGE_GCOV}
221
+ --gcov-ignore-errors=source_not_found
222
+ --filter "@NAME@/model[.]hpp" --filter "@NAME@/interfaces/"
223
+ --txt --html-details ${CMAKE_BINARY_DIR}/coverage/index.html
224
+ --json ${WIREFORGE_RESULTS_DIR}/coverage.json
225
+ --json-summary ${WIREFORGE_RESULTS_DIR}/coverage_summary.json
226
+ --cobertura ${WIREFORGE_RESULTS_DIR}/coverage.cobertura.xml
227
+ --fail-under-line ${WIREFORGE_COVERAGE_MIN_LINE}
228
+ --fail-under-branch ${WIREFORGE_COVERAGE_MIN_BRANCH}
229
+ --fail-under-function ${WIREFORGE_COVERAGE_MIN_FUNCTION})
230
+ if(WIREFORGE_COVERAGE_RUNTIME)
231
+ get_filename_component(wireforge_runtime_abs "${WIREFORGE_RUNTIME_INCLUDE_DIR}" REALPATH)
232
+ list(APPEND wireforge_coverage_steps
233
+ COMMAND ${WIREFORGE_GCOVR} --root ${wireforge_runtime_abs}
234
+ --object-directory ${CMAKE_BINARY_DIR} --gcov-executable ${WIREFORGE_GCOV}
235
+ --gcov-ignore-errors=source_not_found
236
+ --filter "${wireforge_runtime_abs}/wireforge/"
237
+ --json ${WIREFORGE_RESULTS_DIR}/coverage_runtime.json
238
+ --fail-under-line ${WIREFORGE_COVERAGE_RUNTIME_MIN_LINE})
239
+ endif()
240
+ add_custom_target(coverage
241
+ ${wireforge_coverage_steps}
242
+ WORKING_DIRECTORY ${WIREFORGE_GENERATED_DIR}
243
+ DEPENDS @TARGET_NAMES@
244
+ VERBATIM)
245
+ endif()
246
+
247
+ # Facts about the toolchain for the lifecycle documents. No path is written: the file must not
248
+ # change when the project moves.
249
+ execute_process(COMMAND ${CMAKE_COMMAND} --version OUTPUT_VARIABLE wireforge_cmake_version
250
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
251
+ string(REGEX MATCH "[0-9]+[.][0-9]+[.][0-9]+" wireforge_cmake_version "${wireforge_cmake_version}")
252
+ set(wireforge_gcovr_version "not used")
253
+ if(WIREFORGE_COVERAGE)
254
+ execute_process(COMMAND ${WIREFORGE_GCOVR} --version OUTPUT_VARIABLE wireforge_gcovr_version
255
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
256
+ string(REGEX MATCH "[0-9]+[.][0-9]+([.][0-9]+)?" wireforge_gcovr_version
257
+ "${wireforge_gcovr_version}")
258
+ endif()
259
+ if(WIREFORGE_COVERAGE_RUNTIME)
260
+ set(wireforge_runtime_dir "set")
261
+ else()
262
+ set(wireforge_runtime_dir "default")
263
+ endif()
264
+ get_target_property(wireforge_flag_options wireforge_test_flags INTERFACE_COMPILE_OPTIONS)
265
+ string(REPLACE ";" " " wireforge_flags "${CMAKE_CXX_FLAGS} ${wireforge_flag_options}")
266
+ string(REGEX REPLACE "[$]<[^>]*>" "" wireforge_flags "${wireforge_flags}")
267
+ string(REPLACE "\\"" "'" wireforge_flags "${wireforge_flags}")
268
+ file(WRITE ${CMAKE_BINARY_DIR}/results/toolchain.json
269
+ "{
270
+ \\"cxx_compiler_id\\": \\"${CMAKE_CXX_COMPILER_ID}\\",
271
+ \\"cxx_compiler_version\\": \\"${CMAKE_CXX_COMPILER_VERSION}\\",
272
+ \\"cmake_version\\": \\"${wireforge_cmake_version}\\",
273
+ \\"gcovr_version\\": \\"${wireforge_gcovr_version}\\",
274
+ \\"build_type\\": \\"${CMAKE_BUILD_TYPE}\\",
275
+ \\"coverage\\": \\"${WIREFORGE_COVERAGE}\\",
276
+ \\"coverage_runtime\\": \\"${WIREFORGE_COVERAGE_RUNTIME}\\",
277
+ \\"sanitize\\": \\"${WIREFORGE_SANITIZE}\\",
278
+ \\"runtime_include_dir\\": \\"${wireforge_runtime_dir}\\",
279
+ \\"cxx_flags\\": \\"${wireforge_flags}\\"
280
+ }
281
+ ")
282
+ """
283
+
284
+ README = """\
285
+ # Generated tests of {name}
286
+
287
+ Generated by {tool}. Do not edit; regenerate instead.
288
+
289
+ GoogleTest suites for the generated C++ code of `{name}`. Every expectation comes from the
290
+ WireForge reference codec:
291
+
292
+ - `golden_test.cpp`: golden vectors round-trip through `serialize`/`deserialize`, encode to the
293
+ golden frame and loop back through `Link`, `Endpoint` and the `Handler`;
294
+ - `message_test.cpp`: defaults, minimum/maximum and out-of-range values, empty arrays, invalid enum
295
+ values and decode errors (wrong checksum, count above the maximum, constants, truncation);
296
+ - `interface_test.cpp`: dispatch of every message, byte-wise delivery, bytes before the sync,
297
+ corrupted frames and too small buffers.
298
+ {vector_note}
299
+ Profiles are separate executables: {executables}.
300
+
301
+ ```sh
302
+ cmake --preset dev && cmake --build --preset dev && ctest --preset dev
303
+ cmake --preset coverage && cmake --build --preset coverage --target coverage
304
+ cmake --preset asan-ubsan && cmake --build --preset asan-ubsan && ctest --preset asan-ubsan
305
+ cmake --preset clang-tidy && cmake --build --preset clang-tidy
306
+ ```
307
+
308
+ `WIREFORGE_RUNTIME_INCLUDE_DIR` points to the WireForge runtime headers. The `coverage` target
309
+ writes `results/` below the build directory (JUnit, CTest, coverage, toolchain); with
310
+ `WIREFORGE_COVERAGE_RUNTIME=ON` it also measures the runtime headers. The target needs
311
+ gcovr and fails below `WIREFORGE_COVERAGE_MIN_LINE` percent line coverage (default {threshold}) or
312
+ below 100 percent branch and function coverage (`WIREFORGE_COVERAGE_MIN_BRANCH`,
313
+ `WIREFORGE_COVERAGE_MIN_FUNCTION`) of the generated headers. GoogleTest 1.14 is taken from the
314
+ system or downloaded.
315
+ """
316
+
317
+
318
+ def _relative(target: Path, start: Path) -> str:
319
+ """CMake path of `target`: relative to `start` when they share a directory, else absolute."""
320
+ target, start = target.resolve(), start.resolve()
321
+ try:
322
+ common = Path(os.path.commonpath([target, start]))
323
+ except ValueError: # different drives on Windows
324
+ return target.as_posix()
325
+ if common == Path(common.anchor):
326
+ return target.as_posix()
327
+ return "${CMAKE_CURRENT_SOURCE_DIR}/" + Path(os.path.relpath(target, start)).as_posix()
328
+
329
+
330
+ def _profiles(project: Project) -> list[tuple[str, list[str]]]:
331
+ """Per profile: target name suffix and compile definitions."""
332
+ if not project.profiles:
333
+ return [("", [])]
334
+ result = []
335
+ for profile in project.profiles.values():
336
+ definitions = [f"{name}={value}" for name, value in profile.defines.items()]
337
+ definitions.append(f"WIREFORGE_PROFILE_{profile.name}")
338
+ result.append((f"_{profile.name}", definitions))
339
+ return result
340
+
341
+
342
+ def executables(project: Project, kind: str) -> list[str]:
343
+ """Names of the `tests`, `diff` or `fuzz` executables, one per profile."""
344
+ return [f"{project.name}_{kind}{suffix}" for suffix, _ in _profiles(project)]
345
+
346
+
347
+ def _fill(template: str, values: dict[str, str]) -> str:
348
+ for key, value in values.items():
349
+ template = template.replace(f"@{key}@", value)
350
+ return template
351
+
352
+
353
+ def sources(project: Project) -> tuple[str, ...]:
354
+ """Return the test sources of the test executable (the vector test only with vectors)."""
355
+ return (*SOURCES, VECTOR_SOURCE) if project.vectors else SOURCES
356
+
357
+
358
+ def _cmake(project: Project, directory: Path, location: Path, options: TestOptions) -> str:
359
+ profiles = _profiles(project)
360
+ values = {
361
+ "TOOL": f"WireForge {versioning.TOOL_VERSION}",
362
+ "NAME": project.name,
363
+ "THRESHOLD": str(options.threshold),
364
+ "RUNTIME": _relative(options.runtime, directory),
365
+ "MODEL": _relative(location, directory),
366
+ "SOURCES": " ".join(sources(project)),
367
+ "INCLUDES": "".join(
368
+ f" ${{WIREFORGE_MODEL_DIR}}/{path}" for path, _ in project.include_dirs
369
+ ),
370
+ "TARGETS": "".join(
371
+ f'wireforge_add_profile("{suffix}"{"".join(" " + item for item in definitions)})\n'
372
+ for suffix, definitions in profiles
373
+ ),
374
+ "TARGET_NAMES": " ".join(executables(project, "tests")),
375
+ }
376
+ model_dir = CMAKE_MODEL_DIR if project.include_dirs else ""
377
+ return _fill(CMAKE_HEAD + model_dir + CMAKE_BODY, values)
378
+
379
+
380
+ def _presets(project: Project) -> str:
381
+ variants = {
382
+ "dev": {},
383
+ "asan-ubsan": {"WIREFORGE_SANITIZE": "address,undefined"},
384
+ "tsan": {"WIREFORGE_SANITIZE": "thread"},
385
+ "coverage": {"WIREFORGE_COVERAGE": "ON"},
386
+ "clang-tidy": {"WIREFORGE_CLANG_TIDY": "ON"},
387
+ }
388
+ configure = [
389
+ {
390
+ "name": name,
391
+ "displayName": f"{project.name} tests ({name})",
392
+ "binaryDir": f"${{sourceDir}}/build/{name}",
393
+ "cacheVariables": {"CMAKE_BUILD_TYPE": "Debug", **cache},
394
+ }
395
+ for name, cache in variants.items()
396
+ ]
397
+ presets = {
398
+ "version": 3,
399
+ "cmakeMinimumRequired": {"major": 3, "minor": 21, "patch": 0},
400
+ "configurePresets": configure,
401
+ "buildPresets": [{"name": name, "configurePreset": name} for name in variants],
402
+ "testPresets": [
403
+ {"name": name, "configurePreset": name, "output": {"outputOnFailure": True}}
404
+ for name in variants
405
+ ],
406
+ }
407
+ return json.dumps(presets, indent=2) + "\n"
408
+
409
+
410
+ @dataclass(frozen=True)
411
+ class TestOptions:
412
+ """Settings of the generated test project."""
413
+
414
+ plugins: Path | None = None
415
+ runtime: Path = RUNTIME_INCLUDE
416
+ threshold: int = DEFAULT_COVERAGE
417
+
418
+
419
+ def render(
420
+ project: Project, location: Path, directory: Path, options: TestOptions | None = None
421
+ ) -> dict[str, str]:
422
+ """Files of the test project (name -> content) for a project located at `location`.
423
+
424
+ `directory` is where the files will be written; the CMake paths to the runtime headers and the
425
+ project's include directories are relative to it. Raises CodecError when the reference codec
426
+ cannot encode the samples (e.g. a custom checksum without plugin).
427
+ """
428
+ options = options or TestOptions()
429
+ plugins = golden.load_plugins(options.plugins) if options.plugins else None
430
+ golden_vectors = golden.vectors(project, plugins)
431
+ profiles = test_cases.cases(project, plugins)
432
+ report = layout.analyze(project)
433
+ runs = vectors.run(project, plugins)
434
+ names = ", ".join(f"`{name}`" for name in executables(project, "tests"))
435
+ tool = f"WireForge {versioning.TOOL_VERSION}"
436
+ threshold = options.threshold
437
+ return {
438
+ "CMakeLists.txt": _cmake(project, directory, location, options),
439
+ "CMakePresets.json": _presets(project),
440
+ ".clang-tidy": CLANG_TIDY.format(name=project.name),
441
+ "README.md": README.format(
442
+ name=project.name,
443
+ tool=tool,
444
+ executables=names,
445
+ threshold=threshold,
446
+ vector_note=VECTOR_NOTE if project.vectors else "",
447
+ ),
448
+ "test_support.hpp": cpp_tests.SUPPORT_HEADER,
449
+ "wire_support.hpp": cpp_tests.WIRE_SUPPORT_HEADER,
450
+ "diff_harness.cpp": cpp_tests.diff_harness_source(project),
451
+ "fuzz_target.cpp": cpp_tests.fuzz_source(project),
452
+ "selftest.cpp": cpp_tests.selftest_source(project, golden_vectors, profiles, runs),
453
+ "handlers.hpp": cpp_tests.handlers_header(project),
454
+ "golden_test.cpp": cpp_tests.golden_test_source(project, golden_vectors),
455
+ "message_test.cpp": cpp_tests.message_test_source(project, profiles, report),
456
+ "interface_test.cpp": cpp_tests.interface_test_source(project, profiles, report),
457
+ } | (
458
+ {VECTOR_SOURCE: cpp_tests.hlr_vector_test_source(project, runs)} if project.vectors else {}
459
+ )
460
+
461
+
462
+ def write_test_project(
463
+ project: Project, location: Path, output: Path, options: TestOptions | None = None
464
+ ) -> list[Issue]:
465
+ """Write `<output>/<project>/generated_tests/`; unchanged files are not rewritten."""
466
+ directory = output / project.name / DIRECTORY
467
+ try:
468
+ files = render(project, location, directory, options)
469
+ except CodecError as error:
470
+ return [Issue(str(location), None, "", f"cannot compute the test expectations: {error}")]
471
+ except CoveragePointError as error:
472
+ return [Issue(str(location), None, "", str(error))]
473
+ for name, text in files.items():
474
+ target = directory / name
475
+ target.parent.mkdir(parents=True, exist_ok=True)
476
+ if not target.exists() or target.read_text(encoding="utf-8") != text:
477
+ target.write_text(text, encoding="utf-8")
478
+ return []