diff --git a/.github/workflows/main-validation.yml b/.github/workflows/main-validation.yml new file mode 100644 index 0000000..d138448 --- /dev/null +++ b/.github/workflows/main-validation.yml @@ -0,0 +1,79 @@ +name: Main Validation + +on: + push: + branches: + - main + +jobs: + full-validation: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - name: linux-full-release + os: ubuntu-latest + configure_preset: linux-ci + build_preset: linux-ci + build_config_flag: "" + run_full: true + - name: macos-build-test + os: macos-latest + configure_preset: macos-release + build_preset: macos-release + build_config_flag: "" + run_full: false + - name: windows-build-test + os: windows-latest + configure_preset: windows-release + build_preset: windows-release + build_config_flag: --config Release + run_full: false + + steps: + - uses: actions/checkout@v4 + + - name: Install Ninja + uses: seanmiddleditch/gha-setup-ninja@v5 + + - name: Cache FetchContent dependencies + uses: actions/cache@v4 + with: + path: .fc + key: ${{ runner.os }}-fc-${{ hashFiles('CMakeLists.txt', 'cmake/**/*.cmake', 'CMakePresets.json') }} + + - name: Configure + run: >- + cmake --preset ${{ matrix.configure_preset }} + -DRENDER_ALLOW_FETCHCONTENT=ON + -DFETCHCONTENT_BASE_DIR=${{ github.workspace }}/.fc + + - name: Build + run: >- + cmake --build --preset ${{ matrix.build_preset }} + ${{ matrix.build_config_flag }} + + - name: Run unit + headless checks + run: >- + ctest --test-dir out/build/${{ matrix.configure_preset }} + ${{ matrix.os == 'windows-latest' && '-C Release' || '' }} + --output-on-failure -L "unit|headless" + + - name: Run Linux full validation + if: matrix.run_full + run: | + cmake --build --preset ${{ matrix.build_preset }} --target render_shader_check + cmake --build --preset ${{ matrix.build_preset }} --target render_package_validate + + - name: Upload Linux artifacts + if: matrix.run_full + uses: actions/upload-artifact@v4 + with: + name: main-validation-${{ matrix.name }} + path: | + out/build/${{ matrix.configure_preset }}/logs + out/build/${{ matrix.configure_preset }}/Testing/Temporary + out/build/${{ matrix.configure_preset }}/package + if-no-files-found: warn diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml new file mode 100644 index 0000000..576a214 --- /dev/null +++ b/.github/workflows/pr-validation.yml @@ -0,0 +1,83 @@ +name: PR Validation + +on: + pull_request: + +jobs: + build-and-test: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - name: linux-full + os: ubuntu-latest + configure_preset: linux-ci + build_preset: linux-ci + build_config_flag: "" + full_validation: true + - name: macos-build-test + os: macos-latest + configure_preset: macos-debug + build_preset: macos-debug + build_config_flag: "" + full_validation: false + - name: windows-build-test + os: windows-latest + configure_preset: windows-debug + build_preset: windows-debug + build_config_flag: --config Debug + full_validation: false + + steps: + - uses: actions/checkout@v4 + + - name: Install Ninja + uses: seanmiddleditch/gha-setup-ninja@v5 + + - name: Cache FetchContent dependencies + uses: actions/cache@v4 + with: + path: .fc + key: ${{ runner.os }}-fc-${{ hashFiles('CMakeLists.txt', 'cmake/**/*.cmake', 'CMakePresets.json') }} + + - name: Configure + run: >- + cmake --preset ${{ matrix.configure_preset }} + -DRENDER_ALLOW_FETCHCONTENT=ON + -DFETCHCONTENT_BASE_DIR=${{ github.workspace }}/.fc + + - name: Build + run: >- + cmake --build --preset ${{ matrix.build_preset }} + ${{ matrix.build_config_flag }} + + - name: Run unit tests + run: >- + ctest --test-dir out/build/${{ matrix.configure_preset }} + ${{ matrix.os == 'windows-latest' && '-C Debug' || '' }} + --output-on-failure -L unit + + - name: Run headless smoke validation + run: >- + ctest --test-dir out/build/${{ matrix.configure_preset }} + ${{ matrix.os == 'windows-latest' && '-C Debug' || '' }} + --output-on-failure -L headless + + - name: Run shader and packaging checks (Linux full lane) + if: matrix.full_validation + run: | + cmake --build --preset ${{ matrix.build_preset }} --target render_shader_check + cmake --build --preset ${{ matrix.build_preset }} --target render_package_validate + + - name: Upload Linux validation artifacts + if: matrix.full_validation + uses: actions/upload-artifact@v4 + with: + name: validation-${{ matrix.name }} + path: | + out/build/${{ matrix.configure_preset }}/logs + out/build/${{ matrix.configure_preset }}/Testing/Temporary + out/build/${{ matrix.configure_preset }}/package + if-no-files-found: warn diff --git a/CMakeLists.txt b/CMakeLists.txt index ce134ec..7c6f0b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ include(Dependencies) include(Versioning) include(Utils) include(ShaderCompilation) +include(GNUInstallDirs) render_prevent_in_source_builds() render_apply_standard_settings() @@ -26,6 +27,10 @@ render_setup_dependencies() render_setup_versioning() render_setup_shader_compilation() +if(RENDER_BUILD_TESTS) + include(CTest) +endif() + add_library(render_core STATIC engine/core/types.hpp engine/core/math.hpp @@ -149,8 +154,6 @@ if(RENDER_BUILD_TOOLS) endif() if(RENDER_BUILD_TESTS) - include(CTest) - add_executable(render_platform_types_tests tests/platform/platform_types_tests.cpp) target_link_libraries(render_platform_types_tests PRIVATE render::engine) render_apply_project_options(render_platform_types_tests) @@ -173,10 +176,78 @@ if(RENDER_BUILD_TESTS) render_apply_project_options(render_filesystem_tests) render_apply_warnings(render_filesystem_tests) - add_test(NAME render_platform_types_tests COMMAND render_platform_types_tests) - add_test(NAME render_core_runtime_tests COMMAND render_core_runtime_tests) - add_test(NAME render_serialization_tests COMMAND render_serialization_tests) - add_test(NAME render_filesystem_tests COMMAND render_filesystem_tests) + add_executable(render_headless_validation tests/headless/headless_validation.cpp) + target_link_libraries(render_headless_validation PRIVATE render::filesystem render::serialization) + render_apply_project_options(render_headless_validation) + render_apply_warnings(render_headless_validation) + + add_test(NAME unit.platform.types COMMAND render_platform_types_tests) + set_tests_properties(unit.platform.types PROPERTIES LABELS "unit;platform") + add_test(NAME unit.core.runtime COMMAND render_core_runtime_tests) + set_tests_properties(unit.core.runtime PROPERTIES LABELS "unit;core") + add_test(NAME unit.serialization.roundtrip COMMAND render_serialization_tests) + set_tests_properties(unit.serialization.roundtrip PROPERTIES LABELS "unit;serialization") + add_test(NAME unit.filesystem.service COMMAND render_filesystem_tests) + set_tests_properties(unit.filesystem.service PROPERTIES LABELS "unit;filesystem") + add_test(NAME headless.smoke.startup COMMAND render_headless_validation) + set_tests_properties(headless.smoke.startup PROPERTIES LABELS "headless;smoke") + + add_custom_target(render_test_unit + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --label-regex unit + DEPENDS render_platform_types_tests render_core_runtime_tests render_serialization_tests render_filesystem_tests + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + COMMENT "Running render unit tests" + ) + + add_custom_target(render_test_headless + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --label-regex headless + DEPENDS render_headless_validation + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + COMMENT "Running render headless smoke validation" + ) +endif() + +set(RENDER_PACKAGE_STAGE_DIR "${CMAKE_BINARY_DIR}/package/stage") +set(RENDER_PACKAGE_BIN_SUBDIR "${CMAKE_INSTALL_BINDIR}") +set(RENDER_PACKAGE_SHADER_RELATIVE_DIR "shaders/bin") + +install(TARGETS render_shell RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") +install(DIRECTORY "${CMAKE_BINARY_DIR}/bin/shaders/bin/" DESTINATION "shaders/bin" OPTIONAL) + +add_custom_target(render_package_stage + COMMAND ${CMAKE_COMMAND} -E rm -rf "${RENDER_PACKAGE_STAGE_DIR}" + DEPENDS render_shell render_shader_check + COMMENT "Staging render package layout" +) +if(CMAKE_CONFIGURATION_TYPES) + add_custom_command(TARGET render_package_stage + COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --prefix "${RENDER_PACKAGE_STAGE_DIR}" --config $ + ) +else() + add_custom_command(TARGET render_package_stage + COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --prefix "${RENDER_PACKAGE_STAGE_DIR}" + ) +endif() + +add_custom_target(render_package_validate + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/logs" + COMMAND ${CMAKE_COMMAND} + -DRENDER_PACKAGE_STAGE_DIR=${RENDER_PACKAGE_STAGE_DIR} + -DRENDER_PACKAGE_BIN_SUBDIR=${RENDER_PACKAGE_BIN_SUBDIR} + -DRENDER_PACKAGE_SHADER_RELATIVE_DIR=${RENDER_PACKAGE_SHADER_RELATIVE_DIR} + -DRENDER_PACKAGE_LOG_FILE=${CMAKE_BINARY_DIR}/logs/package_validation.log + -P "${CMAKE_SOURCE_DIR}/cmake/validate_package_layout.cmake" + DEPENDS render_package_stage + COMMENT "Validating package stage and archive creation" + VERBATIM +) + +add_custom_target(render_validate_all + DEPENDS render_shader_check render_package_validate + COMMENT "Build/render validation umbrella target" +) +if(RENDER_BUILD_TESTS) + add_dependencies(render_validate_all render_test_unit render_test_headless) endif() message(STATUS "Configured ${PROJECT_NAME} ${PROJECT_VERSION} for ${CMAKE_SYSTEM_NAME}") diff --git a/CMakePresets.json b/CMakePresets.json index f31ddb6..23e4330 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -76,6 +76,14 @@ "CMAKE_BUILD_TYPE": "Debug" } }, + { + "name": "linux-ci", + "inherits": ["linux-debug"], + "cacheVariables": { + "RENDER_BGFX_BUILD_TOOLS": "ON", + "RENDER_REQUIRE_SHADER_COMPILATION": "ON" + } + }, { "name": "linux-release", "inherits": ["linux-base"], @@ -113,6 +121,10 @@ "name": "linux-debug", "configurePreset": "linux-debug" }, + { + "name": "linux-ci", + "configurePreset": "linux-ci" + }, { "name": "linux-release", "configurePreset": "linux-release" @@ -133,6 +145,40 @@ "output": { "outputOnFailure": true } + }, + { + "name": "linux-unit", + "configurePreset": "linux-debug", + "output": { + "outputOnFailure": true + }, + "execution": { + "stopOnFailure": true + }, + "filter": { + "label": { + "include": [ + "unit" + ] + } + } + }, + { + "name": "linux-headless", + "configurePreset": "linux-debug", + "output": { + "outputOnFailure": true + }, + "execution": { + "stopOnFailure": true + }, + "filter": { + "label": { + "include": [ + "headless" + ] + } + } } ] } diff --git a/README.md b/README.md index f432460..2dbc53c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ `render` is a desktop-first C++20 codebase for building a custom 3D rendering engine and a game layer on top of it. -## Current milestone (Statement 7) +## Current milestone (Statement 8) The repository now includes: @@ -12,6 +12,9 @@ The repository now includes: - a runnable shell that clears and submits minimal geometry each frame - a deterministic serialization foundation for save, recipe, item, room, and network payload schemas - an engine-owned filesystem/resource storage layer with category-based roots for source data, cache, saves, logs, screenshots, and crash dumps +- CTest-integrated unit + headless smoke validation entry points +- shader and packaging validation targets suitable for CI quality gates +- GitHub Actions workflows for PR and main-branch validation ## Key boundaries @@ -38,3 +41,5 @@ cmake --build --preset linux-debug - `docs/architecture.md` — platform + renderer boundary and layering notes - `docs/serialization.md` — canonical binary encoding, schema/version policy, and migration hooks - `docs/filesystem.md` — canonical path categories, writable layout, safe write behavior, and dev vs packaged lookup policy +- `docs/testing.md` — testing categories, labels, and local validation commands +- `docs/ci.md` — CI workflow matrix, blocking policy, and artifacts diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index 88e7379..355839e 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -4,6 +4,7 @@ set(RENDER_DEPENDENCY_ROOT "${CMAKE_SOURCE_DIR}/third_party" CACHE PATH "Default set(RENDER_SDL3_ROOT "" CACHE PATH "Optional local path override for SDL3") set(RENDER_BGFX_ROOT "" CACHE PATH "Optional local path override for bgfx.cmake (contains bgfx/bx/bimg)") option(RENDER_ALLOW_FETCHCONTENT "Allow FetchContent fallbacks for dependencies" OFF) +option(RENDER_BGFX_BUILD_TOOLS "Build bgfx tool targets such as shaderc when available" OFF) function(render_setup_dependencies) add_library(render_dependencies INTERFACE) @@ -111,7 +112,11 @@ function(render_configure_bgfx_bundle) message(STATUS "Dependency bgfx/bx/bimg: ${source_hint} path -> ${chosen_path}") if(EXISTS "${chosen_path}/CMakeLists.txt") set(BGFX_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) - set(BGFX_BUILD_TOOLS OFF CACHE BOOL "" FORCE) + if(RENDER_BGFX_BUILD_TOOLS) + set(BGFX_BUILD_TOOLS ON CACHE BOOL "" FORCE) + else() + set(BGFX_BUILD_TOOLS OFF CACHE BOOL "" FORCE) + endif() add_subdirectory("${chosen_path}" "${CMAKE_BINARY_DIR}/_deps/bgfx-build" EXCLUDE_FROM_ALL) else() message(FATAL_ERROR "bgfx.cmake path does not contain CMakeLists.txt: ${chosen_path}") @@ -121,6 +126,11 @@ function(render_configure_bgfx_bundle) if(RENDER_ALLOW_FETCHCONTENT) message(STATUS "Dependency bgfx/bx/bimg: FetchContent fallback enabled") + if(RENDER_BGFX_BUILD_TOOLS) + set(BGFX_BUILD_TOOLS ON CACHE BOOL "" FORCE) + else() + set(BGFX_BUILD_TOOLS OFF CACHE BOOL "" FORCE) + endif() FetchContent_Declare( bgfx_bundle GIT_REPOSITORY https://github.com/bkaradzic/bgfx.cmake.git diff --git a/cmake/ShaderCompilation.cmake b/cmake/ShaderCompilation.cmake index 634e8c4..7713903 100644 --- a/cmake/ShaderCompilation.cmake +++ b/cmake/ShaderCompilation.cmake @@ -1,15 +1,29 @@ function(render_setup_shader_compilation) set(RENDER_SHADER_OUTPUT_DIR "${CMAKE_BINARY_DIR}/bin/shaders/bin" CACHE PATH "Directory for compiled shader binaries") set(RENDER_BGFX_SHADERC "" CACHE FILEPATH "Path to bgfx shaderc executable") + option(RENDER_REQUIRE_SHADER_COMPILATION "Fail configure/build checks when shader compilation is unavailable" OFF) + + set(shaderc_candidate "${RENDER_BGFX_SHADERC}") + if(NOT shaderc_candidate AND TARGET shaderc) + set(shaderc_candidate "$") + endif() add_custom_target(render_shaders COMMAND ${CMAKE_COMMAND} -E make_directory "${RENDER_SHADER_OUTPUT_DIR}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/logs" COMMAND ${CMAKE_COMMAND} -DRENDER_SOURCE_DIR=${CMAKE_SOURCE_DIR} -DRENDER_SHADER_OUTPUT_DIR=${RENDER_SHADER_OUTPUT_DIR} - -DRENDER_BGFX_SHADERC=${RENDER_BGFX_SHADERC} + -DRENDER_BGFX_SHADERC=${shaderc_candidate} + -DRENDER_REQUIRE_SHADER_COMPILATION=${RENDER_REQUIRE_SHADER_COMPILATION} + -DRENDER_SHADER_LOG_FILE=${CMAKE_BINARY_DIR}/logs/shader_compilation.log -P "${CMAKE_SOURCE_DIR}/cmake/compile_shaders.cmake" - COMMENT "Compiling bgfx shaders (if shaderc is available)" + COMMENT "Compiling bgfx shaders" VERBATIM ) + + add_custom_target(render_shader_check + DEPENDS render_shaders + COMMENT "Validating required shader assets" + ) endfunction() diff --git a/cmake/compile_shaders.cmake b/cmake/compile_shaders.cmake index 4340d43..90ac07c 100644 --- a/cmake/compile_shaders.cmake +++ b/cmake/compile_shaders.cmake @@ -2,18 +2,43 @@ if(NOT EXISTS "${RENDER_SOURCE_DIR}/shaders/src") message(FATAL_ERROR "Shader source directory missing: ${RENDER_SOURCE_DIR}/shaders/src") endif() +if(NOT DEFINED RENDER_SHADER_LOG_FILE) + set(RENDER_SHADER_LOG_FILE "") +endif() + +function(render_shader_log line) + if(RENDER_SHADER_LOG_FILE) + file(APPEND "${RENDER_SHADER_LOG_FILE}" "${line}\n") + endif() +endfunction() + +if(RENDER_SHADER_LOG_FILE) + file(WRITE "${RENDER_SHADER_LOG_FILE}" "render shader compilation log\n") +endif() + if(NOT RENDER_BGFX_SHADERC) + if(RENDER_REQUIRE_SHADER_COMPILATION) + message(FATAL_ERROR "RENDER_BGFX_SHADERC not set and RENDER_REQUIRE_SHADER_COMPILATION=ON") + endif() message(WARNING "RENDER_BGFX_SHADERC not set; skipping shader compilation. Expected precompiled files in shaders/bin/.") + render_shader_log("skipped: shaderc unavailable") return() endif() if(NOT EXISTS "${RENDER_BGFX_SHADERC}") - message(FATAL_ERROR "RENDER_BGFX_SHADERC path does not exist: ${RENDER_BGFX_SHADERC}") + if(RENDER_REQUIRE_SHADER_COMPILATION) + message(FATAL_ERROR "RENDER_BGFX_SHADERC path does not exist: ${RENDER_BGFX_SHADERC}") + endif() + message(WARNING "RENDER_BGFX_SHADERC path does not exist: ${RENDER_BGFX_SHADERC}; shader compilation skipped.") + render_shader_log("skipped: shaderc path does not exist: ${RENDER_BGFX_SHADERC}") + return() endif() set(SHADER_SOURCE_DIR "${RENDER_SOURCE_DIR}/shaders/src") set(SHADER_OUTPUT_DIR "${RENDER_SHADER_OUTPUT_DIR}") file(MAKE_DIRECTORY "${SHADER_OUTPUT_DIR}") +render_shader_log("shaderc: ${RENDER_BGFX_SHADERC}") +render_shader_log("output: ${SHADER_OUTPUT_DIR}") set(RENDER_BACKENDS spirv glsl metal dx11) set(RENDER_SHADER_STAGES vs fs) @@ -62,6 +87,7 @@ foreach(backend IN LISTS RENDER_BACKENDS) if(NOT shader_result EQUAL 0) message(FATAL_ERROR "Failed to compile ${in_file} for ${backend}:\n${shader_stdout}\n${shader_stderr}") endif() + render_shader_log("[ok] ${backend}/${stage}_${name}.bin") endforeach() endforeach() endforeach() diff --git a/cmake/validate_package_layout.cmake b/cmake/validate_package_layout.cmake new file mode 100644 index 0000000..d9cf158 --- /dev/null +++ b/cmake/validate_package_layout.cmake @@ -0,0 +1,85 @@ +if(NOT DEFINED RENDER_PACKAGE_STAGE_DIR) + message(FATAL_ERROR "RENDER_PACKAGE_STAGE_DIR is required") +endif() + +if(NOT DEFINED RENDER_PACKAGE_BIN_SUBDIR) + set(RENDER_PACKAGE_BIN_SUBDIR "bin") +endif() + +if(NOT DEFINED RENDER_PACKAGE_SHADER_RELATIVE_DIR) + set(RENDER_PACKAGE_SHADER_RELATIVE_DIR "shaders/bin") +endif() + +if(NOT DEFINED RENDER_PACKAGE_LOG_FILE) + set(RENDER_PACKAGE_LOG_FILE "") +endif() + +function(render_package_log line) + if(RENDER_PACKAGE_LOG_FILE) + file(APPEND "${RENDER_PACKAGE_LOG_FILE}" "${line}\n") + endif() +endfunction() + +if(RENDER_PACKAGE_LOG_FILE) + file(WRITE "${RENDER_PACKAGE_LOG_FILE}" "render package validation log\n") +endif() + +set(stage_dir "${RENDER_PACKAGE_STAGE_DIR}") +set(bin_dir "${stage_dir}/${RENDER_PACKAGE_BIN_SUBDIR}") +set(shader_dir "${stage_dir}/${RENDER_PACKAGE_SHADER_RELATIVE_DIR}") + +if(WIN32) + set(shell_name "render_shell.exe") +else() + set(shell_name "render_shell") +endif() + +set(shell_path "${bin_dir}/${shell_name}") +if(NOT EXISTS "${shell_path}") + message(FATAL_ERROR "Packaging validation failed: missing executable ${shell_path}") +endif() +render_package_log("[ok] executable: ${shell_path}") + +if(NOT EXISTS "${shader_dir}") + message(FATAL_ERROR "Packaging validation failed: missing shader directory ${shader_dir}") +endif() +render_package_log("[ok] shader directory: ${shader_dir}") + +file(GLOB_RECURSE compiled_shaders + "${shader_dir}/*.bin" +) +list(LENGTH compiled_shaders shader_count) +if(shader_count EQUAL 0) + message(FATAL_ERROR "Packaging validation failed: no compiled shader binaries found in ${shader_dir}") +endif() +render_package_log("[ok] compiled shader count: ${shader_count}") + +set(archive_base "${stage_dir}/../render-package") +if(WIN32) + set(archive_path "${archive_base}.zip") +else() + set(archive_path "${archive_base}.tar.gz") +endif() + +file(MAKE_DIRECTORY "${stage_dir}/../") + +if(WIN32) + execute_process( + COMMAND "${CMAKE_COMMAND}" -E tar cf "${archive_path}" --format=zip . + WORKING_DIRECTORY "${stage_dir}" + RESULT_VARIABLE archive_result + ) +else() + execute_process( + COMMAND "${CMAKE_COMMAND}" -E tar czf "${archive_path}" . + WORKING_DIRECTORY "${stage_dir}" + RESULT_VARIABLE archive_result + ) +endif() + +if(NOT archive_result EQUAL 0) + message(FATAL_ERROR "Packaging validation failed: archive creation failed") +endif() + +render_package_log("[ok] archive: ${archive_path}") +message(STATUS "Packaging validation complete: ${archive_path}") diff --git a/docs/build.md b/docs/build.md index bcfbd5b..11a4761 100644 --- a/docs/build.md +++ b/docs/build.md @@ -26,8 +26,9 @@ Runtime dependencies can come from: ## Shader compilation options - `render_shell` expects compiled shaders at runtime under `bin/shaders/bin//`. -- The `render_shaders` target attempts compilation if `RENDER_BGFX_SHADERC` points to bgfx `shaderc`. -- If `RENDER_BGFX_SHADERC` is not set, shader compilation is skipped and the app still runs with clear/debug output. +- The `render_shader_check` target drives shader compilation and validation. +- If `RENDER_BGFX_SHADERC` is not set, compilation is skipped unless `RENDER_REQUIRE_SHADER_COMPILATION=ON`. +- Enable `RENDER_BGFX_BUILD_TOOLS=ON` to build bgfx tooling (including `shaderc`) when available. ## Quick Start (Linux/macOS) @@ -59,3 +60,15 @@ Backend selection can be requested via: - `--backend=metal` - `--backend=vulkan` - `--backend=opengl` + +## Validation commands (local mirrors of CI) + +```bash +cmake --build --preset linux-debug --target render_test_unit +cmake --build --preset linux-debug --target render_test_headless +cmake --build --preset linux-debug --target render_shader_check +cmake --build --preset linux-debug --target render_package_validate +cmake --build --preset linux-debug --target render_validate_all +``` + +See `docs/testing.md` and `docs/ci.md` for test category policy and CI matrix details. diff --git a/docs/ci.md b/docs/ci.md new file mode 100644 index 0000000..7934b36 --- /dev/null +++ b/docs/ci.md @@ -0,0 +1,61 @@ +# CI Foundation + +## Workflows + +- **PR Validation**: `.github/workflows/pr-validation.yml` + - Runs on every pull request. + - Provides fast cross-platform build + test confidence. +- **Main Validation**: `.github/workflows/main-validation.yml` + - Runs on `main` pushes. + - Re-runs cross-platform checks and full Linux packaging/shader gates. + +## CI Matrix Policy + +The matrix is intentionally split for speed and coverage: + +- **Linux (full lane)** + - Build + - Unit tests + - Headless smoke validation + - Shader compilation checks (required) + - Packaging validation + archive creation +- **macOS (selected lane)** + - Build + - Unit tests + - Headless smoke validation +- **Windows (selected lane)** + - Build + - Unit tests + - Headless smoke validation + +Rationale: Linux provides fast and reproducible full validation, while macOS/Windows continuously enforce portability without tripling the most expensive checks. + +## Dependency/bootstrap handling + +CI uses the same CMake preset + dependency model as local development: + +- Configure with repository presets (`linux-ci`, `macos-debug`, `windows-debug`, etc.) +- `RENDER_ALLOW_FETCHCONTENT=ON` for reproducible dependency acquisition +- `FETCHCONTENT_BASE_DIR=.fc` to keep dependency cache stable +- Ninja installed via `seanmiddleditch/gha-setup-ninja` + +## Artifacts and logs + +Linux full lanes upload: + +- `out/build//logs/` (shader + packaging logs) +- `out/build//Testing/Temporary/` (CTest output) +- `out/build//package/` (stage + generated archive) + +This keeps failure triage practical without uploading oversized artifacts. + +## Blocking failure policy + +Blocking checks in CI: + +- Configure/build failures +- Any failing unit/headless CTest case +- Shader compile failures in Linux full lanes +- Packaging layout/archive failures in Linux full lanes + +All checks map to local commands documented in `docs/testing.md`. diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000..bc513ea --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,75 @@ +# Testing and Validation + +This repository uses four validation categories: + +1. **Unit tests** (`unit` CTest label) + - Fast deterministic tests for core/runtime, serialization, filesystem, and platform types. +2. **Headless validation** (`headless` CTest label) + - Non-interactive smoke checks that validate startup/shutdown paths and key services without opening a graphics window. +3. **Shader compilation checks** (`render_shader_check` target) + - Compiles required bgfx shaders and fails on compile errors. +4. **Packaging validation** (`render_package_validate` target) + - Installs into a staging layout, verifies required files, and creates a package archive. + +## CTest structure + +Named tests follow `..` to keep filtering predictable. + +Current labels: + +- `unit` +- `headless` + +Examples: + +```bash +ctest --test-dir out/build/linux-debug --output-on-failure -L unit +ctest --test-dir out/build/linux-debug --output-on-failure -L headless +``` + +## Local developer commands + +Linux/macOS example: + +```bash +cmake --preset linux-debug -DRENDER_ALLOW_FETCHCONTENT=ON +cmake --build --preset linux-debug +cmake --build --preset linux-debug --target render_test_unit +cmake --build --preset linux-debug --target render_test_headless +cmake --build --preset linux-debug --target render_shader_check +cmake --build --preset linux-debug --target render_package_validate +``` + +One-shot helper: + +```bash +scripts/run_validations.sh linux-debug linux-debug -DRENDER_ALLOW_FETCHCONTENT=ON +``` + +Windows example: + +```powershell +cmake --preset windows-debug -DRENDER_ALLOW_FETCHCONTENT=ON +cmake --build --preset windows-debug --config Debug +ctest --test-dir out/build/windows-debug -C Debug --output-on-failure -L unit +ctest --test-dir out/build/windows-debug -C Debug --output-on-failure -L headless +cmake --build --preset windows-debug --config Debug --target render_shader_check +cmake --build --preset windows-debug --config Debug --target render_package_validate +``` + +## Shader compilation policy + +- `RENDER_REQUIRE_SHADER_COMPILATION=OFF` (default): shader check can be skipped locally if `shaderc` is unavailable; a warning is logged. +- `RENDER_REQUIRE_SHADER_COMPILATION=ON` (CI/full validation): missing `shaderc` or shader compile failures are blocking. +- If `shaderc` target exists from bgfx tools, it is used automatically; otherwise set `RENDER_BGFX_SHADERC` explicitly. + +## Packaging validation policy + +`render_package_validate` checks that stage/install output contains: + +- `render_shell` executable +- `shaders/bin` directory +- at least one compiled shader binary (`*.bin`) +- generated package archive (`.tar.gz` on Unix, `.zip` on Windows) + +Use `out/build//logs/package_validation.log` to debug layout failures. diff --git a/scripts/run_validations.ps1 b/scripts/run_validations.ps1 new file mode 100644 index 0000000..30d403b --- /dev/null +++ b/scripts/run_validations.ps1 @@ -0,0 +1,18 @@ +param( + [string]$Preset = "windows-debug", + [string]$BuildPreset = "windows-debug", + [string]$Config = "Debug" +) + +$ErrorActionPreference = "Stop" + +Write-Host "== render validation pipeline ==" +Write-Host "Configure preset: $Preset" +Write-Host "Build preset: $BuildPreset" + +cmake --preset $Preset +cmake --build --preset $BuildPreset --config $Config --target render_shader_check +ctest --test-dir "out/build/$Preset" -C $Config --output-on-failure -L unit|headless +cmake --build --preset $BuildPreset --config $Config --target render_package_validate + +Write-Host "All validation categories completed." diff --git a/scripts/run_validations.sh b/scripts/run_validations.sh new file mode 100755 index 0000000..3ba3d94 --- /dev/null +++ b/scripts/run_validations.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -eu + +PRESET="${1:-linux-debug}" +BUILD_PRESET="${2:-$PRESET}" + +log() { + printf '%s\n' "$*" +} + +log "== render validation pipeline ==" +log "Configure preset: ${PRESET}" +log "Build preset: ${BUILD_PRESET}" + +cmake --preset "${PRESET}" "${@:3}" +cmake --build --preset "${BUILD_PRESET}" --target render_shader_check +ctest --preset "${PRESET}" --output-on-failure --label-regex "unit|headless" +cmake --build --preset "${BUILD_PRESET}" --target render_package_validate + +log "All validation categories completed." diff --git a/tests/README.md b/tests/README.md index af6cccd..dcc6e68 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,5 +1,15 @@ # tests -Contains repository test suites. +Contains repository validation executables registered with CTest. -Expected contents include unit and integration tests covering both engine and game contracts as implementation is added. +## Categories + +- `tests/core`, `tests/platform`, `tests/serialization`, `tests/filesystem`: unit tests (`unit` label). +- `tests/headless`: non-interactive smoke/validation tests (`headless` label). + +Use CTest labels to run subsets: + +```bash +ctest --test-dir out/build/linux-debug --output-on-failure -L unit +ctest --test-dir out/build/linux-debug --output-on-failure -L headless +``` diff --git a/tests/headless/headless_validation.cpp b/tests/headless/headless_validation.cpp new file mode 100644 index 0000000..6393351 --- /dev/null +++ b/tests/headless/headless_validation.cpp @@ -0,0 +1,59 @@ +#include "engine/filesystem/filesystem.hpp" +#include "engine/serialization/serialization.hpp" + +#include +#include +#include +#include + +namespace fs = std::filesystem; + +int main() { + using namespace render; + + std::cout << "[headless] starting smoke validation\n"; + + const fs::path sandbox = fs::temp_directory_path() / "render_headless_validation"; + std::error_code ec; + fs::remove_all(sandbox, ec); + + filesystem::StorageConfig config{}; + config.executable_path = sandbox / "bin" / "render_shell"; + config.source_root_override = sandbox / "source_data"; + config.writable_root_override = sandbox / "user"; + + fs::create_directories(*config.source_root_override, ec); + + filesystem::FileSystemService file_system; + if (!file_system.initialize(config)) { + std::cerr << "[headless] filesystem initialization failed\n"; + return 1; + } + + if (!file_system.write_text(filesystem::PathCategory::Logs, "headless/startup.log", "ok")) { + std::cerr << "[headless] failed to write startup log\n"; + return 2; + } + + serialization::SavePayload payload{}; + payload.header.save_slot = 7; + payload.header.room_count = 0; + payload.header.timestamp_unix_seconds = 1700000000ULL; + + const serialization::ByteBuffer encoded = serialization::serialize_save(payload); + serialization::SavePayload decoded{}; + serialization::Error error{}; + if (!serialization::deserialize_save(encoded, decoded, error)) { + std::cerr << "[headless] serialization roundtrip failed\n"; + return 3; + } + + if (decoded.header.save_slot != payload.header.save_slot) { + std::cerr << "[headless] decoded payload mismatch\n"; + return 4; + } + + std::cout << "[headless] smoke validation complete\n"; + fs::remove_all(sandbox, ec); + return 0; +}