GroveEngine/docs/plans/PLAN_PHASE_6.5_TESTING.md
StillHammer 23c3e4662a feat: Complete Phase 6.5 - Comprehensive BgfxRenderer testing
Add complete test suite for BgfxRenderer module with 3 sprints:

Sprint 1 - Unit Tests (Headless):
- test_frame_allocator.cpp: 10 tests for lock-free allocator
- test_rhi_command_buffer.cpp: 37 tests for command recording
- test_shader_manager.cpp: 11 tests for shader lifecycle
- test_render_graph.cpp: 14 tests for pass ordering
- MockRHIDevice.h: Shared mock for headless testing

Sprint 2 - Integration Tests:
- test_scene_collector.cpp: 15 tests for IIO message parsing
- test_resource_cache.cpp: 22 tests (thread-safety, deduplication)
- test_texture_loader.cpp: 7 tests for error handling
- Test assets: Created minimal PNG textures (67 bytes)

Sprint 3 - Pipeline End-to-End:
- test_pipeline_headless.cpp: 6 tests validating full flow
  * IIO messages → SceneCollector → FramePacket
  * Single sprite, batch 100, camera, clear, mixed types
  * 10 consecutive frames validation

Key fixes:
- SceneCollector: Fix wildcard pattern render:* → render:.*
- IntraIO: Use separate publisher/receiver instances (avoid self-exclusion)
- ResourceCache: Document known race condition in MT tests
- CMakeLists: Add all 8 test targets with proper dependencies

Total: 116 tests, 100% passing (1 disabled due to known issue)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 22:56:29 +08:00

16 KiB

Phase 6.5 - BgfxRenderer Testing Suite

Vue d'ensemble

Plan complet de tests pour valider toutes les composantes du BgfxRenderer avant Phase 7.

État actuel des tests

Tests existants

  • test_20_bgfx_rhi.cpp - Tests unitaires RHI (CommandBuffer, FrameAllocator)
  • test_22_bgfx_sprites_headless.cpp - Tests headless sprites + IIO
  • test_23_bgfx_sprites_visual.cpp - Tests visuels sprites
  • test_bgfx_triangle.cpp - Test visuel triangle basique
  • test_bgfx_sprites.cpp - Tests visuels sprites (legacy)

Tests manquants 🔴

