Review fix pass 1 on the task 7 work; four findings, all closed. 1. The Qt demo's region sat across the middle of the window, which is exactly where its own scripted input check pans: after the check's box zoom the press opened DragMode::MoveRegion, the x range never moved, and the check printed 'input check pan FAIL' -- under an exit 0, because the verdict was printed and not returned. The region now rides the left sixth (as the GLFW demo's does), both checks pass, and the verdict is wired to the exit status. The same audit found two more printed-only verdicts -- the GTK demo's scripted-resize layout check and its screenshot write (and Qt's) -- now wired too. What stays printed-only is the screenshot line of the GLFW host and the ImGui widget, where the dump happens inside the adapter's frame loop. 2. A follow-ing link group was invisible to the input layer: a member's own axis must be XFollow::Off (link_x() refuses a Live member), so Controller's x_zoom_anchor() extrapolated a coordinate from the pointer instead of using the live edge and a wheel over a row's axis strip walked the window back by 19% of its width (behind 0.000000 -> 0.285809 s). The group now publishes its own edge to every member (Axis::live_owner + Plot::set_x_live_owner, written by Figure::push_link, cleared when the group stops following or the row unlinks), and x_zoom_anchor() reads it. Public-header addition, called out. Measured after: behind 0.012992 s and the window's right edge bit-identical to the edge the group published at the gesture. 3. The multiplot summary printed x_link_count() -- a number of groups -- under 'rows in it'. It now reports the members and the group count separately, and every row line carries its own group id. 4. Host::controller_at() promised to clamp a pixel in a row gap to the nearest row and only clamped below the last one, so a gap pixel went to row 0. It now selects by distance, and tests/test_glfw_host.cpp pins it with a 40 px gap -- the first coverage of the host's figure routing, which needs no window. New cases: tests/test_controller.cpp x2 (the anchor and the hint's lifecycle), tests/test_glfw_host.cpp x3 (routing, the gap, the single-plot fallback): 229 cases, 0 failed, ASan clean. All five demos exit 0, the three self-checking modes exit 0, and the amended assertions were confirmed failing against the pre-fix behaviour.
177 lines
8.3 KiB
CMake
177 lines
8.3 KiB
CMake
# EGL powers the surfaceless headless context the GL tests assert pixels
|
|
# against. It is a *test-only* dependency: plot_core never links or includes it,
|
|
# so a machine without EGL can still build the library (-DPLOT_BUILD_TESTS=OFF).
|
|
# Prefer CMake's own module, fall back to pkg-config, and fail loudly if neither
|
|
# can supply EGL rather than silently dropping the GL tests.
|
|
find_package(EGL QUIET)
|
|
if(NOT EGL_FOUND)
|
|
find_package(PkgConfig QUIET)
|
|
if(PkgConfig_FOUND)
|
|
pkg_check_modules(EGL_PC QUIET IMPORTED_TARGET egl)
|
|
endif()
|
|
endif()
|
|
|
|
if(TARGET EGL::EGL)
|
|
set(plot_egl_library EGL::EGL)
|
|
elseif(TARGET PkgConfig::EGL_PC)
|
|
set(plot_egl_library PkgConfig::EGL_PC)
|
|
else()
|
|
message(FATAL_ERROR
|
|
"plot: PLOT_BUILD_TESTS=ON requires EGL (EGL/egl.h plus libEGL) for the headless "
|
|
"OpenGL context used by the GL tests. Install it (e.g. libegl-dev, libegl1-mesa-dev "
|
|
"or mesa-libEGL-devel) or configure with -DPLOT_BUILD_TESTS=OFF.")
|
|
endif()
|
|
|
|
find_package(Threads REQUIRED) # std::thread in the push/read race test
|
|
|
|
add_executable(plot_tests
|
|
main.cpp
|
|
test_transform.cpp
|
|
test_gl_context.cpp
|
|
test_primitives.cpp
|
|
test_ring_buffer.cpp
|
|
test_minmax_pyramid.cpp
|
|
test_series.cpp
|
|
test_ticks.cpp
|
|
test_measure.cpp
|
|
test_plot.cpp
|
|
test_figure.cpp
|
|
test_render_smoke.cpp
|
|
test_controller.cpp
|
|
egl_headless.cpp
|
|
)
|
|
target_link_libraries(plot_tests PRIVATE plot::core ${plot_egl_library} Threads::Threads)
|
|
|
|
# The Dear ImGui widget is the one adapter with a test here: its input translation
|
|
# (edge synthesis, the hover gate, drag capture, wheel containment) is logic this
|
|
# project owns, not a toolkit's. The suite is otherwise ImGui-free, so the source
|
|
# and the link are added only when the adapter exists -- which the top-level
|
|
# CMakeLists.txt guarantees by adding the adapters before this directory.
|
|
if(TARGET plot::imgui)
|
|
target_sources(plot_tests PRIVATE test_imgui_widget.cpp)
|
|
target_link_libraries(plot_tests PRIVATE plot::imgui imgui)
|
|
endif()
|
|
|
|
# The GLFW host's figure-mode routing -- which row's controller a pointer pixel reaches --
|
|
# is this project's logic too, and it needs no window: the cases drive `controller_at()`
|
|
# against the rects `Figure::layout()` computes. Guarded on the adapter's target for the
|
|
# same reason as the ImGui block above.
|
|
if(TARGET plot::glfw)
|
|
target_sources(plot_tests PRIVATE test_glfw_host.cpp)
|
|
target_link_libraries(plot_tests PRIVATE plot::glfw)
|
|
endif()
|
|
|
|
# The Qt adapter's pointer anchor is deliberately toolkit-free (see
|
|
# adapters/qt6/qt_pointer.hpp), so this one piece of that adapter's translation is
|
|
# tested without linking Qt -- and without `PLOT_BUILD_QT6`, since no Qt target has
|
|
# to exist for it to compile.
|
|
target_sources(plot_tests PRIVATE test_qt_pointer.cpp)
|
|
|
|
# Same for the GTK adapter's input translation (see adapters/gtk4/gtk_input.hpp):
|
|
# the device-pixel mapping it derives from GTK's two sizes, the pixel space its drag
|
|
# anchor is kept in, the wheel's unit and sign and the modifier mask are this
|
|
# project's logic, and a headless X server at an integer scale can exercise only
|
|
# part of it.
|
|
target_sources(plot_tests PRIVATE test_gtk_input.cpp)
|
|
target_include_directories(plot_tests PRIVATE "${CMAKE_SOURCE_DIR}/adapters")
|
|
|
|
# Same switches the library is built with: the cost tests read the counters
|
|
# behind them (see include/plot/data/detail/{cover,merge}_count.hpp).
|
|
target_compile_definitions(plot_tests PRIVATE PLOT_PYRAMID_COUNT_COVERS
|
|
PLOT_PYRAMID_COUNT_MERGES
|
|
PLOT_RING_STATS
|
|
PLOT_COUNT_MEASURE_RECOMPUTES
|
|
# The committed source root, so the
|
|
# atlas-generator case can run
|
|
# tools/fonts/gen_atlas.py --check
|
|
# against the committed atlas.
|
|
PLOT_SOURCE_DIR="${CMAKE_SOURCE_DIR}")
|
|
plot_apply_warnings(plot_tests)
|
|
|
|
add_test(NAME plot_tests COMMAND plot_tests)
|
|
|
|
# The GL tests create a real EGL context, and llvmpipe's teardown leaks driver
|
|
# allocations that are not this library's. The suppressions file names those
|
|
# libraries and nothing else (see cmake/plot_asan_suppressions.txt); a report from
|
|
# anything under include/plot/ or src/ is still an error. The environment is set
|
|
# on the test rather than in a preset so `ctest --test-dir build/asan` gets it
|
|
# however it is launched, and print_suppressions makes the counts visible -- a run
|
|
# must show what it suppressed, not hide it.
|
|
if(PLOT_ASAN_ENABLED)
|
|
set_tests_properties(plot_tests PROPERTIES ENVIRONMENT
|
|
"ASAN_OPTIONS=detect_leaks=1:print_suppressions=1:suppressions=${CMAKE_SOURCE_DIR}/cmake/plot_asan_suppressions.txt")
|
|
endif()
|
|
|
|
# --- public headers: standalone compilation and umbrella coverage -------------
|
|
#
|
|
# The plan's Global Constraints require every header under include/plot/ to
|
|
# compile standalone, and include/plot/plot.hpp to include every public one
|
|
# (never an adapter). Both were verified by hand and nothing kept them true, so
|
|
# a header added later could break either silently. These targets make the build
|
|
# enforce both and the ctest entries below make the suite fail when the checks
|
|
# do. The header list is globbed at configure time (CONFIGURE_DEPENDS, so adding
|
|
# or removing a header re-globs) -- a hand-kept list would defeat the point.
|
|
file(GLOB_RECURSE plot_public_headers CONFIGURE_DEPENDS
|
|
RELATIVE "${CMAKE_SOURCE_DIR}/include"
|
|
"${CMAKE_SOURCE_DIR}/include/plot/*.hpp")
|
|
list(SORT plot_public_headers)
|
|
|
|
# One generated TU per header, each holding only that header's #include. An
|
|
# OBJECT library: this is a compile check, with no link step, so any header that
|
|
# needs something it does not itself include fails the build here.
|
|
set(plot_header_check_sources "")
|
|
foreach(plot_header IN LISTS plot_public_headers)
|
|
string(MAKE_C_IDENTIFIER "${plot_header}" plot_header_slug)
|
|
set(plot_header_check_source "${CMAKE_CURRENT_BINARY_DIR}/header_checks/${plot_header_slug}.cpp")
|
|
set(plot_header_check_content
|
|
"// Generated by tests/CMakeLists.txt: <${plot_header}> must compile on its own.\n#include <${plot_header}>\n")
|
|
file(GENERATE OUTPUT "${plot_header_check_source}" CONTENT "${plot_header_check_content}")
|
|
list(APPEND plot_header_check_sources "${plot_header_check_source}")
|
|
endforeach()
|
|
|
|
add_library(plot_header_self_containment OBJECT ${plot_header_check_sources})
|
|
target_link_libraries(plot_header_self_containment PRIVATE plot::core)
|
|
plot_apply_warnings(plot_header_self_containment)
|
|
|
|
# The umbrella on its own: the generated TU includes only <plot/plot.hpp> and
|
|
# constructs one of each layer's entry type, so an umbrella that misses a header
|
|
# -- or that is unusable without further includes -- fails to build. The test
|
|
# runs the linked binary, so a build that produced nothing usable still fails.
|
|
set(plot_umbrella_check_source "${CMAKE_CURRENT_BINARY_DIR}/umbrella_check.cpp")
|
|
file(GENERATE OUTPUT "${plot_umbrella_check_source}" CONTENT
|
|
"// Generated by tests/CMakeLists.txt: the umbrella must be usable by itself.
|
|
#include <plot/plot.hpp>
|
|
|
|
int main() {
|
|
plot::Plot plot;
|
|
plot::Figure figure(2);
|
|
plot::Renderer renderer;
|
|
plot::data::StreamSeries series(16, 0.0, 1.0);
|
|
plot::input::Controller controller(plot);
|
|
(void)figure;
|
|
(void)renderer;
|
|
(void)series;
|
|
(void)controller;
|
|
return 0;
|
|
}
|
|
")
|
|
add_executable(plot_umbrella_check "${plot_umbrella_check_source}")
|
|
target_link_libraries(plot_umbrella_check PRIVATE plot::core)
|
|
plot_apply_warnings(plot_umbrella_check)
|
|
|
|
# Coverage check on the umbrella. Included here so a public header missing from
|
|
# plot.hpp stops the configure; the ctest entry below runs the same script again
|
|
# so the check is also a visible test.
|
|
set(PLOT_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include")
|
|
set(PLOT_UMBRELLA "${CMAKE_SOURCE_DIR}/include/plot/plot.hpp")
|
|
include("${CMAKE_SOURCE_DIR}/cmake/plot_umbrella_check.cmake")
|
|
|
|
add_test(NAME plot_headers_self_contained
|
|
COMMAND ${CMAKE_COMMAND} --build "${CMAKE_BINARY_DIR}" --target plot_header_self_containment)
|
|
add_test(NAME plot_umbrella_covers_public_headers
|
|
COMMAND ${CMAKE_COMMAND}
|
|
-DPLOT_INCLUDE_DIR=${CMAKE_SOURCE_DIR}/include
|
|
-DPLOT_UMBRELLA=${CMAKE_SOURCE_DIR}/include/plot/plot.hpp
|
|
-P "${CMAKE_SOURCE_DIR}/cmake/plot_umbrella_check.cmake")
|
|
add_test(NAME plot_umbrella_check COMMAND plot_umbrella_check)
|