🔍 Failure Diagnosis - Flowchart de Decisión¶
🎯 Clasificación Rápida¶
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ [Test falló] → ¿Falla consistentemente? │
│ ↓ ↓ │
│ SÍ NO │
│ ↓ ↓ │
│ 🐛 Bug real ⚡ Flaky test │
│ │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ │
│ Prueba de consistencia: │
│ Run 10 veces → Si falla todas = Bug real │
│ → Si falla algunas = Flaky │
│ │
└─────────────────────────────────────────────────────────────────────┘
🐛 Bug Real: Patrones Comunes¶
╔═════════════════════════════════════════════════════════════════════╗
║ ║
║ CATEGORÍAS DE BUGS REALES ║
║ ║
╚═════════════════════════════════════════════════════════════════════╝
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 🔴 ASSERTION FAILURE │
│ │
│ Síntoma: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ FAILED: │ │
│ │ REQUIRE( actual == expected ) │ │
│ │ with expansion: │ │
│ │ 440.123 == 440.0 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ Investigar: │
│ → ¿Cambió el algoritmo recientemente? │
│ → ¿Es regresión de un commit específico? │
│ → ¿Tolerancia demasiado estricta? │
│ → ¿Test expectation desactualizada? │
│ │
│ Acción: │
│ 1. git bisect para encontrar commit culpable │
│ 2. Review cambios en ese commit │
│ 3. Verificar si bug o test incorrecto │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 💥 CRASH / SEGFAULT │
│ │
│ Síntoma: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Segmentation fault (core dumped) │ │
│ │ Signal: SIGSEGV │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ Causas comunes: │
│ → Null pointer dereference │
│ → Out of bounds array access │
│ → Use after free │
│ → Stack overflow (infinite recursion) │
│ │
│ Diagnóstico: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ # GDB debugger │ │
│ │ gdb ./test_executable core │ │
│ │ (gdb) bt # Backtrace │ │
│ │ (gdb) frame 0 # Inspect crash frame │ │
│ │ (gdb) print var # Inspect variables │ │
│ │ │ │
│ │ # AddressSanitizer │ │
│ │ cmake -DASAN=ON .. │ │
│ │ ./test_executable # Will pinpoint exact line │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ ⏱️ TIMEOUT │
│ │
│ Síntoma: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Test timed out after 30 seconds │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ Causas comunes: │
│ → Infinite loop │
│ → Deadlock (thread waiting forever) │
│ → Performance regression (código muy lento) │
│ → Blocking I/O sin timeout │
│ │
│ Diagnóstico: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ # Attach debugger mientras corre │ │
│ │ gdb -p <pid> │ │
│ │ (gdb) thread apply all bt # Backtrace de todos threads │ │
│ │ │ │
│ │ # Profile para encontrar hotspot │ │
│ │ perf record ./test_executable │ │
│ │ perf report │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 💾 MEMORY LEAK │
│ │
│ Síntoma: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ ==12345== LEAK SUMMARY: │ │
│ │ ==12345== definitely lost: 1,024 bytes in 1 blocks │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ Causas comunes: │
│ → new sin delete │
│ → malloc sin free │
│ → Smart pointer circular reference │
│ → Resource leak (file handles, sockets) │
│ │
│ Diagnóstico: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ # Valgrind (Linux/macOS) │ │
│ │ valgrind --leak-check=full ./test_executable │ │
│ │ │ │
│ │ # AddressSanitizer con leak detection │ │
│ │ ASAN_OPTIONS=detect_leaks=1 ./test_executable │ │
│ │ │ │
│ │ # Windows: Visual Studio memory profiler │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
⚡ Flaky Test: Causas¶
╔═════════════════════════════════════════════════════════════════════╗
║ ║
║ CATEGORÍAS DE FLAKINESS ║
║ ║
║ 🎲 Race conditions → Threading bug ║
║ ⏰ Timing dependencies → Sleep/wait unreliable ║
║ 🌐 External dependencies → Network, filesystem ║
║ 🎰 Random data → Seed not fixed ║
║ 💻 Platform-specific → OS/compiler differences ║
║ 📊 Floating point → Rounding differences ║
║ ║
╚═════════════════════════════════════════════════════════════════════╝
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 🎲 RACE CONDITIONS │
│ │
│ Ejemplo problemático: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ // Thread 1 │ │
│ │ data = new AudioBuffer(); │ │
│ │ │ │
│ │ // Thread 2 │ │
│ │ process(data); // ⚠️ Puede ejecutar antes de new │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ Síntoma: │
│ → Falla ~20% de las veces │
│ → Falla más en máquinas rápidas/multicore │
│ → Falla difiere entre runs │
│ │
│ Solución: │
│ → NO testear multithreading directamente en unit tests │
│ → Usar mocks para simular threading │
│ → Dedicated integration tests con sincronización explícita │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ ⏰ TIMING DEPENDENCIES │
│ │
│ Ejemplo problemático: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ startAsyncOperation(); │ │
│ │ sleep(100); // "Should be enough" │ │
│ │ REQUIRE(operation.isComplete()); // ⚠️ No garantizado │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ Síntoma: │
│ → Falla en máquinas lentas (CI servers) │
│ → Falla bajo carga │
│ → Timing assumptions inválidos │
│ │
│ Solución: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ // ✅ Wait explícito con condition │ │
│ │ bool waitUntil(condition, timeout) { │ │
│ │ auto start = now(); │ │
│ │ while (!condition() && now() - start < timeout) { │ │
│ │ sleep(10ms); │ │
│ │ } │ │
│ │ return condition(); │ │
│ │ } │ │
│ │ │ │
│ │ REQUIRE(waitUntil([&]{ return op.isComplete(); }, 5s)); │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 🌐 EXTERNAL DEPENDENCIES │
│ │
│ Dependencias problemáticas: │
│ → Network availability │
│ → Specific file existing in filesystem │
│ → System time/date │
│ → Environment variables │
│ → Hardware (audio device, GPU) │
│ │
│ Síntoma: │
│ → Pasa localmente, falla en CI │
│ → Falla en diferentes plataformas │
│ → Falla cuando ejecutas desde diferente directorio │
│ │
│ Solución: │
│ → Mock filesystem (usar test fixtures) │
│ → Mock network (no hacer real HTTP calls) │
│ → Inject time source (no usar system clock directamente) │
│ → Inject dependencies (dependency injection pattern) │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 🎰 RANDOM DATA │
│ │
│ Ejemplo problemático: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ float testValue = random(); // ⚠️ Different cada run │ │
│ │ auto result = process(testValue); │ │
│ │ REQUIRE(result > 0); // Puede fallar con ciertos valores │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ Síntoma: │
│ → Resultados no reproducibles │
│ │ Falla esporádicamente sin patrón │
│ → Difícil de debuggear (no puedes reproducir) │
│ │
│ Solución: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ // ✅ Fix random seed │ │
│ │ srand(42); // Same sequence every run │ │
│ │ │ │
│ │ // O mejor: usar valores determinísticos │ │
│ │ float testValue = 0.5f; // Explicit, reproducible │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
🔧 Quick Fixes Reference¶
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ Problema │ Solución Rápida │
│ ════════════════════════╪═════════════════════════════════ │
│ Timing issue │ Remove sleeps, use mocks │
│ Race condition │ Synchronization primitives │
│ Random failures │ Fix seed: srand(42) │
│ Platform variance │ Adjust tolerances │
│ Filesystem dependency │ Mock filesystem │
│ Float comparison │ Use REQUIRE_THAT(x, Approx(y)) │
│ Network dependency │ Mock HTTP client │
│ System time dependency │ Inject clock, use fake time │
│ Memory leak │ Smart pointers, RAII │
│ Null pointer │ Add null checks, use optional<> │
│ │ │
└─────────────────────────────────────────────────────────────────────┘
🎯 Workflow de Debug¶
╔═════════════════════════════════════════════════════════════════════╗
║ ║
║ FLOWCHART DE DEBUG ║
║ ║
║ 1️⃣ Reproduce localmente ║
║ → Run test en tu máquina ║
║ → Si no reproduce: es flaky o environment-specific ║
║ ║
║ 2️⃣ Run en aislamiento ║
║ → ./test_specific_case (no suite completo) ║
║ → Elimina interferencia de otros tests ║
║ ║
║ 3️⃣ Increase verbosity/logging ║
║ → CATCH_CONFIG_RUNNER con custom reporter ║
║ → Agregar debug prints en código bajo test ║
║ ║
║ 4️⃣ Debugger breakpoint ║
║ → Set breakpoint en línea de falla ║
║ → Inspect variables, stack, memory ║
║ ║
║ 5️⃣ Inspect state ║
║ → ¿Qué valores tienen las variables? ║
║ → ¿Estado de objetos correcto? ║
║ → ¿Punteros válidos? ║
║ ║
║ 6️⃣ Fix → Verify → Document ║
║ → Apply fix ║
║ → Run test 100 veces para confirmar ║
║ → Document el bug y fix en commit message ║
║ ║
╚═════════════════════════════════════════════════════════════════════╝
💡 Prevention: Evitar Failures Futuros¶
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 🛡️ DEFENSIVE TESTING PRACTICES │
│ │
│ ✅ Siempre fix random seeds │
│ ✅ Mock external dependencies │
│ ✅ Use waitUntil() no sleep() │
│ ✅ Floating point: use Approx() con tolerance │
│ ✅ Null checks antes de dereference │
│ ✅ RAII para resource management │
│ ✅ Run tests con sanitizers (ASAN, TSAN, UBSAN) │
│ ✅ Run tests en paralelo para detectar races │
│ ✅ CI debe run cada test múltiples veces │
│ ✅ Document assumptions en comments │
│ │
└─────────────────────────────────────────────────────────────────────┘
🔍 Tools de Diagnóstico¶
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ Tool │ Qué detecta │
│ ════════════════════════╪═════════════════════════════════ │
│ GDB/LLDB │ Crashes, inspección de estado │
│ Valgrind │ Memory leaks, invalid access │
│ AddressSanitizer │ Memory errors, leaks │
│ ThreadSanitizer │ Race conditions, data races │
│ UndefinedBehaviorSan │ Undefined behavior (overflow, etc) │
│ perf/VTune │ Performance bottlenecks │
│ strace/ltrace │ System calls, library calls │
│ Catch2 reporters │ Custom test output formatting │
│ │ │
└─────────────────────────────────────────────────────────────────────┘
📋 Checklist: Before Filing Bug¶
□ Reproducido localmente (no solo en CI)
□ Reproducido en aislamiento (single test)
□ Verificado que no es flaky (run 10+ veces)
□ Stack trace capturado (si crash)
□ Minimal reproducible example creado
□ git bisect ejecutado para encontrar culprit commit
□ Logs y output relevante guardados
□ Environment details documentados (OS, compiler, etc)