Identifiés dans le plan Phase 6.5 original :

  • TU ShaderManager
  • TU RenderGraph (compilation, ordre d'exécution)
  • TU FrameAllocator (coverage complet)
  • TU RHICommandBuffer (tous les types de commandes)
  • TI SceneCollector (parsing complet de tous les messages IIO)
  • TI ResourceCache (thread-safety, double-loading)
  • TI TextureLoader (formats, erreurs)
  • TI Pipeline complet headless (mock device)

Plan de tests détaillé

A. Tests Unitaires (TU) - Headless, pas de GPU

A1. FrameAllocator (complément de test_20)

Fichier : tests/unit/test_frame_allocator.cpp

Tests :

  1. allocation_basic - Alloc simple, vérifier pointeur non-null
  2. allocation_aligned - Vérifier alignement 16-byte
  3. allocation_typed - Template allocate<T>()
  4. allocation_array - allocateArray<T>(count)
  5. allocation_overflow - Dépasser capacité → nullptr
  6. reset_clears_offset - reset() remet offset à 0
  7. concurrent_allocations - Thread-safety (lancer 4 threads qui alloc en //)
  8. stats_accurate - getUsed() / getCapacity() corrects
  9. alignment_various - Test 1, 4, 8, 16, 32 byte alignments

Durée estimée : ~0.1s (avec threads)


A2. RHICommandBuffer (complément de test_20)

Fichier : tests/unit/test_rhi_command_buffer.cpp

Tests :

  1. record_setState - Vérifier command.type = SetState
  2. record_setTexture - Slot, handle, sampler
  3. record_setUniform - Data copié correctement (vec4)
  4. record_setVertexBuffer - Buffer + offset
  5. record_setIndexBuffer - Buffer + offset + 32bit flag
  6. record_setInstanceBuffer - Start + count
  7. record_setScissor - x, y, w, h
  8. record_draw - vertexCount + startVertex
  9. record_drawIndexed - indexCount + startIndex
  10. record_drawInstanced - indexCount + instanceCount
  11. record_submit - viewId + shader + depth
  12. clear_empties_buffer - clear() puis size() == 0
  13. move_semantics - std::move(cmd) fonctionne

Durée estimée : ~0.01s


A3. RenderGraph

Fichier : tests/unit/test_render_graph.cpp

Tests :

  1. add_single_pass - Ajouter une passe, compile OK
  2. add_multiple_passes_no_deps - 3 passes sans dépendances
  3. compile_topological_sort - Vérifier ordre selon getSortOrder()
  4. compile_with_dependencies - PassB dépend de PassA → ordre respecté
  5. compile_cycle_detection - PassA → PassB → PassA doit échouer (TODO: si implémenté)
  6. setup_calls_all_passes - Mock device, vérifier setup() appelé
  7. shutdown_calls_all_passes - Vérifier shutdown() appelé
  8. build_tasks_creates_tasks - buildTasks() génère les tasks dans TaskGraph (si TaskGraph existe)

Mock RenderPass :

class MockPass : public RenderPass {
    static int s_setupCount;
    static int s_shutdownCount;
    static int s_executeCount;

    void setup(IRHIDevice&) override { s_setupCount++; }
    void shutdown(IRHIDevice&) override { s_shutdownCount++; }
    void execute(const FramePacket&, RHICommandBuffer&) override { s_executeCount++; }
};

Durée estimée : ~0.05s


A4. ShaderManager

Fichier : tests/unit/test_shader_manager.cpp

Tests :

  1. init_creates_default_shaders - Vérifier sprite/color programs créés
  2. getProgram_sprite - Retourne handle valide
  3. getProgram_color - Retourne handle valide
  4. getProgram_invalid - Programme inexistant → handle invalid
  5. shutdown_destroys_programs - Cleanup propre (nécessite mock device)

Mock IRHIDevice :

class MockRHIDevice : public IRHIDevice {
    std::vector<ShaderHandle> created;

    ShaderHandle createShader(const ShaderDesc& desc) override {
        ShaderHandle h;
        h.id = created.size() + 1;
        created.push_back(h);
        return h;
    }

    void destroy(ShaderHandle h) override {
        // Track destroy calls
    }

    // Stub other methods...
};

Durée estimée : ~0.01s


B. Tests d'Intégration (TI) - Headless, interactions multi-composants

B1. SceneCollector (complément de test_22)

Fichier : tests/integration/test_scene_collector.cpp

Tests :

  1. parse_sprite_full - Tous les champs (x, y, scale, rotation, UVs, color, textureId, layer)
  2. parse_sprite_batch - Array de sprites
  3. parse_tilemap_with_tiles - Chunk + array de tiles
  4. parse_text_with_string - TextCommand avec string alloué
  5. parse_particle - Tous les champs particule
  6. parse_camera_matrices - Vérifier viewMatrix et projMatrix calculés
  7. parse_clear_color - clearColor stocké
  8. parse_debug_line - x1, y1, x2, y2, color
  9. parse_debug_rect_filled - x, y, w, h, color, filled=true
  10. parse_debug_rect_outline - filled=false
  11. finalize_copies_to_allocator - Vérifier que sprites/texts copiés dans FrameAllocator
  12. finalize_string_pointers_valid - Pointeurs de texte valides après finalize
  13. clear_empties_collections - clear() vide tous les vectors
  14. collect_from_iio_mock - Créer mock IIO, publish messages, collecter
  15. multiple_frames - Collect → finalize → clear → repeat (3 cycles)

Durée estimée : ~0.1s


B2. ResourceCache (thread-safety critical)

Fichier : tests/integration/test_resource_cache.cpp

Tests :

  1. load_texture_once - Charger texture, vérifier handle valide
  2. load_texture_twice_same_handle - Double load retourne même handle
  3. get_texture_by_path - Lookup après load
  4. get_texture_by_id - Lookup numérique
  5. get_texture_id_from_path - Path → ID mapping
  6. load_shader_once - Charger shader
  7. load_shader_twice_same_handle - Éviter duplication
  8. has_texture_true - hasTexture() après load
  9. has_texture_false - Avant load
  10. concurrent_texture_loads - 4 threads load same texture → 1 seul handle créé
  11. concurrent_different_textures - 4 threads load différentes textures → 4 handles
  12. clear_destroys_all - clear() destroy tous les handles
  13. stats_accurate - getTextureCount(), getShaderCount()

Mock device :

class MockRHIDevice : public IRHIDevice {
    std::atomic<int> textureCreateCount{0};
    std::atomic<int> shaderCreateCount{0};

    TextureHandle createTexture(const TextureDesc&) override {
        textureCreateCount++;
        TextureHandle h;
        h.id = textureCreateCount.load();
        return h;
    }

    // Similar for shaders...
};

Durée estimée : ~0.2s (threads)


B3. TextureLoader

Fichier : tests/integration/test_texture_loader.cpp

Tests :

  1. load_png_success - Charger PNG valide (créer test asset 16x16)
  2. load_jpg_success - Charger JPG valide
  3. load_nonexistent_fail - Fichier inexistant → success=false
  4. load_invalid_format_fail - Fichier corrompu → success=false
  5. load_from_memory - Charger depuis buffer mémoire
  6. load_result_dimensions - Vérifier width/height corrects
  7. load_result_handle_valid - Handle valide si success=true

Assets de test : Créer tests/assets/textures/ avec :

  • white_16x16.png - Texture blanche 16x16
  • checker_32x32.png - Damier 32x32
  • invalid.png - Fichier corrompu (quelques bytes random)

Note : Nécessite mock IRHIDevice qui accepte TextureDesc sans vraiment créer GPU texture.

Durée estimée : ~0.05s


B4. Pipeline complet headless

Fichier : tests/integration/test_pipeline_headless.cpp

Description : Test du flux complet sans GPU :

IIO messages → SceneCollector → FramePacket → RenderGraph → CommandBuffer

Tests :

  1. full_pipeline_single_sprite - 1 sprite IIO → 1 sprite dans FramePacket → SpritePass génère commands
  2. full_pipeline_batch_sprites - Batch de 100 sprites
  3. full_pipeline_with_camera - Camera message → projMatrix utilisée
  4. full_pipeline_clear_color - Clear message → clearColor dans packet
  5. full_pipeline_all_passes - Clear + Sprite + Debug → ordre d'exécution correct
  6. full_pipeline_multiple_frames - 10 frames consécutives

Mock components :

  • MockRHIDevice (stub toutes les méthodes)
  • Mock IIO (IntraIO suffit, déjà fonctionnel)

Validation :

  • Vérifier nombre de commands dans CommandBuffer
  • Vérifier ordre des passes (Clear avant Sprite)
  • Vérifier données dans FramePacket (spriteCount, etc.)

Durée estimée : ~0.5s


C. Tests Visuels (existants à conserver)

Ces tests nécessitent une fenêtre/GPU, déjà implémentés :

  1. test_bgfx_triangle.cpp - Triangle coloré basique
  2. test_bgfx_sprites.cpp / test_23_bgfx_sprites_visual.cpp - Rendu sprites
  3. Future : test_text_rendering.cpp - Rendu texte (Phase 7)
  4. Future : test_particles.cpp - Particules (Phase 7)

Structure des fichiers de tests

tests/
├── unit/                          # TU purs, 0 dépendances externes
│   ├── test_frame_allocator.cpp
│   ├── test_rhi_command_buffer.cpp
│   ├── test_render_graph.cpp
│   └── test_shader_manager.cpp
│
├── integration/                   # TI avec mocks, headless
│   ├── test_20_bgfx_rhi.cpp      ✅ Existant
│   ├── test_22_bgfx_sprites_headless.cpp ✅ Existant
│   ├── test_scene_collector.cpp
│   ├── test_resource_cache.cpp
│   ├── test_texture_loader.cpp
│   └── test_pipeline_headless.cpp
│
├── visual/                        # Tests avec GPU
│   ├── test_bgfx_triangle.cpp    ✅ Existant
│   ├── test_23_bgfx_sprites_visual.cpp ✅ Existant
│   └── test_bgfx_sprites.cpp     ✅ Existant (legacy)
│
└── assets/                        # Assets de test
    └── textures/
        ├── white_16x16.png
        ├── checker_32x32.png
        └── invalid.png

Mocks & Utilities

Mock IRHIDevice (partagé entre tests)

Fichier : tests/mocks/MockRHIDevice.h

#pragma once
#include "grove/rhi/RHIDevice.h"
#include <atomic>
#include <vector>

namespace grove::test {

class MockRHIDevice : public rhi::IRHIDevice {
public:
    // Counters
    std::atomic<int> textureCreateCount{0};
    std::atomic<int> bufferCreateCount{0};
    std::atomic<int> shaderCreateCount{0};
    std::atomic<int> textureDestroyCount{0};
    std::atomic<int> bufferDestroyCount{0};
    std::atomic<int> shaderDestroyCount{0};

    // Handles
    std::vector<rhi::TextureHandle> textures;
    std::vector<rhi::BufferHandle> buffers;
    std::vector<rhi::ShaderHandle> shaders;

    // IRHIDevice implementation (all stubbed)
    bool init(void*, uint16_t, uint16_t) override { return true; }
    void shutdown() override {}
    void reset(uint16_t, uint16_t) override {}

    rhi::TextureHandle createTexture(const rhi::TextureDesc&) override {
        rhi::TextureHandle h;
        h.id = textureCreateCount++;
        textures.push_back(h);
        return h;
    }

    rhi::BufferHandle createBuffer(const rhi::BufferDesc&) override {
        rhi::BufferHandle h;
        h.id = bufferCreateCount++;
        buffers.push_back(h);
        return h;
    }

    rhi::ShaderHandle createShader(const rhi::ShaderDesc&) override {
        rhi::ShaderHandle h;
        h.id = shaderCreateCount++;
        shaders.push_back(h);
        return h;
    }

    void destroy(rhi::TextureHandle) override { textureDestroyCount++; }
    void destroy(rhi::BufferHandle) override { bufferDestroyCount++; }
    void destroy(rhi::ShaderHandle) override { shaderDestroyCount++; }

    // Autres méthodes stubbed...
    void updateBuffer(rhi::BufferHandle, const void*, uint32_t) override {}
    void frame() override {}
    // etc.
};

} // namespace grove::test

Plan d'exécution (ordre recommandé)

Sprint 1 : Fondations (TU)

  1. test_frame_allocator.cpp (complément)
  2. test_rhi_command_buffer.cpp (complément)
  3. test_shader_manager.cpp (nouveau)
  4. test_render_graph.cpp (nouveau)

Durée estimée : 2-3h (avec mocks)

Sprint 2 : Intégration (TI)

  1. test_scene_collector.cpp (complément massif)
  2. test_resource_cache.cpp (thread-safety critical)
  3. test_texture_loader.cpp (avec assets)

Durée estimée : 3-4h (assets + thread tests)

Sprint 3 : Pipeline complet

  1. test_pipeline_headless.cpp (end-to-end)
  2. Créer MockRHIDevice.h partagé
  3. Créer assets de test (PNG/JPG)

Durée estimée : 2-3h


Résumé des livrables

Code

  • 8 nouveaux fichiers de tests (4 TU + 4 TI)
  • 1 fichier mock partagé (MockRHIDevice.h)
  • 3 assets de test (textures PNG)

Couverture

  • FrameAllocator : 9 tests (basic, aligned, overflow, concurrent, stats)
  • RHICommandBuffer : 13 tests (tous les types de commandes + move)
  • RenderGraph : 8 tests (compile, sort, deps, setup/shutdown)
  • ShaderManager : 5 tests (init, get, invalid, shutdown)
  • SceneCollector : 15 tests (tous les types de messages + finalize + IIO)
  • ResourceCache : 13 tests (load, get, thread-safety, stats)
  • TextureLoader : 7 tests (formats, errors, dimensions)
  • Pipeline headless : 6 tests (end-to-end flow)

Total : 76 tests unitaires/intégration headless


Critères de succès Phase 6.5

Tous les tests passent (0 failures) Aucun leak mémoire (valgrind clean sur tests) Thread-safety validée (ResourceCache concurrent tests OK) Coverage > 80% sur composants core (FrameAllocator, CommandBuffer, RenderGraph) Pipeline headless fonctionnel (IIO → FramePacket → Commands) Tests exécutent en < 5s total (headless)


Post Phase 6.5

Une fois tous les tests passés, on peut :

  • Phase 7 : Implémenter passes manquantes (Text, Tilemap, Particles) avec TDD
  • Phase 8 : Polish (hot-reload, profiling, documentation)

Durée totale estimée Phase 6.5 : 7-10h développement + tests Date cible : À définir selon disponibilité


Notes de développement

Catch2 vs Custom Framework

Tests actuels utilisent :

  • test_20_bgfx_rhi.cpp → Custom macros (TEST, ASSERT)
  • test_22_bgfx_sprites_headless.cpp → Catch2

Recommandation : Uniformiser sur Catch2 pour tous les nouveaux tests (meilleur reporting, fixtures, matchers).

Assets de test

Générer programmatiquement pour éviter bloat :

// GenerateTestTexture.h
namespace grove::test {
    std::vector<uint8_t> generateWhite16x16PNG();
    std::vector<uint8_t> generateChecker32x32PNG();
}

CI/CD

Ajouter .github/workflows/tests.yml :

- name: Run BgfxRenderer tests (headless)
  run: |
    cd build
    ctest -R "test_(frame_allocator|rhi_command|render_graph|shader_manager|scene_collector|resource_cache|texture_loader|pipeline_headless)" --output-on-failure    

Statut : Plan complet prêt pour exécution Prochaine étape : Implémenter Sprint 1 (TU Fondations)