- 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>
33 KiB
33 KiB
Scénario 12: DataNode Integration Test
Priorité: ⭐⭐ SHOULD HAVE Phase: 2 (SHOULD HAVE) Durée estimée: ~4 minutes Effort implémentation: ~5-7 heures
🎯 Objectif
Valider que le système DataNode (IDataTree/JsonDataTree) fonctionne correctement pour tous les cas d'usage:
- Tree navigation (exact match, pattern matching, queries)
- Hot-reload system (file watch, callbacks, isolation)
- Persistence (save/load, data integrity)
- Hash system (data hash, tree hash, change detection)
- Read-only enforcement (config/ vs data/ vs runtime/)
- Type safety et defaults
- Performance avec large trees (1000+ nodes)
Note: Le DataNode est le système central de configuration et persistence du moteur.
📋 Description
Setup Initial
- Créer un IDataTree avec structure complète:
- config/ - Configuration read-only avec 500 nodes
- data/ - Persistence read-write avec 300 nodes
- runtime/ - State temporaire avec 200 nodes
- Total: ~1000 nodes dans l'arbre
- Fichiers JSON sur disque pour config/ et data/
Test Séquence
Test 1: Tree Navigation & Exact Matching (30s)
- Créer hiérarchie:
config/units/tanks/heavy_mk1 - Tester navigation:
getChild("units")→getChild("tanks")→getChild("heavy_mk1")getChildrenByName("heavy_mk1")- direct children onlygetPath()- verify full path
- Vérifier:
- Nodes trouvés correctement
- Path correct: "config/units/tanks/heavy_mk1"
- getChild retourne nullptr si non trouvé
Test 2: Pattern Matching (Wildcards) (30s)
- Créer nodes:
config/units/tanks/heavy_mk1config/units/tanks/heavy_mk2config/units/infantry/heavy_trooperconfig/units/aircraft/light_fighter
- Tester patterns:
getChildrenByNameMatch("*heavy*")→ 3 matchesgetChildrenByNameMatch("tanks/*")→ 2 matchesgetChildrenByNameMatch("*_mk*")→ 2 matches
- Vérifier tous les matches corrects
Test 3: Property-Based Queries (30s)
- Créer nodes avec propriétés:
heavy_mk1: armor=150, speed=30, cost=1000heavy_mk2: armor=180, speed=25, cost=1200light_fighter: armor=50, speed=120, cost=800
- Query predicates:
queryByProperty("armor", val > 100)→ 2 unitsqueryByProperty("speed", val > 50)→ 1 unitqueryByProperty("cost", val <= 1000)→ 2 units
- Vérifier résultats des queries
Test 4: Hot-Reload System (60s)
- Créer
config/gameplay.jsonsur disque - Charger dans tree avec
onTreeReloadedcallback - Modifier fichier sur disque (changer valeur)
- Appeler
checkForChanges()→ devrait détecter changement - Appeler
reloadIfChanged()→ callback déclenché - Vérifier:
- Callback appelé exactement 1 fois
- Nouvelles valeurs chargées
- Anciens nodes remplacés
Test 5: Hot-Reload Isolation (30s)
- Charger 2 fichiers:
config/units.jsonetconfig/maps.json - Modifier seulement
units.json - Vérifier:
checkForChanges()détecte seulement units.json- Reload ne touche pas maps.json
- Callback reçoit info sur quel fichier changé
Test 6: Persistence (Save/Load) (60s)
- Créer structure data/:
data/player/stats- {kills: 42, deaths: 3}data/player/inventory- {gold: 1000, items: [...]}data/world/time- {day: 5, hour: 14}
- Appeler
saveData()→ écrit sur disque - Vérifier fichiers créés:
data/player/stats.jsondata/player/inventory.jsondata/world/time.json
- Charger dans nouveau tree
- Vérifier data identique (deep comparison)
Test 7: Selective Save (30s)
- Modifier seulement
data/player/stats - Appeler
saveNode("data/player/stats") - Vérifier:
- Seulement stats.json écrit
- Autres fichiers non modifiés (mtime identique)
Test 8: Hash System (Data Hash) (30s)
- Créer node avec data:
{value: 42} - Calculer
getDataHash() - Modifier data:
{value: 43} - Recalculer hash
- Vérifier hashes différents
Test 9: Hash System (Tree Hash) (30s)
- Créer arbre:
root ├─ child1 {data: 1} └─ child2 {data: 2} - Calculer
getTreeHash() - Modifier child1 data
- Recalculer tree hash
- Vérifier hashes différents (propagation)
Test 10: Read-Only Enforcement (30s)
- Tenter
setChild()sur node config/ - Devrait throw exception
- Vérifier:
- Exception levée
- Message descriptif
- Config/ non modifié
Test 11: Type Safety & Defaults (20s)
- Créer node:
{armor: 150, name: "Tank"} - Tester accès:
getInt("armor")→ 150getInt("missing", 100)→ 100 (default)getString("name")→ "Tank"getBool("active", true)→ true (default)getDouble("speed")→ throw ou default
Test 12: Deep Tree Performance (30s)
- Créer tree avec 1000 nodes:
- 10 catégories
- 10 subcatégories each
- 10 items each
- Mesurer temps:
- Pattern matching "*" (tous nodes): < 100ms
- Query by property: < 50ms
- Tree hash calculation: < 200ms
- Vérifier performance acceptable
🏗️ Implémentation
Test Module Structure
// DataNodeTestModule.h
class DataNodeTestModule : public IModule {
public:
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; }
// Test helpers
void createTestTree();
void testNavigation();
void testPatternMatching();
void testQueries();
void testHotReload();
void testPersistence();
void testHashes();
void testReadOnly();
void testTypeAccess();
void testPerformance();
private:
std::shared_ptr<IDataTree> tree;
int reloadCallbackCount = 0;
};
Test Principal
// test_12_datanode.cpp
#include "helpers/TestMetrics.h"
#include "helpers/TestAssertions.h"
#include "helpers/TestReporter.h"
#include <fstream>
int main() {
TestReporter reporter("DataNode Integration Test");
TestMetrics metrics;
// === SETUP ===
std::filesystem::create_directories("test_data/config");
std::filesystem::create_directories("test_data/data");
// Créer IDataTree
auto tree = std::make_shared<JsonDataTree>("test_data");
// ========================================================================
// TEST 1: Tree Navigation & Exact Matching
// ========================================================================
std::cout << "\n=== TEST 1: Tree Navigation & Exact Matching ===\n";
// Créer hiérarchie
auto configRoot = tree->getConfigRoot();
auto units = std::make_shared<JsonDataNode>("units", nlohmann::json::object());
auto tanks = std::make_shared<JsonDataNode>("tanks", nlohmann::json::object());
auto heavyMk1 = std::make_shared<JsonDataNode>("heavy_mk1", nlohmann::json{
{"armor", 150},
{"speed", 30},
{"cost", 1000}
});
tanks->setChild("heavy_mk1", heavyMk1);
units->setChild("tanks", tanks);
configRoot->setChild("units", units);
// Navigation
auto foundUnits = configRoot->getChild("units");
ASSERT_TRUE(foundUnits != nullptr, "Should find units node");
auto foundTanks = foundUnits->getChild("tanks");
ASSERT_TRUE(foundTanks != nullptr, "Should find tanks node");
auto foundHeavy = foundTanks->getChild("heavy_mk1");
ASSERT_TRUE(foundHeavy != nullptr, "Should find heavy_mk1 node");
// Path
std::string path = foundHeavy->getPath();
std::cout << "Path: " << path << "\n";
ASSERT_TRUE(path.find("heavy_mk1") != std::string::npos, "Path should contain node name");
// Not found
auto notFound = foundTanks->getChild("does_not_exist");
ASSERT_TRUE(notFound == nullptr, "Should return nullptr for missing child");
reporter.addAssertion("navigation_exact", true);
std::cout << "✓ TEST 1 PASSED\n";
// ========================================================================
// TEST 2: Pattern Matching (Wildcards)
// ========================================================================
std::cout << "\n=== TEST 2: Pattern Matching ===\n";
// Ajouter plus de nodes
auto heavyMk2 = std::make_shared<JsonDataNode>("heavy_mk2", nlohmann::json{
{"armor", 180},
{"speed", 25},
{"cost", 1200}
});
tanks->setChild("heavy_mk2", heavyMk2);
auto infantry = std::make_shared<JsonDataNode>("infantry", nlohmann::json::object());
auto heavyTrooper = std::make_shared<JsonDataNode>("heavy_trooper", nlohmann::json{
{"armor", 120},
{"speed", 15},
{"cost", 500}
});
infantry->setChild("heavy_trooper", heavyTrooper);
units->setChild("infantry", infantry);
auto aircraft = std::make_shared<JsonDataNode>("aircraft", nlohmann::json::object());
auto lightFighter = std::make_shared<JsonDataNode>("light_fighter", nlohmann::json{
{"armor", 50},
{"speed", 120},
{"cost", 800}
});
aircraft->setChild("light_fighter", lightFighter);
units->setChild("aircraft", aircraft);
// Pattern: *heavy*
auto heavyUnits = configRoot->getChildrenByNameMatch("*heavy*");
std::cout << "Pattern '*heavy*' matched: " << heavyUnits.size() << " nodes\n";
for (const auto& node : heavyUnits) {
std::cout << " - " << node->getName() << "\n";
}
// Should match: heavy_mk1, heavy_mk2, heavy_trooper
ASSERT_EQ(heavyUnits.size(), 3, "Should match 3 'heavy' units");
reporter.addMetric("pattern_heavy_count", heavyUnits.size());
// Pattern: *_mk*
auto mkUnits = configRoot->getChildrenByNameMatch("*_mk*");
std::cout << "Pattern '*_mk*' matched: " << mkUnits.size() << " nodes\n";
// Should match: heavy_mk1, heavy_mk2
ASSERT_EQ(mkUnits.size(), 2, "Should match 2 '_mk' units");
reporter.addAssertion("pattern_matching", true);
std::cout << "✓ TEST 2 PASSED\n";
// ========================================================================
// TEST 3: Property-Based Queries
// ========================================================================
std::cout << "\n=== TEST 3: Property-Based Queries ===\n";
// Query: armor > 100
auto armoredUnits = configRoot->queryByProperty("armor",
[](const IDataValue& val) {
return val.isNumber() && val.asInt() >= 100;
});
std::cout << "Units with armor >= 100: " << armoredUnits.size() << "\n";
for (const auto& node : armoredUnits) {
int armor = node->getInt("armor");
std::cout << " - " << node->getName() << " (armor=" << armor << ")\n";
ASSERT_GE(armor, 100, "Armor should be >= 100");
}
// Should match: heavy_mk1 (150), heavy_mk2 (180), heavy_trooper (120)
ASSERT_EQ(armoredUnits.size(), 3, "Should find 3 armored units");
// Query: speed > 50
auto fastUnits = configRoot->queryByProperty("speed",
[](const IDataValue& val) {
return val.isNumber() && val.asInt() > 50;
});
std::cout << "Units with speed > 50: " << fastUnits.size() << "\n";
// Should match: light_fighter (120)
ASSERT_EQ(fastUnits.size(), 1, "Should find 1 fast unit");
reporter.addAssertion("property_queries", true);
std::cout << "✓ TEST 3 PASSED\n";
// ========================================================================
// TEST 4: Hot-Reload System
// ========================================================================
std::cout << "\n=== TEST 4: Hot-Reload System ===\n";
// Créer fichier config
nlohmann::json gameplayConfig = {
{"difficulty", "normal"},
{"maxPlayers", 4},
{"timeLimit", 3600}
};
std::ofstream configFile("test_data/config/gameplay.json");
configFile << gameplayConfig.dump(2);
configFile.close();
// Charger dans tree
tree->loadConfigFile("gameplay.json");
// Setup callback
int callbackCount = 0;
tree->onTreeReloaded([&callbackCount]() {
callbackCount++;
std::cout << " → Reload callback triggered (count=" << callbackCount << ")\n";
});
// Vérifier contenu initial
auto gameplay = configRoot->getChild("gameplay");
ASSERT_TRUE(gameplay != nullptr, "gameplay node should exist");
std::string difficulty = gameplay->getString("difficulty");
ASSERT_EQ(difficulty, "normal", "Initial difficulty should be 'normal'");
std::cout << "Initial difficulty: " << difficulty << "\n";
// Modifier fichier
std::this_thread::sleep_for(std::chrono::milliseconds(100));
gameplayConfig["difficulty"] = "hard";
gameplayConfig["maxPlayers"] = 8;
std::ofstream configFile2("test_data/config/gameplay.json");
configFile2 << gameplayConfig.dump(2);
configFile2.close();
// Force file timestamp update
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// Check for changes
bool hasChanges = tree->checkForChanges();
std::cout << "Has changes: " << (hasChanges ? "YES" : "NO") << "\n";
ASSERT_TRUE(hasChanges, "Should detect file modification");
// Reload
bool reloaded = tree->reloadIfChanged();
std::cout << "Reloaded: " << (reloaded ? "YES" : "NO") << "\n";
ASSERT_TRUE(reloaded, "Should reload changed file");
// Vérifier callback
ASSERT_EQ(callbackCount, 1, "Callback should be called exactly once");
// Vérifier nouvelles valeurs
gameplay = configRoot->getChild("gameplay");
difficulty = gameplay->getString("difficulty");
int maxPlayers = gameplay->getInt("maxPlayers");
std::cout << "After reload - difficulty: " << difficulty << ", maxPlayers: " << maxPlayers << "\n";
ASSERT_EQ(difficulty, "hard", "Difficulty should be updated to 'hard'");
ASSERT_EQ(maxPlayers, 8, "maxPlayers should be updated to 8");
reporter.addAssertion("hot_reload", true);
reporter.addMetric("reload_callback_count", callbackCount);
std::cout << "✓ TEST 4 PASSED\n";
// ========================================================================
// TEST 5: Hot-Reload Isolation
// ========================================================================
std::cout << "\n=== TEST 5: Hot-Reload Isolation ===\n";
// Créer second fichier
nlohmann::json mapsConfig = {
{"defaultMap", "desert"},
{"mapCount", 10}
};
std::ofstream mapsFile("test_data/config/maps.json");
mapsFile << mapsConfig.dump(2);
mapsFile.close();
tree->loadConfigFile("maps.json");
// Modifier seulement gameplay.json
std::this_thread::sleep_for(std::chrono::milliseconds(100));
gameplayConfig["difficulty"] = "extreme";
std::ofstream configFile3("test_data/config/gameplay.json");
configFile3 << gameplayConfig.dump(2);
configFile3.close();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// Check changes
hasChanges = tree->checkForChanges();
ASSERT_TRUE(hasChanges, "Should detect gameplay.json change");
// Verify maps.json not affected
auto maps = configRoot->getChild("maps");
std::string defaultMap = maps->getString("defaultMap");
ASSERT_EQ(defaultMap, "desert", "maps.json should not be affected");
reloaded = tree->reloadIfChanged();
ASSERT_TRUE(reloaded, "Should reload only changed file");
// Verify maps still intact
maps = configRoot->getChild("maps");
defaultMap = maps->getString("defaultMap");
ASSERT_EQ(defaultMap, "desert", "maps.json should still be 'desert' after isolated reload");
reporter.addAssertion("reload_isolation", true);
std::cout << "✓ TEST 5 PASSED\n";
// ========================================================================
// TEST 6: Persistence (Save/Load)
// ========================================================================
std::cout << "\n=== TEST 6: Persistence (Save/Load) ===\n";
auto dataRoot = tree->getDataRoot();
// Créer structure data/
auto player = std::make_shared<JsonDataNode>("player", nlohmann::json::object());
auto stats = std::make_shared<JsonDataNode>("stats", nlohmann::json{
{"kills", 42},
{"deaths", 3},
{"level", 15}
});
auto inventory = std::make_shared<JsonDataNode>("inventory", nlohmann::json{
{"gold", 1000},
{"items", nlohmann::json::array({"sword", "shield", "potion"})}
});
player->setChild("stats", stats);
player->setChild("inventory", inventory);
dataRoot->setChild("player", player);
auto world = std::make_shared<JsonDataNode>("world", nlohmann::json::object());
auto time = std::make_shared<JsonDataNode>("time", nlohmann::json{
{"day", 5},
{"hour", 14},
{"minute", 30}
});
world->setChild("time", time);
dataRoot->setChild("world", world);
// Save all data
tree->saveData();
// Vérifier fichiers créés
ASSERT_TRUE(std::filesystem::exists("test_data/data/player/stats.json"),
"stats.json should exist");
ASSERT_TRUE(std::filesystem::exists("test_data/data/player/inventory.json"),
"inventory.json should exist");
ASSERT_TRUE(std::filesystem::exists("test_data/data/world/time.json"),
"time.json should exist");
std::cout << "Files saved successfully\n";
// Créer nouveau tree et charger
auto tree2 = std::make_shared<JsonDataTree>("test_data");
tree2->loadDataDirectory();
auto dataRoot2 = tree2->getDataRoot();
auto player2 = dataRoot2->getChild("player");
ASSERT_TRUE(player2 != nullptr, "player node should load");
auto stats2 = player2->getChild("stats");
int kills = stats2->getInt("kills");
int deaths = stats2->getInt("deaths");
std::cout << "Loaded: kills=" << kills << ", deaths=" << deaths << "\n";
ASSERT_EQ(kills, 42, "kills should be preserved");
ASSERT_EQ(deaths, 3, "deaths should be preserved");
reporter.addAssertion("persistence", true);
std::cout << "✓ TEST 6 PASSED\n";
// ========================================================================
// TEST 7: Selective Save
// ========================================================================
std::cout << "\n=== TEST 7: Selective Save ===\n";
// Get mtime of inventory.json before
auto inventoryPath = std::filesystem::path("test_data/data/player/inventory.json");
auto mtimeBefore = std::filesystem::last_write_time(inventoryPath);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// Modify only stats
stats->setInt("kills", 100);
// Save only stats
tree->saveNode("data/player/stats");
// Check inventory.json not modified
auto mtimeAfter = std::filesystem::last_write_time(inventoryPath);
ASSERT_EQ(mtimeBefore, mtimeAfter, "inventory.json should not be modified");
// Load stats and verify
auto tree3 = std::make_shared<JsonDataTree>("test_data");
tree3->loadDataDirectory();
auto stats3 = tree3->getDataRoot()->getChild("player")->getChild("stats");
int newKills = stats3->getInt("kills");
ASSERT_EQ(newKills, 100, "Selective save should update only stats");
reporter.addAssertion("selective_save", true);
std::cout << "✓ TEST 7 PASSED\n";
// ========================================================================
// TEST 8: Hash System (Data Hash)
// ========================================================================
std::cout << "\n=== TEST 8: Hash System (Data Hash) ===\n";
auto testNode = std::make_shared<JsonDataNode>("test", nlohmann::json{
{"value", 42}
});
std::string hash1 = testNode->getDataHash();
std::cout << "Hash 1: " << hash1.substr(0, 16) << "...\n";
// Modify data
testNode->setInt("value", 43);
std::string hash2 = testNode->getDataHash();
std::cout << "Hash 2: " << hash2.substr(0, 16) << "...\n";
ASSERT_TRUE(hash1 != hash2, "Hashes should differ after data change");
reporter.addAssertion("data_hash", true);
std::cout << "✓ TEST 8 PASSED\n";
// ========================================================================
// TEST 9: Hash System (Tree Hash)
// ========================================================================
std::cout << "\n=== TEST 9: Hash System (Tree Hash) ===\n";
auto root = std::make_shared<JsonDataNode>("root", nlohmann::json::object());
auto child1 = std::make_shared<JsonDataNode>("child1", nlohmann::json{{"data", 1}});
auto child2 = std::make_shared<JsonDataNode>("child2", nlohmann::json{{"data", 2}});
root->setChild("child1", child1);
root->setChild("child2", child2);
std::string treeHash1 = root->getTreeHash();
std::cout << "Tree Hash 1: " << treeHash1.substr(0, 16) << "...\n";
// Modify child1
child1->setInt("data", 999);
std::string treeHash2 = root->getTreeHash();
std::cout << "Tree Hash 2: " << treeHash2.substr(0, 16) << "...\n";
ASSERT_TRUE(treeHash1 != treeHash2, "Tree hash should change when child changes");
reporter.addAssertion("tree_hash", true);
std::cout << "✓ TEST 9 PASSED\n";
// ========================================================================
// TEST 10: Read-Only Enforcement
// ========================================================================
std::cout << "\n=== TEST 10: Read-Only Enforcement ===\n";
auto readOnlyNode = configRoot->getChild("gameplay");
bool exceptionThrown = false;
try {
auto newChild = std::make_shared<JsonDataNode>("illegal", nlohmann::json{{"bad", true}});
readOnlyNode->setChild("illegal", newChild);
} catch (const std::runtime_error& e) {
std::cout << "✓ Exception thrown: " << e.what() << "\n";
exceptionThrown = true;
}
ASSERT_TRUE(exceptionThrown, "Should throw exception when modifying read-only node");
reporter.addAssertion("readonly_enforcement", true);
std::cout << "✓ TEST 10 PASSED\n";
// ========================================================================
// TEST 11: Type Safety & Defaults
// ========================================================================
std::cout << "\n=== TEST 11: Type Safety & Defaults ===\n";
auto typeNode = std::make_shared<JsonDataNode>("types", nlohmann::json{
{"armor", 150},
{"name", "Tank"},
{"active", true},
{"speed", 30.5}
});
int armor = typeNode->getInt("armor");
ASSERT_EQ(armor, 150, "getInt should return correct value");
int missing = typeNode->getInt("missing", 100);
ASSERT_EQ(missing, 100, "getInt with default should return default");
std::string name = typeNode->getString("name");
ASSERT_EQ(name, "Tank", "getString should return correct value");
bool active = typeNode->getBool("active");
ASSERT_EQ(active, true, "getBool should return correct value");
bool defaultBool = typeNode->getBool("nothere", false);
ASSERT_EQ(defaultBool, false, "getBool with default should return default");
double speed = typeNode->getDouble("speed");
ASSERT_EQ(speed, 30.5, "getDouble should return correct value");
reporter.addAssertion("type_safety", true);
std::cout << "✓ TEST 11 PASSED\n";
// ========================================================================
// TEST 12: Deep Tree Performance
// ========================================================================
std::cout << "\n=== TEST 12: Deep Tree Performance ===\n";
auto perfRoot = std::make_shared<JsonDataNode>("perf", nlohmann::json::object());
// Create 1000 nodes: 10 x 10 x 10
int nodeCount = 0;
for (int cat = 0; cat < 10; cat++) {
auto category = std::make_shared<JsonDataNode>("cat_" + std::to_string(cat),
nlohmann::json::object());
for (int sub = 0; sub < 10; sub++) {
auto subcategory = std::make_shared<JsonDataNode>("sub_" + std::to_string(sub),
nlohmann::json::object());
for (int item = 0; item < 10; item++) {
auto itemNode = std::make_shared<JsonDataNode>("item_" + std::to_string(item),
nlohmann::json{
{"id", nodeCount},
{"value", nodeCount * 10}
});
subcategory->setChild("item_" + std::to_string(item), itemNode);
nodeCount++;
}
category->setChild("sub_" + std::to_string(sub), subcategory);
}
perfRoot->setChild("cat_" + std::to_string(cat), category);
}
std::cout << "Created " << nodeCount << " nodes\n";
ASSERT_EQ(nodeCount, 1000, "Should create 1000 nodes");
// Pattern matching: find all items
auto start = std::chrono::high_resolution_clock::now();
auto allItems = perfRoot->getChildrenByNameMatch("item_*");
auto end = std::chrono::high_resolution_clock::now();
float patternTime = std::chrono::duration<float, std::milli>(end - start).count();
std::cout << "Pattern matching found " << allItems.size() << " items in " << patternTime << "ms\n";
ASSERT_EQ(allItems.size(), 1000, "Should find all 1000 items");
ASSERT_LT(patternTime, 100.0f, "Pattern matching should be < 100ms");
reporter.addMetric("pattern_time_ms", patternTime);
// Query by property
start = std::chrono::high_resolution_clock::now();
auto queryResults = perfRoot->queryByProperty("value",
[](const IDataValue& val) {
return val.isNumber() && val.asInt() > 5000;
});
end = std::chrono::high_resolution_clock::now();
float queryTime = std::chrono::duration<float, std::milli>(end - start).count();
std::cout << "Query found " << queryResults.size() << " results in " << queryTime << "ms\n";
ASSERT_LT(queryTime, 50.0f, "Query should be < 50ms");
reporter.addMetric("query_time_ms", queryTime);
// Tree hash
start = std::chrono::high_resolution_clock::now();
std::string treeHash = perfRoot->getTreeHash();
end = std::chrono::high_resolution_clock::now();
float hashTime = std::chrono::duration<float, std::milli>(end - start).count();
std::cout << "Tree hash calculated in " << hashTime << "ms\n";
ASSERT_LT(hashTime, 200.0f, "Tree hash should be < 200ms");
reporter.addMetric("treehash_time_ms", hashTime);
reporter.addAssertion("performance", true);
std::cout << "✓ TEST 12 PASSED\n";
// ========================================================================
// CLEANUP
// ========================================================================
std::filesystem::remove_all("test_data");
// ========================================================================
// RAPPORT FINAL
// ========================================================================
metrics.printReport();
reporter.printFinalReport();
return reporter.getExitCode();
}
📊 Métriques Collectées
| Métrique | Description | Seuil |
|---|---|---|
| pattern_heavy_count | Nodes matchés par pattern "heavy" | 3 |
| reload_callback_count | Callbacks déclenchés lors reload | 1 |
| pattern_time_ms | Temps pattern matching 1000 nodes | < 100ms |
| query_time_ms | Temps property query 1000 nodes | < 50ms |
| treehash_time_ms | Temps calcul tree hash 1000 nodes | < 200ms |
✅ Critères de Succès
MUST PASS
- ✅ Navigation exacte fonctionne (getChild, getPath)
- ✅ Pattern matching trouve tous les matches
- ✅ Property queries retournent résultats corrects
- ✅ Hot-reload détecte changements fichier
- ✅ Hot-reload callback déclenché
- ✅ Hot-reload isolation (un fichier modifié n'affecte pas autres)
- ✅ Persistence save/load préserve data
- ✅ Selective save modifie seulement node ciblé
- ✅ Data hash change quand data modifié
- ✅ Tree hash change quand children modifiés
- ✅ Read-only nodes throw exception si modifiés
- ✅ Type access avec defaults fonctionne
- ✅ Performance acceptable sur 1000 nodes
NICE TO HAVE
- ✅ Pattern matching < 50ms (optimal)
- ✅ Query < 25ms (optimal)
- ✅ Tree hash < 100ms (optimal)
🐛 Cas d'Erreur Attendus
| Erreur | Cause | Action |
|---|---|---|
| Pattern pas match | Regex incorrecte | FAIL - fix wildcard conversion |
| Query vide | Predicate trop strict | WARN - vérifier logique |
| Hot-reload pas détecté | File watch bug | FAIL - fix checkForChanges() |
| Callback pas appelé | onTreeReloaded bug | FAIL - fix callback system |
| Persistence data corrompu | JSON malformé | FAIL - add validation |
| Hash identiques | Hash calculation bug | FAIL - fix getDataHash() |
| Read-only pas enforced | isReadOnly check manquant | FAIL - add check |
| Type mismatch crash | Pas de default handling | FAIL - add try/catch |
| Performance > seuils | Algorithme O(n²) | FAIL - optimize |
📝 Output Attendu
================================================================================
TEST: DataNode Integration Test
================================================================================
=== TEST 1: Tree Navigation & Exact Matching ===
Path: config/units/tanks/heavy_mk1
✓ TEST 1 PASSED
=== TEST 2: Pattern Matching ===
Pattern '*heavy*' matched: 3 nodes
- heavy_mk1
- heavy_mk2
- heavy_trooper
Pattern '*_mk*' matched: 2 nodes
✓ TEST 2 PASSED
=== TEST 3: Property-Based Queries ===
Units with armor >= 100: 3
- heavy_mk1 (armor=150)
- heavy_mk2 (armor=180)
- heavy_trooper (armor=120)
Units with speed > 50: 1
✓ TEST 3 PASSED
=== TEST 4: Hot-Reload System ===
Initial difficulty: normal
Has changes: YES
Reloaded: YES
→ Reload callback triggered (count=1)
After reload - difficulty: hard, maxPlayers: 8
✓ TEST 4 PASSED
=== TEST 5: Hot-Reload Isolation ===
✓ TEST 5 PASSED
=== TEST 6: Persistence (Save/Load) ===
Files saved successfully
Loaded: kills=42, deaths=3
✓ TEST 6 PASSED
=== TEST 7: Selective Save ===
✓ TEST 7 PASSED
=== TEST 8: Hash System (Data Hash) ===
Hash 1: 5d41402abc4b2a76...
Hash 2: 7c6a180b36896a0e...
✓ TEST 8 PASSED
=== TEST 9: Hash System (Tree Hash) ===
Tree Hash 1: a1b2c3d4e5f6g7h8...
Tree Hash 2: 9i8j7k6l5m4n3o2p...
✓ TEST 9 PASSED
=== TEST 10: Read-Only Enforcement ===
✓ Exception thrown: Cannot modify read-only node 'gameplay'
✓ TEST 10 PASSED
=== TEST 11: Type Safety & Defaults ===
✓ TEST 11 PASSED
=== TEST 12: Deep Tree Performance ===
Created 1000 nodes
Pattern matching found 1000 items in 45.3ms
Query found 500 results in 23.7ms
Tree hash calculated in 134.2ms
✓ TEST 12 PASSED
================================================================================
METRICS
================================================================================
Pattern heavy count: 3
Reload callback count: 1
Pattern time: 45.3ms (threshold: < 100ms) ✓
Query time: 23.7ms (threshold: < 50ms) ✓
Tree hash time: 134.2ms (threshold: < 200ms) ✓
================================================================================
ASSERTIONS
================================================================================
✓ navigation_exact
✓ pattern_matching
✓ property_queries
✓ hot_reload
✓ reload_isolation
✓ persistence
✓ selective_save
✓ data_hash
✓ tree_hash
✓ readonly_enforcement
✓ type_safety
✓ performance
Result: ✅ PASSED (12/12 tests)
================================================================================
📅 Planning
Jour 1 (4h):
- Setup JsonDataTree avec test directory
- Implémenter tests 1-6 (navigation, patterns, queries, hot-reload, persistence)
Jour 2 (3h):
- Implémenter tests 7-12 (selective save, hashes, readonly, types, performance)
- Debug + validation
Prochaine étape: scenario_13_cross_system.md