Skip to content

Commit 4154230

Browse files
authored
Ingest CI test execution stats (#4191)
This PR adds a flow step to the repository build ingesting test results for internal analysis. :memo: So far, this is implemented as a step within this repository CI flow, it can be easily factored out as an action if we want to follow the same strategy in other repositories. The step: 1. Converts the go tests XML reports into NDJSON format using `test-results.sh` which is just a wrapper for a `yq` query. 2. Uploads the NDJSON to a ClickHouse ingestion Query Endpoint. If the endpoint failes (no response, connection interrupted or status code >= 400), the upload is retried a up to 5 times with power-of-two exponential back-off. This is done as the last step in the build so failures are not disruptive. ## For internal reviewers You should check the following details related to internal management of this data: - Data model: https://linear.app/clickhouse/issue/DBI-671/store-peerdb-ci-test-results-for-flake-analysis#comment-62e1e006 - QA validation: https://linear.app/clickhouse/issue/DBI-671/store-peerdb-ci-test-results-for-flake-analysis#comment-0b4514cb - Internal documentation: https://www.notion.so/PeerDB-Test-Infrastructure-3482bd19f29080ea8199dcf57d822e88?v=3212bd19f29080be82b2000c45d4fd04&source=copy_link#3482bd19f2908073878cffb9992a25aa ## Ready when - [x] Pipeline ingestion is tested. ## What does this PR resolve? Part of: https://linear.app/clickhouse/issue/DBI-671
1 parent ed5080c commit 4154230

2 files changed

Lines changed: 93 additions & 0 deletions

File tree

.github/scripts/test-results.sh

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/bin/bash
2+
3+
set -eo pipefail # For jq to propagate yq failures.
4+
5+
# This script processes the XML tests results and outputs a NDJSON stream with one document per test execution.
6+
# Hence, each document corresponds to a testcase entry in the XML. It contains the following fields:
7+
#
8+
# - 'timestamp': As the testsuite (parent of the entry) timestamp field.
9+
# - 'suite_name': As the entry class name.
10+
# - 'test': As the entry name.
11+
# - 'result': success/failure/skipped from the optional child node of the entry. No child node means success.
12+
# - 'reason': As the entry child (skipped or failure) contents. For example, in the source, <failure>SOURCE FOR REASON</failure>.
13+
# - 'duration_seconds': As the time field of the entry.
14+
# - 'workflow_run_id': As the content of the environment variable 'WORKFLOW_RUN_ID'
15+
# - 'workflow_run_link': As the content of the environment variable 'WORKFLOW_RUN_LINK'
16+
# - 'workflow_head_branch': As the content of the environment variable 'WORKFLOW_HEAD_BRANCH'
17+
# - 'workflow_retry_number': As the content of the environment variable 'WORKFLOW_RUN_NUMBER'
18+
# - 'commit_sha': As the content of the environment variable 'COMMIT_SHA'
19+
#
20+
# INPUT:
21+
# - The XML file(s) to process. For example, the test results generated by JUnit in the 'target/surefire-reports' directory.
22+
# - The following environment variables to enrich with the workflow information:
23+
# - WORKFLOW_RUN_ID
24+
# - WORKFLOW_RUN_LINK
25+
# - WORKFLOW_HEAD_BRANCH
26+
# - WORKFLOW_RUN_NUMBER
27+
# - COMMIT_SHA
28+
#
29+
# OUTPUT: NDJSON stream through stdout.
30+
#
31+
yq -p=xml -o=json \
32+
'
33+
((.testsuites.testsuite | select(kind == "seq") | .[]), (.testsuites.testsuite | select(kind == "map"))) |
34+
."+@timestamp" as $ts |
35+
select(has("testcase")) |
36+
((.testcase | select(kind == "seq") | .[]), (.testcase | select(kind == "map"))) |
37+
{
38+
"timestamp": $ts,
39+
"suite_name": ."+@classname",
40+
"test": ."+@name",
41+
"result": ((keys | map(select(. == "failure" or . == "skipped")) | .[0]) // "success"),
42+
"reason": ((.failure."+content" // .skipped."+@message") // null),
43+
"duration_seconds": (."+@time" | select(. != "") | tonumber // null),
44+
"workflow_run_id": (strenv(WORKFLOW_RUN_ID) | tonumber),
45+
"workflow_run_link": strenv(WORKFLOW_RUN_LINK),
46+
"workflow_head_branch": strenv(WORKFLOW_HEAD_BRANCH),
47+
"workflow_retry_number": (strenv(WORKFLOW_RUN_NUMBER) | tonumber),
48+
"commit_sha": strenv(COMMIT_SHA)
49+
} |
50+
select(.suite_name != null)
51+
' \
52+
"$@" \
53+
| jq -c . # jq for compacting the output to NDJSON.

.github/workflows/flow.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ jobs:
116116
sudo apt-get remove --purge man-db
117117
sudo apt-get install libgeos-dev
118118
119+
- name: install retry tool
120+
run: |
121+
sudo apt-get install -y retry
122+
119123
- run: go mod download
120124
working-directory: ./flow
121125

@@ -550,3 +554,39 @@ jobs:
550554
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
551555
with:
552556
token: ${{ secrets.CODECOV_TOKEN }}
557+
- name: Ingest tests results for analysis
558+
if: success() || failure()
559+
env:
560+
WORKFLOW_RUN_ID: ${{ github.run_id }}
561+
WORKFLOW_RUN_LINK: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
562+
WORKFLOW_HEAD_BRANCH: ${{ github.head_ref || github.ref_name }}
563+
WORKFLOW_RUN_NUMBER: ${{ github.run_attempt }}
564+
COMMIT_SHA: ${{ github.sha }}
565+
SUMMARY_HEADER: "x-clickhouse-summary"
566+
run: |
567+
if [ -z "${{ secrets.CI_O11Y_TARGET_API_KEY_ID }}" ] \
568+
|| [ -z "${{ secrets.CI_O11Y_TARGET_API_KEY_SECRET }}" ] \
569+
|| [ -z "${{ secrets.CI_O11Y_TARGET_QUERY_ENDPOINT }}" ]; then
570+
echo "Secrets not configured; Test results ingestion is disabled."
571+
exit 0
572+
fi
573+
.github/scripts/test-results.sh logs/test-results.xml > logs/normalized-test-results.ndjson
574+
if [ ! -s logs/normalized-test-results.ndjson ]; then
575+
echo "Failed to extract test results"
576+
exit 1
577+
fi
578+
# Retry upload with exponential backoff
579+
retry --times=5 --delay "1,2,4,8,16,32,64,128" -- \
580+
curl --fail-with-body -i -H "Content-Type: application/octet-stream" \
581+
--user "${{ secrets.CI_O11Y_TARGET_API_KEY_ID }}:${{ secrets.CI_O11Y_TARGET_API_KEY_SECRET }}" \
582+
"${{ secrets.CI_O11Y_TARGET_QUERY_ENDPOINT }}" \
583+
--data-binary @logs/normalized-test-results.ndjson \
584+
| tee upload.log
585+
WRITTEN_ROWS=$(cat upload.log | grep $SUMMARY_HEADER | sed "s/$SUMMARY_HEADER: //" | jq '.written_rows' -r)
586+
if [ -z "$WRITTEN_ROWS" ] || [ "$WRITTEN_ROWS" -eq 0 ]; then
587+
echo "No results uploaded. Query client logs:"
588+
cat upload.log
589+
exit 2
590+
fi
591+
echo "Number of ingested test results: $WRITTEN_ROWS"
592+
exit 0

0 commit comments

Comments
 (0)