Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.0 KiB
/e2e-cpp — Crear/ejecutar tests e2e para apps C++
Genera y corre tests e2e con Dear ImGui Test Engine sobre las apps C++ del registry. Cada app gana un ejecutable <app>_tests que reabre la app dentro de un harness de testing y ejecuta scripts de UI (clicks, escritura, asserts) sobre los componentes ImGui.
Suite ya instalada en cpp/vendor/imgui_test_engine/. Integracion en framework: fn::run_app_test() (ver cpp/framework/app_base.h). Opt-in via -DFN_BUILD_TESTS=ON. Sin la opcion los builds normales de /compile no cambian.
Argumento
$ARGUMENTS — formato libre. Casos:
<app_name>— solo el nombre. Si la app ya tiene tests, los ejecuta. Si no, pide al usuario que describa el flujo a testear.<app_name> <descripcion del flujo>— genera un test nuevo para ese flujo y lo ejecuta. Ej:chart_demo abrir cada tab y verificar que renderiza.- vacio — detectar app desde
pwd(si estas encpp/apps/<X>/oprojects/*/apps/<X>/); si no, listar apps disponibles.
Pasos
1. Resolver app y directorio
ROOT=$HOME/fn_registry
ARGS="$ARGUMENTS"
APP_ARG="${ARGS%% *}" # primera palabra
FLOW_DESC="${ARGS#* }" # resto (puede coincidir con APP_ARG si solo hay una palabra)
[ "$FLOW_DESC" = "$APP_ARG" ] && FLOW_DESC=""
# Detectar desde CWD si no hay arg
if [ -z "$APP_ARG" ]; then
CWD="$(pwd)"
case "$CWD" in
"$ROOT"/cpp/apps/*|"$ROOT"/projects/*/apps/*)
APP_ARG="$(basename "$CWD")" ;;
esac
fi
if [ -z "$APP_ARG" ]; then
echo "Apps C++ disponibles:"
ls "$ROOT"/cpp/apps/ 2>/dev/null
ls "$ROOT"/projects/*/apps/ 2>/dev/null
echo "Uso: /e2e-cpp <app> [descripcion del flujo]"
exit 1
fi
APP_DIR=""
for cand in "$ROOT/cpp/apps/$APP_ARG" "$ROOT"/projects/*/apps/"$APP_ARG"; do
[ -d "$cand" ] && [ -f "$cand/CMakeLists.txt" ] && APP_DIR="$cand" && break
done
[ -z "$APP_DIR" ] && { echo "App C++ no encontrada: $APP_ARG"; exit 1; }
echo "App: $APP_ARG"
echo "Dir: $APP_DIR"
2. Inspeccionar la app
Lee:
$APP_DIR/main.cpp— identifica:- El nombre de la funcion principal de render (suele ser
render()ostatic void render()). - El window title que aparece en
ImGui::Begin("...")— sera el primer arg dectx->SetRef("...")en los tests. Si tiene em-dash u otros UTF-8 no ASCII, anotar la secuencia de bytes (ej:\xe2\x80\x94para—). - Los IDs/labels de los widgets candidatos: tabs (
BeginTabItem), botones (Button), inputs (InputText), checkboxes, etc.
- El nombre de la funcion principal de render (suele ser
$APP_DIR/app.md— para entender el dominio y proposito.$APP_DIR/CMakeLists.txt— para saber que.cppdel registry enlaza la app (los tests linkearan los mismos).
3. Decidir tests a escribir
Si $FLOW_DESC esta vacio: pregunta al usuario que flujo testear. Sugiere 2-3 candidatos basados en los widgets vistos en main.cpp. NO inventes flujos sin confirmacion.
Si $FLOW_DESC viene en el comando: convierte la descripcion en una secuencia de pasos atomicos del Test Context API. Ejemplos canonicos:
| Descripcion humano | Llamada Test Engine |
|---|---|
| "abrir tab X" | ctx->ItemClick("##tabs/X") o el path real del TabBar |
| "escribir 'hola' en el input search" | ctx->ItemInput("Search", "hola") |
| "click boton Aceptar" | ctx->ItemClick("Aceptar") |
| "verificar que aparece el modal Y" | IM_CHECK(ctx->WindowInfo("Y").ID != 0) |
| "checkbox Z marcado" | IM_CHECK(ctx->ItemIsChecked("Z")) |
| "menu File > Open" | ctx->MenuClick("File/Open") |
Ver cpp/vendor/imgui_test_engine/imgui_te_context.h para el catalogo completo de helpers.
4. Preparar la app para tests (idempotente)
Si es la primera vez que la app gana tests, hay que:
a) Hacer la funcion render() linkable desde otra TU
// Antes: static void render() { ... }
// Despues: void render() { ... }
b) Excluir int main() con guarda FN_TEST_BUILD
#ifndef FN_TEST_BUILD
int main() {
return fn::run_app({...}, render);
}
#endif
Verifica con grep -n "FN_TEST_BUILD\|^static void render" "$APP_DIR/main.cpp". Si ya esta, no toques nada.
5. Generar/extender el archivo de tests
$APP_DIR/tests/<app>_tests.cpp — un solo archivo por app, varias IM_REGISTER_TEST dentro de register_tests().
Plantilla:
// E2E tests para <app> — Dear ImGui Test Engine.
// Construido solo con -DFN_BUILD_TESTS=ON. Reusa el mismo main.cpp con
// FN_TEST_BUILD definido para excluir su int main().
#include "app_base.h"
#include "imgui.h"
#include "imgui_te_engine.h"
#include "imgui_te_context.h"
void render(); // definido en <app>/main.cpp
static void register_tests(ImGuiTestEngine* e) {
ImGuiTest* t = nullptr;
t = IM_REGISTER_TEST(e, "<app>", "<test_name>");
t->TestFunc = [](ImGuiTestContext* ctx) {
ctx->SetRef("<window_title_exacto>");
// ... pasos del flujo
};
// mas tests aqui
}
int main() {
fn::AppConfig cfg{};
cfg.title = "<app>_tests";
cfg.width = 1280;
cfg.height = 800;
return fn::run_app_test(cfg, render, register_tests);
}
Si el archivo ya existe: AGREGA un nuevo IM_REGISTER_TEST dentro de la funcion register_tests existente. NO sobreescribas tests previos.
6. Actualizar CMakeLists.txt (idempotente)
Si $APP_DIR/CMakeLists.txt no tiene aun el bloque de tests, agregar al final:
# --- E2E tests (opt-in via -DFN_BUILD_TESTS=ON) ---
if(FN_BUILD_TESTS)
add_imgui_app(<app>_tests
main.cpp
tests/<app>_tests.cpp
# mismos .cpp del registry que la app principal
${CMAKE_SOURCE_DIR}/functions/<dom>/<func>.cpp
...
)
target_compile_definitions(<app>_tests PRIVATE FN_TEST_BUILD)
endif()
Las fuentes deben replicar las del target principal (mismas funciones del registry). Si la app ya tiene un bloque if(FN_BUILD_TESTS), no lo dupliques.
7. Build
cd "$ROOT/cpp"
cmake -S . -B build/linux_tests -DFN_BUILD_TESTS=ON 2>&1 | tail -5
cmake --build build/linux_tests --target ${APP_ARG}_tests -j4 2>&1 | tail -20
Si el build falla:
- Errores de compilacion en
tests/...cpp→ revisa nombres de widgets/paths con el codigo real de main.cpp. - "undefined reference to render" → falta quitar
statico falta el#ifndef FN_TEST_BUILDen main.cpp. - "multiple definition of main" → falta el
target_compile_definitions(... FN_TEST_BUILD)en CMakeLists.
8. Ejecutar (headless preferente — sin parpadeo)
fn::run_app_test crea la ventana GLFW oculta por defecto (GLFW_VISIBLE=FALSE, ver cpp/framework/app_base.cpp). El contexto GL real se crea igual, así que el render que ejercita el Test Engine es fiel, pero la ventana nunca se mapea en pantalla: cero parpadeo, no roba foco. Por eso los tests de frontend C++ corren headless por defecto, sin tocar el código de cada app.
Dos formas de lanzar, según el entorno:
cd "$ROOT/cpp/build/linux_tests"
TEST_BIN="$(find . -name "${APP_ARG}_tests" -type f -executable | head -1)"
[ -z "$TEST_BIN" ] && { echo "no encuentro el binario de tests"; exit 1; }
if [ -n "$DISPLAY" ] && command -v glxinfo >/dev/null 2>&1 \
&& glxinfo 2>/dev/null | grep -q "OpenGL core profile version"; then
# Host con GL nativo (PC enmanuel, X11 + GPU): binario directo.
# La ventana ya nace oculta -> sin parpadeo, y usa la GPU real (rapido).
timeout 90 "$TEST_BIN" 2>&1
else
# CI / WSL sin GLX 4.3 nativo: display virtual en RAM + software Mesa.
timeout 90 xvfb-run -a -s "-screen 0 1280x800x24" \
env LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe \
"$TEST_BIN" 2>&1
fi
EXIT=$?
echo "EXIT: $EXIT"
Ambas vías son headless. xvfb-run sigue siendo seguro en host con display (corre en su propio display virtual), así que si el sniff de GL falla puedes usar siempre la rama xvfb.
Para depurar un test a ojo (ver la UI mientras el engine la maneja), desactiva el headless con FN_HEADLESS=0:
FN_HEADLESS=0 timeout 90 "$TEST_BIN" 2>&1
9. Reportar
- Si
EXIT == 0y la salida contieneTests Result: OK→ reportaN/M tests passedcon la lista de tests ejecutados. - Si
EXIT != 0→ muestra el bloque de log del test fallido (test engine imprime el path del widget que no encontro, el archivo y la linea del IM_CHECK que fallo). Sugiere correcciones (widget renombrado, path mal escrito, race entre frames — usarctx->Yield()).
10. Despues de añadir tests
NO ejecutes fn index automaticamente — los tests no son funciones del registry, son artefactos de la app. Si el usuario los queria persistir, ya los tiene en <app_dir>/tests/.
Si la app es un sub-repo (lo normal segun ADR 0002), recordar al usuario que los archivos nuevos viven dentro del repo de la app y necesitan un commit alli (no en fn_registry).
Referencias
- API de Test Context:
cpp/vendor/imgui_test_engine/imgui_te_context.h - API del engine:
cpp/vendor/imgui_test_engine/imgui_te_engine.h - Implementacion del harness:
cpp/framework/app_base.cpp(funcionfn::run_app_test) - Ejemplo canonico:
cpp/apps/chart_demo/tests/chart_demo_tests.cpp - Licencia del test engine: personal/open-source gratis (
cpp/vendor/imgui_test_engine/LICENSE.txt)