Las capas de prueba
Las pruebas están en tres capas, y conviene distinguirlas porque solo la primera es gratis. Dos de ellas levantan nueve contenedores, y quien decide si espera merece saber qué compra con esa espera.
| Capa | Orden | Docker | Tiempo | Qué demuestra |
|---|---|---|---|---|
| L1, el contrato | make test | no | unos 5 s | los bytes exactos que cada destino pone en el cable |
| L2, los almacenes | make test-e2e-docker | sí | 53 s en caliente | que un almacén real acepta esos bytes y conserva la fecha del hecho |
| L3, los dashboards | el mismo objetivo | sí | incluido arriba | que las consultas propias de los cinco dashboards responden contra lo que escribieron los destinos |
L1 corre en cada push. L2 y L3 son una sola suite tras la etiqueta de
compilación dockere2e, así que go test ./... no levanta ningún contenedor;
en CI corren semanalmente, a demanda y como puerta de publicación.
L1: los bytes en el cable
Sección titulada «L1: los bytes en el cable»test/e2e compila el binario real, lo ejecuta contra un GitHub falso cuyas
fixtures están en test/e2e/testdata y cuya tabla de rutas es test/e2e/fakegh,
y apunta cada destino a un servidor de captura httptest. Después comprueba los
bytes: el line protocol, el sobre _bulk, las sentencias SQL, la ruta de
Graphite, la carga OTLP.
El falso además cobra sus respuestas como lo hace api.github.com: un ETag por
fixture REST y ninguno en las respuestas GraphQL, un 304 sin coste para la
petición que lo presenta, uno por cada otra respuesta de la API, nada por el
almacenamiento de objetos al que redirige un log de job, y el bloque de
presupuesto en cada respuesta GraphQL. Eso es lo que permite que
TestTheSecondSweepIsPricedByTheCache ejecute dos pasadas en un mismo proceso
y sostenga que cada URL respondida 200
la primera vez volvió 304 la segunda, que la segunda pasada cobró menos de la
mitad de las peticiones core de la primera, y que own_cost en la fila
gh_rate_limit es el número de consultas que hizo el proceso y no cero.
Esa es la prueba correcta para un formato, y es lo bastante rápida para correr mientras se toca un destino. Lo que no puede detectar es nada sobre lo que el receptor tenga una opinión. Un servidor de captura responde 204 a todo. No tiene tipos de columna, ni mapping, ni planificador de consultas, ni esquema.
L2: los almacenes de verdad
Sección titulada «L2: los almacenes de verdad»test/e2e/docker levanta los almacenes reales en contenedores, ejecuta una
pasada desde el mismo GitHub falso hacia todos ellos y luego relee cada almacén
y comprueba el valor, las etiquetas y sobre todo la marca de tiempo.
La regla de fechado es el producto de esta herramienta: una estrella se fecha cuando se dio, una ejecución de workflow cuando terminó, un día de tráfico en la fecha de ese día. Ninguna prueba de captura puede demostrar que el almacén guardó la fecha del hecho y no la de la pasada, porque guardarla es trabajo del almacén.
Estos son los defectos por los que existe esta capa, todos reales:
InfluxDB fija el tipo de una columna la primera vez que la ve. InfluxDB 3
decide si una columna es etiqueta o campo en cuanto la ve y rechaza toda
escritura posterior que no coincida: 400 invalid column type for column 'owner', expected iox::column_type::tag. Un servidor de captura responde 204 y
no se entera. Esto ya costó borrar una base de datos, y reproducirlo fue lo
primero que se hizo con el stack en contenedores.
El mapping dinámico de Elasticsearch decide si los dashboards pueden agregar.
Un panel saca una url con un top_metrics, que normalmente necesita un campo
keyword y no uno de texto. Dos auditorías distintas lo anotaron como no
verificable por falta de un Elasticsearch real. Esta capa lo responde:
indexando por _bulk y releyendo el mapping que el clúster se construyó solo.
PostgreSQL tiene que aceptar el DDL. El destino SQL emite sentencias en
lugar de hablar el protocolo, así que hasta ahora nadie las había parseado
nunca. La suite las pasa por psql, inserta y planifica las consultas de los
paneles contra el esquema que creó el destino, y no contra uno transcrito de
InfluxDB.
Las rutas de Graphite deben tener la profundidad que indexan los dashboards. Los dashboards direccionan los nodos de la ruta por posición, y el acuerdo entre esas posiciones y lo que escribe el destino lo mantenía una tabla a mano que no comprobaba nadie. Aquí el destino escribe a carbon y se le pide la ruta de vuelta a la API de render.
L3: las consultas propias de los dashboards
Sección titulada «L3: las consultas propias de los dashboards»Con los almacenes ya cargados, los cinco dashboards generados se ejecutan por
/api/ds/query de Grafana, que es el camino que toma cmd/check_dashboards
contra un Grafana vivo. Cada datasource se provisiona al arrancar con un uid
fijo y el arnés emite un token de cuenta de servicio, así que la consulta de un
panel pasa por Grafana igual que para una persona que mira el dashboard.
Eso es lo que convierte tres comprobadores manuales en algo que la CI ejecuta, y lo que zanja la duda de Elasticsearch de arriba: un panel que no puede agregar no devuelve ningún data frame.
Lo que no detecta ninguna
Sección titulada «Lo que no detecta ninguna»Todas las capas corren contra el GitHub falso, así que aquí nadie se entera de
que GitHub cambie una carga, retire un endpoint o limite de otra manera. Para
eso está ghchronicle -once contra un token real.
Tampoco demuestran nada sobre un almacén que la suite no levanta. La respuesta cubre InfluxDB 3 Core, PostgreSQL 18, Elasticsearch 9, Graphite 1.1, Prometheus 3, Loki 3, el colector de OpenTelemetry y Telegraf, en las versiones fijadas. OpenSearch, TimescaleDB y todo lo que quede detrás del salto de Telegraf o de OTLP siguen siendo una inferencia a partir del formato.
Hay cinco cosas más, y ninguna es una capa. Cada una se enciende con una
variable de entorno y se salta cuando no está, así que un go test ./...
corriente sigue sin red.
El almacén que tú ejecutas. test/live envía por push un puñado de puntos a
un Loki o a un colector de OpenTelemetry nombrados en GHC_LIVE_LOKI o
GHC_LIVE_OTLP. Responde a la única pregunta que los contenedores no pueden: si
tu instancia los acepta.
GHC_LIVE_LOKI=http://localhost:3100 go test ./test/live/La API real, de extremo a extremo. GHC_E2E_LIVE=1 corre TestLiveAPI
contra GitHub en vez de contra el falso, con un GITHUB_TOKEN de verdad, y hace
la pasada sobre la cuenta que nombra GHC_E2E_USER.
GHC_E2E_LIVE=1 GHC_E2E_USER=octocat GITHUB_TOKEN=ghp_... go test ./test/e2e/ -run TestLiveAPILo que cuesta una pasada en caché. GHC_LIVE_CONFIG apunta
TestLiveSweepCacheFootprint a un fichero de configuración y hace la pasada
sobre la cuenta que nombra, informando de las entradas y los bytes que guarda la
caché de peticiones condicionales tras cada pasada. Esas son las cifras sobre
las que descansan la cota de 256 MB y el
coste de una pasada, y así es como se reproducen
para tu propia cuenta. GHC_LIVE_DUMP=1 añade la lista por url a la salida
estándar.
GHC_LIVE_CONFIG=config.yaml go test ./internal/ghapi/ -run TestLiveSweepCacheFootprint -vUn repositorio, una familia. cmd/probe corre los colectores contra un solo
repositorio e imprime la línea que escribirían, sin escribir nada en ningún
sitio. GHC_DUMP=<familia> imprime entero cada punto de esa familia, que es la
forma más rápida de ver lo que produce de verdad un colector.
go run ./cmd/probe owner/nameGHC_DUMP=actions go run ./cmd/probe owner/nameLas imágenes de la tarjeta. GHC_CARD_GALLERY nombra un directorio que ya
existe y TestCardGallery dibuja en él una tarjeta por diseño, desde el GitHub
falso y no desde la cuenta de nadie. De ahí salen las imágenes de
la página de diseños, y un diseño que cambia
de forma queda a un comando de tener unas imágenes que concuerden con él. La
cuenta son los fixtures base con test/e2e/testdata/gallery/ superpuesto: un
año de contribuciones, los catorce días completos de tráfico de GitHub, cinco
repositorios que ordenar y uno de ellos en seis lenguajes, que la cuenta más
pequeña sobre la que afirma el resto de suites no puede dar a una imagen. Un
fixture llamado <repo>~<fixture> responde solo por ese repositorio, y
cualquier otro toma prestado el de hello-world. Cada diseño sale de una sola
pasada con -card-theme both como dos ficheros, card-<diseño>.svg en la
paleta clara y card-<diseño>_dark.svg en la oscura, que es lo que leen el
ThemeImage del sitio y el <picture> del README. Los dos diseños que hacen
bucle salen una segunda vez con -card-motion loop, como
card-<diseño>-loop.svg y su gemela _dark. Solo esos dos: en cualquier otro
diseño loop dibuja la misma tarjeta que once, así que una imagen en bucle
suya sería una segunda copia de la primera con un nombre que promete otra cosa.
Cuáles son lo dice Loops en el registro, y la galería lo lee en vez de
mantener su propia lista.
mkdir -p /tmp/cardsGHC_CARD_GALLERY=/tmp/cards go test ./test/e2e/ -run TestCardGallerymake check-gallery dibuja la galería en un directorio temporal y falla,
nombrando cada diferencia, si el conjunto guardado en el repositorio ya no
coincide byte a byte; make gallery la regenera en su sitio. El job
«Generated artifacts» de CI ejecuta la comprobación en cada pull request.
Levantar el stack
Sección titulada «Levantar el stack»Docker con el plugin de compose, y sitio para las imágenes. Luego:
make test-e2e-dockerArriba, ejecutar, abajo por todos los caminos incluido el de una comprobación
fallida, y después una verificación de que docker ps no deja nada del
proyecto. Una suite que deja nueve contenedores tras un fallo es una suite que
nadie ejecuta dos veces.
Arranque, medido en frío con las imágenes ya descargadas: Elasticsearch 29 s, Loki 21 s, Grafana 13 s, la API de render de Graphite 10 s, InfluxDB 8 s, PostgreSQL 6 s, el resto 6 s. El stack está listo en 30 s; el objetivo de principio a fin, con el desmontaje incluido, son 53 s.
Depurar con el stack en pie
Sección titulada «Depurar con el stack en pie»El motivo para fallar una comprobación es ir a mirar el almacén, y un almacén que ya se ha desmontado no se puede mirar. Por eso las dos mitades son objetivos separados, y el arnés reutiliza un stack que encuentra ya levantado y lo deja levantado.
-
Levanta los almacenes y déjalos en pie. La orden imprime el puerto en el que acabó cada servicio.
Ventana de terminal make e2e-docker-up -
Ejecuta la suite, o una sola prueba de ella, tantas veces como haga falta.
Ventana de terminal go test -count=1 -tags dockere2e -timeout 30m -v ./test/e2e/docker/GHCHRONICLE_E2E_KEEP=1además impide que el binario de pruebas desmonte un stack que levantó él mismo, que es lo que quieres cuando falla un único-run. -
Pregúntale al almacén qué piensa y luego desmóntalo.
Ventana de terminal make e2e-docker-logs SERVICE=influxdbmake e2e-docker-down
Con los puertos del paso 1:
# Qué cree InfluxDB que es cada columna. Esta es la respuesta a un 400 al escribir.curl -s "http://127.0.0.1:<influx>/api/v3/query_sql?db=ghchronicle" \ --data-urlencode "q=SELECT * FROM information_schema.columns WHERE table_name = 'gh_repo'"
# El mapping que Elasticsearch se construyó solo.curl -s "http://127.0.0.1:<es>/ghchronicle-*/_mapping?pretty"
# Lo que el destino SQL creó de verdad.psql "postgres://ghchronicle:ghchronicle@127.0.0.1:<pg>/ghchronicle" -c '\d+ gh_repo'
# La ruta de Graphite, nodo a nodo.curl -s "http://127.0.0.1:<graphite>/metrics/find?query=github.repo.*"Grafana está en el puerto que publicó, con admin y admin, y todos los
datasources vienen provisionados, así que la consulta de un panel se puede
pegar en Explore y ejecutar a mano.
Tres cosas que hubo que decirle al stack
Sección titulada «Tres cosas que hubo que decirle al stack»Cada una de estas produjo en silencio una respuesta equivocada antes de que se encontrara, y cada una está en el fichero de compose o en su configuración con la medición al lado:
- Carbon descarta sin avisar un punto más antiguo que su archivo más largo.
Una estrella fechada en 2020 desapareció bajo una retención de seis años y la
escritura se dio por aceptada. Por eso la retención es
1d:12y. - El
MAX_CREATES_PER_MINUTEpor defecto de carbon es 50, menos rutas de las que crea una sola pasada, así que se perdería la mayor parte de la primera. - Loki responde 204 a un push y no lo sirve hasta que se vuelca el chunk.
La prueba sondea en vez de preguntar una vez, y
chunk_idle_periodes 5 s.
Una máquina que añade la regla ejecuta esas comprobaciones por primera vez, que
es cuando promNeedsHistory en dashboards_test.go empieza a importar: el
exportador vive solo lo que dura el test, así que cada panel de series
temporales y cada panel construido sobre increase() se comprueba contra nada,
y solo se afirman los paneles instantáneos.
.github/workflows/e2e.yml ejecuta make test-e2e-docker y tiene tres formas
de entrar: lanzamiento manual con una ref opcional, una programación semanal
sobre main y workflow_call, para que un pipeline de publicación condicione
una etiqueta a él con una línea en vez de con una copia del trabajo que se
desvía del original.
No es una comprobación obligatoria en una pull request: nueve contenedores y unos 10 GB de imágenes son demasiado para cada push, y para eso está L1. La ejecución semanal es el sentido de la programación. Nada más en el repositorio levanta un contenedor, así que sin ella la suite solo correría cuando alguien se acordara, que es como una suite acaba rota durante semanas sin que nadie lo sepa.
La misma suite corre también bajo el detector de carreras, en
.github/workflows/race.yml: cada semana, en cada publicación junto a la
puerta de E2E, y a mano. El arnés compila el colector con -race y lo arranca
con GORACE=halt_on_error=1, así que una carrera dentro del colector hace
fallar la prueba que lo lanzó, con el informe. En local es
make test-e2e-docker-race.