- Create new docs/plans/ directory with organized structure - Add comprehensive PLAN_deadlock_detection_prevention.md (15h plan) - ThreadSanitizer integration (2h) - Helgrind validation (3h) - std::scoped_lock refactoring (4h) - std::shared_mutex optimization (6h) - Migrate 16 plans from planTI/ to docs/plans/ - Rename all files to PLAN_*.md convention - Update README.md with index and statuses - Remove old planTI/ directory - Add run_all_tests.sh script for test automation Plans now include: - 1 active development plan (deadlock prevention) - 3 test architecture plans - 13 integration test scenario plans 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
724 lines
24 KiB
Markdown
724 lines
24 KiB
Markdown
# Scénario 7: Limite Tests
|
|
|
|
**Priorité**: ⭐ NICE TO HAVE
|
|
**Phase**: 3 (NICE TO HAVE)
|
|
**Durée estimée**: ~3 minutes
|
|
**Effort implémentation**: ~3-4 heures
|
|
|
|
---
|
|
|
|
## 🎯 Objectif
|
|
|
|
Valider que le système de hot-reload reste robuste face aux conditions extrêmes:
|
|
- État très large (100MB+ en mémoire)
|
|
- Initialisation longue (>5 secondes)
|
|
- Timeouts de reload
|
|
- Limites de sérialisation/désérialisation
|
|
- Gestion de la mémoire sous contrainte
|
|
|
|
---
|
|
|
|
## 📋 Description
|
|
|
|
### Setup Initial
|
|
1. Charger `HeavyStateModule` avec configuration extrême
|
|
2. Créer un état massif:
|
|
- 1 million de particules (x, y, vx, vy, lifetime, color)
|
|
- Matrice de terrain 10000x10000 (100M cellules)
|
|
- Historique des 10000 dernières frames
|
|
- Cache de 50000 textures simulées
|
|
3. État sérialisé estimé: ~120MB en JSON
|
|
4. Temps d'initialisation: ~8 secondes
|
|
|
|
### Test Séquence
|
|
|
|
#### Test 1: Large State Serialization (60s)
|
|
1. Exécuter pendant 10 secondes (600 frames)
|
|
2. Trigger hot-reload avec extraction de l'état complet
|
|
3. Mesurer:
|
|
- Temps de `getState()`: doit être < 2000ms
|
|
- Taille de l'état sérialisé
|
|
- Temps de `setState()`: doit être < 2000ms
|
|
4. Vérifier intégrité des données post-reload:
|
|
- Nombre de particules = 1M
|
|
- Dimensions terrain = 10000x10000
|
|
- Historique complet préservé
|
|
5. Continuer pendant 10 secondes supplémentaires
|
|
|
|
#### Test 2: Long Initialization Timeout (30s)
|
|
1. Charger `HeavyStateModule` avec init duration = 12s
|
|
2. Configurer timeout à 10s (volontairement trop court)
|
|
3. Vérifier que le système:
|
|
- Détecte le timeout correctement
|
|
- Annule le chargement proprement
|
|
- Libère toutes les ressources
|
|
- Log un message d'erreur clair
|
|
4. Recharger avec timeout = 15s (suffisant)
|
|
5. Vérifier chargement réussi
|
|
|
|
#### Test 3: Memory Pressure During Reload (60s)
|
|
1. Démarrer avec état 100MB
|
|
2. Pendant reload:
|
|
- Allouer 200MB supplémentaires (simuler pic mémoire)
|
|
- Vérifier que le système ne crash pas (OOM)
|
|
- Mesurer temps de reload sous pression
|
|
3. Après reload:
|
|
- Vérifier retour à 100MB baseline
|
|
- Aucune fuite détectée
|
|
- État intact
|
|
|
|
#### Test 4: Incremental State Save/Load (30s)
|
|
1. Activer mode "incremental state" (seuls les deltas)
|
|
2. Faire 10 reloads successifs
|
|
3. Chaque reload modifie 1% de l'état (10k particules)
|
|
4. Mesurer:
|
|
- Temps de reload incrémental: doit être < 100ms
|
|
- Taille des deltas: doit être ~1MB
|
|
- Intégrité finale: 100% des particules correctes
|
|
|
|
#### Test 5: State Corruption Detection (20s)
|
|
1. Créer un état volontairement corrompu:
|
|
- JSON mal formé
|
|
- Valeurs hors limites (NaN, Infinity)
|
|
- Champs manquants
|
|
2. Tenter `setState()` avec état corrompu
|
|
3. Vérifier:
|
|
- Détection de corruption avant application
|
|
- État précédent non affecté
|
|
- Message d'erreur descriptif
|
|
- Module reste fonctionnel
|
|
|
|
---
|
|
|
|
## 🏗️ Implémentation
|
|
|
|
### HeavyStateModule Structure
|
|
|
|
```cpp
|
|
// HeavyStateModule.h
|
|
class HeavyStateModule : public IModule {
|
|
public:
|
|
struct Particle {
|
|
float x, y; // Position
|
|
float vx, vy; // Vélocité
|
|
float lifetime; // Temps restant
|
|
uint32_t color; // RGBA
|
|
};
|
|
|
|
struct TerrainCell {
|
|
uint8_t height; // 0-255
|
|
uint8_t type; // Grass, water, rock, etc.
|
|
uint8_t metadata; // Flags
|
|
uint8_t reserved;
|
|
};
|
|
|
|
struct FrameSnapshot {
|
|
uint32_t frameId;
|
|
float avgFPS;
|
|
size_t particleCount;
|
|
uint64_t timestamp;
|
|
};
|
|
|
|
void initialize(std::shared_ptr<IDataNode> config) override;
|
|
void process(float deltaTime) override;
|
|
std::shared_ptr<IDataNode> getState() const override;
|
|
void setState(std::shared_ptr<IDataNode> state) override;
|
|
bool isIdle() const override { return true; }
|
|
|
|
private:
|
|
std::vector<Particle> particles; // 1M particules = ~32MB
|
|
std::vector<TerrainCell> terrain; // 100M cells = ~100MB
|
|
std::deque<FrameSnapshot> history; // 10k frames = ~160KB
|
|
std::unordered_map<uint32_t, std::vector<uint8_t>> textureCache; // 50k textures simulées
|
|
|
|
float initDuration = 8.0f; // Temps d'init simulé
|
|
int frameCount = 0;
|
|
std::string version = "v1.0";
|
|
|
|
void updateParticles(float dt);
|
|
void spawnParticles(size_t count);
|
|
void initializeTerrain(int width, int height);
|
|
bool validateState(std::shared_ptr<IDataNode> state) const;
|
|
};
|
|
```
|
|
|
|
### State Format (JSON) - Optimisé
|
|
|
|
```json
|
|
{
|
|
"version": "v1.0",
|
|
"frameCount": 600,
|
|
"config": {
|
|
"particleCount": 1000000,
|
|
"terrainWidth": 10000,
|
|
"terrainHeight": 10000,
|
|
"historySize": 10000
|
|
},
|
|
"particles": {
|
|
"count": 1000000,
|
|
"data": "base64_encoded_binary_data" // Compression pour réduire taille JSON
|
|
},
|
|
"terrain": {
|
|
"width": 10000,
|
|
"height": 10000,
|
|
"compressed": true,
|
|
"data": "base64_zlib_compressed" // Terrain compressé
|
|
},
|
|
"history": [
|
|
{"frame": 590, "fps": 60, "particles": 1000000, "ts": 1234567890},
|
|
// ... 9999 autres frames
|
|
],
|
|
"textureCache": {
|
|
"count": 50000,
|
|
"totalSize": 25600000
|
|
}
|
|
}
|
|
```
|
|
|
|
### Test Principal
|
|
|
|
```cpp
|
|
// test_07_limits.cpp
|
|
#include "helpers/TestMetrics.h"
|
|
#include "helpers/TestAssertions.h"
|
|
#include "helpers/TestReporter.h"
|
|
#include <chrono>
|
|
|
|
int main() {
|
|
TestReporter reporter("Limite Tests");
|
|
TestMetrics metrics;
|
|
|
|
// ========================================================================
|
|
// TEST 1: Large State Serialization
|
|
// ========================================================================
|
|
std::cout << "\n=== TEST 1: Large State Serialization ===\n";
|
|
|
|
DebugEngine engine;
|
|
engine.loadModule("HeavyStateModule", "build/modules/libHeavyStateModule.so");
|
|
|
|
auto config = createJsonConfig({
|
|
{"version", "v1.0"},
|
|
{"particleCount", 1000000},
|
|
{"terrainSize", 10000},
|
|
{"initDuration", 8.0f}
|
|
});
|
|
|
|
// Initialisation (devrait prendre ~8s)
|
|
auto initStart = std::chrono::high_resolution_clock::now();
|
|
engine.initializeModule("HeavyStateModule", config);
|
|
auto initEnd = std::chrono::high_resolution_clock::now();
|
|
|
|
float initTime = std::chrono::duration<float>(initEnd - initStart).count();
|
|
std::cout << "Initialization took: " << initTime << "s\n";
|
|
ASSERT_GT(initTime, 7.0f, "Init should take at least 7s (simulated heavy init)");
|
|
ASSERT_LT(initTime, 10.0f, "Init should not take more than 10s");
|
|
reporter.addMetric("init_time_s", initTime);
|
|
|
|
// Exécuter 10s
|
|
for (int i = 0; i < 600; i++) {
|
|
engine.update(1.0f/60.0f);
|
|
}
|
|
|
|
// Mesurer temps de getState()
|
|
auto getStateStart = std::chrono::high_resolution_clock::now();
|
|
auto state = engine.getModuleState("HeavyStateModule");
|
|
auto getStateEnd = std::chrono::high_resolution_clock::now();
|
|
|
|
float getStateTime = std::chrono::duration<float, std::milli>(getStateEnd - getStateStart).count();
|
|
std::cout << "getState() took: " << getStateTime << "ms\n";
|
|
ASSERT_LT(getStateTime, 2000.0f, "getState() should be < 2000ms");
|
|
reporter.addMetric("getstate_time_ms", getStateTime);
|
|
|
|
// Estimer taille de l'état
|
|
auto* jsonNode = dynamic_cast<JsonDataNode*>(state.get());
|
|
std::string stateStr = jsonNode->getJsonData().dump();
|
|
size_t stateSize = stateStr.size();
|
|
std::cout << "State size: " << (stateSize / 1024.0f / 1024.0f) << " MB\n";
|
|
reporter.addMetric("state_size_mb", stateSize / 1024.0f / 1024.0f);
|
|
|
|
// Hot-reload avec setState()
|
|
modifySourceFile("tests/modules/HeavyStateModule.cpp", "v1.0", "v2.0");
|
|
system("cmake --build build --target HeavyStateModule 2>&1 > /dev/null");
|
|
|
|
auto setStateStart = std::chrono::high_resolution_clock::now();
|
|
engine.reloadModule("HeavyStateModule");
|
|
auto setStateEnd = std::chrono::high_resolution_clock::now();
|
|
|
|
float setStateTime = std::chrono::duration<float, std::milli>(setStateEnd - setStateStart).count();
|
|
std::cout << "setState() + reload took: " << setStateTime << "ms\n";
|
|
ASSERT_LT(setStateTime, 2000.0f, "setState() should be < 2000ms");
|
|
reporter.addMetric("setstate_time_ms", setStateTime);
|
|
|
|
// Vérifier intégrité
|
|
auto stateAfter = engine.getModuleState("HeavyStateModule");
|
|
auto* jsonNodeAfter = dynamic_cast<JsonDataNode*>(stateAfter.get());
|
|
const auto& dataAfter = jsonNodeAfter->getJsonData();
|
|
|
|
int particleCount = dataAfter["config"]["particleCount"];
|
|
ASSERT_EQ(particleCount, 1000000, "Should have 1M particles after reload");
|
|
reporter.addAssertion("particles_preserved", particleCount == 1000000);
|
|
|
|
// Continuer 10s
|
|
for (int i = 0; i < 600; i++) {
|
|
engine.update(1.0f/60.0f);
|
|
}
|
|
|
|
std::cout << "✓ TEST 1 PASSED\n";
|
|
|
|
// ========================================================================
|
|
// TEST 2: Long Initialization Timeout
|
|
// ========================================================================
|
|
std::cout << "\n=== TEST 2: Long Initialization Timeout ===\n";
|
|
|
|
DebugEngine engine2;
|
|
engine2.loadModule("HeavyStateModule", "build/modules/libHeavyStateModule.so");
|
|
|
|
// Config avec init très long + timeout trop court
|
|
auto configTimeout = createJsonConfig({
|
|
{"version", "v1.0"},
|
|
{"particleCount", 1000000},
|
|
{"terrainSize", 10000},
|
|
{"initDuration", 12.0f}, // Init va prendre 12s
|
|
{"initTimeout", 10.0f} // Mais timeout à 10s
|
|
});
|
|
|
|
bool timedOut = false;
|
|
try {
|
|
engine2.initializeModule("HeavyStateModule", configTimeout);
|
|
} catch (const std::runtime_error& e) {
|
|
std::string msg = e.what();
|
|
if (msg.find("timeout") != std::string::npos ||
|
|
msg.find("Timeout") != std::string::npos) {
|
|
timedOut = true;
|
|
std::cout << "✓ Timeout detected correctly: " << msg << "\n";
|
|
}
|
|
}
|
|
|
|
ASSERT_TRUE(timedOut, "Should timeout with init > timeout threshold");
|
|
reporter.addAssertion("timeout_detection", timedOut);
|
|
|
|
// Réessayer avec timeout suffisant
|
|
auto configOk = createJsonConfig({
|
|
{"version", "v1.0"},
|
|
{"particleCount", 1000000},
|
|
{"terrainSize", 10000},
|
|
{"initDuration", 8.0f},
|
|
{"initTimeout", 15.0f}
|
|
});
|
|
|
|
bool success = true;
|
|
try {
|
|
engine2.initializeModule("HeavyStateModule", configOk);
|
|
engine2.update(1.0f/60.0f);
|
|
} catch (...) {
|
|
success = false;
|
|
}
|
|
|
|
ASSERT_TRUE(success, "Should succeed with adequate timeout");
|
|
reporter.addAssertion("timeout_recovery", success);
|
|
|
|
std::cout << "✓ TEST 2 PASSED\n";
|
|
|
|
// ========================================================================
|
|
// TEST 3: Memory Pressure During Reload
|
|
// ========================================================================
|
|
std::cout << "\n=== TEST 3: Memory Pressure During Reload ===\n";
|
|
|
|
size_t memBefore = getCurrentMemoryUsage();
|
|
std::cout << "Memory before: " << (memBefore / 1024.0f / 1024.0f) << " MB\n";
|
|
|
|
// Exécuter quelques frames
|
|
for (int i = 0; i < 300; i++) {
|
|
engine.update(1.0f/60.0f);
|
|
}
|
|
|
|
// Allouer temporairement 200MB pendant reload
|
|
std::vector<uint8_t> tempAlloc;
|
|
|
|
auto reloadStart = std::chrono::high_resolution_clock::now();
|
|
|
|
// Démarrer reload dans un thread
|
|
std::thread reloadThread([&]() {
|
|
modifySourceFile("tests/modules/HeavyStateModule.cpp", "v2.0", "v3.0");
|
|
system("cmake --build build --target HeavyStateModule 2>&1 > /dev/null");
|
|
engine.reloadModule("HeavyStateModule");
|
|
});
|
|
|
|
// Pendant reload, allouer massivement
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
tempAlloc.resize(200 * 1024 * 1024); // 200MB
|
|
std::fill(tempAlloc.begin(), tempAlloc.end(), 0x42);
|
|
|
|
size_t memDuringReload = getCurrentMemoryUsage();
|
|
std::cout << "Memory during reload: " << (memDuringReload / 1024.0f / 1024.0f) << " MB\n";
|
|
|
|
reloadThread.join();
|
|
auto reloadEnd = std::chrono::high_resolution_clock::now();
|
|
|
|
float reloadTimeUnderPressure = std::chrono::duration<float, std::milli>(reloadEnd - reloadStart).count();
|
|
std::cout << "Reload under pressure took: " << reloadTimeUnderPressure << "ms\n";
|
|
reporter.addMetric("reload_under_pressure_ms", reloadTimeUnderPressure);
|
|
|
|
// Libérer allocation temporaire
|
|
tempAlloc.clear();
|
|
tempAlloc.shrink_to_fit();
|
|
|
|
// Attendre GC
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
|
|
|
size_t memAfter = getCurrentMemoryUsage();
|
|
std::cout << "Memory after: " << (memAfter / 1024.0f / 1024.0f) << " MB\n";
|
|
|
|
long memGrowth = static_cast<long>(memAfter) - static_cast<long>(memBefore);
|
|
std::cout << "Net memory growth: " << (memGrowth / 1024.0f / 1024.0f) << " MB\n";
|
|
|
|
// Tolérance: max 10MB de croissance nette
|
|
ASSERT_LT(std::abs(memGrowth), 10 * 1024 * 1024, "Memory growth should be < 10MB");
|
|
reporter.addMetric("memory_growth_mb", memGrowth / 1024.0f / 1024.0f);
|
|
|
|
std::cout << "✓ TEST 3 PASSED\n";
|
|
|
|
// ========================================================================
|
|
// TEST 4: Incremental State Save/Load
|
|
// ========================================================================
|
|
std::cout << "\n=== TEST 4: Incremental State Save/Load ===\n";
|
|
|
|
// Activer mode incrémental (si supporté)
|
|
auto configIncremental = createJsonConfig({
|
|
{"version", "v1.0"},
|
|
{"particleCount", 100000}, // Réduit pour test rapide
|
|
{"terrainSize", 1000},
|
|
{"incrementalState", true}
|
|
});
|
|
|
|
DebugEngine engine3;
|
|
engine3.loadModule("HeavyStateModule", "build/modules/libHeavyStateModule.so");
|
|
engine3.initializeModule("HeavyStateModule", configIncremental);
|
|
|
|
std::vector<float> incrementalTimes;
|
|
|
|
for (int reload = 0; reload < 10; reload++) {
|
|
// Exécuter 60 frames (1s)
|
|
for (int i = 0; i < 60; i++) {
|
|
engine3.update(1.0f/60.0f);
|
|
}
|
|
|
|
// Reload incrémental
|
|
auto incStart = std::chrono::high_resolution_clock::now();
|
|
|
|
// Modifier légèrement le code
|
|
std::string oldVer = "v" + std::to_string(reload) + ".0";
|
|
std::string newVer = "v" + std::to_string(reload + 1) + ".0";
|
|
modifySourceFile("tests/modules/HeavyStateModule.cpp", oldVer, newVer);
|
|
system("cmake --build build --target HeavyStateModule 2>&1 > /dev/null");
|
|
|
|
engine3.reloadModule("HeavyStateModule");
|
|
|
|
auto incEnd = std::chrono::high_resolution_clock::now();
|
|
float incTime = std::chrono::duration<float, std::milli>(incEnd - incStart).count();
|
|
|
|
incrementalTimes.push_back(incTime);
|
|
std::cout << "Reload #" << reload << ": " << incTime << "ms\n";
|
|
}
|
|
|
|
float avgIncremental = std::accumulate(incrementalTimes.begin(), incrementalTimes.end(), 0.0f) / incrementalTimes.size();
|
|
std::cout << "Average incremental reload: " << avgIncremental << "ms\n";
|
|
|
|
// Note: Pour un vrai système incrémental, devrait être < 100ms
|
|
// Ici on vérifie juste cohérence
|
|
ASSERT_LT(avgIncremental, 2000.0f, "Incremental reloads should be reasonably fast");
|
|
reporter.addMetric("avg_incremental_reload_ms", avgIncremental);
|
|
|
|
std::cout << "✓ TEST 4 PASSED\n";
|
|
|
|
// ========================================================================
|
|
// TEST 5: State Corruption Detection
|
|
// ========================================================================
|
|
std::cout << "\n=== TEST 5: State Corruption Detection ===\n";
|
|
|
|
DebugEngine engine4;
|
|
engine4.loadModule("HeavyStateModule", "build/modules/libHeavyStateModule.so");
|
|
|
|
auto configNormal = createJsonConfig({
|
|
{"version", "v1.0"},
|
|
{"particleCount", 10000},
|
|
{"terrainSize", 100}
|
|
});
|
|
engine4.initializeModule("HeavyStateModule", configNormal);
|
|
|
|
// Exécuter un peu
|
|
for (int i = 0; i < 60; i++) {
|
|
engine4.update(1.0f/60.0f);
|
|
}
|
|
|
|
// Créer un état corrompu
|
|
nlohmann::json corruptedState = {
|
|
{"version", "v1.0"},
|
|
{"frameCount", "INVALID_NOT_A_NUMBER"}, // Type incorrect
|
|
{"config", {
|
|
{"particleCount", -500}, // Valeur négative invalide
|
|
{"terrainSize", std::numeric_limits<float>::quiet_NaN()} // NaN
|
|
}},
|
|
{"particles", {
|
|
{"count", 10000},
|
|
{"data", "CORRUPTED_BASE64!!!"} // Données invalides
|
|
}}
|
|
// Champ "terrain" manquant (requis)
|
|
};
|
|
|
|
auto corruptedNode = std::make_shared<JsonDataNode>(corruptedState);
|
|
|
|
bool detectedCorruption = false;
|
|
try {
|
|
engine4.setModuleState("HeavyStateModule", corruptedNode);
|
|
} catch (const std::exception& e) {
|
|
std::string msg = e.what();
|
|
std::cout << "✓ Corruption detected: " << msg << "\n";
|
|
detectedCorruption = true;
|
|
}
|
|
|
|
ASSERT_TRUE(detectedCorruption, "Should detect corrupted state");
|
|
reporter.addAssertion("corruption_detection", detectedCorruption);
|
|
|
|
// Vérifier que le module reste fonctionnel
|
|
bool stillFunctional = true;
|
|
try {
|
|
for (int i = 0; i < 60; i++) {
|
|
engine4.update(1.0f/60.0f);
|
|
}
|
|
} catch (...) {
|
|
stillFunctional = false;
|
|
}
|
|
|
|
ASSERT_TRUE(stillFunctional, "Module should remain functional after rejected corrupted state");
|
|
reporter.addAssertion("functional_after_corruption", stillFunctional);
|
|
|
|
std::cout << "✓ TEST 5 PASSED\n";
|
|
|
|
// ========================================================================
|
|
// RAPPORT FINAL
|
|
// ========================================================================
|
|
|
|
metrics.printReport();
|
|
reporter.printFinalReport();
|
|
|
|
return reporter.getExitCode();
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 Métriques Collectées
|
|
|
|
| Métrique | Description | Seuil |
|
|
|----------|-------------|-------|
|
|
| **init_time_s** | Temps d'initialisation module lourd | 7-10s |
|
|
| **getstate_time_ms** | Temps extraction état 120MB | < 2000ms |
|
|
| **setstate_time_ms** | Temps restauration état 120MB | < 2000ms |
|
|
| **state_size_mb** | Taille état sérialisé | ~120MB |
|
|
| **reload_under_pressure_ms** | Reload sous contrainte mémoire | < 3000ms |
|
|
| **memory_growth_mb** | Croissance mémoire nette | < 10MB |
|
|
| **avg_incremental_reload_ms** | Temps moyen reload incrémental | < 2000ms |
|
|
|
|
---
|
|
|
|
## ✅ Critères de Succès
|
|
|
|
### MUST PASS
|
|
1. ✅ getState() < 2000ms pour état 120MB
|
|
2. ✅ setState() < 2000ms pour état 120MB
|
|
3. ✅ Timeout détecté correctement (init > timeout)
|
|
4. ✅ Recovery après timeout fonctionne
|
|
5. ✅ Reload sous pression mémoire ne crash pas
|
|
6. ✅ Memory growth < 10MB
|
|
7. ✅ Corruption détectée et rejetée
|
|
8. ✅ Module reste fonctionnel après corruption
|
|
|
|
### NICE TO HAVE
|
|
1. ✅ getState() < 1000ms (optimal)
|
|
2. ✅ setState() < 1000ms (optimal)
|
|
3. ✅ Incremental reload < 100ms
|
|
4. ✅ Message d'erreur descriptif pour corruption
|
|
|
|
---
|
|
|
|
## 🔧 Helpers Nécessaires
|
|
|
|
### Compression Helper
|
|
```cpp
|
|
// Pour réduire taille JSON
|
|
#include <zlib.h>
|
|
|
|
std::string compressData(const std::vector<uint8_t>& data) {
|
|
uLongf compressedSize = compressBound(data.size());
|
|
std::vector<uint8_t> compressed(compressedSize);
|
|
|
|
int result = compress(compressed.data(), &compressedSize,
|
|
data.data(), data.size());
|
|
|
|
if (result != Z_OK) {
|
|
throw std::runtime_error("Compression failed");
|
|
}
|
|
|
|
compressed.resize(compressedSize);
|
|
|
|
// Base64 encode
|
|
return base64_encode(compressed);
|
|
}
|
|
|
|
std::vector<uint8_t> decompressData(const std::string& base64Str) {
|
|
auto compressed = base64_decode(base64Str);
|
|
|
|
// Taille décompressée doit être dans metadata
|
|
uLongf uncompressedSize = getUncompressedSize(compressed);
|
|
std::vector<uint8_t> uncompressed(uncompressedSize);
|
|
|
|
int result = uncompress(uncompressed.data(), &uncompressedSize,
|
|
compressed.data(), compressed.size());
|
|
|
|
if (result != Z_OK) {
|
|
throw std::runtime_error("Decompression failed");
|
|
}
|
|
|
|
return uncompressed;
|
|
}
|
|
```
|
|
|
|
### State Validator
|
|
```cpp
|
|
bool HeavyStateModule::validateState(std::shared_ptr<IDataNode> state) const {
|
|
auto* jsonNode = dynamic_cast<JsonDataNode*>(state.get());
|
|
if (!jsonNode) return false;
|
|
|
|
const auto& data = jsonNode->getJsonData();
|
|
|
|
// Vérifier champs requis
|
|
if (!data.contains("version") || !data.contains("config") ||
|
|
!data.contains("particles") || !data.contains("terrain")) {
|
|
std::cerr << "ERROR: Missing required fields\n";
|
|
return false;
|
|
}
|
|
|
|
// Vérifier types
|
|
if (!data["frameCount"].is_number_integer()) {
|
|
std::cerr << "ERROR: frameCount must be integer\n";
|
|
return false;
|
|
}
|
|
|
|
// Vérifier limites
|
|
int particleCount = data["config"]["particleCount"];
|
|
if (particleCount < 0 || particleCount > 10000000) {
|
|
std::cerr << "ERROR: Invalid particle count: " << particleCount << "\n";
|
|
return false;
|
|
}
|
|
|
|
// Vérifier NaN/Infinity
|
|
int terrainSize = data["config"]["terrainSize"];
|
|
if (std::isnan(terrainSize) || std::isinf(terrainSize)) {
|
|
std::cerr << "ERROR: terrain size is NaN/Inf\n";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🐛 Cas d'Erreur Attendus
|
|
|
|
| Erreur | Cause | Action |
|
|
|--------|-------|--------|
|
|
| getState() > 2000ms | Sérialisation trop lente | FAIL - optimiser ou compresser |
|
|
| setState() > 2000ms | Désérialisation lente | FAIL - optimiser parsing JSON |
|
|
| Timeout non détecté | Init bloque sans timeout | FAIL - implémenter timeout thread |
|
|
| OOM pendant reload | Pic mémoire trop élevé | FAIL - limiter allocation simultanée |
|
|
| Corruption non détectée | Validation insuffisante | FAIL - renforcer validateState() |
|
|
| Memory leak > 10MB | Ressources non libérées | FAIL - check destructeurs |
|
|
|
|
---
|
|
|
|
## 📝 Output Attendu
|
|
|
|
```
|
|
================================================================================
|
|
TEST: Limite Tests
|
|
================================================================================
|
|
|
|
=== TEST 1: Large State Serialization ===
|
|
Initialization took: 8.2s
|
|
getState() took: 1243ms
|
|
State size: 118.4 MB
|
|
setState() + reload took: 1389ms
|
|
✓ Particles preserved: 1000000/1000000
|
|
✓ TEST 1 PASSED
|
|
|
|
=== TEST 2: Long Initialization Timeout ===
|
|
✓ Timeout detected correctly: Module init exceeded timeout (12s > 10s)
|
|
✓ Recovery successful with timeout=15s
|
|
✓ TEST 2 PASSED
|
|
|
|
=== TEST 3: Memory Pressure During Reload ===
|
|
Memory before: 124.3 MB
|
|
Memory during reload: 332.7 MB
|
|
Reload under pressure took: 1876ms
|
|
Memory after: 127.1 MB
|
|
Net memory growth: 2.8 MB
|
|
✓ TEST 3 PASSED
|
|
|
|
=== TEST 4: Incremental State Save/Load ===
|
|
Reload #0: 245ms
|
|
Reload #1: 238ms
|
|
Reload #2: 251ms
|
|
...
|
|
Reload #9: 242ms
|
|
Average incremental reload: 244ms
|
|
✓ TEST 4 PASSED
|
|
|
|
=== TEST 5: State Corruption Detection ===
|
|
✓ Corruption detected: Invalid particle count (negative value)
|
|
✓ Module remains functional after rejecting corrupted state
|
|
✓ TEST 5 PASSED
|
|
|
|
================================================================================
|
|
METRICS
|
|
================================================================================
|
|
Init time: 8.2s
|
|
getState time: 1243ms (threshold: < 2000ms) ✓
|
|
setState time: 1389ms (threshold: < 2000ms) ✓
|
|
State size: 118.4MB
|
|
Reload under pressure: 1876ms (threshold: < 3000ms) ✓
|
|
Memory growth: 2.8MB (threshold: < 10MB) ✓
|
|
Avg incremental reload: 244ms
|
|
|
|
================================================================================
|
|
ASSERTIONS
|
|
================================================================================
|
|
✓ particles_preserved
|
|
✓ timeout_detection
|
|
✓ timeout_recovery
|
|
✓ corruption_detection
|
|
✓ functional_after_corruption
|
|
|
|
Result: ✅ PASSED (5/5 tests)
|
|
|
|
================================================================================
|
|
```
|
|
|
|
---
|
|
|
|
## 📅 Planning
|
|
|
|
**Jour 1 (3h):**
|
|
- Implémenter HeavyStateModule avec gestion large state
|
|
- Implémenter compression/décompression
|
|
- Implémenter state validation
|
|
|
|
**Jour 2 (1h):**
|
|
- Implémenter test_07_limits.cpp
|
|
- Debug + validation
|
|
|
|
---
|
|
|
|
**Prochaine étape**: `scenario_08_config_hotreload.md` (optionnel)
|