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.
- wireforge/__init__.py +3 -0
- wireforge/_data/cmake/WireForgeConfig.cmake +29 -0
- wireforge/_data/cmake/WireForgeConfigVersion.cmake +15 -0
- wireforge/_data/cmake/WireForgeGenerate.cmake +64 -0
- wireforge/_data/runtime/LICENSE +21 -0
- wireforge/_data/runtime/include/wireforge/array_access.hpp +35 -0
- wireforge/_data/runtime/include/wireforge/bit_utils.hpp +226 -0
- wireforge/_data/runtime/include/wireforge/byte_io.hpp +387 -0
- wireforge/_data/runtime/include/wireforge/checksum.hpp +201 -0
- wireforge/_data/runtime/include/wireforge/codec_support.hpp +412 -0
- wireforge/_data/runtime/include/wireforge/config.hpp +41 -0
- wireforge/_data/runtime/include/wireforge/delegate.hpp +101 -0
- wireforge/_data/runtime/include/wireforge/dispatcher.hpp +339 -0
- wireforge/_data/runtime/include/wireforge/expr_support.hpp +38 -0
- wireforge/_data/runtime/include/wireforge/frame_decoder.hpp +262 -0
- wireforge/_data/runtime/include/wireforge/frame_layout.hpp +20 -0
- wireforge/_data/runtime/include/wireforge/link.hpp +223 -0
- wireforge/_data/runtime/include/wireforge/lock.hpp +146 -0
- wireforge/_data/runtime/include/wireforge/ring_buffer.hpp +226 -0
- wireforge/_data/runtime/include/wireforge/span.hpp +116 -0
- wireforge/_data/runtime/include/wireforge/static_vector.hpp +264 -0
- wireforge/_data/runtime/include/wireforge/status.hpp +75 -0
- wireforge/_data/runtime/include/wireforge/transport.hpp +95 -0
- wireforge/_data/runtime/include/wireforge/validation.hpp +153 -0
- wireforge/_data/runtime/include/wireforge/version.hpp +22 -0
- wireforge/_data/runtime/requirements.yaml +438 -0
- wireforge/_data/schema/deviations.schema.json +47 -0
- wireforge/_data/schema/fragment.schema.json +79 -0
- wireforge/_data/schema/llr_ids.schema.json +25 -0
- wireforge/_data/schema/project.schema.json +1240 -0
- wireforge/_data/schema/runtime_requirements.schema.json +34 -0
- wireforge/_data/ui/assets/index-6Pomu6U4.js +44 -0
- wireforge/_data/ui/assets/index-GVdDyUAG.css +1 -0
- wireforge/_data/ui/index.html +13 -0
- wireforge/bundle.py +177 -0
- wireforge/ci_check.py +155 -0
- wireforge/cli.py +617 -0
- wireforge/codec.py +897 -0
- wireforge/compat.py +536 -0
- wireforge/coverage_points.py +64 -0
- wireforge/cpp.py +273 -0
- wireforge/cpp_codec.py +440 -0
- wireforge/cpp_interface.py +874 -0
- wireforge/cpp_tests.py +1993 -0
- wireforge/differential.py +586 -0
- wireforge/docs_markdown.py +72 -0
- wireforge/expr.py +598 -0
- wireforge/findings.py +187 -0
- wireforge/fmt.py +351 -0
- wireforge/generate.py +644 -0
- wireforge/golden.py +253 -0
- wireforge/hlr_import.py +112 -0
- wireforge/impact.py +249 -0
- wireforge/importers.py +402 -0
- wireforge/issues.py +27 -0
- wireforge/layout.py +681 -0
- wireforge/lifecycle.py +1266 -0
- wireforge/lifecycle_results.py +205 -0
- wireforge/lifecycle_rules.py +78 -0
- wireforge/lifecycle_runtime.py +375 -0
- wireforge/llr.py +693 -0
- wireforge/llr_ids.py +104 -0
- wireforge/loader.py +422 -0
- wireforge/mcp_server.py +357 -0
- wireforge/model.py +322 -0
- wireforge/plain.py +15 -0
- wireforge/py.typed +0 -0
- wireforge/reqif.py +289 -0
- wireforge/resources.py +43 -0
- wireforge/results.py +377 -0
- wireforge/runtime_reqs.py +147 -0
- wireforge/schema.py +151 -0
- wireforge/server/__init__.py +1 -0
- wireforge/server/app.py +619 -0
- wireforge/server/oidc.py +99 -0
- wireforge/server/storage.py +146 -0
- wireforge/service.py +1354 -0
- wireforge/templates/command/project.yaml +38 -0
- wireforge/templates/raw/project.yaml +28 -0
- wireforge/templates/telemetry/project.yaml +51 -0
- wireforge/test_cases.py +475 -0
- wireforge/test_project.py +478 -0
- wireforge/trace.py +442 -0
- wireforge/validate.py +1241 -0
- wireforge/vcs.py +44 -0
- wireforge/vectors.py +222 -0
- wireforge/versioning.py +137 -0
- wireforge/wireshark.py +792 -0
- wireforge/wireshark_runtime.lua +725 -0
- wireforge-0.1.0.dist-info/METADATA +91 -0
- wireforge-0.1.0.dist-info/RECORD +96 -0
- wireforge-0.1.0.dist-info/WHEEL +5 -0
- wireforge-0.1.0.dist-info/entry_points.txt +2 -0
- wireforge-0.1.0.dist-info/licenses/LICENSE +98 -0
- wireforge-0.1.0.dist-info/licenses/runtime/LICENSE +21 -0
- 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 []
|